To check if Subpanel is populated with record and stop submit process on click SAVE if no record added to the subpanel on opportunities

I have created subpanel in Opportunities module. relationship is as below

I have written a custom code so the above subpanel is visible only on field (is_partner_influence=true) checkbox is checked. Also on click of SAVE button, I need to check if subpanel has any record ...if not it will show error and submit process should stop. 

How to check if subpanel records count >0 and stop the submit process. Below is my code....I tried to put 

event.preventDefault(); but it does not work. also return false does not stop submit process.


({
extendsFrom: 'SubpanelsLayout',

_hiddenSubpanels: [],

initialize: function(options) {
this._super('initialize', [options]);
this._bindEvents();
this.registerModelEvents();
app.events.on('app:init', function() {
app.events.off('subpanel:reload:after', this.afterSubpanelReload, this);
app.events.on('subpanel:reload:after', this.afterSubpanelReload, this);
}, this);
this.context.on('button:save_button:click', this.checkPartnerInfluenceSubpanel, this);
},

checkPartnerInfluenceSubpanel: function(event) {
if (event) {
event.preventDefault(); // Prevent the save button from submitting the form
}
const oppsSubpanel = document.querySelector('div[data-subpanel-link="opportunities_accounts_1"]');
const noDataAvailable = oppsSubpanel.querySelector('div.no-data-available');
const isPopulated = noDataAvailable.classList.contains('hidden');
var isPartnerInfluence = this.context.get('model').get('is_partner_influence_c');

if (isPartnerInfluence && !isPopulated){
app.alert.show("partner-influence-subpanel-alert", {
level: "error",
messages: "The Partner Influence subpanel must be populated with accounts before saving."
});

return false; // prevent save if subpanel is not populated with accounts
}

app.alert.dismiss("partner-influence-subpanel-alert"); // dismiss alert if subpanel is populated with accounts
this._super('saveClicked', [event]); // Manually trigger the saveClicked method after the subpanel check
},

afterSubpanelReload: function() {
this.context.off('button:save_button:click', this.checkPartnerInfluenceSubpanel, this);
this.context.on('button:save_button:click', this.checkPartnerInfluenceSubpanel, this);
},

/**
* Add the model change events for fields that determine when a subpanel should be hidden
*/
registerModelEvents: function(){
this.model.on('change:is_partner_influence_c',function(model) {
var link = 'opportunities_accounts_1';
if (model.get('is_partner_influence_c') === true){
this.unhideSubpanel(link);
}else{
this.hideSubpanel(link);
}
},this);
},

/**
* Override showSubpanel to re-hide subpanels when outside changes occur, like reordering subpanel
* @inheritdoc
*/
showSubpanel: function(linkName) {
this._super('showSubpanel',[linkName]);

_.each(this._hiddenSubpanels, function(link) {
this.hideSubpanel(link);
},this);
},

/**
* Helper for getting the Subpanel View for a specific link
*/
getSubpanelByLink: function(link){
return this._components.find(function(component){
return component.context.get('link') === link;
});
},

/**
* Add to the _hiddenSubpanels array, and hide the subpanel
*/
hideSubpanel: function(link){
this._hiddenSubpanels.push(link);
var component = this.getSubpanelByLink(link);
if (!_.isUndefined(component)){
component.hide();
}
this._hiddenSubpanels = _.unique(this._hiddenSubpanels);
},

/**
* Unhide the Subpanel and remove from _hiddenSubpanels array
*/
unhideSubpanel: function(link){
var index = this._hiddenSubpanels.findIndex(function(l){
return l == link;
});
if (_.isUndefined(index)){
delete this._hiddenSubpanels[index];
}
var component = this.getSubpanelByLink(link);
if (!_.isUndefined(component)){
component.show();
}
}
})



Please advice. thanks in advance


  • If I understand correctly you want to stop the user from saving changes to the Opportunity if is_partner_influence=true and there are no related Partner Influence records.

    In general, the handleSave function in the record.js controller is where I would start (as well as making the checkbox read only in the create view itself so they can't set it before they even create the Opportunity).

    For example, in my custom Contracts record I want to check if a Contract was modified after approval, and if so, I want to reset the approval status. 

    Users are allowed to change the Approval Notes and Assigned User without resetting approvals but any other field will require the reset.

    I use a white_list to list the fields that can be changed without checking.

    The full list of changed fields is retrieved here:

    Object.keys(self.model.changedAttributes(self.model.getSynced()))

      handleSave: function(){
        var self = this,
            changed = Object.keys(self.model.changedAttributes(self.model.getSynced())),
            white_list = ['approval_notes','assigned_user_id', 'assigned_user_name', 'assigned_user_link']; //set of fields anyone can update w/o resetting approvals 
        if(!_.isEmpty(_.difference(changed,white_list))){
          this.checkApprovalStatus();
        }else{
          //do not check approval status if all we've saved are fields in the white list, just move on to handle the save
          this._super('handleSave');
        }
      },

    As you can see here if there is anything in the changed list other than the white listed I skip to a custom checkApprovalStatus() function.

    In the checkApprovalStatus

      checkApprovalStatus: function(){
        var self = this,
            isSysAdmin = (app.user.get('type') == 'admin'),
            approval_status = self.model.get('contract_approval_status_c');
        if(approval_status == 'Approved' && !isSysAdmin){
          //An Alert asks the user if they really want to save
          //and reset the approavals
          app.alert.show('editing_approved', {
            level: 'confirmation',
            messages: 'Contract already Approved, Confirm to Save Changes and reset Approval Status',
            autoClose: false,
            onConfirm: function(evt){
               //here the user confirmed they want the changes to take place and reset approvals
               //my code to reset the approvals is here... cutting out for clarity
               
               //by calling the _super handleSave I proceed to actually saving the record
               self._super('handleSave');
            },
            onCancel: function(){
               //here I cancel the Save, the user did not wish to reset the approvals.
               self.handleCancel();
            }
          });
        }else{
          //if the contract is not Approved, skip all that and just save
          self._super('handleSave');
        }
      },
    


    As you can see the key takeaway is to intercept the handleSave with an extension, do your checks, and stop the execution of the save by using the self.handleCancel(); or continue to save by using the self._super('handleSave');

    Now, to see if there are related Partner Influence records you can use the getRelatedCollection for example

        var parentOpportunity = this.context.get('model'),
            detailsLink='the_link_name_from_your_relationship_definition',
            relatedPartnerInfluenceCollection = parentOpportunity.getRelatedCollection(detailsLink);

    Now all you have to do is check if your related collection is empty.

    (Please note that these are snippets from much more complex code and edited here for clarity, so they may not quite work straight out, they are just showing conceptual processes).

    Hope this helps,
    FrancescaS

  • Hi FrancescaS,

    Thanks for your help and directing me to the right file. I wrote custom code under custom/modules/Opportunities/clients/base/views/record/record.js and extend from RecordView. But still self.handleCancel(); does not work and says undefined. code is below....what is wrong you think? Thanks

    ({
    extendsFrom: 'RecordView',

    initialize: function (options) {

    this._super('initialize', [options]);

    this.model.addValidationTask('check_is_partner_influence_c', _.bind(this._doValidatePartnerInfluence, this));
    },

    _doValidatePartnerInfluence: function(fields, errors, callback) {

    var self = this,
    isPartnerInfluence = this.model.get('is_partner_influence_c');
    const oppsSubpanel = document.querySelector('div[data-subpanel-link="opportunities_accounts_1"]');
    const spanTxt = oppsSubpanel.querySelector('span.count');
    let spanNumber = 0;
    if(spanTxt) {
    spanNumber = parseInt(spanTxt.textContent.match(/\d+/)[0]);
    }

    if(isPartnerInfluence && spanNumber === 0){
    app.alert.show("partner-influence-subpanel-alert", {
    level: "error",
    messages: "The Partner Influence subpanel must be populated with accounts before saving!!"
    });

    self.handleCancel();

    }else{
    //just move on to handle the save
    this._super('_doValidatePartnerInfluence');
    }


    callback(null, fields, errors);
    },
    })
  • I think the difference is that I am extending the handleSave function. So I'm intercepting the Save and stopping its completion by using the Cancel. I don't think that putting the cancel inside the validation will stop the execution of the super  handleSave and therefore the record still saves.

    Also, I am assuming that the doValidatePartnerInfluence function is fully custom, so there is no _super('_doValidatePartnerInfluence')... so I'm a bit confused about how all this comes together for you...

    You may need to review how field validation works:
    https://support.sugarcrm.com/Documentation/Sugar_Developer/Sugar_Developer_Guide_12.0/Cookbook/Adding_Field_Validation_to_the_Record_View/

    FrancescaS

  • Hi FrancescaS, I have figured it out and now working as expected. Thanks a lot for your help