Apexとフローの使い分け（画面あり編）

LWC（Lightning Web Components）で画面開発が必要なケースとして、**標準UIだけでは実現できない動的な操作やリアルタイム更新**が求められる場面を挙げます。  
具体的な事例を5つ紹介し、それぞれの**要件**と**LWC実装の概要**を記載します。  

---

## **LWCが必要なケース（標準UIでは対応困難なもの）**

### **1. 商談の見積明細を動的に追加・削除できるカスタムUI**
#### **要件**
- `Opportunity`のレコードページに、関連する`QuoteLineItem`を表示。
- 行を追加・削除できるインタラクティブなUIを提供。
- 計算結果（合計金額など）をリアルタイム更新。

#### **LWC実装の概要**
- `lightning-datatable`を使用して見積明細を表示。
- ボタン操作で行の追加・削除を可能にする。
- `@track`を使い、合計金額などの計算値を即時更新。
- `Apex`でデータの取得・保存を処理。

---

### **2. リアルタイム在庫チェックと注文処理**
#### **要件**
- `Product`オブジェクトの在庫数をリアルタイムで取得。
- `OpportunityLineItem`追加時に、在庫が不足していれば警告を表示。
- **WebSocket** や **Platform Events** を利用し、他ユーザーの変更を即時反映。

#### **LWC実装の概要**
- `@wire`を使ってApexから在庫データを取得。
- `lightning-input`で注文数を入力し、在庫と比較。
- 在庫不足時は`lightning-toast`でアラート表示。
- **Platform Events** を購読し、他ユーザーの更新を反映。

---

### **3. 商談の進捗をガントチャートで可視化**
#### **要件**
- `Opportunity`の各フェーズをガントチャートで表示。
- **ドラッグ＆ドロップ**でフェーズを変更可能。
- 変更内容は即座にSalesforceへ保存。

#### **LWC実装の概要**
- `third-party JavaScriptライブラリ（D3.js, Chart.js）`を活用。
- `lightning-record-edit-form`を利用してドラッグ＆ドロップ後にデータ更新。
- `@wire`で商談フェーズのデータを取得・更新。

---

### **4. ユーザーがファイルをドラッグ＆ドロップでアップロードし、プレビュー**
#### **要件**
- `Case`（問い合わせ）にファイルを添付する際、ドラッグ＆ドロップでアップロード可能にする。
- アップロード前に画像プレビューを表示。
- PDFや画像ファイルの内容をプレビュー可能。

#### **LWC実装の概要**
- `lightning-file-upload`を使用。
- `onchange`イベントでファイルを一時保存し、プレビュー表示。
- `ContentDocumentLink`を利用し、ファイルを`Case`に関連付け。

---

### **5. 顧客検索＆即時編集可能なカスタム検索コンポーネント**
#### **要件**
- `Account`を検索できるコンポーネントを作成。
- 部分一致検索が可能（標準の`lookup`では難しい）。
- 検索結果をクリックすると、その場で編集＆保存。

#### **LWC実装の概要**
- `lightning-input`で検索キーワードを入力。
- `@wire`を使用し、Apexで`LIKE`検索を実施。
- `lightning-datatable`に結果を表示し、`inline editing`を有効化。

---

まずは **「商談の見積明細を動的に追加・削除できるカスタムUI」** の実装例を示します。  
このLWCコンポーネントは、`Opportunity` のレコードページに配置し、`QuoteLineItem`（見積明細）の追加・削除、合計金額のリアルタイム更新ができるようにします。  

---

# **1. 商談の見積明細を動的に追加・削除できるカスタムUI**
## **実装概要**
- **見積明細（QuoteLineItem）を `lightning-datatable` で表示**
- **ユーザーが行を追加・削除可能**
- **合計金額をリアルタイムで計算**
- **SalesforceデータをApex経由で取得・更新**

---

## **1.1 Apexクラス**
LWC から `Opportunity` の `QuoteLineItem` データを取得・更新する Apex クラスを作成します。

```apex
public with sharing class QuoteLineItemController {
    @AuraEnabled(cacheable=true)
    public static List<QuoteLineItem> getQuoteLineItems(Id opportunityId) {
        return [SELECT Id, QuoteId, Product2Id, Quantity, UnitPrice, TotalPrice 
                FROM QuoteLineItem 
                WHERE Quote.OpportunityId = :opportunityId];
    }

    @AuraEnabled
    public static void saveQuoteLineItems(List<QuoteLineItem> quoteLineItems) {
        upsert quoteLineItems;
    }

    @AuraEnabled
    public static void deleteQuoteLineItem(Id quoteLineItemId) {
        delete [SELECT Id FROM QuoteLineItem WHERE Id = :quoteLineItemId];
    }
}
```

---

## **1.2 LWCコンポーネント**
### **HTML: `quoteLineItemTable.html`**
```html
<template>
    <lightning-card title="見積明細（QuoteLineItem）">
        <div class="slds-m-around_medium">
            <lightning-datatable
                key-field="Id"
                data={quoteLineItems}
                columns={columns}
                draft-values={draftValues}
                onsave={handleSave}
                onrowaction={handleRowAction}>
            </lightning-datatable>

            <lightning-button
                label="行を追加"
                onclick={addRow}
                class="slds-m-top_medium">
            </lightning-button>

            <p class="slds-m-top_medium">
                <strong>合計金額: {totalAmount} 円</strong>
            </p>
        </div>
    </lightning-card>
</template>
```

---

### **JavaScript: `quoteLineItemTable.js`**
```javascript
import { LightningElement, api, track, wire } from 'lwc';
import getQuoteLineItems from '@salesforce/apex/QuoteLineItemController.getQuoteLineItems';
import saveQuoteLineItems from '@salesforce/apex/QuoteLineItemController.saveQuoteLineItems';
import deleteQuoteLineItem from '@salesforce/apex/QuoteLineItemController.deleteQuoteLineItem';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';

const COLUMNS = [
    { label: '商品ID', fieldName: 'Product2Id', editable: true },
    { label: '数量', fieldName: 'Quantity', type: 'number', editable: true },
    { label: '単価', fieldName: 'UnitPrice', type: 'currency', editable: true },
    { label: '合計', fieldName: 'TotalPrice', type: 'currency' },
    { type: 'action', typeAttributes: { rowActions: [{ label: '削除', name: 'delete' }] } }
];

export default class QuoteLineItemTable extends LightningElement {
    @api recordId;
    @track quoteLineItems = [];
    columns = COLUMNS;
    draftValues = [];

    @wire(getQuoteLineItems, { opportunityId: '$recordId' })
    wiredQuoteLineItems({ error, data }) {
        if (data) {
            this.quoteLineItems = data;
        } else if (error) {
            this.showToast('エラー', '見積明細の取得に失敗しました', 'error');
        }
    }

    get totalAmount() {
        return this.quoteLineItems.reduce((sum, item) => sum + (item.TotalPrice || 0), 0);
    }

    handleSave(event) {
        const updatedFields = event.detail.draftValues;
        saveQuoteLineItems({ quoteLineItems: updatedFields })
            .then(() => {
                this.showToast('成功', '見積明細が更新されました', 'success');
                return refreshApex(this.wiredQuoteLineItems);
            })
            .catch(error => {
                this.showToast('エラー', '保存に失敗しました', 'error');
            });
    }

    handleRowAction(event) {
        const actionName = event.detail.action.name;
        const row = event.detail.row;
        if (actionName === 'delete') {
            deleteQuoteLineItem({ quoteLineItemId: row.Id })
                .then(() => {
                    this.showToast('成功', '行を削除しました', 'success');
                    return refreshApex(this.wiredQuoteLineItems);
                })
                .catch(error => {
                    this.showToast('エラー', '削除に失敗しました', 'error');
                });
        }
    }

    addRow() {
        const newItem = {
            Id: 'new_' + Date.now(),
            Product2Id: '',
            Quantity: 1,
            UnitPrice: 0,
            TotalPrice: 0
        };
        this.quoteLineItems = [...this.quoteLineItems, newItem];
    }

    showToast(title, message, variant) {
        this.dispatchEvent(new ShowToastEvent({ title, message, variant }));
    }
}
```

---

### **メタデータXML: `quoteLineItemTable.js-meta.xml`**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"
    fqn="quoteLineItemTable">
    <apiVersion>58.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__RecordPage">
            <property name="recordId" type="String" label="レコードID" />
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>
```

---

## **2. 実装ポイント**
1. **データの取得**
   - `@wire(getQuoteLineItems, { opportunityId: '$recordId' })` を使用し、Apexからデータ取得。

2. **リアルタイムで合計金額を計算**
   - `get totalAmount()` を利用し、`quoteLineItems` の合計金額を計算。

3. **見積明細の編集・保存**
   - `lightning-datatable` の `onsave` イベントを `handleSave` で処理。

4. **行の追加・削除**
   - `addRow()` で仮の新規レコードを追加。
   - `handleRowAction()` で `deleteQuoteLineItem` を呼び出し削除。

---

## **3. まとめ**
このLWCは、**Salesforceの標準画面では実現できない「動的なUI」** を提供し、  
✅ **リアルタイムでデータ変更**  
✅ **ダイナミックなUI操作**  
✅ **Apexと連携したデータ管理**  
を可能にします。

このパターンを応用すれば、**リアルタイム在庫チェックやガントチャート表示** なども実装できます！🎯
## **全体のまとめ**
LWCが必要なケースは、  
✅ **リアルタイム更新**（在庫チェック・WebSocket・Platform Events）  
✅ **複雑なUI操作**（ドラッグ＆ドロップ・カスタム検索）  
✅ **即時フィードバック**（インタラクティブなフォーム・データプレビュー）  
が求められる場面です。  

Apex + LWCを組み合わせることで、Salesforce標準UIでは難しい高度な画面開発を実現できます！