Implement Search in transaction pages using PO# or memo SCA Extension

An extension is created to search Sales orders based on PO# and quotes based on memo. The transaction column is enabled in SCA configuration. The extension extends list header js, quote list and purchase list. A template is also being overridden

define(
    'SCG.SearchTransaction.ListHeaderCustom', [
        'SC.Configuration', 'RecordViews.View', 'OrderHistory.List.Tracking.Number.View', 'Quote.List.View', 'OrderHistory.List.View', 'Overview.Home.View', 'ListHeader.View', 'Quote.Collection', 'OrderHistory.Collection', 'search_transaction_list_header.tpl', 'AjaxRequestsKiller', 'Handlebars', 'Backbone.CollectionView'
    ],
    function(
        Configuration, RecordViewsView, OrderHistoryListTrackingNumberView, QuoteListView, OrderHistoryListView, OverviewHomeView, ListHeaderView, QuoteCollection, OrderHistoryCollection, search_transaction_list_header_tpl, AjaxRequestsKiller, Handlebars, BackboneCollectionView
    ) {
        'use strict';

        return {
            mountToApp: function mountToApp(container) {
                // using the 'Layout' component we add a new child view inside the 'Header' existing view 
                // (there will be a DOM element with the HTML attribute data-view="Header.Logo")
                // more documentation of the Extensibility API in
                // https://system.netsuite.com/help/helpcenter/en_US/APIs/SuiteCommerce/Extensibility/Frontend/index.html

                /** @type {LayoutComponent} */
                var layout = container.getComponent('Layout');

                // if(layout)
                // {
                //     layout.addChildView('Header.Logo', function() { 
                //         return new ListHeaderCustomView({ container: container });
                //     });
                // }

                _.extend(QuoteListView.prototype, {
                    setupListHeader: _.wrap(QuoteListView.setupListHeader, function(fn) { //EXTENDED TO SHOW SEARCH FIELD IN QUOTE LIST PAGE

                        // manges sorting and filtering of the collection
                        this.listHeader = new ListHeaderView({
                            view: this,
                            application: this.application,
                            collection: this.collection,
                            filters: this.filterOptions,
                            sorts: this.sortOptions,
                            allowEmptyBoundaries: true,
                            showSearch: true, //TO SHOW SEARCH FIELD
                            searchField: 'Memo'
                        });
                    })
                });


                _.extend(OrderHistoryListView.prototype, {
                    initialize: _.wrap(OrderHistoryListView.prototype.initialize, function wrappedInitialize(fn, options) {
                        fn.apply(this, _.toArray(arguments).slice(1));
                        // Manages sorting and filtering of the collection
                        this.listHeader = new ListHeaderView({
                            view: this,
                            application: this.application,
                            collection: this.collection,
                            sorts: this.sortOptions,
                            rangeFilter: 'date',
                            rangeFilterLabel: _('From').translate(),
                            hidePagination: true,
                            allowEmptyBoundaries: true,
                            showSearch: true, //TO SHOW SEARCH FIELD
                            searchField: 'PO#'
                        });
                    })
                });

                _.extend(ListHeaderView.prototype.events, {
                    'submit .list-header-search-form': 'searchHandler'

                });

                _.extend(ListHeaderView.prototype, {
                    template: search_transaction_list_header_tpl,
                    initialize: _.wrap(ListHeaderView.prototype.initialize, function wrappedInitialize(fn, options) { //GETS SEARCH TEXT
                        fn.apply(this, _.toArray(arguments).slice(1));
                        this.searchFilter = options.showSearch;
                        this.searchField = options.searchField; //PO or Memo
                    }),

                    getContext: _.wrap(ListHeaderView.prototype.getContext, function wrappedInitialize(fn, options) {

                        var res = fn.apply(this, _.toArray(arguments).slice(1));
                        //console.log("this", this);
                        _.extend(res, {
                            showHeader: res.showHeader || this.search !== '', //ADD SEARCH TEXT TO FIELD EVEN AFTER SUBMIT
                            searchFilter: this.searchFilter,
                            search: this.search,
                            searchField: this.searchField
                        });
                        return res;
                    }),

                    searchHandler: function(e) {
                        e.preventDefault();

                        var $form = jQuery(e.currentTarget);
                        // unselect all elements
                        this.unselectAll({
                            silent: true
                        });
                        // sets the selected filter
                        this.search = $form.find('.list-header-view-accordion-body-input-search').val();
                        // updates the url and the collection
                        this.updateUrl();
                    },
                    updateUrl: function() {
                        var url = Backbone.history.fragment;


                        this.searchParam = (this.search || '').replace(/ /g, '!').replace(/\t/g, ''); //REPLACE SPACE AND TABS SO THAT SEARCH WILL BE DONE IN ONE GO
                        url = !this.search ?
                            _.removeUrlParameter(url, 'search') :
                            _.setUrlParameter(url, 'search', this.searchParam); //UPDATE URL IF HAVE SEARCH TEXT



                        // if the selected filter is the default one
                        //   remove the filter parameter
                        // else change it for the selected value
                        url = this.isDefaultFilter(this.selectedFilter) ?
                            _.removeUrlParameter(url, 'filter') :
                            _.setUrlParameter(url, 'filter', _.isFunction(this.selectedFilter.value) ? this.selectedFilter.value.apply(this.view) : this.selectedFilter.value);
                        // if the selected sort is the default one
                        //   remove the sort parameter
                        // else change it for the selected value
                        url = this.isDefaultSort(this.selectedSort) ? _.removeUrlParameter(url, 'sort') : _.setUrlParameter(url, 'sort', this.selectedSort.value);
                        // if the selected order is the default one
                        //   remove the order parameter
                        // else change it for the selected value
                        url = this.order === 1 ? _.removeUrlParameter(url, 'order') : _.setUrlParameter(url, 'order', 'inverse');
                        // if range from and range to are set up
                        //   change them in the url
                        // else remove the parameter
                        if (this.selectedRange) {
                            url = this.selectedRange.from || this.selectedRange.to ? _.setUrlParameter(url, 'range', (this.selectedRange.from || '') + 'to' + (this.selectedRange.to || '')) : _.removeUrlParameter(url, 'range');
                        }

                        url = _.removeUrlParameter(url, 'page');
                        this.page = 1;

                        // just go there already, but warn no one
                        Backbone.history.navigate(url, { trigger: false });

                        return this.updateCollection();
                    },
                    updateCollection: function() {
                        var range = null;
                        var collection = this.collection;
                        if (this.selectedRange) {
                            //@class RangeFilter
                            //If there is no date selected i keep the range empty in order to get "transactions" dated in the future
                            range = {
                                //@property {String} from
                                from: this.selectedRange.from || (this.allowEmptyBoundaries ? '' : this.rangeFilterOptions.fromMin),
                                //@property {String} to
                                to: this.selectedRange.to || (this.allowEmptyBoundaries ? '' : this.rangeFilterOptions.toMax)
                            };
                        }
                        //@lass Collection.Filter
                        collection.update && collection.update({
                            searchText: this.searchParam
                                //@property {value:String} filter
                                ,
                            filter: this.selectedFilter
                                //@property {RangeFilter} range
                                ,
                            range: range
                                //@property {value:String} sort
                                ,
                            sort: this.selectedSort
                                //@property {String} order
                                ,
                            order: this.order,
                            page: this.page,
                            status: this.view.status
                                //@property {Number} killerId
                                ,
                            killerId: AjaxRequestsKiller.getKillerId()
                        }, this);

                        //@class ListHeader.View
                        return this;
                    },
                });

                _.extend(QuoteCollection.prototype, {
                    update: function update(options) {

                        this.searchText = options.searchText;
                        var range = options.range || {},
                            from = range.from && _.stringToDate(range.from),
                            to = range.to && _.stringToDate(range.to);

                        this.fetch({
                            data: {
                                filter: options.filter.value,
                                sort: options.sort.value,
                                order: options.order,
                                from: from || null,
                                to: to || null,
                                page: options.page,
                                searchText: this.searchText
                            },
                            reset: true,
                            killerId: options.killerId
                        });
                    },

                    parse: function parse(response) {
                        this.totalRecordsFound = response.totalRecordsFound;
                        this.recordsPerPage = response.recordsPerPage;
                        this.isSearch = response.isSearch;
                        //console.log('search',this.isSearch);
                        return response.records;
                    }
                });

                _.extend(OrderHistoryCollection.prototype, {
                    update: function(options) {
                        var range = options.range || {};
                        this.searchText = options.searchText;
                        var data = {
                            filter: this.customFilters || options.filter && options.filter.value,
                            sort: options.sort.value,
                            order: options.order,
                            from: range.from,
                            to: range.to,
                            page: options.page,
                            searchText: options.searchText
                        };
                        this.fetch({
                            data: data,
                            reset: true,
                            killerId: options.killerId
                        });
                        //console.log('this',this);
                    },
                    parse: function(response) {
                        var self = this;
                        this.totalRecordsFound = response.totalRecordsFound;
                        this.recordsPerPage = response.recordsPerPage;
                        this.isSearch = response.isSearch;

                        var records = response.records;
                        // this.searchText = (this.searchText || '').trim();

                        // if (this.searchText != undefined && this.searchText != '' && this.searchText != null) {
                        //     records = _.filter(records, function(eachRec) {
                        //         if (eachRec.otherrefnum) {
                        //             var otherrefnum = eachRec.otherrefnum.toLowerCase().replace(/ /g, '!');
                        //             var key = self.searchText.toLowerCase();
                        //             if (otherrefnum.indexOf(key) >= 0) {
                        //                 return eachRec;
                        //             }
                        //         }
                        //     });
                        // }

                        return records;
                    }
                });



                _.extend(OverviewHomeView.prototype.childViews, { //TO ADD MEMO FIELD IN RECENT PURCHASE

                    'Order.History.Results': function() {
                        var self = this,
                            records_collection = new Backbone.Collection(this.collection.map(function(order) {

                                var dynamic_column;

                                if (self.isSCISIntegrationEnabled) {
                                    dynamic_column = {
                                        label: _('Origin:').translate(),
                                        type: 'origin',
                                        name: 'origin',
                                        value: _.findWhere(Configuration.get('transactionRecordOriginMapping'), { origin: order.get('origin') }).name
                                    };
                                } else {
                                    dynamic_column = {
                                        label: _('Status:').translate(),
                                        type: 'status',
                                        name: 'status',
                                        value: order.get('status').name
                                    };
                                }

                                var columns = [{
                                        label: _('Date:').translate(),
                                        type: 'date',
                                        name: 'date',
                                        value: order.get('trandate')
                                    }, {
                                        label: _('Amount:').translate(),
                                        type: 'currency',
                                        name: 'amount',
                                        value: order.get('amount_formatted')
                                    },
                                    {
                                        label: _('PO Number:').translate(),
                                        type: 'po',
                                        name: 'po',
                                        value: order.get('otherrefnum')
                                    },
                                    {
                                        type: 'tracking-number',
                                        name: 'trackingNumber',
                                        compositeKey: 'OrderHistoryListTrackingNumberView',
                                        composite: new OrderHistoryListTrackingNumberView({
                                            model: new Backbone.Model({
                                                trackingNumbers: order.get('trackingnumbers')
                                            }),
                                            showContentOnEmpty: true,
                                            contentClass: '',
                                            collapseElements: true
                                        })
                                    }
                                ];

                                columns.splice(2, 0, dynamic_column);

                                var model = new Backbone.Model({
                                    title: new Handlebars.SafeString(_('<span class="tranid">$(0)</span>').translate(order.get('tranid'))),
                                    touchpoint: 'customercenter',
                                    detailsURL: '/purchases/view/' + order.get('recordtype') + '/' + order.get('internalid'),
                                    recordType: order.get('recordtype'),
                                    id: order.get('internalid'),
                                    internalid: order.get('internalid'),
                                    trackingNumbers: order.get('trackingnumbers'),
                                    columns: columns
                                });

                                return model;
                            }));

                        return new BackboneCollectionView({
                            childView: RecordViewsView,
                            collection: records_collection,
                            viewsPerRow: 1
                        });
                    }
                });

            }
        }
    })
SS

define('SCG.Transaction.Search', [
    'SC.Model', 'Application', 'Models.Init', 'Utils', 'ServiceController', 'OrderHistory.Model', 'Quote.Model'
], function(
    SCModel, Application, ModelsInit, Utils, ServiceController, OrderHistoryModel, QuoteModel
) {
    'use strict';
    _.extend(OrderHistoryModel, {
        setExtraListFilters: function() {
            var searchText = this.data.searchText;
            console.log('searchText',searchText)

            if (searchText && searchText !== '') {
                searchText=searchText.replace(/!/g, ' ')
                 console.log('searchText2',searchText)
                this.filters.searchtext_operator = 'and';
                this.filters.searchtext =['otherrefnum', 'equalto', searchText];

            }
            if (this.data.filter === 'status:open') // Status is open and this is only valid for Sales Orders.
            {
                this.filters.type_operator = 'and';
                this.filters.type = ['type', 'anyof', ['SalesOrd']];
                this.filters.status_operator = 'and';
                this.filters.status = ['status', 'anyof', ['SalesOrd:A', 'SalesOrd:B', 'SalesOrd:D', 'SalesOrd:E', 'SalesOrd:F']];
            } else if (this.isSCISIntegrationEnabled) {
                if (this.data.filter === 'origin:instore') // SCIS Integration enabled, only In Store records (Including Sales Orders from SCIS)
                {
                    this.filters.scisrecords_operator = 'and';
                    this.filters.scisrecords = [
                        ['type', 'anyof', ['CashSale', 'CustInvc', 'SalesOrd']], 'and', ['createdfrom.type', 'noneof', ['SalesOrd']], 'and', ['location.locationtype', 'is', Configuration.get('locationTypeMapping.store.internalid')], 'and', ['source', 'is', '@NONE@']
                    ];
                } else // SCIS Integration enabled, all records
                {
                    this.filters.scisrecords_operator = 'and';
                    this.filters.scisrecords = [
                        [
                            ['type', 'anyof', ['CashSale', 'CustInvc']], 'and', ['createdfrom.type', 'noneof', ['SalesOrd']], 'and', ['location.locationtype', 'is', Configuration.get('locationTypeMapping.store.internalid')], 'and', ['source', 'is', '@NONE@']
                        ], 'or', [
                            ['type', 'anyof', ['SalesOrd']]
                        ]
                    ];
                }
            } else // SCIS Integration is disabled, show all the Sales Orders
            {
                this.filters.type_operator = 'and';
                this.filters.type = ['type', 'anyof', ['SalesOrd']];
            }

            var is_contact = ModelsInit.session.getCustomer().getFieldValues().contactloginid !== '0';
            if (!_.isEmpty(ModelsInit.session.getSiteSettings().cartsharingmode) && ModelsInit.session.getSiteSettings().cartsharingmode === 'PER_CONTACT' && is_contact) {
                var contactId = parseInt(ModelsInit.session.getCustomer().getFieldValues().contactloginid),
                    email = nlapiLookupField('contact', contactId, 'email');
                this.filters.email_opeartor = 'and';
                this.filters.email = ['email', 'is', email];
            }

        }
    });

    _.extend(QuoteModel, {

        setExtraListFilters: function() {
             var searchText = this.data.searchText;
              console.log('searchText',searchText)
            if (searchText && searchText !== '') {
                searchText=searchText.replace(/!/g, ' ')
                console.log('searchText2',searchText)
                this.filters.searchtext_operator = 'and';
                this.filters.searchtext = ['memo', 'contains', searchText];

            }
            if (this.data.filter && this.data.filter !== 'ALL') {
                this.filters.entitystatus_operator = 'and';
                this.filters.entitystatus = ['entitystatus', 'is', this.data.filter];
            }
        }

    });
});
Service Controller

define('SCG.Transaction.Search.Services', [
    'SC.Model', 'Application', 'Models.Init', 'OrderHistory.ServiceController', 'Quote.ServiceController', 'Utils', 'ServiceController', 'OrderHistory.Model', 'Quote.Model', 'SCG.Transaction.Search'
], function(
    SCModel, Application, ModelsInit, OrderHistoryServiceController, QuoteServiceController, Utils, ServiceController, OrderHistoryModel,QuoteModel, SCGTransactionSearch
) {
    'use strict';
    _.extend(OrderHistoryServiceController, {
        get: function() {
            var recordtype = this.request.getParameter('recordtype'),
                id = this.request.getParameter('internalid');
                console.log('Search',this.request.getParameter('searchText'))
            //If the id exist, sends the response of Order.get(id), else sends the response of (Order.list(options) || [])
            if (recordtype && id) {
                return OrderHistoryModel.get(recordtype, id);
            } else {
                return OrderHistoryModel.list({
                    searchText: this.request.getParameter('searchText'),
                    filter: this.request.getParameter('filter'),
                    order: this.request.getParameter('order'),
                    sort: this.request.getParameter('sort'),
                    from: this.request.getParameter('from'),
                    to: this.request.getParameter('to'),
                    origin: this.request.getParameter('origin'),
                    page: this.request.getParameter('page') || 1,
                    results_per_page: this.request.getParameter('results_per_page')
                });
            }
        }
    });

    _.extend(QuoteServiceController, {
        get: function() {
            var id = this.request.getParameter('internalid');
            if (id) {
                return QuoteModel.get('estimate', id);
            } else {
                console.log('Search',this.request.getParameter('searchText'))
                return QuoteModel.list({
                    filter: this.request.getParameter('filter'),
                    order: this.request.getParameter('order'),
                    sort: this.request.getParameter('sort'),
                    from: this.request.getParameter('from'),
                    to: this.request.getParameter('to'),
                    page: this.request.getParameter('page') || 1,
                    types: this.request.getParameter('types'),
                    searchText: this.request.getParameter('searchText')
                });
            }
        }
    });
});
//Mainfest

{
    "name": "SearchTransaction",
    "fantasyName": "Search Transaction Using Memo",
    "vendor": "SCG",
    "version": "1.0.0",
    "type": "extension",
    "target": "SCA,SCS",
    "description": "Search Quotes using Memo field",
    "skins": [],
    "assets": {
        "img": {
            "files": []
        },
        "fonts": {
            "files": []
        },
        "services": {
            "files": []
        }
    },
    "templates": {
        "application": {
            "myaccount": {
                "files": [
                    "Modules/ListHeaderCustom/Templates/search_transaction_list_header.tpl"
                ]
            }
        }
    },
    "javascript": {
        "entry_points": {
            "myaccount": "Modules/ListHeaderCustom/JavaScript/SCG.SearchTransaction.ListHeaderCustom.js"
        },
        "application": {
            "myaccount": {
                "files": [
                    "Modules/ListHeaderCustom/JavaScript/SCG.SearchTransaction.ListHeaderCustom.js",
                    "Modules/ListHeaderCustom/JavaScript/ListHeaderCustom.View.js"
                ]
            }
        }
    },
    "ssp-libraries": {
        "entry_point": "Modules/ListHeaderCustom/SuiteScript/SCG.Transaction.Search.Services.js",
        "files": [
            "Modules/ListHeaderCustom/SuiteScript/SCG.Transaction.Search.js",
            "Modules/ListHeaderCustom/SuiteScript/SCG.Transaction.Search.Services.js"
        ]
    },
    "local_folder": "Workspace\\SearchTransaction"
}
Template

{{#if showHeader}}
	<div class="list-header-view" data-type="accordion">
		<div class="list-header-view-accordion" data-action="accordion-header">



			<div class="list-header-view-accordion-link">{{headerMarkup}}</div>
			{{#if showHeaderExpandable}}
			<div class="list-header-view-accordion-header">
				<button class="list-header-view-filter-button" data-action="toggle-filters">
					{{translate 'Filter'}} <i class="list-header-view-filter-button-icon" ></i>
				</button>
			</div>
			<div class="list-header-view-accordion-body {{initiallyCollapsed}}" data-type="accordion-body" {{{accordionStyle}}}>
				<div class="list-header-view-accordion-body-header {{classes}}">

					
					{{#if rangeFilter}}
						<div class="list-header-view-datepicker-from">
							<label class="list-header-view-from" for="from">{{rangeFilterLabel}}</label>

							<div class="list-header-view-datepicker-container-input">
								<input class="list-header-view-accordion-body-input" id="from" name="from" type="date" autocomplete="off" data-format="yyyy-mm-dd" data-start-date="{{rangeFilterFromMin}}" data-end-date="{{rangeFilterFromMax}}" value="{{selectedRangeFrom}}" data-action="range-filter" data-todayhighlight="true"/>

								<i class="list-header-view-accordion-body-calendar-icon"></i>
								<a class="list-header-view-accordion-body-clear" data-action="clear-value">
									<i class="list-header-view-accordion-body-clear-icon"></i>
								</a>
							</div>
						</div>

						<div class="list-header-view-datepicker-to">
							<label class="list-header-view-to" for="to">{{translate 'to'}}</label>

							<div class="list-header-view-datepicker-container-input">
								<input class="list-header-view-accordion-body-input" id="to" name="to" type="date" autocomplete="off" data-format="yyyy-mm-dd" data-start-date="{{rangeFilterToMin}}" data-end-date="{{rangeFilterToMax}}" value="{{selectedRangeTo}}" data-action="range-filter" data-todayhighlight="true"/>

								<i class="list-header-view-accordion-body-calendar-icon"></i>
								<a class="list-header-view-accordion-body-clear" data-action="clear-value">
									<i class="list-header-view-accordion-body-clear-icon"></i>
								</a>
							</div>
						</div>
					{{/if}}

					{{#if searchFilter}}
                <div class="list-header-view-test">
                    <form action="" class="list-header-search-form">
                        <input class="list-header-view-accordion-body-input-search" data-action="text-filter" name="text-filter" type="text" autocomplete="off" value="{{search}}" placeholder="Search by {{searchField}}" />
                        <button type="submit" style="color: white;">
                            <i class="site-search-button-icon"></i>
                        </button>
                    </form>
                </div>
                {{/if}}
                
					{{#if sorts}}
						<span class="list-header-view-sorts">
							<label class="list-header-view-filters">
								<select name="sort" class="list-header-view-accordion-body-select" data-action="sort">
									{{#each sorts}}
										<option value="{{value}}" data-permissions="{{permission}}" {{#if selected}} selected {{/if}}>{{name}}</option>
									{{/each}}
								</select>
							</label>

							<button class="list-header-view-accordion-body-button-sort" data-action="toggle-sort">
								<i class="list-header-view-accordion-body-button-sort-up {{sortIconUp}}"></i>
								<i class="list-header-view-accordion-body-button-sort-down {{sortIconDown}}"></i>
							</button>
						</span>
					{{/if}}

					{{#if filters}}
						<label class="list-header-view-filters">
							<select name="filter" class="list-header-view-accordion-body-select" data-action="filter">
								{{#each filters}}
									<option value="{{itemValue}}" class="{{cssClassName}}" data-permissions="{{permission}}" {{#if selected}} selected {{/if}}>{{name}}</option>
								{{/each}}
							</select>
						</label>
					{{/if}}
				</div>
			</div>
			{{/if}}
		</div>
	</div>
{{/if}}

{{#if showSelectAll}}
	<div class="list-header-view-select-all">
		<label class="list-header-view-select-all-label" for="select-all">
			{{#if unselectedLength}}
				<input type="checkbox" name="select-all" id="select-all" data-action="select-all">{{translate 'Select All ($(0))' collectionLength}}
			{{else}}
				<input type="checkbox" name="select-all" id="select-all" data-action="unselect-all" checked>{{translate 'Unselect All ($(0))' collectionLength}}
			{{/if}}
		</label>
	</div>
{{/if}}

{{#if showPagination}}
	<div class="list-header-view-paginator">
		<div data-view="GlobalViews.Pagination"></div>
		{{#if showCurrentPage}}
			<div data-view="GlobalViews.ShowCurrentPage"></div>
		{{/if}}
	</div>
{{/if}}




{{!----
Use the following context variables when customizing this template:

	showHeader (Array)
	classes (String)
	showHeaderExpandable (Boolean)
	rangeFilterLabel (String)
	rangeFilterFromMin (String)
	rangeFilterFromMax (String)
	rangeFilterToMin (String)
	rangeFilterToMax (String)
	filters (Array)
	sorts (Array)
	sortIconUp (String)
	sortIconDown (String)
	showSelectAll (Boolean)
	unselectedLength (Number)
	collectionLength (Number)
	showPagination (Boolean)
	showCurrentPage (Boolean)
	initiallyCollapsed (String)
	headerMarkup (Object)
	headerMarkup.string (String)
	rangeFilter (String)

----}}

Leave a comment

Your email address will not be published. Required fields are marked *