Make Revenue Line Item 'Unit Price' editable after the Relationship is populated with Product Catalogue record?

Posting here (dev) after initially looking at this from admin and also found an admin page with the same question:

How can I make the unit price editable after a revenue line item is created? 

We do not just have one unit price for each product, instead we sell via Partners and each of those has their own price list - for the same product.  So each product can have many prices.  (Note that ERP system is SAP by Design and we dont have integration to that)

So, I am wondering if anybody else knows a solution for this?  Ideally I think the following would work nicely:

  • From Opportuinity> Add the Revenue Line Item (RLI) (from dashlet on side panel)..
  • In RLI the Product (catalogue record) is selected but the Unit Price is not editable. (Hoping it can be).
  • If Unit Price could be populated from the Catalogue, but then edited it would allow our sales folk the ability to alter the price per each project.I then do not need a unit price - it could be zero?  Though i do not know the impact that will have on Forecasts further down the road.

Note - with no product (catalogue item) relationship the Unit price can be manually added...

I am scared of breaking forecasts or quotes which we have not yet configured for this.

-We have looked at vardefs but see nothing which seems to make the Unit Price do what it does.

-I don't think the Unit Price field's formula in studio seems to effect this kind of desired change.

-We can Inspect the field in browser and remove disable for that fields input... but then it doesnt really work correctly with regard to Best/Likely/Worst etc..  and if you save and come back in again its still only looking at the Unit price from the relationship.

Any suggestions very welcome!   I am stuck.

Other options are:

- to import all our dealer price lists (200+!) into the catalogue... that's alot of duplication, what about price annual updates, and even then sales will still alter the prices by using discounts per project... but this not very slick either.

-Or I can import an MRSP price which they discount from... same un-slick approach. Plus they create these mrsp's per currency so that throws up its own questions too...

Thanks for any other advice on this one...!

Luke Ridgway     Admin

john Fieldsend    Dev

  • Hi Luke Ridgway 

    The dependency which makes discount_price readonly is defined at modules/RevenueLineItems/metadata/dependencydefs.php, so you need to create a custom version of such dependency (custom/modules/RevenueLineItems/metadata/dependencydefs.php) and update it accordingly.

    Regards

    André Lopes
    Lampada Global
    Skype: andre.lampada
  • Thanks ( again ;-) ) André Lopes,

    Good info.  Sorry for the late response - we have not yet had chance to look into much further... one of many jobs.

    The requirement may also have changed...  Looks like they now have no need for any prices in the product catalogue items - so RLI will be for a product (catalogue) and its quantity.  All the money stuff from price per unit and approval is done elsewhere in ERP system.

    Will update here though if anything relevant may help others out the with a similar request.

    Thanks again, Luke.

  • Thanks André Lopes

    Works like charm

    you saved my day.

  • Hi Andre,

    Your reply saved my day. I was able to make the discount_price editable but anyhow whenever i create a new record and give a different value in Unit Price , it is still overridden by the price mentioned in product catalog. I can see there is an internal function called to map fields from product template on first time save. Can u please help me how can i customize this function

  • Hi Andre,

    I just tested the dependency change and this worked partly. When we save a opportunity it seems to work but the unit price is still updated with the product from the catalogue. Do you know how we can fix this? Is it some sort of javascript that is kicking in?

  • It is configured by the attribute populate_list on RevenueLineItems / Products vardefs, field definition for product_template_name. You need to create an extended vardefs in order to redefine such an attribute.

    André Lopes
    Lampada Global
    Skype: andre.lampada
  • Find my answer above

    André Lopes
    Lampada Global
    Skype: andre.lampada
  • Thanks Andre for the extra information. But I couldn't get it to work properly. I created a custom vardefs for the product_template_name field: 

    <?php
     // created: 2021-10-19 07:43:03
    $dictionary['RevenueLineItem']['fields']['product_template_name']['audited']=false;
    $dictionary['RevenueLineItem']['fields']['product_template_name']['hidemassupdate']=false;
    $dictionary['RevenueLineItem']['fields']['product_template_name']['duplicate_merge']='disabled';
    $dictionary['RevenueLineItem']['fields']['product_template_name']['duplicate_merge_dom_value']=0;
    $dictionary['RevenueLineItem']['fields']['product_template_name']['merge_filter']='disabled';
    $dictionary['RevenueLineItem']['fields']['product_template_name']['calculated']=false;
    $dictionary['RevenueLineItem']['fields']['product_template_name']['populate_list']= array(
        'name' => 'name',
        'category_id' => 'category_id',
        'category_name' => 'category_name',
        'mft_part_num' => 'mft_part_num',
        'list_price' => 'list_price',
        'cost_price' => 'cost_price',
        'list_usdollar' => 'list_usdollar',
        'cost_usdollar' => 'cost_usdollar',
        'currency_id' => 'currency_id',
        'base_rate' => 'base_rate',
        'tax_class' => 'tax_class',
        'weight' => 'weight',
        'manufacturer_id' => 'manufacturer_id',
        'manufacturer_name' => 'manufacturer_name',
        'type_id' => 'type_id',
        'type_name' => 'type_name',
        'service_start_date' => 'service_start_date',
        'service_end_date' => 'service_end_date',
        'service_duration_value' => ['service_duration_value', 'catalog_service_duration_value'],
        'service_duration_unit' => ['service_duration_unit', 'catalog_service_duration_unit'],
        'renewable' => 'renewable',
        'service' => 'service',
    );
    
     ?>

    As you can see I removed the discount_price fields from the populate list. For some reason that didn't work after a Q&R. So I checked the revenuelineitems  bean object and noticed the following: 

        /**
         * Handle the mapping of the fields from the product template to the product
         */
        protected function mapFieldsFromProductTemplate()
        {
            if ($this->product_template_id && (
                $this->fetched_row === false || $this->fetched_row['product_template_id'] != $this->product_template_id
            )) {
                /* @var $pt ProductTemplate */
                $pt = BeanFactory::getBean('ProductTemplates', $this->product_template_id);
    
                $this->category_id = $pt->category_id;
                $this->mft_part_num = $pt->mft_part_num;
                $this->list_price = SugarCurrency::convertAmount($pt->list_price, $pt->currency_id, $this->currency_id);
                $this->cost_price = SugarCurrency::convertAmount($pt->cost_price, $pt->currency_id, $this->currency_id);
                $this->discount_price = SugarCurrency::convertAmount($pt->discount_price, $pt->currency_id, $this->currency_id); // discount_price = unit price on the front end...
                $this->list_usdollar = $pt->list_usdollar;
                $this->cost_usdollar = $pt->cost_usdollar;
                $this->discount_usdollar = $pt->discount_usdollar;
                $this->tax_class = $pt->tax_class;
                $this->weight = $pt->weight;
            }
        }

    As you can see here the discount is always retrieved and overwritten in certain situations. So what I did was to disable the assignment of discount_price and this seems to work. But i'm not to happy to change core bean files. Is there something I missed which should get the populate_list vardefs working? 

  • indeed I had forgotten that some fields are managed by the RLI class itself.

    Instead of modifying core code you can create a custom RLI class which extends the core one and modify it accordingly.

    Find below a working example on how to create a custom bean class:

    custom/Extension/application/Ext/Include/some_file.php

    $moduleList[] = 'ProductTemplates';
    $objectList['ProductTemplates'] = 'ProductTemplate';
    $beanList['ProductTemplates'] = 'CustomProductTemplate';
    $beanFiles['CustomProductTemplate'] = 'custom/modules/ProductTemplates/ProductTemplate.php';
    if (isset($modInvisList) && is_array($modInvisList)) {
        foreach ($modInvisList as $key => $mod) {
            if ($mod === 'ProductTemplates') {
                unset($modInvisList[$key]);
            }
        }
    }
    
    

    custom/modules/ProductTemplates/ProductTemplate.php

    require_once('modules/ProductTemplates/ProductTemplate.php');
    
    class CustomProductTemplate extends ProductTemplate {
    	public function __construct() {
    		parent::__construct();
    		$this->disable_row_level_security = false;
    	}
    }
    

    Remember to run QRR after saving your custom files.

    Regards

    André Lopes
    Lampada Global
    Skype: andre.lampada
  • Thanks for the quick response and confirming that we indeed need to extend the base class. For completeness I have added the changes I made:

    First we add custom/Extension/application/Ext/Include/customRLI.php to map the new class to the rli object

    <?php
    //
    /**
    * The $objectList array, maps the module name to the Vardef property
    * By default only a few core modules have this defined, since their Class/Object names differs from their Vardef Property
    **/
    $objectList['RevenueLineItems'] = 'RevenueLineItem';
    
    // $beanList maps the Bean/Module name to the Class name
    $beanList['RevenueLineItems'] = 'CustomRevenueLineItem';
    
    // $beanFiles maps the Class name to the PHP Class file
    $beanFiles['CustomRevenueLineItem'] = 'custom/modules/RevenueLineItems/CustomRevenueLineItem.php';

    next we create a new class which extends default Rli class. In this case I overwritten the mapFieldsFromProductTemplate function since I don't want the discount price from the template when we create or change the rli

    <?php
    
    require_once('modules/RevenueLineItems/RevenueLineItem.php');
    
    
    class CustomRevenueLineItem extends RevenueLineItem
    {
        public function __construct()
        {
            parent::__construct();
        }
    
        protected function mapFieldsFromProductTemplate(){
            $GLOBALS['log']->fatal('Custom RLI -> mapFieldsFromProductTemplate called');
            if ($this->product_template_id && (
                    $this->fetched_row === false || $this->fetched_row['product_template_id'] != $this->product_template_id
                )) {
                /* @var $pt ProductTemplate */
                $pt = BeanFactory::getBean('ProductTemplates', $this->product_template_id);
    
                $this->category_id = $pt->category_id;
                $this->mft_part_num = $pt->mft_part_num;
                $this->list_price = SugarCurrency::convertAmount($pt->list_price, $pt->currency_id, $this->currency_id);
                $this->cost_price = SugarCurrency::convertAmount($pt->cost_price, $pt->currency_id, $this->currency_id);
    //            $this->discount_price = SugarCurrency::convertAmount($pt->discount_price, $pt->currency_id, $this->currency_id); // discount_price = unit price on the front end...
                $this->list_usdollar = $pt->list_usdollar;
                $this->cost_usdollar = $pt->cost_usdollar;
    //            $this->discount_usdollar = $pt->discount_usdollar;
                $this->tax_class = $pt->tax_class;
                $this->weight = $pt->weight;
            }
        }
    
    }

    Also we need to prevent the discount price from populating to from the catalogue. Create the following file: 

    custom/Extension/modules/RevenueLineItems/Ext/Vardefs/sugarfield_product_template_name.php. As you can see I rmoved the discount_price fields from the popuplate list.

    <?php
    $dictionary['RevenueLineItem']['fields']['product_template_name']['audited']=false;
    $dictionary['RevenueLineItem']['fields']['product_template_name']['hidemassupdate']=false;
    $dictionary['RevenueLineItem']['fields']['product_template_name']['duplicate_merge']='disabled';
    $dictionary['RevenueLineItem']['fields']['product_template_name']['duplicate_merge_dom_value']=0;
    $dictionary['RevenueLineItem']['fields']['product_template_name']['merge_filter']='disabled';
    $dictionary['RevenueLineItem']['fields']['product_template_name']['calculated']=false;
    $dictionary['RevenueLineItem']['fields']['product_template_name']['populate_list']= array(
        'name' => 'name',
        'category_id' => 'category_id',
        'category_name' => 'category_name',
        'mft_part_num' => 'mft_part_num',
        'list_price' => 'list_price',
        'cost_price' => 'cost_price',
        'list_usdollar' => 'list_usdollar',
        'cost_usdollar' => 'cost_usdollar',
        'currency_id' => 'currency_id',
        'base_rate' => 'base_rate',
        'tax_class' => 'tax_class',
        'weight' => 'weight',
        'manufacturer_id' => 'manufacturer_id',
        'manufacturer_name' => 'manufacturer_name',
        'type_id' => 'type_id',
        'type_name' => 'type_name',
        'service_start_date' => 'service_start_date',
        'service_end_date' => 'service_end_date',
        'service_duration_value' => ['service_duration_value', 'catalog_service_duration_value'],
        'service_duration_unit' => ['service_duration_unit', 'catalog_service_duration_unit'],
        'renewable' => 'renewable',
        'service' => 'service',
    );
    ?>

    Hit Q&R and all should be working. Just be sure that in the custom bean class you don't use namespaces since for some reason which namespaces sugar can't find the class and throws 500 errors all over te place.