0
0

【Salesforce】手動の共有設定を子レコードにも一括で反映させるLWC

Posted at

やりたいこと

親レコードの共有の情報を子レコードにも反映させたい!

前提条件

・カスタムで「機密情報」と「機密情報詳細」オブジェクトがあるとします
・機密情報詳細は機密情報への参照項目を持っています
・「機密情報」と「機密情報詳細」オブジェクトは基本的に非公開で、詳細画面の共有ボタンから共有されているユーザまたはグループにしか権限がないとします
機密情報の詳細画面
image.png

設定の共有設定
image.png

詳細画面から手動で設定する共有
image.png

上記の共有設定を子レコードにもボタンひとつで反映させたい

実装内容

機密情報オブジェクト(親)に子コードも含めて、共有設定の詳細が確認できるコンポーネントをLWCで実装しました
image.png

「この共有設定を以下のレコードにも反映」ボタンを押下すると、確認画面が開きます
image.png

「はい」を押下し、ページをリフレッシュすると、子レードにも親と同じ共有設定になっていることがわかります
image.png

コーディング詳細

reflectShareInfoという名前でLWCを作成しました。JavascriptとHTMLは以下の通りです

reflectShareInfo.js
import { api, LightningElement, wire, track } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import secretRelatedInfo from '@salesforce/apex/GetShareInfoController.secretRelatedInfo';
import startReflectShareInfo from '@salesforce/apex/ReflectShareInfoController.startReflectShareInfo';
import MyModal from 'c/reflectShareinfoModal';

const columns = [
    // { label: '機密情報名', fieldName: 'recordName', type: 'text' },
    { label: 'ユーザまたはグループ名', fieldName: 'UserOrGroupName', type: 'text' },
    { label: 'アクセス種別', fieldName: 'RowCause', type: 'text' },
    { label: 'アクセスレベル', fieldName: 'AccessLevel', type: 'text' },
];

export default class ReflectShareInfo extends LightningElement {
    @api recordId;
    @track thisRecordtData;
    @track tableData;
    @track columns = columns;

    @wire(secretRelatedInfo, {siId: '$recordId'})
    gettableData({ error, data }) {
        if (data) {
            // secretInfoDetailAndShareInfoは編集できないため、編集できる形に再定義
            var targetData = JSON.parse(JSON.stringify(data.secretInfoDetailAndShareInfo));
            let tmpTableData = []
            // 関連レコードの共有設定のテーブル情報
            Object.keys(targetData).forEach(function(element){
                for(var i = 0; i<targetData[element].length; i++) {
                    // テーブルデータは参照情報を扱ってくれないため、参照情報を取り出してテーブルデータとして定義
                    var targetDataUserOrGroupName = targetData[element][i]['UserOrGroup']['Name'];
                    targetData[element][i]['UserOrGroupName'] = targetDataUserOrGroupName;
                    var sidRecordInfo = data.siDetailMap[targetData[element][i]['ParentId']];
                    targetData[element][i]['targetRecordName'] = sidRecordInfo['Name'];
                    var recordName = targetData[element][i]['Parent']['Name'];
                    targetData[element][i]['recordName'] = recordName;
                    
                }
                tmpTableData.push({key:element, value:targetData[element]});
            });
            this.tableData = tmpTableData;

            // 表示されているレコードの共有情報整備
            var displayedShareData = JSON.parse(JSON.stringify(data.sishlst));
            displayedShareData.forEach(element => {
                // console.log(element);
                element['UserOrGroupName'] = element['UserOrGroup']['Name'];
            });
            this.thisRecordtData = displayedShareData;
        }else if(error){
            console.log('Something happened: ' + JSON.parse(JSON.stringify(error)));
        }
    }

    // 共有設定を反映するボタンを押下した際に起動
    async handleClick() {
        console.log('Click pushed');
        var result = await MyModal.open({
            size: 'small',
            label: 'sample label',
            description: 'Confirm the share info'
        });
        console.log(result);
        
        if (result) {
            await startReflectShareInfo({siId: this.recordId}).then(result =>{
                console.log('success');
                this.showSuccessToast('成功', '共有設定の反映が成功しました。画面をリフレッシュして共有情報をご確認ください')
            }).catch(error =>{
                console.log('Something happened: ' + JSON.parse(JSON.stringify(error)));
                this.showErrorToast('失敗', '共有設定の反映が失敗しました')
            })
        }
    }

    showErrorToast(title, message) {
        const evt = new ShowToastEvent({
            title: title,
            message: message,
            variant: 'error',
            mode: 'dismissable'
        });
        this.dispatchEvent(evt);
    }

    showSuccessToast(title, message) {
        const evt = new ShowToastEvent({
            title: title,
            message: message,
            variant: 'success',
            mode: 'sticky'
        });
        this.dispatchEvent(evt);
    }
}
reflectShareInfo.html
<template>
    <lightning-card title='このレコードの共有情報' icon-name="custom:custom11" if:true={thisRecordtData}>
        <lightning-datatable
                key-field="id"
                data={thisRecordtData}
                columns={columns}
                hide-checkbox-column
                show-row-number-column>
        </lightning-datatable>
        <div slot="footer" class="slds-clearfix"> 
            <lightning-button
                label="この共有設定を以下のレコードにも反映"
                title="Non-primary action"
                class="slds-var-m-left_x-small slds-float_left"
                onclick={handleClick}>
            </lightning-button>
        </div>
    </lightning-card>
    <template for:each={tableData} for:item="shareData">
        <lightning-card title={shareData.key} icon-name="custom:custom11" if:true={tableData} key={shareData.key}>
            <lightning-datatable
                    key-field="id"
                    data={shareData.value}
                    columns={columns}
                    hide-checkbox-column
                    show-row-number-column>
            </lightning-datatable>
        </lightning-card>
    </template>
</template>

モーダル画面はreflectShareinfoModalという名前で作成しています

reflectShareinfoModal.js
import LightningModal from 'lightning/modal';

export default class ReflectShareinfoModal extends LightningModal {
    handleYes() {
        this.close(1);
    }
    handleNo() {
        this.close(0);
    }
}
reflectShareinfoModal.html
<template>
    <lightning-modal-header label="以下の内容でよろしいですか?"></lightning-modal-header>
    <lightning-modal-body> 
        <p>既存の共有設定を上書きしますがよろしいですか?</p>
    </lightning-modal-body>
    <lightning-modal-footer>
        <lightning-button label="はい" onclick={handleYes} class="slds-m-right_x-small"></lightning-button>
        <lightning-button label="いいえ" onclick={handleNo}></lightning-button>
    </lightning-modal-footer>
</template>

現状の共有設定の取得処理
※共有の情報を管理しているオブジェクトは[オブジェクト名]__Shareというオブジェクトです(標準オブジェクトの場合は[オブジェクト名]Share)
詳細は以下を参照ください

GetShareInfoController.cls
public with sharing class GetShareInfoController {

    public class SecretRelatedInfoReturn {
        @AuraEnabled public List<SecretInfo__Share> sishlst;
        @AuraEnabled public Map<Id, List<SecretInfo__Share>> secretInfoAndShareInfo;
        @AuraEnabled public List<SecretInfoDetail__Share> srdlst;
        @AuraEnabled public Map<Id, SecretInfoDetail__c> siDetailMap;
        @AuraEnabled public Map<String, List<SecretInfoDetail__Share>> secretInfoDetailAndShareInfo;

    }

    @AuraEnabled(cacheable=true)
    public static SecretRelatedInfoReturn secretRelatedInfo(Id siId) {
        System.debug('########start getSecretInfo method');
        System.debug(siId);
        SecretRelatedInfoReturn sri = new SecretRelatedInfoReturn();

        Map<Id, SecretInfo__Share> secretInfoShareMap = new Map<Id, SecretInfo__Share>(
            [
                SELECT Id, ParentId, UserOrGroupId, AccessLevel, RowCause, UserOrGroup.Name
                FROM SecretInfo__Share
                WHERE ParentId = :siId
            ]
        );
        sri.sishlst = secretInfoShareMap.values();
        Map<Id, List<SecretInfo__Share>> parentIdSecretInfoShare = new Map<Id, List<SecretInfo__Share>>();
        for (SecretInfo__Share variable : secretInfoShareMap.values()) {
            System.debug(variable);
            if (!parentIdSecretInfoShare.keySet().contains(variable.ParentId)) {
                parentIdSecretInfoShare.put(variable.ParentId, new List<SecretInfo__Share>());
            }
            parentIdSecretInfoShare.get(variable.ParentId).add(variable);
        }

        sri.secretInfoAndShareInfo = parentIdSecretInfoShare;

        Map<Id, SecretInfoDetail__c> secretInfoDetailMap = new Map<Id, SecretInfoDetail__c>(
            [
                SELECT Id, Name, SecretInfo__c
                FROM SecretInfoDetail__c
                WHERE SecretInfo__c = :siId
                WITH SECURITY_ENFORCED
            ]
        );

        sri.siDetailMap = secretInfoDetailMap;

        Map<Id, SecretInfoDetail__Share> secretInfoDetailShareMap = new Map<Id, SecretInfoDetail__Share>(
            [
                SELECT Id, Parent.Name, ParentId, UserOrGroupId, AccessLevel, RowCause, UserOrGroup.Name
                FROM SecretInfoDetail__Share
                WHERE ParentId IN :secretInfoDetailMap.keySet()
            ]
        );
        Map<String, List<SecretInfoDetail__Share>> parentIdSecretInfoDetailShare = new Map<String, List<SecretInfoDetail__Share>>();
        String keyElement;
        for (SecretInfoDetail__Share variable : secretInfoDetailShareMap.values()) {
            System.debug(variable);
            keyElement = variable.Parent.Name + '-' + variable.ParentId;
            if (!parentIdSecretInfoDetailShare.keySet().contains(keyElement)) {
                parentIdSecretInfoDetailShare.put(keyElement, new List<SecretInfoDetail__Share>());
            }
            parentIdSecretInfoDetailShare.get(keyElement).add(variable);
        }

        sri.secretInfoDetailAndShareInfo = parentIdSecretInfoDetailShare;
        System.debug('sri: ' + sri);
        sri.srdlst = secretInfoDetailShareMap.values();
        return sri;
    }
}

共有設定の反映を担当しているApex。
子レコードに既存の共有設定がある場合は、削除してから親レコードの共有設定の内容を上書きしています。

ReflectShareInfoController.cls
public without sharing class ReflectShareInfoController {
    @AuraEnabled
    public static Boolean startReflectShareInfo(Id siId) {
        
        // 表示している機密情報レコードの共有情報を取得
        List<SecretInfo__Share> siShareList = [
            SELECT Id, ParentId, UserOrGroupId, AccessLevel, RowCause
            FROM SecretInfo__Share
            WHERE ParentId = :siId
            AND RowCause = 'Manual'
        ];
        System.debug('siShareList取得後');
        System.debug(siShareList);

        // 表示している機密情報レコードに紐づく機密情報詳細レコードを取得
        List<SecretInfoDetail__c> sIDList = [
            SELECT Id, Name, SecretInfo__c, SecretInfo__r.Name
            FROM SecretInfoDetail__c
            WHERE SecretInfo__c = :siId
        ];
        System.debug('sIDList取得後');
        System.debug(sIDList);

        // 機密情報詳細レコードの共有情報で手動設定されたものを取得
        List<SecretInfoDetail__Share> sidShareList = [
            SELECT Id, ParentId, UserOrGroupId, AccessLevel, RowCause
            FROM SecretInfoDetail__Share
            WHERE ParentId IN :sIDList
            AND RowCause = 'Manual'
        ];
        System.debug('sidShareList取得後');
        System.debug(sidShareList);

        if (Schema.SObjectType.SecretInfoDetail__Share.isDeletable()) {
            if (sidShareList.size() > 0) {
                System.debug('delete前');
                delete sidShareList;
            }
        }else {
            System.debug('共有設定の削除権限がありません');
            return False;
        }
        // 機密情報詳細レコードの共有情報を取得
        List<SecretInfoDetail__Share> afterDeleteShareList = [
            SELECT Id, ParentId, UserOrGroupId, AccessLevel, RowCause
            FROM SecretInfoDetail__Share
            WHERE ParentId IN :sIDList
        ];
        // Mapを作成
        Map<Id, SecretInfoDetail__Share> secretDetailShare = new Map<Id, SecretInfoDetail__Share>();

        // UserOrGroupIdをキー、SecretInfoDetail__Shareをバリューにするマップを作成
        for (SecretInfoDetail__Share scretShareInfo : afterDeleteShareList) {
            secretDetailShare.put(scretShareInfo.UserOrGroupId, scretShareInfo);
        }
        System.debug('afterDeleteShareList取得後');
        System.debug(secretDetailShare);

        // 表示している機密情報レコードの共有情報を関連する機密情報詳細レコードへコピー
        List<SecretInfoDetail__Share> newSecretInfoDetailShareList = new List<SecretInfoDetail__Share>();
        for (SecretInfoDetail__c targetSecretInfoDetail : sIDList) {
            for (SecretInfo__Share siShareInfo : siShareList) {
                // 機密情報詳細レコードの所有者や設定の共有でデフォルトで共有されているユーザやグループを考慮
                if (secretDetailShare.containsKey(siShareInfo.UserOrGroupId)) {
                    System.debug('共有済みのためスキップ');
                    continue;
                }
                // 新規の共有設定を作成
                SecretInfoDetail__Share sidShare = new SecretInfoDetail__Share();
                sidShare.ParentId = targetSecretInfoDetail.Id;
                sidShare.UserOrGroupId = siShareInfo.UserOrGroupId;
                sidShare.AccessLevel = siShareInfo.AccessLevel;
                newSecretInfoDetailShareList.add(sidShare);
            }
        }
        if (newSecretInfoDetailShareList.size() > 0) {
            System.debug(newSecretInfoDetailShareList);
            insert newSecretInfoDetailShareList;
        }

        return True;
    }
}

終わりに

ある特定の部署などによっては、レコードの情報をオープンにしたくないという要望があるかと思います。
そんな時のための参考になれば幸いです:grinning:

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0