function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Kenichi MatsudaKenichi Matsuda 

Visualforceで複数オブジェクトの項目を編集したい

Visualforceページでのご相談です。
表示したい項目が商談と取引先にオブジェクトに跨っているので、このようなvisualforceページを作りました。
商談レコードのカスタムボタンで以下のVisualforceを指定し、内容の更新をしたいと思っています。
ボタン自体は商談のカスタムボタンなのですが、②の様に取引先の情報を更新したいのですが保存をしても
取引先の項目が保存した内容に反映されません。
①は保存できているのですが...オブジェクトを跨いでいるのでNGなのでしょうか?
それとも他に何か方法があるでしょうか?

<apex:page standardController="Opportunity" sidebar="false" showHeader="false" docType="html-5.0">
  <apex:form >
    <apex:pageBlock>
      <apex:pageBlockButtons >
        <apex:commandButton value=" 保存 " onclick="saveInfo();refresh();" reRender="form"/>
        <apex:actionFunction name="saveInfo" action="{!save}" rerender="out" status="myStatus"/>
      </apex:pageBlockButtons>

      <apex:pageBlockSection>
        <apex:outputField value="{!Opportunity.Name}"/>
        <apex:inputField value="{!Opportunity.Type}"/> ・・・①
        <apex:outputField value="{!Opportunity.Account.Name}"/>
        <apex:inputField value="{!Opportunity.Account.state__c}"/> ・・・②
      </apex:pageBlockSection>
    </apex:pageBlock>
  </apex:form>
</apex:page>
Best Answer chosen by Mitch Okamoto
Taiki YoshikawaTaiki Yoshikawa
オブジェクトをまたがる更新が必要な場合はカスタムコントローラの作成が必要になります。保存ボタンをクリック時に商談→取引先と順番に更新処理を行いどちらかでエラーが発生した場合はロールバックして対応します。

ロールバック処理でINSERTやUPDATEの結果はリセットされますが、変数にセットされたID項目の値はリセットされないので注意してください。(Cloneなどで対応できると思います。)

入力規則エラーなどはDmlExceptionで対応できます。

サンプルです。
<apex:page standardController="Opportunity" extensions="OpportunityEditController" showHeader="true" sidebar="false"  docType="html-5.0" id="page">
    <apex:form id="form">
        <apex:pageBlock id="block">
            <apex:pageMessages id="msg" />
            <apex:pageBlockButtons id="buttons">
                <apex:commandButton value=" 保存 " title=" 保存 " action="{!doSave}" reRender="form" />
                <apex:commandButton value=" キャンセル " title=" キャンセル " action="{!Cancel}" reRender="form" />
            </apex:pageBlockButtons>
            <apex:pageBlockSection id="section">
                <apex:outputField value="{!opportunity.Name}"/>
                <apex:inputField value="{!opportunity.Type}"/>
                <apex:inputField value="{!account.Name}"/>
            </apex:pageBlockSection>
        </apex:pageBlock>
    </apex:form>
</apex:page>
public with sharing class OpportunityEditController {

    public Opportunity opportunity {get; set;}
    public Account account {get; set;}
    
    /**
     * コンストラクタ
     */
    public OpportunityEditController(ApexPages.StandardController stdController) {
        try {
            Id opportunityId = stdController.getId();
            this.opportunity = this.getOpportunity(opportunityId);
            this.account = this.getAccount(this.opportunity.AccountId);
        } catch(Exception e) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL,e.getMessage()));
            return;
        }
    }

    /**
     * 保存ボタン処理
     */
    public PageReference doSave() {
        Savepoint sp = Database.setSavepoint();
        Opportunity upsertOpportunity = new Opportunity();
        try {
            // ID項目はロールバックでリセットされないのでクローンで対応
            upsertOpportunity = this.opportunity.clone(true, false, true, true);
            upsert upsertOpportunity;
            // 取引先更新
            upsert this.account;
        } catch(DmlException e) {
            Database.rollback(sp);
           ApexPages.addMessages(e);
            return null;
        } catch(Exception e) {
           Database.rollback(sp);
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL,e.getMessage()));
            return null;
        }

        return new PageReference('/' + upsertOpportunity.Id);
    }

    /**
     * 商談情報取得
     */
    private Opportunity getOpportunity(Id opportunityId) {
        List<Opportunity> opportunities = [
            SELECT
                 Id
                ,Name
                ,AccountId
                ,Type
            FROM
                Opportunity
            WHERE
                Id =: opportunityId
            LIMIT 1
        ];

        return opportunities.isEmpty() ? new Opportunity() : opportunities[0];
    }

    /**
     * 取引先情報取得
     */
    private Account getAccount(Id accountId) {
        List<Account> accounts = [
            SELECT
                 Id
                ,Name
            FROM
                Account
            WHERE
                Id =: accountId
            LIMIT 1
        ];

        return accounts.isEmpty() ? new Account() : accounts[0];
    }
}

All Answers

Ryosuke KobayashiRyosuke Kobayashi
standardControllerのsaveメソッドは、単一オブジェクト(ここでいう商談)に対する更新になるので、
取引先の情報は更新されないと思います。

取引先も同時に更新したいのであれば、Extensionクラスでsaveメソッドをオーバーライドし、取引先を更新するロジックを組む必要があるかと。
Taiki YoshikawaTaiki Yoshikawa
オブジェクトをまたがる更新が必要な場合はカスタムコントローラの作成が必要になります。保存ボタンをクリック時に商談→取引先と順番に更新処理を行いどちらかでエラーが発生した場合はロールバックして対応します。

ロールバック処理でINSERTやUPDATEの結果はリセットされますが、変数にセットされたID項目の値はリセットされないので注意してください。(Cloneなどで対応できると思います。)

入力規則エラーなどはDmlExceptionで対応できます。

サンプルです。
<apex:page standardController="Opportunity" extensions="OpportunityEditController" showHeader="true" sidebar="false"  docType="html-5.0" id="page">
    <apex:form id="form">
        <apex:pageBlock id="block">
            <apex:pageMessages id="msg" />
            <apex:pageBlockButtons id="buttons">
                <apex:commandButton value=" 保存 " title=" 保存 " action="{!doSave}" reRender="form" />
                <apex:commandButton value=" キャンセル " title=" キャンセル " action="{!Cancel}" reRender="form" />
            </apex:pageBlockButtons>
            <apex:pageBlockSection id="section">
                <apex:outputField value="{!opportunity.Name}"/>
                <apex:inputField value="{!opportunity.Type}"/>
                <apex:inputField value="{!account.Name}"/>
            </apex:pageBlockSection>
        </apex:pageBlock>
    </apex:form>
</apex:page>
public with sharing class OpportunityEditController {

    public Opportunity opportunity {get; set;}
    public Account account {get; set;}
    
    /**
     * コンストラクタ
     */
    public OpportunityEditController(ApexPages.StandardController stdController) {
        try {
            Id opportunityId = stdController.getId();
            this.opportunity = this.getOpportunity(opportunityId);
            this.account = this.getAccount(this.opportunity.AccountId);
        } catch(Exception e) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL,e.getMessage()));
            return;
        }
    }

    /**
     * 保存ボタン処理
     */
    public PageReference doSave() {
        Savepoint sp = Database.setSavepoint();
        Opportunity upsertOpportunity = new Opportunity();
        try {
            // ID項目はロールバックでリセットされないのでクローンで対応
            upsertOpportunity = this.opportunity.clone(true, false, true, true);
            upsert upsertOpportunity;
            // 取引先更新
            upsert this.account;
        } catch(DmlException e) {
            Database.rollback(sp);
           ApexPages.addMessages(e);
            return null;
        } catch(Exception e) {
           Database.rollback(sp);
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL,e.getMessage()));
            return null;
        }

        return new PageReference('/' + upsertOpportunity.Id);
    }

    /**
     * 商談情報取得
     */
    private Opportunity getOpportunity(Id opportunityId) {
        List<Opportunity> opportunities = [
            SELECT
                 Id
                ,Name
                ,AccountId
                ,Type
            FROM
                Opportunity
            WHERE
                Id =: opportunityId
            LIMIT 1
        ];

        return opportunities.isEmpty() ? new Opportunity() : opportunities[0];
    }

    /**
     * 取引先情報取得
     */
    private Account getAccount(Id accountId) {
        List<Account> accounts = [
            SELECT
                 Id
                ,Name
            FROM
                Account
            WHERE
                Id =: accountId
            LIMIT 1
        ];

        return accounts.isEmpty() ? new Account() : accounts[0];
    }
}
This was selected as the best answer