dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

[salesforce]商談の「取引先責任者の役割」や「パートナー」をコピーする

商談に限らず取引先、ケース、契約などの項目にある
「取引先責任者の役割」や「パートナー」ですが、
これをApexでコピーする方法。
特に、「パートナー」をコピーするあたりで色々ハマったのでメモ。
それぞれどんなもの?というのはこのあたりを参考にしてください。

取引先責任者の役割

対象のSObjectさえわかればそれほど難しくありません。

OpportunityContactRoles

を使います。

/*
 * 「取引先責任者の役割」をコピー
 * opp : コピー元の商談(Opportunity)オブジェクト
 * newOpp : コピー先(新たに作成したい)の商談オブジェクト
 */
List<OpportunityContactRole> contactRoleList = [
    SELECT
      ContactId,
      Role,
      IsPrimary
    FROM
      OpportunityContactRole
    WHERE
      OpportunityId = :opp.id
];

List<OpportunityContactRole> newContactRoleList = new List<OpportunityContactRole>();
for (OpportunityContactRole contactRole : contactRoleList) {
    newContactRoleList.add(new OpportunityContactRole(
      OpportunityId = newOpp.id,
      ContactId = contactRole.contactId,
      Role = contactRole.role,
      IsPrimary = contactRole.isPrimary
    ));
}
insert newContactRoleList;

パートナー

こっちでけっこうつまづきました。
取引先責任者の役割がOpportunityContactRoleなので
パートナーはOpportunityPartnerだろうってことで
上をそのままOpportunityPartnerに変えるとエラー。。。

で、調べてみた結果

Salesforce Developers

This read-only object represents a partner relationship between an Account and an Opportunity. This object is automatically created when a Partner object is created for a partner relationship between an account and an opportunity.

ん?Read-Only?
Partnerが作成されたときに自動生成されるもの?

というわけで、パートナーについては
OpportunityPartnerではなくPartner
というSObjectを使えばいいそうです。

そしてもう1つ注意点。
先ほどの取引先責任者の役割のコードを流用して、次のようなコードを実行してみる。

/*
 * 「パートナー」をコピー(失敗例)
 * opp, newOppは先ほどと同じ
 */
// 取引先IdはAccountToIdというフィールドで取得
List<Partner> partnerList = [
    SELECT
      AccountToId,
      Role,
      IsPrimary
    FROM
      Partner
    WHERE
      OpportunityId = :opp.id
];

List<Partner> newPartnerList = new List<Partner>();
for (Partner partner : partnerList) {
    newPartnerList.add(new Partner(
      OpportunityId = newOpp.id,
      AccountToId = partner.accountToId,
      Role = partner.role,
      IsPrimary = partner.isPrimary
    ));
}
insert newPartnerList;

その実行結果がこちら。
何やら、エラーが出ている...

14:18:04:635 EXCEPTION_THROWN [102]|System.DmlException: Insert failed. First exception on row 1; first error: FIELD_INTEGRITY_EXCEPTION, field integrity exception: OpportunityId, AccountToId (account to cannot be opportunity account): [OpportunityId, AccountToId]

きちんとfor文の中を確認すればよかったのだけれども
その前に色々調べていて次の記事を見つけた。

Custom Clone button that also clones the Partner records??? - Salesforce Developer Community

また、冒頭に記載したリンク

でも、次のようなことが書いてある。

取引先や商談で 1 つのパートナーを選択すると、自動的に逆のパートナー関係も作成され、両取引先がもう一方の取引先をパートナーとして表示します。指定した役割がパートナー取引先に適用され、そのパートナーから見た役割がもう一方の取引先に適用されます。

なるほど、
パートナーという関係は一方通行ではなく双方向なので
新規で追加した時には逆向きのパートナー情報を表すレコードも自動生成されるんですね。

例)
新しく商談を作成する。取引先は「株式会社ABC」。  
「パートナー」として別の取引先「株式会社DEF」を選択。  
この時に生成されるのは、以下の2つのレコード。

ちなみに、この例ではRoleに「Agency」を選択したところ
逆方向のパートナーには「Client」が選択されましたが
この対応は「パートナーの役割」で参照することができます。

以上を踏まえて、パートナーをコピーする際の正しいApexはこちら。

/*
 * 「パートナー」をコピー(正しい例)
 * opp, newOppは先ほどと同じ
 */
List<Partner> partnerList = [
    SELECT
      AccountToId,
      Role,
      IsPrimary
    FROM
      Partner
    WHERE
      OpportunityId = :opp.id
];

List<Partner> newPartnerList = new List<Partner>();
for (Partner partner : partnerList) {
    if (partner.accountToId != opp.accountId) { // これを追記!
      newPartnerList.add(new Partner(
        OpportunityId = newOpp.id,
        AccountToId = partner.accountToId,
        Role = partner.role,
        IsPrimary = partner.isPrimary
      ));
    }
}
insert newPartnerList;

いやー、ややこしい。
ちなみに、パートナーを選択した後で商談の取引先を変更すると
パートナーはリセットされ、レコードもなくなります。