Let the platform do the work

Quotes

Overview

As of Sugar 7.9.0.0, the quotes module has been moved out of Backward Compatibility mode into Sidecar. Customizations to the Quotes interface will need to be rewritten for the new Sidecar framework as an automated migration of these customizations is not possible. This document will outline common customizations and implementation methods.

Quote Identifiers

Administrators can create custom quote identifiers that are more complex than the default auto-incrementing scheme found in an out of box installation. Utilizing this functionality, we can create numbering schemes that match any business needs.

Creating Custom Identifiers

To create a custom identifier, navigate to Admin > Studio > Quotes > Fields. Next, create a TextField type field named to your liking and check the "Calculated Value" checkbox and click "Edit Formula". Once you see the formula builder, you can choose an example below or create your own. Once created, You can add the new field to your Quote module's layouts as desired.

User Field with Pseudo-Random Values

This example creates a quote number in the format of <user last name>-##### that translates to Smith-87837. This identifier starts with the last name of the user creating it, followed by 5 pseudo-random numbers based on the creation time.

concat(related($created_by_link, "last_name"), "-", subStr(toString(timestamp($date_entered)), 5, 5))

Note: when using Sugar Logic, updates to related fields will update any corresponding Sugar Logic fields. The example being that an update to the Created By last name field will update all related records with a field using this formula.

This example creates a quote number in the format of <billing account name>-#### that translates to Start Over Trust-0001. This number starts with the billing account name followed by a 4 digit auto-incrementing number.

concat(related($billing_accounts, "name"), "-", subStr(toString(add($quote_num, 10000)), 1, 4))

Note: when using Sugar Logic, updates to related fields will update any corresponding Sugar Logic fields. The example being that an update to the Billing Account name will update all related records with a field using this formula.

Record Layout

The following sections outline various changes that can be done to modify the record layout of the Quotes module, by outlining the extra views that can be updated.

Grand Totals Header

The Grand Total Header contains the calculated totals for the quote.

Modifying Fields in the Grand Totals Header

To modify the Grand Totals Header fields, you can copy ./modules/Quotes/clients/base/views/quote-data-grand-totals-header/quote-data-grand-totals-header.php to ./custom/modules/Quotes/clients/base/views/quote-data-grand-totals-header/quote-data-grand-totals-header.php or you may create a metadata extension in ./custom/Extension/modules/Quotes/clients/base/views/quote-data-grand-totals-header/. Next, modify the $viewdefs['Quotes']['base']['view']['quote-data-grand-totals-header']['panels'][0]['fields'] index to add or remove your preferred fields.

Example

The example below will add the "Quote Number" field (quotes.quote_num) field to the layout and removes the "Total Discount" (quotes.deal_tot) from the layout. If adding a custom field from the Quotes module to the view, you will also need to add that field to the related_fields array as outlined in the Record View documentation below.

./custom/modules/Quotes/clients/base/views/quote-data-grand-totals-header/quote-data-grand-totals-header.php

  <?php

$viewdefs['Quotes']['base']['view']['quote-data-grand-totals-header'] = array(
    ...
    'panels' => array(
        array(
            'name' => 'panel_quote_data_grand_totals_header',
            'label' => 'LBL_QUOTE_DATA_GRAND_TOTALS_HEADER',
            'fields' => array(
                array(
                    'name' => 'quote_num',
                    'label' => 'LBL_LIST_QUOTE_NUM',
                    'css_class' => 'quote-totals-row-item',
                ),
                array(
                    'name' => 'new_sub',
                    'css_class' => 'quote-totals-row-item',
                ),
                array(
                    'name' => 'tax',
                    'label' => 'LBL_TAX_TOTAL',
                    'css_class' => 'quote-totals-row-item',
                ),
                array(
                    'name' => 'shipping',
                    'css_class' => 'quote-totals-row-item',
                ),
                array(
                    'name' => 'total',
                    'label' => 'LBL_LIST_GRAND_TOTAL',
                    'css_class' => 'quote-totals-row-item',
                ),
            ),
        ),
    ),
);

Modifying Buttons in the Grand Totals Header

The Quotes List Header contains row actions to create quoted line items, comments, and groupings. The actions are identified by the plus icon in the top left of the header. Editing the 'actions' will allow you to add or remove buttons. The following section will outline how these items can be modified.

GrandTotalsActionRow

To modify the Row Actions in the Grand Total Header, you can copy ./modules/Quotes/clients/base/views/quote-data-grand-totals-header/quote-data-grand-totals-header.php to ./custom/modules/Quotes/clients/base/views/quote-data-grand-totals-header/quote-data-grand-totals-header.php or you may create a metadata extension in ./custom/Extension/modules/Quotes/clients/base/views/quote-data-grand-totals-header/. Next, modify the $viewdefs['Quotes']['base']['view']['quote-data-grand-totals-header']['buttons'] index to add or remove your preferred row actions.

Example

The example below will create a new view that extends the QuotesQuoteDataGrandTotalsHeader view and append a button to the Grand Total Header button list.

First, create your custom view type that will extend the QuotesQuoteDataGrandTotalsHeader view.

./custom/modules/Quotes/clients/base/views/quote-data-grand-totals-header/quote-data-grand-totals-header.js

  ({
    extendsFrom: 'QuotesQuoteDataGrandTotalsHeaderView',

    initialize: function(options) {
        this.events = _.extend({}, this.events, options.def.events, {
            'click [name="gth-custom-button"]': 'buttonClicked'
        });

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

    /**
     * Click event
     */
    buttonClicked: function() {
        app.alert.show('success_alert', {
            level: 'success',
            title: 'Grand Total Header Button was clicked!'
        });
    },
})

This code will append a click event to our button named gth-custom-button and trigger the buttonClicked method. Once completed, add your new button array to the grand total header in the $viewdefs['Quotes']['base']['view']['quote-data-grand-totals-header']['buttons'] index.

./custom/modules/Quotes/clients/base/views/quote-data-grand-totals-header/quote-data-grand-totals-header.php

  <?php

$viewdefs['Quotes']['base']['view']['quote-data-grand-totals-header'] = array(
    'buttons' => array(
        array(
            'type' => 'quote-data-actiondropdown',
            'name' => 'panel_dropdown',
            'no_default_action' => true,
            'buttons' => array(
                array(
                    'type' => 'button',
                    'icon' => 'fa-plus',
                    'name' => 'create_qli_button',
                    'label' => 'LBL_CREATE_QLI_BUTTON_LABEL',
                    'acl_action' => 'create',
                    'tooltip' => 'LBL_CREATE_QLI_BUTTON_TOOLTIP',
                ),
                array(
                    'type' => 'button',
                    'icon' => 'fa-plus',
                    'name' => 'create_comment_button',
                    'label' => 'LBL_CREATE_COMMENT_BUTTON_LABEL',
                    'acl_action' => 'create',
                    'tooltip' => 'LBL_CREATE_COMMENT_BUTTON_TOOLTIP',
                ),
                array(
                    'type' => 'button',
                    'icon' => 'fa-plus',
                    'name' => 'create_group_button',
                    'label' => 'LBL_CREATE_GROUP_BUTTON_LABEL',
                    'acl_action' => 'create',
                    'tooltip' => 'LBL_CREATE_GROUP_BUTTON_TOOLTIP',
                ),
                array(
                    'type' => 'button',
                    'icon' => 'fa-plus',
                    'name' => 'gth-custom-button',
                    'label' => 'LBL_GTH_CUSTOM_BUTTON',
                    'acl_action' => 'create',
                    'tooltip' => 'LBL_GTH_CUSTOM_BUTTON_TOOLTIP',
                ),
            ),
        ),
    ),
    ...
);

Finally, create labels under Quotes for the label and tooltip indexes. To accomplish this, create a language extension:

./custom/Extension/modules/Quotes/Ext/Language/en_us.custom-button.php

  <?php

$mod_strings['LBL_GTH_CUSTOM_BUTTON'] = 'Custom Button';
$mod_strings['LBL_GTH_CUSTOM_BUTTON_TOOLTIP'] = 'Custom Button Tooltip';

Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. Your changes will now be reflected in the system.

List Header

The Quotes List Header is a complex view that pulls data from two different locations. The JavaScript controller is located in ./modules/Quotes/clients/base/views/quote-data-list-header/quote-data-list-header.js and pulls the metadata from ./modules/Products/clients/base/views/quote-data-group-list/quote-data-group-list.php. You should note that ProductBundleNotes combines its description field with Product fields in this list.

ListHeader

Modifying Fields in the List Header

To modify the List Header fields, you can copy ./modules/Products/clients/base/views/quote-data-group-list/quote-data-group-list.php to ./custom/modules/Products/clients/base/views/quote-data-group-list/quote-data-group-list.php or you may create a metadata extension in ./custom/Extension/modules/Products/Ext/clients/base/views/quote-data-group-list/. Next, modify the $viewdefs['Products']['base']['view']['quote-data-group-list']['panels'][0]['fields'] index to add or remove your preferred fields.

Example

The example below will remove the "Part Number" (products.mft_part_num) field and replace it with the "Weight" (products.weight) field.

./custom/modules/Products/clients/base/views/quote-data-group-list/quote-data-group-list.php

  <?php

$viewdefs['Products']['base']['view']['quote-data-group-list'] = array(
    'panels' => array(
        array(
            'name' => 'products_quote_data_group_list',
            'label' => 'LBL_PRODUCTS_QUOTE_DATA_LIST',
            'fields' => array(
                array(
                    'name' => 'line_num',
                    'label' => null,
                    'widthClass' => 'cell-xsmall',
                    'css_class' => 'line_num tcenter',
                    'type' => 'line-num',
                    'readonly' => true,
                ),
                array(
                    'name' => 'quantity',
                    'label' => 'LBL_QUANTITY',
                    'widthClass' => 'cell-small',
                    'css_class' => 'quantity',
                    'type' => 'float',
                ),
                array(
                    'name' => 'product_template_name',
                    'label' => 'LBL_ITEM_NAME',
                    'widthClass' => 'cell-large',
                    'type' => 'quote-data-relate',
                    'required' => true,
                ),
                array(
                    'name' => 'weight',
                    'label' => 'LBL_WEIGHT',
                    'type' => 'float',
                ),
                array(
                    'name' => 'discount_price',
                    'label' => 'LBL_DISCOUNT_PRICE',
                    'type' => 'currency',
                    'convertToBase' => true,
                    'showTransactionalAmount' => true,
                    'related_fields' => array(
                        'discount_price',
                        'currency_id',
                        'base_rate',
                    ),
                ),
                array(
                    'name' => 'discount',
                    'type' => 'fieldset',
                    'css_class' => 'quote-discount-percent',
                    'label' => 'LBL_DISCOUNT_AMOUNT',
                    'fields' => array(
                        array(
                            'name' => 'discount_amount',
                            'label' => 'LBL_DISCOUNT_AMOUNT',
                            'type' => 'discount',
                            'convertToBase' => true,
                            'showTransactionalAmount' => true,
                        ),
                        array(
                            'type' => 'discount-select',
                            'name' => 'discount_select',
                            'no_default_action' => true,
                            'buttons' => array(
                                array(
                                    'type' => 'rowaction',
                                    'name' => 'select_discount_amount_button',
                                    'label' => 'LBL_DISCOUNT_AMOUNT',
                                    'event' => 'button:discount_select_change:click',
                                ),
                                array(
                                    'type' => 'rowaction',
                                    'name' => 'select_discount_percent_button',
                                    'label' => 'LBL_DISCOUNT_PERCENT',
                                    'event' => 'button:discount_select_change:click',
                                ),
                            ),
                        ),
                    ),
                ),
                array(
                    'name' => 'total_amount',
                    'label' => 'LBL_LINE_ITEM_TOTAL',
                    'type' => 'currency',
                    'widthClass' => 'cell-medium',
                    'showTransactionalAmount' => true,
                    'related_fields' => array(
                        'total_amount',
                        'currency_id',
                        'base_rate',
                    ),
                ),
            ),
        ),
    ),
);

Next, create the LBL_WEIGHT label under Quotes as this is not included in the stock installation. To accomplish this, we will need to create a language extension:

./custom/Extension/modules/Quotes/Ext/Language/en_us.weight.php

  <?php

$mod_strings['LBL_WEIGHT'] = 'Weight';

If adding a custom field from the Quotes module to the view, you will also need to add that field to the related_fields array as outlined in the Record View documentation below. Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. Your changes will now be reflected in the system.

Modifying Row Actions in the List Header

The Quotes List Header contains row actions to create and delete QLI groupings. The actions are identified by, the "check all" checkbox and vertical ellipsis or "hamburger" icon buttons. Editing the 'actions' will allow you to add more buttons to (or remove from) the "Group Selected" and "Delete Selected" buttons. The following section will outline how these items can be modified.

ListHeaderActionRow

To modify the Row Actions in the List Header, you can copy ./modules/Quotes/clients/base/views/quote-data-list-header/quote-data-list-header.php to ./custom/modules/Quotes/clients/base/views/quote-data-list-header/quote-data-list-header.php or you may create a metadata extension in ./custom/Extension/modules/Quotes/clients/base/views/quote-data-list-header/. Next, modify the $viewdefs['Quotes']['base']['view']['quote-data-list-header']['selection']['actions'] index to add or remove your preferred actions.

Example

The example below will create a new view that extends the QuotesQuoteDataListHeader view and append a row action to the List Header action list.

First, create your custom view type that will extend the QuotesQuoteDataListHeader view.

./custom/modules/Quotes/clients/base/views/quote-data-list-header/quote-data-list-header.js

  ({
    extendsFrom: 'QuotesQuoteDataListHeaderView',

    initialize: function(options) {
        this.events = _.extend({}, this.events, options.def.events, {
            'click [name="lh-custom-button"]': 'actionClicked'
        });

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

    /**
     * Click event
     */
    actionClicked: function() {
        app.alert.show('success_alert', {
            level: 'success',
            title: 'List Header Row Action was clicked!'
        });
    },
})

This code will append a click event to our field named lh-custom-button and trigger the actionClicked method. Once completed, add your new row action to the list header in the $viewdefs['Quotes']['base']['view']['quote-data-list-header']['selection']['actions'] index.

./custom/modules/Quotes/clients/base/views/quote-data-list-header/quote-data-list-header.php

  <?php

$viewdefs['Quotes']['base']['view']['quote-data-list-header'] = array(
    'selection' => array(
        'type' => 'multi',
        'actions' => array(
            array(
                'name' => 'group_button',
                'type' => 'rowaction',
                'label' => 'LBL_CREATE_GROUP_SELECTED_BUTTON_LABEL',
                'tooltip' => 'LBL_CREATE_GROUP_SELECTED_BUTTON_TOOLTIP',
                'acl_action' => 'edit',
            ),
            array(
                'name' => 'massdelete_button',
                'type' => 'rowaction',
                'label' => 'LBL_DELETE_SELECTED_LABEL',
                'tooltip' => 'LBL_DELETE_SELECTED_TOOLTIP',
                'acl_action' => 'delete',
            ),
            array(
                'name' => 'lh-custom-button',
                'type' => 'rowaction',
                'label' => 'LBL_LH_CUSTOM_ACTION',
                'tooltip' => 'LBL_LH_CUSTOM_ACTION_TOOLTIP',
                'acl_action' => 'edit',
            ),
        ),
    ),
);

Finally, create labels under Quotes for the label and tooltip indexes. To accomplish this, create a language extension:

./custom/Extension/modules/Quotes/Ext/Language/en_us.lh-custom-action.php

  <?php

$mod_strings['LBL_LH_CUSTOM_ACTION'] = 'Custom Action';
$mod_strings['LBL_LH_CUSTOM_ACTION_TOOLTIP'] = 'Custom Action Tooltip';

Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. Your changes will now be reflected in the system.

Group Header

The Group Header contains the name and options for each grouping of quoted line items.

GroupHeader

 

Modifying Fields in the Group Header

To modify the Group Header fields, you can copy ./modules/ProductBundles/clients/base/views/quote-data-group-header/quote-data-group-header.php to ./custom/modules/ProductBundles/clients/base/views/quote-data-group-header/quote-data-group-header.php or you may create a metadata extension in ./custom/Extension/modules/Products/clients/base/views/quote-data-group-header/. Next, modify the $viewdefs['ProductBundles']['base']['view']['quote-data-group-header'] index to add or remove your preferred fields.

Example

The example below will append the group total (product_bundles.subtotal) field to the Group Header. It's important to note that when adding additional fields, that changes to the corresponding .hbs file may be necessary to correct any formatting issues.

./custom/modules/ProductBundles/clients/base/views/quote-data-group-header/quote-data-group-header.php

  <?php

$viewdefs['ProductBundles']['base']['view']['quote-data-group-header'] = array(
    ...
    'panels' => array(
        array(
            'name' => 'panel_quote_data_group_header',
            'label' => 'LBL_QUOTE_DATA_GROUP_HEADER',
            'fields' => array(
                array(
                    'name' => 'name',
                    'type' => 'quote-group-title',
                    'css_class' => 'group-name',
                ),
                'subtotal',
            ),
        ),
    ),
);

If adding a custom field from the Quotes module to the view, you will also need to add that field to the related_fields array as outlined in the Record View documentation below. Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. Your changes will now be reflected in the system.

Modifying Row Actions in the Group Header

The Quotes Group Header contains row actions to add QLIs and comments as well as editing and deleting QLI groupings. The actions are identified by, the plus and vertical ellipsis or "hamburger" icon buttons. The following section will outline how these items can be modified.

GroupHeaderRowActions

To modify the buttons in the Group Header, you can copy ./modules/ProductBundles/clients/base/views/quote-data-group-header/quote-data-group-header.php to ./custom/modules/ProductBundles/clients/base/views/quote-data-group-header/quote-data-group-header.php or you may create a metadata extension in ./custom/Extension/modules/ProductBundles/clients/base/views/quote-data-group-header/. Next, modify the $viewdefs['ProductBundles']['base']['view']['quote-data-group-header']['buttons'] index to add or remove your preferred actions.

Example

The example below will create a new view that extends the ProductBundlesQuoteDataGroupHeader view and append a row action to the Group Header vertical elipsis action list.

First, create your custom view type that will extend the ProductBundlesQuoteDataGroupHeader view.

./custom/modules/ProductBundles/clients/base/views/quote-data-group-header/quote-data-group-header.js

  ({
    extendsFrom: 'ProductBundlesQuoteDataGroupHeaderView',

    initialize: function(options) {
        this.events = _.extend({}, this.events, options.def.events, {
            'click [name="gh-custom-action"]': 'actionClicked'
        });

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

    /**
     * Click event
     */
    actionClicked: function() {
        app.alert.show('success_alert', {
            level: 'success',
            title: 'Group Header Button was clicked!'
        });
    },
})

This code will append a click event to our field named gh-custom-action and trigger the actionClicked method. Once completed, add your new row action to the appropriate button group in $viewdefs['Quotes']['base']['view']['quote-data-group-header']['buttons']. For this example we will target a row action to the edit drop down that is identified in the arrays as having a name of "edit-dropdown". Once found, add your new array to the buttons index of that array.

./custom/modules/ProductBundles/clients/base/views/quote-data-group-header/quote-data-group-header.php

  <?php

$viewdefs['ProductBundles']['base']['view']['quote-data-group-header'] = array(
    'buttons' => array(
        array(
            'type' => 'quote-data-actiondropdown',
            'name' => 'create-dropdown',
            'icon' => 'fa-plus',
            'no_default_action' => true,
            'buttons' => array(
                array(
                    'type' => 'rowaction',
                    'css_class' => 'btn-invisible',
                    'icon' => 'fa-plus',
                    'name' => 'create_qli_button',
                    'label' => 'LBL_CREATE_QLI_BUTTON_LABEL',
                    'tooltip' => 'LBL_CREATE_QLI_BUTTON_TOOLTIP',
                    'acl_action' => 'create',
                ),
                array(
                    'type' => 'rowaction',
                    'css_class' => 'btn-invisible',
                    'icon' => 'fa-plus',
                    'name' => 'create_comment_button',
                    'label' => 'LBL_CREATE_COMMENT_BUTTON_LABEL',
                    'tooltip' => 'LBL_CREATE_COMMENT_BUTTON_TOOLTIP',
                    'acl_action' => 'create',
                ),
            ),
        ),
        array(
            'type' => 'quote-data-actiondropdown',
            'name' => 'edit-dropdown',
            'icon' => 'fa-ellipsis-v',
            'no_default_action' => true,
            'buttons' => array(
                array(
                    'type' => 'rowaction',
                    'name' => 'edit_bundle_button',
                    'label' => 'LBL_EDIT_BUTTON',
                    'tooltip' => 'LBL_EDIT_BUNDLE_BUTTON_TOOLTIP',
                    'acl_action' => 'edit',
                ),
                array(
                    'type' => 'rowaction',
                    'name' => 'delete_bundle_button',
                    'label' => 'LBL_DELETE_GROUP_BUTTON',
                    'tooltip' => 'LBL_DELETE_BUNDLE_BUTTON_TOOLTIP',
                    'acl_action' => 'delete',
                ),
                array(
                    'type' => 'rowaction',
                    'name' => 'gh-custom-action',
                    'label' => 'LBL_GH_CUSTOM_ACTION',
                    'tooltip' => 'LBL_GH_CUSTOM_ACTION_TOOLTIP',
                    'acl_action' => 'edit',
                ),
            ),
        ),
    ),
    ...
);

Finally, create labels under Quotes for the label and tooltip indexes. To accomplish this, create a language extension:

./custom/Extension/modules/Quotes/Ext/Language/en_us.gh-custom-action.php

  <?php

$mod_strings['LBL_GH_CUSTOM_ACTION'] = 'Custom Action';
$mod_strings['LBL_GH_CUSTOM_ACTION_TOOLTIP'] = 'Custom Action Tooltip';

Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. Your changes will now be reflected in the system.

Group List

The Group List contains the comments and selected quoted line items.

GroupList

Modifying Fields in the Group List

To modify the Group List fields, you can copy ./modules/Products/clients/base/views/quote-data-group-list/quote-data-group-list.php to ./custom/modules/Products/clients/base/views/quote-data-group-list/quote-data-group-list.php or you may create a metadata extension in ./custom/Extension/modules/Products/Ext/clients/base/views/quote-data-group-list/. Next, modify the $viewdefs['Products']['base']['view']['quote-data-group-list']['panels'] index to add or remove your preferred fields.

Example

The example below will remove the "Manufacturer Part Number" (products.mft_part_num) and append the "Vendor Part Number" (products.vendor_part_num) field to the Group List.

./custom/modules/Products/clients/base/views/quote-data-group-list/quote-data-group-list.php

  <?php

$viewdefs['Products']['base']['view']['quote-data-group-list'] = array(
    'panels' => array(
        array(
            'name' => 'products_quote_data_group_list',
            'label' => 'LBL_PRODUCTS_QUOTE_DATA_LIST',
            'fields' => array(
                array(
                    'name' => 'line_num',
                    'label' => null,
                    'widthClass' => 'cell-xsmall',
                    'css_class' => 'line_num tcenter',
                    'type' => 'line-num',
                    'readonly' => true,
                ),
                array(
                    'name' => 'quantity',
                    'label' => 'LBL_QUANTITY',
                    'widthClass' => 'cell-small',
                    'css_class' => 'quantity',
                    'type' => 'float',
                ),
                array(
                    'name' => 'product_template_name',
                    'label' => 'LBL_ITEM_NAME',
                    'widthClass' => 'cell-large',
                    'type' => 'quote-data-relate',
                    'required' => true,
                ),
                array(
                    'name' => 'vendor_part_num',
                    'label' => 'LBL_VENDOR_PART_NUM',
                    'type' => 'base',
                ),
                array(
                    'name' => 'discount_price',
                    'label' => 'LBL_DISCOUNT_PRICE',
                    'type' => 'currency',
                    'convertToBase' => true,
                    'showTransactionalAmount' => true,
                    'related_fields' => array(
                        'discount_price',
                        'currency_id',
                        'base_rate',
                    ),
                ),
                array(
                    'name' => 'discount',
                    'type' => 'fieldset',
                    'css_class' => 'quote-discount-percent',
                    'label' => 'LBL_DISCOUNT_AMOUNT',
                    'fields' => array(
                        array(
                            'name' => 'discount_amount',
                            'label' => 'LBL_DISCOUNT_AMOUNT',
                            'type' => 'discount',
                            'convertToBase' => true,
                            'showTransactionalAmount' => true,
                        ),
                        array(
                            'type' => 'discount-select',
                            'name' => 'discount_select',
                            'no_default_action' => true,
                            'buttons' => array(
                                array(
                                    'type' => 'rowaction',
                                    'name' => 'select_discount_amount_button',
                                    'label' => 'LBL_DISCOUNT_AMOUNT',
                                    'event' => 'button:discount_select_change:click',
                                ),
                                array(
                                    'type' => 'rowaction',
                                    'name' => 'select_discount_percent_button',
                                    'label' => 'LBL_DISCOUNT_PERCENT',
                                    'event' => 'button:discount_select_change:click',
                                ),
                            ),
                        ),
                    ),
                ),
                array(
                    'name' => 'total_amount',
                    'label' => 'LBL_LINE_ITEM_TOTAL',
                    'type' => 'currency',
                    'widthClass' => 'cell-medium',
                    'showTransactionalAmount' => true,
                    'related_fields' => array(
                        'total_amount',
                        'currency_id',
                        'base_rate',
                    ),
                ),
            ),
        ),
    ),
);

Next, create the LBL_VENDOR_PART_NUM label under Quotes as this is not included in the stock installation. To accomplish this, we will need to create a language extension:

./custom/Extension/modules/Quotes/Ext/Language/en_us.vendor.php

  <?php

$mod_strings['LBL_VENDOR_PART_NUM'] = 'Vendor Part Number';

If adding a custom field from the Quotes module to the view, you will also need to add that field to the related_fields array as outlined in the Record View documentation below. Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. Your changes will now be reflected in the system.

Modifying Row Actions in the Group List

The Quotes Group List contains row actions to add/remove QLIs and comments. The actions are identified by, the vertical ellipsis icon buttons. The following section will outline how these items can be modified.

GroupListActionRow

To modify the buttons in the Group List , you can copy ./modules/ProductBundles/clients/base/views/quote-data-group-list/quote-data-group-list.php to ./custom/modules/ProductBundles/clients/base/views/quote-data-group-list/quote-data-group-list.php or you may create a metadata extension in ./custom/Extension/modules/ProductBundles/clients/base/views/quote-data-group-list/. Next, modify the $viewdefs['ProductBundles']['base']['view']['quote-data-group-list']['selection'] index to add or remove your preferred actions.

Example

The example below will create a new view that extends the ProductBundlesQuoteDataGroupList view and append a row action to the Group List's vertical elipsis action list.

First, create your custom view type that will extend the ProductBundlesQuoteDataGroupList view. This will contain the JavaScript for your action.

./custom/modules/ProductBundles/clients/base/views/quote-data-group-list/quote-data-group-list.js

  ({
    extendsFrom: 'ProductBundlesQuoteDataGroupListView',

    initialize: function(options) {
        this.events = _.extend({}, this.events, options.def.events, {
            'click [name="gl-custom-action"]': 'actionClicked'
        });

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

    /**
     * Click event
     */
    actionClicked: function() {
        app.alert.show('success_alert', {
            level: 'success',
            title: 'List Header Row Action was clicked!'
        });
    },
})

This code will append a click event to our field named gl-custom-action and trigger the actionClicked method. Once completed, add your new row action to the group list in the $viewdefs['ProductBundles']['base']['view']['quote-data-group-list']['selection']['actions'] index.

./custom/modules/ProductBundles/clients/base/views/quote-data-group-list/quote-data-group-list.php

  <?php

$viewdefs['ProductBundles']['base']['view']['quote-data-group-list'] = array(
    'selection' => array(
        'type' => 'multi',
        'actions' => array(
            array(
                'type' => 'rowaction',
                'name' => 'edit_row_button',
                'label' => 'LBL_EDIT_BUTTON',
                'tooltip' => 'LBL_EDIT_BUTTON',
                'acl_action' => 'edit',
            ),
            array(
                'type' => 'rowaction',
                'name' => 'delete_row_button',
                'label' => 'LBL_DELETE_BUTTON',
                'tooltip' => 'LBL_DELETE_BUTTON',
                'acl_action' => 'delete',
            ),
            array(
                'type' => 'rowaction',
                'name' => 'gl-custom-action',
                'label' => 'LBL_GL_CUSTOM_ACTION',
                'tooltip' => 'LBL_GL_CUSTOM_ACTION_TOOLTIP',
                'acl_action' => 'edit',
            ),
        ),
    ),
);

Finally, create labels under Quotes for the label and tooltip indexes. To accomplish this, create a language extension:

./custom/Extension/modules/Quotes/Ext/Language/en_us.gh-custom-action.php

  <?php

$mod_strings['LBL_GL_CUSTOM_ACTION'] = 'Custom Action';
$mod_strings['LBL_GL_CUSTOM_ACTION_TOOLTIP'] = 'Custom Action Tooltip';

Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. Your changes will now be reflected in the system.

The Group Footer contains the total for each grouping of quoted line items.

GroupFooter

To modify the GroupFooter fields, you can copy ./modules/ProductBundles/clients/base/views/quote-data-group-footer/quote-data-group-footer.php to ./custom/modules/ProductBundles/clients/base/views/quote-data-group-footer/quote-data-group-footer.php or you may create a metadata extension in ./custom/Extension/modules/ProductBundles/clients/base/views/quote-data-group-footer/. Next, modify the $viewdefs['ProductBundles']['base']['view']['quote-data-group-footer'] index to add or remove your preferred fields.

Example

The example below will append the bundle stage (product_bundles.bundle_stage) field to the Group Footer. It's important to note that when adding additional fields, that changes to the corresponding .hbs file may be necessary to correct any formatting issues.

./custom/modules/ProductBundles/clients/base/views/quote-data-group-header/quote-data-group-footer.php

  <?php

$viewdefs['ProductBundles']['base']['view']['quote-data-group-footer'] = array(
    'panels' => array(
        array(
            'name' => 'panel_quote_data_group_footer',
            'label' => 'LBL_QUOTE_DATA_GROUP_FOOTER',
            'fields' => array(
                'bundle_stage',
                array(
                    'name' => 'new_sub',
                    'label' => 'LBL_GROUP_TOTAL',
                    'type' => 'currency',
                ),
            ),
        ),
    ),
);

If adding custom fields from the Product Bundles module to the view, you will also need to add that field to the related_fields array as outlined in the Record View documentation below. Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. Your changes will now be reflected in the system.

The Grand Total Footer contains the calculated totals for the quote. It mimics the information found in the Grand Total Header.

GrandTotalsFooter

To modify the Grand Totals Footer fields, you can copy ./modules/Quotes/clients/base/views/quote-data-grand-totals-footer/quote-data-grand-totals-footer.php to ./custom/modules/Quotes/clients/base/views/quote-data-grand-totals-footer/quote-data-grand-totals-footer.php or you may create a metadata extension in ./custom/Extension/modules/Quotes/clients/base/views/quote-data-grand-totals-footer/. Next, modify the $viewdefs['Quotes']['base']['view']['quote-data-grand-totals-footer']['panels'][0]['fields'] index to add or remove your preferred fields.

Example

The example below will remove the "Shipping" field (quotes.shipping) field from the layout.

./custom/modules/Quotes/clients/base/views/quote-data-grand-totals-footer/quote-data-grand-totals-footer.php

  <?php

$viewdefs['Quotes']['base']['view']['quote-data-grand-totals-footer'] = array(
    'panels' => array(
        array(
            'name' => 'panel_quote_data_grand_totals_footer',
            'label' => 'LBL_QUOTE_DATA_GRAND_TOTALS_FOOTER',
            'fields' => array(
                'quote_num',
                array(
                    'name' => 'new_sub',
                    'type' => 'currency',
                ),
                array(
                    'name' => 'tax',
                    'type' => 'currency',
                    'related_fields' => array(
                        'taxrate_value',
                    ),
                ),
                array(
                    'name' => 'total',
                    'label' => 'LBL_LIST_GRAND_TOTAL',
                    'type' => 'currency',
                    'css_class' => 'grand-total',
                ),
            ),
        ),
    ),
);

If adding custom fields from the Quotes module to the view, you will also need to add that field to the related_fields array as outlined in the Record View documentation below. Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. Your changes will now be reflected in the system.

Record View

The record view for Quotes is updated and used like the standard Record View for all modules. The one major difference to note is that the JavaScript models used by the previous views above, are derived from the Model defined in the record view metadata.

In the Record View metadata, the first panel panel_header containing the picture and name fields for the Quote record, contains a related_fields property in the name definition that defines the related Product Bundles and Products data that is pulled into the JavaScript model. When custom fields are added to the above mentioned views you will need to add them to the related_fields property in their respective related module so that they are properly loaded during the page load.

The following example adds the custom_product_bundle_field_c field from the Product Bundles module, and the custom_product_field_c from the Products module into the retrieved Model.

./custom/modules/Quotes/clients/base/views/record/record.php

  <?php
$viewdefs['Quotes']['base']['view']['record'] = array(
    ...
    'panels' => array(
        array(
            'name' => 'panel_header',
            'label' => 'LBL_PANEL_HEADER',
            'header' => true,
            'fields' => array(
                array(
                    'name' => 'picture',
                    'type' => 'avatar',
                    'size' => 'large',
                    'dismiss_label' => true,
                    'readonly' => true,
                ),
                array(
                   'name' => 'name',
                   'events' => array(
                       'keyup' => 'update:quote',
                   ),
                   'related_fields' => array(
                       array(
                           'name' => 'bundles',
                           'fields' => array(
                              'id',
                              'bundle_stage',
                              'currency_id',
                              'base_rate',
                              'currencies',
                              'name',
                              'deal_tot',
                              'deal_tot_usdollar',
                              'deal_tot_discount_percentage',
                              'new_sub',
                              'new_sub_usdollar',
                              'position',
                              'related_records',
                              'shipping',
                              'shipping_usdollar',
                              'subtotal',
                              'subtotal_usdollar',
                              'tax',
                              'tax_usdollar',
                              'taxrate_id',
                              'team_count',
                              'team_count_link',
                              'team_name',
                              'taxable_subtotal',
                              'total',
                              'total_usdollar',
                              'default_group',
                              'custom_product_bundle_field_c',
                              array(
                                  'name' => 'product_bundle_items',
                                  'fields' => array(
                                      'name',
                                      'quote_id',
                                      'description',
                                      'quantity',
                                      'product_template_name',
                                      'product_template_id',
                                      'deal_calc',
                                      'mft_part_num',
                                      'discount_price',
                                      'discount_amount',
                                      'tax',
                                      'tax_class',
                                      'subtotal',
                                      'position',
                                      'currency_id',
                                      'base_rate',
                                      'discount_select',
                                      'custom_product_field_c',
                                  ),
                                  'max_num' => -1,
                              ),
                          ),
                          'max_num' => -1,
                          'order_by' => 'position:asc',
                      ),
                  ),
              ),
          ),
      ),
      ...
  ),
);

Quote PDFs

Ungrouped Quoted Line Items and Notes

As of Sugar 7.9, the quotes module allows users to add quoted line items and notes without first adding a group. Adding at least one group to your quote was mandatory in earlier versions. This document will cover how to update your custom PDF templates to support ungrouped QLIs and notes. Ungrouped items are technically added to a default group. The goal is to exclude this group when no items exist in it.

PDF Manager Templates

In your PDF Manager templates, you may have code similar to what is shown below to iterate over the groups in a quote:

  {foreach from=$product_bundles item="bundle"}
    ....
{/foreach}

To correct this for 7.9, we will need to add an if statement to check to see if the group is empty. If it is empty, it will be ignored in your PDF. The benefit is that it will also exclude any other empty groups in your quote and clean the generated PDF.

  {foreach from=$product_bundles item="bundle"}
    {if $bundle.products|@count}
        ....
    {/if}
{/foreach}

Smarty Templates

In Smarty templates, you may have code similar to what is shown below to iterate over the groups in a quote:

  {literal}{foreach from=$product_bundles item="bundle"}{/literal}
    ...
{literal}{/foreach}{/literal}

To correct this for 7.9, we will need to add an if statement to check to see if the group is empty. If it is empty, it will be ignored in your PDF. The benefit is that it will also exclude any other empty groups in your quote and clean the generated PDF.

  {literal}{foreach from=$product_bundles item="bundle"}{/literal}
    {literal}{if $bundle.products|@count}{/literal}
        ...
    {literal}{/if}{/literal}
{literal}{/foreach}{/literal}

Creating Custom PDF Templates

With Sugar, there are generally two routes that can be taken to create custom PDF templates. The first and most recommended route is to use the PDF Manager found in the Admin > PDF Manager. The second route is to extend the Sugarpdf class and write your own template using PHP and the TCPDF library. This method should only be used if you are unable to accomplish your business needs through the PDF Manager.

Creating the TCPDF Template

The first step is to create your custom TCPDF template in ./custom/modules/Quotes/sugarpdf/. For our example, we will extend QuotesSugarpdfStandard, found at ./modules/Quotes/sugarpdf/sugarpdf.standard.php, to a new file in ./custom/modules/Quotes/sugarpdf/ to create a custom invoice. The QuotesSugarpdfStandard class sets up our general quote requirements and extends the Sugarpdf class. Technical documentation on templating can be found in the Sugar PDF documentation. Our class will be named QuotesSugarpdfCustomInvoice as the naming must be in the format of <module>Sugarpdf<pdf view>.

./custom/modules/Quotes/sugarpdf/sugarpdf.custominvoice.php

  <?php

if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');

require_once('modules/Quotes/sugarpdf/sugarpdf.standard.php');

class QuotesSugarpdfCustomInvoice extends QuotesSugarpdfStandard
{
    function preDisplay()
    {
        global $mod_strings, $timedate;
        parent::preDisplay();

        $quote[0]['TITLE'] = $mod_strings['LBL_PDF_INVOICE_NUMBER'];
        $quote[1]['TITLE'] = $mod_strings['LBL_PDF_QUOTE_DATE'];
        $quote[2]['TITLE'] = $mod_strings['LBL_PURCHASE_ORDER_NUM'];
        $quote[3]['TITLE'] = $mod_strings['LBL_PAYMENT_TERMS'];
        $quote[0]['VALUE']['value'] = format_number_display($this->bean->quote_num, $this->bean->system_id);
        $quote[1]['VALUE']['value'] = $timedate->nowDate();
        $quote[2]['VALUE']['value'] = $this->bean->purchase_order_num;
        $quote[3]['VALUE']['value'] = $this->bean->payment_terms;
        // these options override the params of the $options array.
        $quote[0]['VALUE']['options'] = array();
        $quote[1]['VALUE']['options'] = array();
        $quote[2]['VALUE']['options'] = array();
        $quote[3]['VALUE']['options'] = array();

        $html = $this->writeHTMLTable($quote, true, $this->headerOptions);
        $this->SetHeaderData(PDF_HEADER_LOGO, PDF_HEADER_LOGO_WIDTH, $mod_strings['LBL_PDF_INVOICE_TITLE'], $html);
    }

    /**
     * This method build the name of the PDF file to output.
     */
    function buildFileName()
    {
        global $mod_strings;

        $fileName = preg_replace("#[^A-Z0-9\-_\.]#i", "_", $this->bean->shipping_account_name);

        if (!empty($this->bean->quote_num)) {
            $fileName .= "_{$this->bean->quote_num}";
        }

        $fileName = $mod_strings['LBL_INVOICE'] . "_{$fileName}.pdf";

        if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/MSIE/", $_SERVER['HTTP_USER_AGENT'])) {
            //$fileName = $locale->translateCharset($fileName, $locale->getExportCharset());
            $fileName = urlencode($fileName);
        }

        $this->fileName = $fileName;
    }
}

Next, register the layout. This is a two step process. The first step is to create ./custom/modules/Quotes/Layouts.php which will map our view action to the physical PHP file.

./custom/modules/Quotes/Layouts.php

  <?php

$layouts['CustomInvoice'] = 'custom/modules/Quotes/sugarpdf/sugarpdf.custominvoice.php';

The second step is to update the layouts_dom dropdown list by navigating to Admin > Dropdown Editor. Once there, create a new entry in the list with an Item Name of "CustomInvoice" and a Display Label of your choice. It's also recommended to remove the Quote and Invoice Templates if they are not being used to avoid confusion. This will create a ./custom/Extension/application/Ext/Language/en_us.sugar_layouts_dom.php file that should look similar to:

./custom/Extension/application/Ext/Language/en_us.sugar_layouts_dom.php

  <?php

$app_list_strings['layouts_dom']=array (
  'CustomInvoice' => 'Custom Invoice',
);

Once the layout is registered, we need to extend the pdfaction field for the Quotes module to render our custom pdf templates in the record view. An example of this is shown below:

./custom/modules/Quotes/clients/base/fields/pdfaction/pdfaction.js

  
/**
 * @class View.Fields.Base.Quotes.PdfactionField
 * @alias SUGAR.App.view.fields.BaseQuotesPdfactionField
 * @extends View.Fields.Base.PdfactionField
 */
({
    extendsFrom: 'PdfactionField',

    /**
     * @inheritdoc
     * Create PDF Template collection in order to get available template list.
     */
    initialize: function(options) {
        this._super('initialize', [options]);
    },

    /**
     * Define proper filter for PDF template list.
     * Fetch the collection to get available template list.
     * @private
     */
    _fetchTemplate: function() {
        this.fetchCalled = true;
        var collection = this.templateCollection;
        collection.filterDef = {'$and': [{
            'base_module': this.module
        }, {
            'published': 'yes'
        }]};
        collection.fetch({
            success: function(){
                var models = [];
                _.each(app.lang.getAppListStrings('layouts_dom'), function(template, id){
                    var model = new Backbone.Model({
                        id: id,
                        name: template
                    });
                    models.push(model);
                });
                collection.add(models);
            }
        });
    },

    /**
     * Build download link url.
     *
     * @param {String} templateId PDF Template id.
     * @return {string} Link url.
     * @private
     */
    _buildDownloadLink: function(templateId) {
        var sugarpdf = this._isUUID(templateId)? 'pdfmanager' : templateId;
        var urlParams = $.param({
            'action': 'sugarpdf',
            'module': this.module,
            'sugarpdf': sugarpdf,
            'record': this.model.id,
            'pdf_template_id': templateId
        });
        return '?' + urlParams;
    },

    /**
     * Build email pdf link url.
     *
     * @param {String} templateId PDF Template id.
     * @return {string} Email pdf url.
     * @private
     */
    _buildEmailLink: function(templateId) {
        var sugarpdf = this._isUUID(templateId)? 'pdfmanager' : templateId;
        return '#' + app.bwc.buildRoute(this.module, null, 'sugarpdf', {
                'sugarpdf': sugarpdf,
                'record': this.model.id,
                'pdf_template_id': templateId,
                'to_email': '1'
            });
    },

    /**
     * tests to see if a templateId is a uuid or template name.
     *
     * @param {String} templateId PDF Template id
     * @return {boolean} true if uuid, false if not
     * @private
     */
    _isUUID: function(templateId) {
        var regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
        return regex.test(templateId);
    }
})

Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. Your changes will now be reflected in the system and navigating to a url in the format of index.php?module=Quotes&record=<record id>&action=sugarpdf&sugarpdf=CustomInvoice will generate the pdf document.

Hiding PDF Buttons

In some circumstances, users may not have a need to generate PDFs from Quotes. If this should occur, a developer can copy ./modules/Quotes/clients/base/views/record/record.php to ./custom/modules/Quotes/clients/base/views/record/record.php if it doesn't already exist. Next, find the array with a name of main_dropwdown in the $viewdefs['Quotes']['base']['view']['record']['buttons'] index. Once found, you will then need to locate the buttons index within that array. You will then need to remove the following arrays from that index:

  array(
    'type' => 'pdfaction',
    'name' => 'download-pdf',
    'label' => 'LBL_PDF_VIEW',
    'action' => 'download',
    'acl_action' => 'view',
),
array(
    'type' => 'pdfaction',
    'name' => 'email-pdf',
    'label' => 'LBL_PDF_EMAIL',
    'action' => 'email',
    'acl_action' => 'view',
),

Your file should look similar to what is shown below:

custom/modules/Quotes/clients/base/views/record/record.php

  <?php

$viewdefs['Quotes']['base']['view']['record'] = array(
    'buttons' => array(
        array(
            'type' => 'button',
            'name' => 'cancel_button',
            'label' => 'LBL_CANCEL_BUTTON_LABEL',
            'css_class' => 'btn-invisible btn-link',
            'showOn' => 'edit',
            'events' => array(
                'click' => 'button:cancel_button:click',
            ),
        ),
        array(
            'type' => 'rowaction',
            'event' => 'button:save_button:click',
            'name' => 'save_button',
            'label' => 'LBL_SAVE_BUTTON_LABEL',
            'css_class' => 'btn btn-primary',
            'showOn' => 'edit',
            'acl_action' => 'edit',
        ),
        array(
            'type' => 'actiondropdown',
            'name' => 'main_dropdown',
            'primary' => true,
            'showOn' => 'view',
            'buttons' => array(
                array(
                    'type' => 'rowaction',
                    'event' => 'button:edit_button:click',
                    'name' => 'edit_button',
                    'label' => 'LBL_EDIT_BUTTON_LABEL',
                    'acl_action' => 'edit',
                ),
                array(
                    'type' => 'shareaction',
                    'name' => 'share',
                    'label' => 'LBL_RECORD_SHARE_BUTTON',
                    'acl_action' => 'view',
                ),
                array(
                    'type' => 'divider',
                ),
                array(
                    'type' => 'convert-to-opportunity',
                    'event' => 'button:convert_to_opportunity:click',
                    'name' => 'convert_to_opportunity_button',
                    'label' => 'LBL_QUOTE_TO_OPPORTUNITY_LABEL',
                    'acl_module' => 'Opportunities',
                    'acl_action' => 'create',
                ),
                array(
                    'type' => 'divider',
                ),
                array(
                    'type' => 'rowaction',
                    'event' => 'button:historical_summary_button:click',
                    'name' => 'historical_summary_button',
                    'label' => 'LBL_HISTORICAL_SUMMARY',
                    'acl_action' => 'view',
                ),
                array(
                    'type' => 'rowaction',
                    'event' => 'button:audit_button:click',
                    'name' => 'audit_button',
                    'label' => 'LNK_VIEW_CHANGE_LOG',
                    'acl_action' => 'view',
                ),
                array(
                    'type' => 'rowaction',
                    'event' => 'button:find_duplicates_button:click',
                    'name' => 'find_duplicates_button',
                    'label' => 'LBL_DUP_MERGE',
                    'acl_action' => 'edit',
                ),
                array(
                    'type' => 'divider',
                ),
                array(
                    'type' => 'rowaction',
                    'event' => 'button:delete_button:click',
                    'name' => 'delete_button',
                    'label' => 'LBL_DELETE_BUTTON_LABEL',
                    'acl_action' => 'delete',
                ),
            ),
        ),
        array(
            'name' => 'sidebar_toggle',
            'type' => 'sidebartoggle',
        ),
    ),
    ...
);

Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. Your changes will now be reflected in the system.