PageSourceSearch

https://app.exabler.com/app/components/simple-filing/simple-filing.js

js exabler.com collected 2026-09-24 10:23:09 UTC 173,940 bytes, 4,114 lines download raw bytes

1app.controller('simpleFilingFormController', function ($scope, $rootScope, $timeout, orgServices, $location, $uibModal, modalService,
2    Data, TransactionService, TradeModelService, WorkflowService, AuthService, FastCheckService, tbServices, FxService,
3    DocumentService, MessageService, $q, $http, Upload) {
4
5    let ctrl = this;
6
7    ctrl.isRequestNow = false;
8    ctrl.isRequestNow = $location.path().includes('simple-request');
9    ctrl.isBroker = AuthService.User.is_acting_as_trader == false; 
10    ctrl.view = {};
11    $scope.isTrader = true;
12    $scope.brokerEditMode = false;
13    
14    // initialisation function 
15    (async function () {
16        $scope.isTrader = _.get(AuthService, 'User.role') !== 'provider';
17        
18        ctrl.view.initialisation = true;
19
20        $timeout(async () => {
21
22            if ($scope.isTrader) {
23            
24                const loadProviders = async () => {
25                    // NOTE: 'customs filing' checks extra_data. This is not a FastCheck service.
26                    $scope.sponsor = await WorkflowService.getSponsor('customs filing');
27                    if ($scope.sponsor === false) {
28                        Data.toast({status: "error", message: "Unable to find your customs brokerage providers. Please contact support"});
29                    }
30                }
31                loadProviders();
32            
33            }
34    
35            if (ctrl.isRequestNow) {
36
37                // Main vertical forms look at the trader's version
38                if ($scope.isTrader) {
39                    $scope.mainTrade = $scope.$resolve.workflow.data;
40                } else {
41                    $scope.mainTrade = $scope.$resolve.workflow.interim_data;
42                }
43
44                $scope.mainWorkflowMeta = angular.copy($scope.$resolve.workflow);
45                delete($scope.mainWorkflowMeta.data);
46                delete($scope.mainWorkflowMeta.interim_data);
47
48                // Broker actions look at the main request
49                $scope.mainTradeBroker = $scope.$resolve.workflow.interim_data;
50                $scope.svc_request = $scope.$resolve.workflow.svc_request;
51
52                const shipment = $scope.mainTradeBroker.shipments[0];
53                if (shipment.user_customs === "export") {
54                    ctrl.traderOrg = shipment.origin.exporter;
55                } else if (shipment.user_customs === "import") {
56                    ctrl.traderOrg = shipment.destination.importer;
57                } else {
58                    throw ("Unknown customs direction");
59                }
60                
61                $scope.broker = {header_short_hand: populateBrokerShortHandViews($scope.mainTradeBroker)};
62
63                // MessageService.getMessages($scope.mainTradeBroker.object_id, $scope.$resolve.workflow.object_id);
64
65
66                // @Divyesh
67                // I've loaded the docs back in to the docPNGs and this makes the dropdown select appear
68                // but these are not PNGs, so we need to turn them into PNGs either at load or when the user selects one (this would need to )
69                // also we don't want to let them remove the button
70                // but we do want to be able to upload a new document (particularly the broker)
71                const documentsForTradeWithHistory = await DocumentService.getDocumentsForTradeWithHistory($scope.mainTrade.id, $scope.mainTrade.object_id);
72                $scope.docPNGs = [];
73
74                for (let i = 0; i < documentsForTradeWithHistory.length; i++) {
75                    let item = documentsForTradeWithHistory[i];
76                    
77                    if (item.fldUrl && (item.fldUrl !== '' || item.fldUrl !== null)) {
78
79                        item = {...item, ...{ filename: item.fldFilename }};
80                        
81                        if (i === 0) {
82                            const pngFilesResult = await DocumentService.convertPDFToBase68PNGs(item.fldUrl);
83                            item = {...item, ...{ pages: pngFilesResult }};
84                            $scope.docPNGs.push(item);
85
86                            $scope.doc_number = 1;
87                            $scope.selected_doc = item;
88                            $scope.handlePageNumber();
89                        } else {
90                            $scope.docPNGs.push(item);
91                        }
92                    }
93                }
94
95                // Show the last page on page load
96                $scope.lastPage();
97
98            } else {
99
100                const getMyOrg = async () => {
101                    ctrl.traderOrg = await orgServices.getMyOrganisation()
102                }
103                getMyOrg();
104
105                // simple filing trader view
106                const tradedraft = await TransactionService.indexedDB_get('tradedraft');
107            
108                if (tradedraft && _.size(tradedraft) > 0) {
109                    showDetectDraftModal();
110                } else {
111                    // If old trade not found then start new trade
112                    await openDraftTrade(false);
113                }
114
115                // Show the first page on page load
116                $scope.firstPage();
117
118            }
119
120
121            ctrl.view.initialisation = false;
122
123        }, 50);
124        
125    })();
126
127    ctrl.enableBrokerEditMode = function() {
128        try { JL('Wizard').info({mx_event: 'broker edit mode', value: 'true'}) } catch {}
129        $scope.brokerEditMode = true;
130        $scope.$broadcast('broker-edit-mode', true);
131    }
132    ctrl.disableBrokerEditMode = function() {
133        try { JL('Wizard').info({mx_event: 'broker edit mode', value: 'false'}) } catch {}
134        $scope.brokerEditMode = false;
135        $scope.$broadcast('broker-edit-mode', false);
136    }
137
138    FxService.getLatestOfficial();
139
140    if (localStorage.getItem('sidebar') === 'open') {
141        $("#nav-toggle").trigger("click");
142        localStorage.setItem('sidebar', 'closed');
143    }
144    
145
146    $scope.$on('user-customs-change',  async (event, data) => {
147
148        if (data.newVal==data.oldVal) return;
149        reOrderPagesForService();
150
151    });
152
153    
154    $scope.pages = [
155        { id: "from-to", label: "From/To", text: "Are you importing or exporting?", template: "file-drop", services: ["import", "export"], startPage: true, isVisible: true, visited: 0 },    // countries, drop doc
156        { id: "seller", label: "Exporter", text: "Exporter details", template: "origin-orgs", services: ["import", "export"],  isVisible: false,  visited: 0 },
157        // TODO: bind this 'buyer' page to consignee for exports, change the id to "export-buyer"
158        { id: "buyer", label: "Consignee", text: "Consignee details", template: "destination-orgs", services: ["import", "export"], isVisible: false, visited: 0 },
159        // TODO: create a "import-buyer" page for imports, bound to consignee and also allowing a separate importer to be added
160        { id: "delivery", label: "Delivery", text: "Delivery terms", template: "delivery-carrier", services: ["import", "export"], isVisible: false, visited: 0 },
161        { id: "filing-im", label: "Filing", text: "Arrival information", template: "customs-general-import", services: ["import"], isVisible: false, visited: 0 },    // inspection location, border location
162        { id: "filing-ex", label: "Filing", text: "Departure information", template: "customs-general-export", services: ["export"], isVisible: false, visited: 0 },    // inspection location, exit office
163        { id: "import-transport", label: "Transport", text: "Means of transport", template: "transport-ids-import", services: ["import"], isVisible: false, visited: 0 },   // transport at border, container
164        { id: "export-transport", label: "Transport", text: "Means of transport", template: "transport-ids-export", services: ["export"], isVisible: false, visited: 0 },   // transport depart + border
165        { id: "items", label: "Items", text: "Item details", template: "items-table", services: ["import", "export"], isVisible: false, visited: 0 },       // items, freight, insurance
166        { id: "packing", label: "Packing", text: "Consignment packing", template: "packs-summary", services: ["import", "export"], isVisible: false, visited: 0 },   // packages, gross weight, net weight
167        { id: "fiscal", label: "Fiscal", text: "How will you settle duties and VAT?", template: "customs-payments", services: ["import"], isVisible: false, visited: 0 },    // defaults to apply to all
168        { id: "references", label: "Review & Send", text: "Review and send", template: "ref-instructions", services: ["import", "export"], finalPage: true, isVisible: false, visited: 0 },
169    ];
170    
171    $scope.servicePages = angular.copy($scope.pages);    // make a separate copy
172
173    $scope.detectColour = function(servicePage, pageIndex, index) {
174
175            let classList = []
176
177            if (pageIndex === index) {
178               classList.push('selected-circle')
179            }
180
181            if (servicePage.visited==0 && ! ctrl.isRequestNow) {
182                classList.push('twgrey');
183                return classList.join(" "); 
184            }
185
186            if ( servicePage.percent_complete == 0 ) {
187                classList.push('twgrey')
188            }
189
190            if ( 0 < servicePage.percent_complete < 100) {
191                classList.push('twamber')
192            }
193
194            if ( servicePage.percent_complete == 100) {
195                classList.push('twgreen')
196            }
197
198            return classList.join(" ");
199                
200    }
201
202
203
204    var reOrderPagesForService = function() {
205
206        // checks which direction the change is and swaps round
207        if ($scope.mainTrade.shipments[0].user_customs=="import") {
208            $scope.mainTrade.type = "import_filing";
209            // if an importer created already exists
210            let oldImporter = $scope.mainTrade.shipments[0].destination.importer;
211            $scope.mainTrade.shipments[0].origin.exporter = oldImporter; // swapped
212            $scope.mainTrade.shipments[0].destination.importer = ctrl.traderOrg;
213
214        } else if  ($scope.mainTrade.shipments[0].user_customs=="export")   {
215            $scope.mainTrade.type = "export_filing";
216            let oldExporter = $scope.mainTrade.shipments[0].origin.exporter;
217            $scope.mainTrade.shipments[0].origin.exporter = ctrl.traderOrg;
218            $scope.mainTrade.shipments[0].destination.importer = oldExporter;
219        }
220        $scope.mainTrade.contract.fast_check.selected_service = $scope.mainTrade.type;
221
222        const buyerIndex = _.findIndex($scope.pages, { id: 'buyer' });
223        const sellerIndex = _.findIndex($scope.pages, { id: 'seller' });
224        const tempBuyer = $scope.pages[buyerIndex];
225        const tempSeller = $scope.pages[sellerIndex];
226
227        // swap buyer or seller
228        if ($scope.mainTrade.shipments[0].user_customs === 'import') { 
229             
230           $scope.servicePages[1] = tempBuyer;
231           $scope.servicePages[2] = tempSeller;
232           if (AuthService.User.role === 'trader') {
233            $scope.servicePages[1].text = 'Your details';
234           }
235           $scope.servicePages[2].text = 'Exporter details'
236        
237        } else if ($scope.mainTrade.shipments[0].user_customs === 'export') {
238                   
239           $scope.servicePages[1] = tempSeller;
240           $scope.servicePages[2] = tempBuyer;
241           if (AuthService.User.role === 'trader') {
242            $scope.servicePages[1].text = 'Your details';
243           }
244           $scope.servicePages[2].text = 'Consignee details'
245        
246        }
247
248        
249
250        // function to update service pages for new pages and also filter out the change to dots
251        $scope.pages.forEach((p, index) => { 
252
253            let svc = $scope.mainTrade.shipments[0].user_customs;
254            if (!svc) return;
255            let isVisible = p.services.includes(svc)
256            $scope.servicePages[index].isVisible = isVisible;
257
258        });
259        
260    }
261   
262
263    $scope.pageIndex = -1;
264    $scope.cardType = false;
265    $scope.view = {
266        disableNavButtons:false,
267        processing_docs: false,
268        show_docs: false,
269        doc_number: 0,
270        page_number: 0,
271        showMessagePanel: true
272    };
273
274    ctrl.toggleMessagePanel = function() {
275        $scope.view.showMessagePanel = !$scope.view.showMessagePanel;
276    }
277
278    // Note: some of the values e.g. load_type.containerised: null are required to initialise correctly (whereas TradeClass etc. do not)
279    $scope.mainTrade = {
280        contract: {
281            currency: "",
282            seller: {
283                organisation: "",
284                extra_data: {
285                    addresses: { registered: [], trading: [] },
286                    accounting_details: { EORIno: "" }
287                }
288            },
289            buyer: {
290                organisation: "",
291                extra_data: {
292                    addresses: { registered: [], trading: [] },
293                    accounting_details: { EORIno: "" }
294                }
295            }
296        },
297        shipments: [
298            {
299                // user_customs: "export",
300                incoterm: { code: "" },
301                handover: {
302                    // country: { name: "United Kingdom", alpha_2: "GB" },
303                },
304                carrier: {
305                    contact: {
306                        organisation: "",
307                        extra_data: { accounting_details: {EORIno: ""}}},
308                        transporter_category: "carrier"
309                },
310                origin: {
311                    // country: { alpha_2: "GB", name: "United Kingdom" },
312                    customs: {
313                        // filing_country: { alpha_2: "GB", name: "United Kingdom" },
314                        border_post: { desc: "", code: "" },
315                        country_fields: { seals: { qty: 0, ids: [{text: false}] } },
316                        transit: { bool: undefined }
317                    }
318                },
319                consignor: {
320                    organisation: "",
321                    extra_data: {
322                        addresses: { registered: [], trading: [] },
323                        accounting_details: { EORIno: "" }
324                    }
325                },
326                destination: {
327                    // country: { alpha_2: "DE", name: "Germany" }
328                },
329                consignee: {
330                    organisation: "",
331                    extra_data: {
332                        addresses: { registered: [], trading: [] },
333                        accounting_details: { EORIno: "" }
334                    }
335                },
336                quantity: 0,
337                items: [
338                    // {
339                    //     indexNo: 0, amount: 100, currency: "EUR", description: "Something", phybool: true,
340                    //     dutiable_weight: 1.5, HS_code: { export: { Code: "06011000", country: { name: "United Kingdom", alpha_2: "GB" } } },
341                    //     origin_country: { name: "United Kingdom", alpha_2: "GB" }, pref: "true",
342                    //     packs: []
343                    // },
344                    // {
345                    //     indexNo: 1, amount: 100, currency: "EUR", description: "Something else", phybool: true,
346                    //     dutiable_weight: 500, HS_code: { export: { Code: "06011000", country: { name: "United Kingdom", alpha_2: "GB" } } },
347                    //     origin_country: { name: "China", alpha_2: "CN" }, pref: "false",
348                    //     packs: []
349                    // }
350                ],
351                load_type: { containerised: null},
352                packages: [
353                    // { kind: "BX" }
354                ],
355                costs: {
356                    freight: { currency: "", amount: 0 },
357                    insurance: { currency: "", amount: 0 }
358                },
359                calc_pack_qty: true,
360                calc_net_wt: true
361            }
362        ]
363    };
364
365    const blankTrade = _.merge({}, TradeClass, {shipments: [ShipmentClass], contract: ContractClass, invoices: [InvoiceClass], feedback: [FeesClass]});
366    const contractOrgs = _.merge(blankTrade, {contract: {buyer: {extra_data: OrganisationClass}, seller: {extra_data: OrganisationClass}}});
367    const shipOrgs = _.merge(blankTrade, {shipments: [{ consignee: {extra_data: OrganisationClass}, consignor:  {extra_data: OrganisationClass}}]});
368    const customsOrgs = _.merge(blankTrade, {shipments: [{ destination: {importer: {extra_data: OrganisationClass}}, origin: {exporter: {extra_data: OrganisationClass}}}]});
369
370    $scope.mainTrade = _.merge(blankTrade, contractOrgs, shipOrgs, customsOrgs, $scope.mainTrade);
371
372
373    $scope.firstPage = async function() {
374        try {
375            $scope.view.disableNavButtons = true;
376            $timeout(function () {
377                const svc = $scope.mainTrade.shipments[0].user_customs;
378                $scope.pages.forEach((p, index) => { 
379
380                    if (!svc) return;
381                    let isVisible = p.services.includes(svc)
382                    $scope.servicePages[index].isVisible = isVisible;
383        
384                });
385    
386                $scope.pageIndex = 0;
387                const fromPageId = $scope.currentPage ? $scope.currentPage.id : "na";
388                $scope.currentPage = $scope.servicePages[$scope.pageIndex];
389                $scope.cardType = $scope.servicePages[$scope.pageIndex].template;
390                try { JL('Wizard').info({mx_event: 'wizard firstPage', to: $scope.currentPage.id, from: fromPageId}) } catch {}
391            });
392        } finally {
393            $timeout(function () { $scope.view.disableNavButtons = false; }, 500);
394        }
395    }
396
397    $scope.lastPage = async function() {
398        try {
399            $scope.view.disableNavButtons = true;
400            $timeout(function () {
401                const svc = $scope.mainTrade.shipments[0].user_customs;
402                $scope.pages.forEach((p, index) => { 
403
404                    if (!svc) return;
405                    let isVisible = p.services.includes(svc)
406                    $scope.servicePages[index].isVisible = isVisible;
407        
408                });
409    
410                $scope.pageIndex = $scope.servicePages.length - 1;
411                const fromPageId = $scope.currentPage ? $scope.currentPage.id : "na";
412                $scope.currentPage = $scope.servicePages[$scope.pageIndex];
413                $scope.cardType = $scope.servicePages[$scope.pageIndex].template;
414                try { JL('Wizard').info({mx_event: 'wizard lastPage', to: $scope.currentPage.id, from: fromPageId}) } catch {}
415            });
416        } finally {
417            $timeout(function () { $scope.view.disableNavButtons = false; }, 500);
418        }
419    }
420
421    $scope.nextPage = async function () {
422        try {
423            $scope.view.disableNavButtons = true;
424            
425            // $timeout(function () {
426                const svc = $scope.mainTrade.shipments[0].user_customs;
427                $scope.pages.forEach((p, index) => { 
428    
429                    if (!svc) return;
430                    let isVisible = p.services.includes(svc)
431                    $scope.servicePages[index].isVisible = isVisible;
432        
433                });
434    
435                let currentPageIndex;
436                if (!$scope.currentPage) { currentPageIndex = -1; }
437                else { currentPageIndex = $scope.servicePages.findIndex((p) => { return p.id === $scope.currentPage.id }); }
438
439                if ($scope.pageIndex === 0) {
440                    if (!['export','import'].includes($scope.mainTrade.shipments[0].user_customs)) {
441                        Data.toast({status: "info", message: "Select import or export to continue"});
442                        return
443                    }
444                }
445                if (currentPageIndex >= 0 && $scope.servicePages[currentPageIndex].percent_complete < 100) {
446                    console.log($scope.servicePages[currentPageIndex]);
447                    // Data.toast({status: "info", message: "Your broker may ask for more information before they can start the filing"});
448                    // await $timeout(() => {  }, 500);
449                    // return
450                }
451    
452                let nextPageIndex;
453                for(let i=currentPageIndex+1; i<=$scope.servicePages.length; i++) {
454                    if (!$scope.servicePages[i].isVisible) { continue } 
455                    else {
456                       nextPageIndex = i;
457                       break 
458                    }  
459                }
460    
461                $scope.pageIndex = nextPageIndex;
462                const fromPageId = $scope.currentPage ? $scope.currentPage.id : "na";
463                $scope.currentPage = $scope.servicePages[$scope.pageIndex];
464                $scope.cardType = $scope.servicePages[$scope.pageIndex].template;
465                try { JL('Wizard').info({mx_event: 'wizard nextPage', to: $scope.currentPage.id, from: fromPageId}) } catch {}
466                
467            // });
468        } finally {
469            $timeout(function () { $scope.view.disableNavButtons = false; }, 500);
470        }
471    };
472
473    $scope.previous = function () {
474        try {
475            $scope.view.disableNavButtons = true;
476            $timeout(function () {
477                const svc = $scope.mainTrade.shipments[0].user_customs;
478                $scope.pages.forEach((p, index) => { 
479
480                    if (!svc) return;
481                    let isVisible = p.services.includes(svc)
482                    $scope.servicePages[index].isVisible = isVisible;
483        
484                });
485
486                let currentPageIndex = $scope.servicePages.findIndex((p) => { return p.id === $scope.currentPage.id });
487
488                let prevPageIndex;
489                for(let i=currentPageIndex-1; i>=0; i--) {
490                    if (!$scope.servicePages[i].isVisible) { continue } 
491                    else {
492                    prevPageIndex = i;
493                    break 
494                    }  
495                }
496
497                $scope.pageIndex = prevPageIndex;
498                const fromPageId = $scope.currentPage ? $scope.currentPage.id : "na";
499                $scope.currentPage = $scope.servicePages[$scope.pageIndex];
500                $scope.cardType = $scope.servicePages[$scope.pageIndex].template;
501                try { JL('Wizard').info({mx_event: 'wizard previousPage', to: $scope.currentPage.id, from: fromPageId}) } catch {}
502            });
503        } finally {
504            $timeout(function () { $scope.view.disableNavButtons = false; }, 500);
505        }
506    }
507
508    $scope.jumpToPage = function (pageId) {
509        try {
510            $scope.view.disableNavButtons = true;
511            const svc = $scope.mainTrade.shipments[0].user_customs;
512            $scope.pages.forEach((p, index) => {
513
514                if (!svc) return;
515                let isVisible = p.services.includes(svc)
516                $scope.servicePages[index].isVisible = isVisible;
517
518            });
519
520            const targetPageIndex = $scope.servicePages.findIndex((p) => { return p.id === pageId });
521            $scope.pageIndex = targetPageIndex;
522            const fromPageId = $scope.currentPage ? $scope.currentPage.id : "na";
523            $scope.currentPage = $scope.servicePages[$scope.pageIndex];
524            $scope.cardType = $scope.servicePages[$scope.pageIndex].template;
525            try { JL('Wizard').info({mx_event: 'wizard jumpToPage', to: $scope.currentPage.id, from: fromPageId}) } catch {}
526        } finally {
527            $timeout(function () { $scope.view.disableNavButtons = false; }, 500);
528        }
529    }
530
531
532    $scope.whatHappensOnSubmit = function() {
533        
534        try { JL('Wizard').info({mx_event: "opened whatHappensOnSubmit"}) } catch {}
535        openWhatIfModal();
536
537    }
538
539    let openWhatIfModal = function () {
540
541        var modalInstance = $uibModal.open({
542            animation: true,
543            component: 'whatHappensComponent'
544        });
545
546        return modalInstance.result
547        .then(function (selectedItem) {
548            try { JL('Wizard').info({mx_event: "closed whatHappensOnSubmit"}) } catch {}
549        })
550        .catch((error) => {
551            try { JL('Wizard').info({mx_event: "error in whatHappensOnSubmit", error: error}) } catch {}
552        });
553    };
554
555
556
557    let statValueModal = function(trade, current_rate, saved_rate) {
558        return new Promise((positive) => {
559            var modalInstance = $uibModal.open({
560                animation: true,
561                component: 'statValueComponent',
562                controllerAs: '$ctrl',
563                size: 'md',
564                resolve: {
565                    trade: function () { return trade},
566                    current_rate: function () { return current_rate},
567                    saved_rate: function () { return saved_rate},
568                }
569            });
570
571            modalInstance.result
572            .then(function (updated_trade) {
573                try { JL('Wizard').info({mx_event: "closed statValueModal"}) } catch {}
574                positive(updated_trade);
575            })
576            .catch((error) => {
577                positive('cancel');
578            })
579        })
580    }
581
582
583    let openSubmitModal = function (payload, sponsor) {
584        return new Promise((resolve) => {
585    
586            var modalInstance = $uibModal.open({
587                animation: true,
588                component: 'sendModalComponent',
589                controllerAs: '$ctrl',
590                size: 'md',
591                resolve: {
592                    payload: function () { return payload},
593                    sponsor: function () { return sponsor}
594                }
595            });
596    
597            modalInstance.result
598            .then(function (selectedItem) {
599                try { JL('Wizard').info({mx_event: "closed submitModal", selectedItem: selectedItem}) } catch {}
600                resolve(selectedItem);
601            })
602            .catch((error) => {
603                resolve('cancel');
604            })
605            
606        })
607
608    }
609
610    // let justSubmitted = false;
611
612    $scope.submit = async function () {
613
614        $scope.view.disableSend = true;
615        try {
616            if ($scope.mainTrade.shipments[0].user_customs === 'export') {      // only relevant for exports
617                await FxService.getLatestOfficial();
618                const current_rate = FxService.convertOfficial({currency: "GBP", amount: 1}, $scope.mainTrade.contract.currency, "GB").amount;
619                const saved_rate = Number(_.get($scope.mainTrade.shipments[0], "origin.customs.official_fx.rate", 0));
620                if (saved_rate === 0) {
621                    Data.toast({status: "error", message: "Unable to find the exchange rate. Please try again or contact support"});
622                    try { JL('Wizard').info({mx_event: "Error finding saved exchange rate"}) } catch {}
623                }
624                if (Math.abs(saved_rate - current_rate) >= saved_rate * 0.00005) {
625                    const updated_trade = await (statValueModal($scope.mainTrade, current_rate, saved_rate));
626                    if (updated_trade === 'cancel') {
627                        try { JL('Wizard').info({mx_event: "Cancelled send due to official FX change"}) } catch {}
628                        $scope.view.disableSend = false;
629                        return
630                    } else {
631                        $scope.mainTrade = updated_trade;
632                    }
633                }
634            }
635
636            const service = $scope.mainTrade.shipments[0].user_customs === 'import' ? 'import_filing' : 'export_filing';
637            const review = await FastCheckService.canStartWorkflowTradeOnly($scope.mainTrade, service);
638            
639            if (review.canProceed === false) {
640                // Data.toast({status: "error", message: review.reason});
641                var modalOptions = {
642                    actionButtonText: "#", // remove the action button
643                    closeButtonText: 'Ok',
644                    headerText: "Almost there",
645                    bodyText: "<p><ul><li>" + review.messages.join("</li><li>") + "</li></ul></p>",
646                };
647                modalService.showModal({}, modalOptions)
648                .then(function (not_used) {
649                });
650
651                // alert(review.reason);
652                $scope.view.disableSend = false;
653                return
654            }
655
656            if (!$scope.docNatives || $scope.docNatives.length == 0) {
657                Data.toast({status: "error", message: "Please upload a document"});
658                $scope.view.disableSend = false;
659                return
660            }
661
662            try { JL('Wizard').info({mx_event: "Sending instruction from wizard"}) } catch {}
663
664            const payload = preparePayload();
665            console.log(payload);
666            
667            const decision = await (openSubmitModal(payload, $scope.sponsor));
668            console.log(decision);
669            if (decision === 'cancel') {
670                $scope.view.disableSend = false;
671                return
672            }
673
674            WorkflowService.initial_message = self.special_instructions;
675
676            const result = await TransactionService.createInstructionFromWizard(payload);
677            if (result && result.data) {
678                if (result.data.error) {
679                    try { JL('Wizard').info({mx_event: "Error instructing", error: result.data}) } catch {}
680                    alert('There was a problem. Please try again or contact support');
681                    return
682                }
683                try { JL('Wizard').info({
684                    mx_event: "Wizard instruction successful",
685                    tx_object_id: result.data.tx_object_id,
686                    workflow_object_id: result.data.workflow_object_id
687                }) } catch {}
688
689                // await TransactionService.indexedDB_clear();
690                deleteDraftTrade({value: $scope.mainTrade.object_id});
691                // Data.toast({status: "success", message: "Instru
691ction sent successfully"});
692
693                // Go to the /trade-assist route with the workflow
694                $timeout(() => {
695                    $location.path('/simple-request/' + result.data.tx_object_id + '/' + result.data.workflow_object_id);
696                }, 1000);
697                // justSubmitted = true;
698                var date = new Date();
699                var dateStr = date.toLocaleString();
700                var modalOptions = {
701                    actionButtonText: "Ok",
702                    closeButtonText: '#', // remove the close button
703                    headerText: "Completed",
704                    bodyText: "<p>The instruction has been sent to " + $scope.sponsor.organisation + " at " + dateStr +
705                        "</p><p>Add any further questions or instructions in the message panel</p>",
706                };
707
708                modalService.showModal({}, modalOptions)
709                .then(function (not_used) {
710                });
711
712                ctrl.isRequestNow = true;
713                // $location.path('/trade-assist/' + result.data.tx_object_id + '/' + result.data.workflow_object_id);
714                // $location.path('/home');
715            }
716            $scope.view.disableSend = false;
717            return; 
718
719        } catch (error) {
720            console.error(error);
721            Data.toast({status: "error", message: error});
722            $scope.view.disableSend = false;
723        }
724    }
725
726    var preparePayload = function () {
727    
728        // const tx_object_id = "trd-" + uuid();
729        const workflow_object_id = "wkfz-" + uuid();
730        // $scope.mainTrade.object_id = tx_object_id;
731
732        const buy_sell = $scope.mainTrade.shipments[0].user_customs === 'import' ? 'buy' : 'sell';
733        const defaults_org = $scope.mainTrade.shipments[0].user_customs === 'import' ? 'buyer' : 'seller';
734
735        // Hard-code to customs filing types
736        const requestedService = WorkflowService.service_names.find((svc) => { return svc.value === 'export_filing' || svc.value === 'import_filing'});
737        const start_wf = WorkflowService.wfstartTable.find((el) => {
738            return (el.direction === buy_sell && el.workflow === requestedService.group && el.instrument === requestedService.value)
739        });
740        const nextStatus = WorkflowService.getNextwfStatus(start_wf.workflow, start_wf.instrument, "captive", { name: start_wf.start_status });
741        // TODO: replace start_wf with the next step before using it
742        $scope.mainTrade.type = $scope.mainTrade.shipments[0].user_customs === 'import' ? 'import_filing' : 'export_filing';
743        $scope.mainTrade.contract.type = $scope.mainTrade.type;
744
745        const blankTrade = _.merge({}, TradeClass, { shipments: [ShipmentClass], contract: ContractClass });
746        $scope.mainTrade = _.merge(blankTrade, $scope.mainTrade);
747
748        // Short-cut assumptions not allowed to be changed in the UI
749        if (!Array.isArray(_.get($scope.mainTrade.contract.buyer.extra_data, "addresses.registered", false))) {
750            _.set($scope.mainTrade.contract.buyer.extra_data, "addresses.registered", []);
751        }
752        if (!Array.isArray(_.get($scope.mainTrade.contract.seller.extra_data, "addresses.registered", false))) {
753            _.set($scope.mainTrade.contract.seller.extra_data, "addresses.registered", []);
754        }
755        _.set($scope.mainTrade.contract.buyer.extra_data, "addresses.registered[0].country", $scope.mainTrade.shipments[0].destination.country);
756        _.set($scope.mainTrade.contract.seller.extra_data, "addresses.registered[0].country", $scope.mainTrade.shipments[0].origin.country);
757
758        const tradeToSave = TransactionService.completeTrade($scope.mainTrade, false, buy_sell, defaults_org);
759        
760
761        // const workflow = _.merge({}, WorkflowClass, {
762        //     owner_orgid: ctrl.traderOrg.orgid,
763        //     fldTxid: 0,
764        //     provider_orgid: $scope.sponsor.orgid,
765        //     tx_object_id: tx_object_id,
766        //     object_id: workflow_object_id,
767        //     fldType: nextStatus.workflow,         // buy, sell, dx, tr, etc.
768        //     fldRoute: "captive",
769        //     fldLevel: nextStatus.instrument,      // e.g. 'export_filing'
770        //     fldStatus: nextStatus.start_status,   // e.g. 'Reviewing'
771        //     fldCreated: "",
772        //     fldUpdated: "",
773        //     data: tradeToSave,
774        //     interim_data: tradeToSave
775        // });
776
777        let trade_docs = [];
778        if ($scope.docNatives) {        // should not be required
779            for (let i=0; i<$scope.docNatives.length; i++) {
780                const doc = $scope.docNatives[i];
781                const name_parts = doc.filename.split(".");
782                const img = $scope.docPNGs[i];
783                trade_docs.push({
784                    file: doc.b64,
785                    fileName: doc.filename,                 // fldFilename
786                    name: doc.filename,                     // TODO: fldName - allow a user-defined name
787                    type: img.doc_type ? img.doc_type : 'not set',        // fldType - Commercial Invoice, Packing List, etc.
788                    workflow: $scope.mainTrade.shipments[0].user_customs === 'import' ? 'buy' : 'sell',
789                    status: 'New',
790                    extDoc : name_parts[name_parts.length - 1],
791                    action : 'First upload',
792                    originalid : null,
793                    template : '',
794                    sharing : '',
795                    documentComment : '',
796                    content : '',
797                });
798            }
799        } else {
800            try { JL('Wizard').info({mx_event: "Uploaded documents lost"}) } catch {}
801        }
802
803        const payload = {
804            trade: tradeToSave,
805            // workflow: workflow,
806            workflow_object_id: workflow_object_id,
807            requested_service: requestedService,
808            start_wf: nextStatus,
809            special_instructions: self.special_instructions,
810            docs: trade_docs
811        };
812
813        return payload;
814
815    }
816
817
818    $scope.$on('set_initial_message', (event, message) => {
819        self.special_instructions = message;
820    });
821
822    $scope.$on('trade-change', (event, tradeHolder) => {
823
824        $timeout(async () => {
825
826            // runs the trade and draft entry side
827            wizardProgress(tradeHolder);
828
829            // const active_tradedraft = await TransactionService.indexedDB_get('active_tradedraft');
830            const active_tradedraft = $scope.mainTrade && $scope.mainTrade.object_id;
831            let tradedraft = await TransactionService.indexedDB_get('tradedraft');
832            
833            if (active_tradedraft && tradedraft && tradedraft[active_tradedraft]) {
834                let nestedObject = tradedraft[active_tradedraft];
835                
836                tradedraft = {
837                    ...tradedraft,
838                    [active_tradedraft]: {
839                        ...nestedObject,
840                        'mainTrade': $scope.mainTrade,
841                        'servicePagesIndex': $scope.pageIndex,
842                    },
843                };
844                
845                await TransactionService.indexedDB_set('tradedraft', tradedraft);
846            }
847            
848        }, 10);
849
850        if (["export","import"].includes($scope.mainTrade.shipments[0].user_customs)) {
851            const service = $scope.mainTrade.shipments[0].user_customs === "export" ? "export_filing" : "import_filing";
852            $rootScope.$broadcast("updateInfoBlock", service);
853        }
854    });
855
856
857    $scope.$on('request-change', (event, request) => {
858        
859        $timeout(async () => {
860            $scope.mainTradeBroker = request;
861            saveMainTradeBroker(request)
862        },10);
863
864        
865    });
866
867    
868    /**
869     * Similar to the one in trade-assist 
870     * Not including the diff work to show incremental changes
871     * 
872     * 
873     *      
874     * */
875    let saveMainTradeBroker = function(request) {
876
877        var post = {};
878
879        post.interim_data = JSON.stringify(request);    // Only SPs make changes to workflow, and autosave only interim_data
880        post.assigned_to = $scope.mainWorkflowMeta.assigned_to;
881
882        let meta = {
883            tx_id: $scope.mainWorkflowMeta.fldTxid,
884            idtblWkflow: $scope.mainWorkflowMeta.idtblWkflow,
885            tx_object_id: $scope.mainWorkflowMeta.tx_object_id
886        }
887
888        WorkflowService.updateWorkflow(meta, post)
889        // WorkflowService.updateWorkflowIncremental(newVal.idtblWkflow, diffsToSave)
890        .then(() => {
891
892        })
893        .catch(() => {
894            
895        })
896        .finally(() => {
897            
898        });
899    }
900
901    $scope.docPNGs = [];
902
903    $scope.checks_list = [];        // FastCheck messages are bound to this
904
905
906    function removePopoverAttributes() {
907        $('[data-toggle="popover"]').each(function (test) {
908            $(this).popover("destroy");
909            // debugger
910            if ($(this).hasClass('marked-as-popover') && $(this).hasClass('ng-touched')) {
911                $(this).removeClass('border-bottom-red');
912                $(this).css('border-bottom', '2px solid green');
913            }
914            if ($(this).hasClass('marked-as-popover') && $(this).hasClass('custom-label')) {
915                // use add / remove class red norm instead
916                $(this).removeClass('border-bottom-red');
917            }
918        //    console.log("Changing element")
919       });
920    }
921
922
923    $scope.$on(('run-fast-check', (event, request)=>{
924        debugger
925    }));
926    
927
928    const wizardProgress = function(tradeHolder) {
929        let pageCompletions = {};
930        $scope.pages.forEach((page) => { pageCompletions[page.id] = {groups: {}}; });       // initialise empty structure
931        
932        if (!tradeHolder.trade.shipments[0].user_customs) return;
933        const service = tradeHolder.trade.shipments[0].user_customs === 'import' ? 'import_filing' : 'export_filing';
934        const version = "v2";
935        FastCheckService.checkAsync({data_holder: tradeHolder, service: service, version})
936        .then((fastCheckMessages) => {
937            // Reset and remove all Highlight elements with a name attribute with a red border
938            // THere are few meta fields that it keeps finding
939            console.log(fastCheckMessages);
940            
941            removePopoverAttributes();
942
943
944            // Grab the label from the static source page array, but apply it to the dynamic servicesPages array
945            $scope.servicePages.forEach((service_page, service_page_idx) => {
946
947            
948                const sourceLabel = $scope.pages.find((page) => { return page.id === service_page.id; }).label;
949                const found = fastCheckMessages
950                    .filter((msg) => { return (msg.wz_page === service_page.id) && (msg.priority !== "info"); })
951                    .map((msg) => { return { message: msg.comment, field: msg.fields } });
952                    
953                if (found.length > 0) {
954
955                   service_page.percent_complete = 50;
956
957                   if ($scope.pageIndex == service_page_idx) { // highlighting only needs to be run when on current page
958 
959                        let jsonData = found;
960                        
961                        // Create a mapping of field names to messages
962                        const fieldToMessage = {};
963                        jsonData.forEach(item => {
964                            if (!item.field) return;
965                            item.field.forEach(fieldName => {
966                                fieldToMessage[fieldName] = item.message;
967                            });
968                        });
969
970                        // Highlight elements with a name attribute with a red border
971                        // Does not need to be in the service page loop
972                        $('[name]').each(function (index, element) {
973
974                           //debugger
975
976                            const fieldName = $(this).attr('name');
977                
978                            if (fieldToMessage[fieldName]) {
979                                $(this).addClass('marked-as-popover');  // replace css below with class add / remove class
980                                $(this).attr('data-toggle', 'popover');
981                                $(this).attr('data-content', fieldToMessage[fieldName]);
982
983                                if ((($(this).hasClass('ng-touched') || service_page.visited>=2) ||  ($(this).hasClass('custom-label') && service_page.visited>=2))) {
984
985                                        $(this).addClass('border-bottom-red');
986
987                                }
988                            }
989                        });
990
991                    }
992
993                    service_page.verboseLabel = "" + sourceLabel + "<br>" + found.length + " warnings found"
994             
995                } else {
996
997                    service_page.percent_complete = 100;
998                    service_page.verboseLabel = "" + sourceLabel + "";
999
1000                }
1001
1002                // Overrrides
1003
1004                // if ($scope.pageIndex < service_page_idx) {  // white if the user hasn't reached the page yet
1005                //     service_page.percent_complete = 0;
1006                // }
1007
1008                if (service_page.id === "from-to") {        // from-to requires docs even if everything else is done
1009                    if (!$scope.docNatives || $scope.docNatives.length == 0) {
1010                        service_page.percent_complete = 50;
1011                    }
1012                }
1013
1014                if (service_page.id === "references" && ! ctrl.isRequestNow) {     // last page is always white
1015                    service_page.percent_complete = 0;
1016                }
1017
1018                if (service_page.id === "references" && ctrl.isRequestNow) {     // last page is always green
1019                    service_page.percent_complete = 50;
1020                }
1021
1022
1023                // Initialize Bootstrap Popovers
1024                $('[data-toggle="popover"]').popover({
1025                    trigger: 'hover',
1026                    placement: 'top',
1027                    container: 'body',
1028                    html: true
1029                });
1030
1031
1032            });
1033
1034            // simulate a mouseover on the div with id "circle-from-to"
1035            $('#circle-'+$scope.servicePages[$scope.pageIndex].id).trigger('mouseover');
1036            $('#circle-'+$scope.servicePages[$scope.pageIndex].id).trigger('mouseleave');
1037        })
1038    }
1039
1040
1041    $scope.updateDocTypes = function() {
1042        $timeout(() => {
1043            updateDocsInIndexedDB();
1044        }, 10);
1045    }
1046
1047    $scope.$on('added-document', async (event, newFiles) => {
1048        // if ($scope.docPNGs === undefined) {
1049            $scope.docPNGs = newFiles.pngs_list;  // [...$scope.docPNGs, ...newFiles.pngs_list];
1050            $scope.docNatives = newFiles.natives_list;
1051        // }
1052        await updateDocsInIndexedDB();
1053        
1054        $scope.selected_doc = $scope.docPNGs[$scope.docPNGs.length-1];
1055        $scope.doc_number = $scope.docPNGs.length;
1056        wizardProgress({trade: $scope.mainTrade, documents: $scope.docPNGs});
1057
1058        const loadCropper = newFiles.loadCropper;
1059        if (loadCropper) $scope.handlePageNumber();
1060
1061        // if provider add a simple start message
1062        // todo
1063        
1064        if (!ctrl.isRequestNow) {
1065            // Make sure the browser updates the view
1066            $scope.$apply();
1067
1068        }
1069    });
1070
1071    var updateDocsInIndexedDB = async function() {
1072        const active_tradedraft = await TransactionService.indexedDB_get('active_tradedraft');
1073        let tradedraft = await TransactionService.indexedDB_get('tradedraft');
1074        if (active_tradedraft && tradedraft && tradedraft[active_tradedraft]) {
1075            let nestedObject = tradedraft[active_tradedraft];
1076
1077            tradedraft = {
1078                ...tradedraft,
1079                [active_tradedraft]: {
1080                    ...nestedObject,
1081                    'docPNGs': $scope.docPNGs,
1082                    'docNatives': $scope.docNatives,
1083                },
1084            };
1085
1086            await TransactionService.indexedDB_set('tradedraft', tradedraft);
1087        }
1088    }
1089
1090
1091    $scope.deleteDoc = async function(doc) {
1092        const docIndex = $scope.docPNGs.findIndex((row) => { return row.unique_id === doc.unique_id; });
1093        $scope.docPNGs.splice(docIndex, 1);
1094        $scope.docNatives.splice(docIndex, 1);
1095        let actualTarget = Math.max(0, Math.min(docIndex, $scope.docPNGs.length));
1096        actualTarget = actualTarget >= $scope.docPNGs.length ? $scope.docPNGs.length - 1 : actualTarget;
1097        
1098        $scope.selected_doc = $scope.docPNGs[actualTarget];
1099        $scope.doc_number = actualTarget+1;
1100        $scope.handleDocNumber();
1101        updateDocsInIndexedDB();
1102    }
1103
1104    $scope.doc_number = 1;
1105    $scope.page_number = 1;
1106    $scope.selected_doc = {};
1107
1108    function dataURLtoFile(dataurl, filename) {
1109        var arr = dataurl.split(','),
1110            mime = arr[0].match(/:(.*?);/)[1],
1111            bstr = atob(arr[1]),
1112            n = bstr.length,
1113            u8arr = new Uint8Array(n);
1114
1115        while(n--){
1116            u8arr[n] = bstr.charCodeAt(n);
1117        }
1118
1119        return new File([u8arr], filename, {type:mime});
1120    }
1121    
1122    /* 
1123    * Start - Image Cropper */
1124    $scope.showLoader = false;
1125    $scope.image = $('#documentImage');
1126    $scope.cropper = null;
1127    
1128    $scope.isLoadCaptureMode = false;
1129    $scope.toggleAction = false;
1130
1131    
1132    $scope.doLoadCaptureMode = async () => {
1133        $timeout(async () => {
1134            // $scope.image = $('#documentImage');
1135            if ($scope.cropper) {
1136                $scope.cropper.destroy();
1137            }
1138
1139            //$scope.image = $('#documentImage');
1140
1141            $scope.image.cropper({
1142                aspectRatio: '',
1143                initialAspectRatio: 5,
1144                dragMode: 'move',
1145                viewMode: 1,
1146                autoCrop: false,
1147                responsive: false,
1148                crop: function(event) {
1149                },
1150            });
1151
1152            $scope.cropper = $scope.image.data('cropper');
1153            await $scope.cropper.replace($scope.scropperImageUrl);
1154
1155            $timeout(() => {
1156
1157                const imgData = $scope.cropper.getImageData();
1158                const imgWidth = imgData.naturalWidth;
1159                const containerData = $scope.cropper.getContainerData();
1160                const containerWidth = containerData.width;
1161                const scale = containerWidth / imgWidth;
1162
1163                $scope.cropper.zoomTo(scale);
1164                $scope.cropper.moveTo(0);
1165
1166            }, 100);
1167            
1168        },200);
1169    };
1170    $scope.initImageCropper = async () => {
1171        $scope.showLoader = true;
1172        await $scope.doLoadCaptureMode();
1173        $scope.showLoader = false;
1174        $scope.isLoadCaptureMode = true;
1175    };
1176
1177    $scope.turnONImageCaptureMode = async () => {
1178        await $scope.cropper.crop();
1179    };
1180    $scope.turnOFFImageCaptureMode = () => {
1181        $scope.cropper.clear();
1182    };
1183
1184    $scope.loadOCR = async () => {
1185        if ($scope.isLoadCaptureMode) {
1186            await $scope.turnONImageCaptureMode();
1187        } else {
1188            await $scope.initImageCropper();
1189        }
1190    };
1191
1192    $scope.zoomIn = () => {
1193        $scope.cropper.zoom(0.1);
1194    };
1195    $scope.zoomOut = () => {
1196        $scope.cropper.zoom(-0.1);
1197    };
1198    $scope.rotate = () => {
1199        $scope.cropper.rotate(90);
1200    };
1201
1202    $scope.download = () => {
1203        if ($scope.selected_doc && $scope.selected_doc.fldUrl) {
1204            const parts = $scope.selected_doc.fldUrl.split('/');
1205            const downloadFileName = parts[parts.length - 1];
1206            const saveFileName = ($scope.selected_doc.fldFilename) ? $scope.selected_doc.fldFilename : downloadFileName; 
1207            DocumentService.downloadPDFUsingBase64($scope.selected_doc.fldUrl, saveFileName);
1208        }
1209    };
1210    
1211    $scope.initScan = () => {
1212        $scope.toggleAction = true;
1213        $scope.loadOCR();
1214    };
1215    $scope.confirmCaptureImage = async () => {
1216        $scope.fileObj = null;  
1217        const canvasObject = $scope.cropper.getCroppedCanvas({ maxWidth: 4096, maxHeight: 4096 });
1218        await canvasObject.toBlob(async (blob) => {
1219            blobToBase64(blob).then(async dataUrl => {
1220                
1221                $scope.toggleAction = false;
1222                const randNum = Math.floor(Math.random()*89999+10000);
1223                const currTime = Date.now();
1224                const fileName = `${currTime}_${randNum}.png`;
1225                const fileObj = await dataURLtoFile(dataUrl, fileName);
1226
1227                $scope.handleTurnOffCaptureMod();
1228            });
1229        });
1230    };
1231
1232    // Turn OFF image capture mode
1233    $scope.handleTurnOffCaptureMod = () => {
1234        $scope.toggleAction = false;
1235        $scope.cropper.clear();
1236    };
1237    
1238    $scope.$on('started-processing-docs', (event) => {
1239        $scope.view.processing_docs = true;
1240    });
1241
1242    $scope.$on('finished-processing-docs', (event) => {
1243        $scope.doc_number = 1;
1244        $scope.page_number = 1;
1245        $scope.view.processing_docs = false;
1246        $scope.handleDocNumber();
1247    });
1248    
1249    $scope.$on('selected-document', (event, doc_number) => {
1250        $scope.doc_number = doc_number;
1251        $scope.page_number = 1;
1252        $scope.handleDocNumber();
1253    });
1254
1255    $scope.handleDocNumber = () => {
1256        if ($scope.docPNGs && $scope.docPNGs.length > 0) {
1257            $scope.scropperImageUrl = _.get($scope.docPNGs, `[${$scope.doc_number - 1}].pages[${$scope.page_number - 1}]`, '');
1258            $scope.selected_doc = _.get($scope.docPNGs, `[${$scope.doc_number - 1}]`, {});
1259            $scope.initImageCropper();
1260        }
1261        
1262    };
1263
1264    $scope.handleDocSelector = async (doc) => {
1265
1266        if (!doc) return
1267        const doc_id = this.isRequestNow ? "fldtransactionDocId" : "unique_id";
1268        const index = $scope.docPNGs.findIndex((row) => { return row[doc_id] === doc[doc_id] });
1269        // $scope.selected_doc = doc;
1270        $scope.doc_number = index + 1;
1271        
1272        let numPagesNew = null;
1273        if (_.get($scope.docPNGs, `[${index}].pages.length`)) {
1274            numPagesNew = $scope.docPNGs[index].pages.length;
1275        } else {
1276            let item = $scope.docPNGs[index];
1277            const pngFilesResult = await DocumentService.convertPDFToBase68PNGs(item.fldUrl);
1278            item = {...item, ...{ pages: pngFilesResult }};
1279            $scope.docPNGs[index] = item;
1280            numPagesNew = $scope.docPNGs[index].pages.length;
1281        }
1282
1283        const targetPage = Math.min(numPagesNew, $scope.page_number);
1284        $scope.handlePageNumber(targetPage);
1285        $scope.handleDocNumber();
1286    }
1287
1288    $scope.handlePageNumber = (targetPageNumber = false) => {
1289        if ($scope.docPNGs.length === 0) return
1290        const doc = $scope.docPNGs[$scope.doc_number - 1];
1291        if (targetPageNumber !== false)
1292            $scope.page_number = Math.max(1, Math.min(targetPageNumber, doc.pages.length));
1293        
1294        if ($scope.docPNGs && $scope.docPNGs.length > 0) {
1295            if (doc.pages.length <= $scope.page_number) {
1296                $scope.page_number = doc.pages.length;
1297            }
1298            $scope.scropperImageUrl = doc.pages[$scope.page_number - 1];
1299            
1300            $scope.initImageCropper();
1301        }
1302    };
1303    
1304    /* 
1305    * End - Image Cropper */
1306
1307    const openDraftTrade = async (isExisting) => {
1308
1309        const active_tradedraft = await TransactionService.indexedDB_get('active_tradedraft');
1310        const tradedraft = await TransactionService.indexedDB_get('tradedraft');
1311
1312        let active_trade_data = null;
1313        if (tradedraft && active_tradedraft && tradedraft[active_tradedraft]) {
1314            active_trade_data = tradedraft[active_tradedraft];
1315        }
1316        
1317        const { mainTrade, docNatives, docPNGs, servicePagesIndex } = active_trade_data || {};
1318        
1319        if (isExisting) {
1320            // const pdfsForVerticalApp = await TransactionService.indexedDB_get('attachDocuments');
1321            $scope.docPNGs = docPNGs || null;
1322            $scope.docNatives = docNatives || null;
1323
1324            // Start - SLoad OCR
1325            if ($scope.docPNGs && $scope.docPNGs.length > 0) {
1326                $scope.scropperImageUrl = $scope.docPNGs[$scope.doc_number - 1].pages[$scope.page_number - 1];
1327                $scope.selected_doc = $scope.docPNGs[0];
1328                
1329                $scope.loadOCR();
1330            }
1331            // End - SLoad OCR
1332
1333            try { JL('Wizard').info({mx_event: "Resuming old draft wizard trade"}) } catch {}
1334            $scope.mainTrade = mainTrade || $scope.mainTrade;
1335            $scope.pageIndex = servicePagesIndex || $scope.pageIndex;
1336            reOrderPagesForService();
1337            const servicePages = _.get($scope.servicePages, `[${$scope.pageIndex}].id`);
1338            // if (ctrl.isBroker) {        // load on the last page so the colour-coding is visible
1339            //     $scope.pageIndex = $scope.servicePages.length - 1;
1340            // }
1341            if (servicePages) {
1342                $scope.jumpToPage(servicePages);
1343            }
1344        } else {
1345            // await TransactionService.indexedDB_clear();
1346
1347            const tx_object_id = "trdz-" + uuid();
1348            $scope.mainTrade.object_id = tx_object_id;
1349            let draftTrade = await TransactionService.indexedDB_get('tradedraft');
1350            draftTrade = draftTrade || {};
1351            draftTrade[tx_object_id] = {
1352                'mainTrade': $scope.mainTrade
1353            };
1354            TransactionService.indexedDB_set('active_tradedraft', tx_object_id);
1355            TransactionService.indexedDB_set('tradedraft', draftTrade);
1356        }
1357      
1358    };
1359
1360    const deleteDraftTrade = async (draftDataForDelete) => {
1361        let draftTrade = await TransactionService.indexedDB_get('tradedraft');
1362
1363        const deleteRequestDraftKey = draftDataForDelete && draftDataForDelete.value;
1364        if (draftTrade && deleteRequestDraftKey && draftTrade[deleteRequestDraftKey]) {
1365            delete draftTrade[deleteRequestDraftKey];
1366        }
1367
1368        await TransactionService.indexedDB_set('tradedraft', draftTrade);
1369    };
1370    
1371
1372    const showDetectDraftModal = () => {
1373        $uibModal.open({
1374            animation: true,
1375            templateUrl: 'app/components/simple-filing/detect-draft-modal.html',
1376            controller: 'detectDraftModalCtrl',
1377            size: 'lg',
1378            scope: $scope,
1379            resolve: {
1380                data: function () {
1381                    return {
1382                        openDraftTrade: async function (data) {
1383                            
1384                            await openDraftTrade(data);
1385                            
1386                        },
1387                        deleteDraftTrade: async function (data) {
1388
1389                            await deleteDraftTrade(data);
1390
1391                        }
1392                    }
1393                }
1394            }
1395        });
1396    };
1397
1398
1399
1400    const populateBrokerShortHandViews = (request) => {
1401        let header_short_hand = {};
1402        let nature_of_transaction = [];
1403        let requested_procedure = [];
1404        let additional_procedure = [];
1405        request.shipments[0].commodity_group.groups.forEach((group) => {
1406            nature_of_transaction.push(group.country_fields.nature_of_transaction);
1407            requested_procedure.push(group.country_fields.requested_procedure);
1408            additional_procedure.push(_.get(group.country_fields, "additional_procedures[0].code", ""));
1409        });
1410        const nature_of_transaction_set = new Set(nature_of_transaction);
1411        const requested_procedure_set = new Set(requested_procedure);
1412        const additional_procedure_set = new Set(additional_procedure);
1413        if (nature_of_transaction_set.size === 1) {
1414            header_short_hand.nature_of_transaction = nature_of_transaction[0];
1415        }
1416        if (requested_procedure_set.size === 1) {
1417            header_short_hand.requested_procedure = requested_procedure[0];
1418        }
1419        if (additional_procedure_set.size === 1) {
1420            header_short_hand.additional_procedure = additional_procedure[0];
1421        }
1422        return header_short_hand;
1423    };
1424    
1425    ctrl.onBrokerChange = function(shipmentId=0, field) {
1426        const user_customs = $scope.mainTradeBroker.shipments[shipmentId].user_customs;
1427        if (!user_customs || user_customs==="") return
1428
1429        // These fields apply one value to all items
1430        if (['nature_of_transaction','requested_procedure'].includes(field)) {
1431            $scope.mainTradeBroker.shipments[shipmentId].items.forEac
1431h((item) => {
1432                item.HS_code[user_customs].country_fields[field] = $scope.broker.header_short_hand[field];
1433            })
1434        }
1435
1436        // // Apply to all items, first sub-item array element
1437        if (['additional_procedure'].includes(field)) {
1438            const attribute_name = field+"s";
1439            $scope.mainTradeBroker.shipments[shipmentId].items.forEach((item) => {
1440                _.set(item.HS_code[user_customs].country_fields, attribute_name+"[0].code", $scope.broker.header_short_hand[field]);
1441            })
1442        }
1443
1444        // Apply to the active item only, first sub-item array element
1445        // if (['taric_add_national','taric_additional'].includes(field)) {
1446        //     const attribute_name = field+"s";
1447        //     $scope.mainTradeBroker.shipments[shipmentId].items[self.view.activeItemTab]
1448        //     .HS_code[user_customs].country_fields[attribute_name][0] = $scope.broker[field];
1449        // }
1450
1451        // // Apply to all items, first sub-item array element, by attribute
1452        // const prev_doc_object = {'prev_doc_class':'class','prev_doc_type':'type','prev_doc_ref':'ref'};
1453        // if (Object.keys(prev_doc_object).includes(field)) {
1454        //     const attribute_name = prev_doc_object[field];
1455        //     $scope.mainTradeBroker.shipments[shipmentId].items.forEach((item) => {
1456        //         item.HS_code[user_customs].country_fields.previous_docs[0][attribute_name] = $scope.broker[field];
1457        //     })
1458        // }
1459
1460        if (field === 'consignment_ref_user_editable') {
1461            const checkRef = (user_ref) => {
1462                // TODO: bring in the checkRef function from SGS
1463                // If used before, it sets self.view.consignment_ref_is_duplicated
1464                // If not a valid reference, it sets self.view.consignment_ref_is_invalid
1465            }
1466            const makeLRN = () => {
1467                return self.view.consignment_ref_start + self.view.consignment_ref_user_editable;
1468            }
1469            $scope.mainTradeBroker.shipments[shipmentId].origin.customs.country_fields.cds.lrn = makeLRN();
1470        }
1471
1472
1473
1474        if (field === 'item') {
1475            console.log(self.view.activeItemTab);
1476        }
1477
1478
1479        $scope.mainTradeBroker.shipments[shipmentId].anyItemPacks = TransactionService.arePacksUnderItems({
1480            items_array: $scope.mainTradeBroker.shipments[shipmentId].items,
1481            import_export: $scope.mainTradeBroker.shipments[shipmentId].user_customs
1482        });
1483        $scope.mainTradeBroker = TransactionService.calcTxnSummary($scope.mainTradeBroker);
1484
1485
1486        // $scope.$emit('request-change', self.request);
1487        saveMainTradeBroker($scope.mainTradeBroker);
1488        return
1489    }
1490
1491    ctrl.refreshCDSLocResults = function (params) {
1492        return new Promise((resolve, reject) => {
1493            
1494            const user_input = params.search.toLowerCase();
1495            if (user_input.length < 2) {
1496                ctrl.CDS_locations = [{desc: "Keep typing..."}];
1497                return resolve(ctrl.CDS_locations);
1498            }
1499            let shortList;
1500            const border = $scope.mainTradeBroker.shipments[0].user_customs==='export' ? 'origin' : 'destination';
1501            const gvms_only = $scope.mainTradeBroker.shipments[0][border].customs.country_fields.gvms_boolean;
1502            if (gvms_only == 'gvms') {
1503                const gvmsList = TradeModelService.CDS_locations_array.filter((loc) => {
1504                    return loc.desc.contains('GVM')
1505                });
1506                shortList = gvmsList;
1507            } else {
1508                shortList = TradeModelService.CDS_locations_array;
1509            }
1510
1511            const found = shortList.filter((loc) => {
1512
1513                if (loc.code.toLowerCase().includes(user_input) || loc.desc.toLowerCase().includes(user_input)) {
1514                    return true
1515                } else {
1516                    return false
1517                }
1518            });
1519
1520            if (found.length <= 50) {
1521                ctrl.CDS_locations = found;
1522            } else {
1523                const first50 = found.slice(0, 50);
1524                ctrl.CDS_locations = [...first50, {desc: "More available..."}];
1525            }
1526            
1527            // const new_post = {
1528            //     "isTag": true,
1529            //     "id": Date.now(), "type": "user input", "code": params.search, "desc": "(User-created code - don't forget to start with 'GBAU' or similar)"
1530            // };
1531            // ctrl.CDS_locations.push(new_post);
1532            
1533            return resolve(found);
1534            
1535        });
1536    };
1537
1538    ctrl.refreshCDSCOLocResults = function (params) {
1539        return new Promise((resolve, reject) => {
1540            
1541            const user_input = params.search.toLowerCase();
1542            if (user_input.length < 2) {
1543                ctrl.CDS_COL_locations = [{desc: "Keep typing..."}]
1544                return resolve(ctrl.CDS_COL_locations);
1545            }
1546
1547            const found = TradeModelService.CDS_COL_locations.filter((loc) => {
1548                if (loc.code.toLowerCase().includes(user_input) || loc.desc.toLowerCase().includes(user_input)) {
1549                    return true
1550                } else {
1551                    return false
1552                }
1553            });
1554            
1555            if (found.length <= 50) {
1556                ctrl.CDS_COL_locations = found;
1557            } else {
1558                const first50 = found.slice(0, 50);
1559                ctrl.CDS_COL_locations = [...first50, {desc: "More available..."}];
1560            }
1561            // const new_post = {
1562            //     "isTag": true,
1563            //     "id": Date.now(), "type": "user input", "code": params.search, "desc": "(User-created code - don't forget to start with 'GBAU' or similar)"
1564            // };
1565            // ctrl.CDS_COL_locations.push(new_post);
1566            
1567            return resolve(found);
1568            
1569        });
1570    }
1571
1572});
1573
1574
1575
1576app.controller('detectDraftModalCtrl', function ($scope, $uibModalInstance, TransactionService, $location, Data) {
1577    $scope.maxDrafts = 5;
1578    
1579    const openDraftTrade = $scope.$resolve.data.openDraftTrade;
1580    const deleteDraftTrade = $scope.$resolve.data.deleteDraftTrade;
1581    $scope.active_tradedraft = '';
1582    
1583    $scope.cancel = function () {
1584        $location.path('/home');
1585    }
1586    
1587    $scope.load = async function (filing) {
1588        await TransactionService.indexedDB_set('active_tradedraft', filing.value);
1589        const isExisting = true;
1590        openDraftTrade(isExisting);
1591        $uibModalInstance.close('load');
1592    };
1593
1594    $scope.new = function () {
1595        if ($scope.recentFilings.length >= $scope.maxDrafts) {
1596            Data.toast({status: 'error', message: `You can only have ${$scope.maxDrafts} drafts at a time.`});
1597            return;
1598        }
1599        const isExisting = false;
1600        openDraftTrade(isExisting);
1601        $uibModalInstance.dismiss('cancel');
1602    };
1603
1604    const getKeyByValue = (object, value) => {
1605        return Object.keys(object).find(key => object[key].value === value.value);
1606    };
1607    
1608    $scope.handleDeleterecentFiling = (recentFiling) => {
1609        const index = getKeyByValue($scope.recentFilings, recentFiling);
1610        $scope.recentFilings.splice(index, 1);
1611        
1612        deleteDraftTrade(recentFiling);
1613    };
1614
1615    (async function () {
1616
1617        $scope.tradedraft = await TransactionService.indexedDB_get('tradedraft');
1618        const active_tradedraft = await TransactionService.indexedDB_get('active_tradedraft');
1619
1620        $scope.recentFilings = [];
1621
1622        if (_.size($scope.tradedraft) > 0) {
1623            angular.forEach($scope.tradedraft, function (tradeData, key) {
1624        
1625                let trade = tradeData.mainTrade;
1626                let value = key;
1627                
1628                let label = { 
1629                    "invoice_ref": trade.invoices[0].invoice_ref,
1630                    "type":   trade.type,
1631                    "country": trade.type=='import_filing' ? trade.shipments[0].origin.country : trade.shipments[0].destination.country
1632                };
1633
1634                // if (trade.invoices && trade.invoices[0].invoice_ref != '') {
1635                //     label = trade.invoices[0].invoice_ref;
1636                // } else if (trade.shipments[0].user_customs != '') {
1637                //     label = trade.shipments[0].user_customs;
1638
1639                //     if (trade.shipments[0].user_customs === 'import') {
1640                //         label += (_.get(trade, 'shipments[0].origin.country.name')) ? ` - ${trade.shipments[0].origin.country.name}` : '';
1641                //     } else if (trade.shipments[0].user_customs === 'export') {
1642                //         label += (_.get(trade, 'shipments[0].destination.country.name')) ? ` - ${trade.shipments[0].destination.country.name}` : '';
1643                //     }
1644                // }
1645
1646                $scope.$apply(function () {
1647                    $scope.recentFilings.push({
1648                        label,
1649                        value,
1650                    });
1651                });
1652                
1653            });   
1654        }
1655        
1656        const temp = _.find($scope.recentFilings, { value: active_tradedraft });
1657        $scope.active_tradedraft = temp || '';
1658        
1659    })();
1660
1661
1662});
1663
1664app.component('verticalApp', {
1665    controllerAs: '$ctrl',
1666    bindings: {
1667        templateUrl: "<",
1668        trade: "=",
1669        sponsor: "<",
1670        docImages: "=",
1671        request: "<?",  // placeholder for a broker true or false
1672        mainWorkflowMeta: "<?",
1673    },
1674    template: '<div ng-include="$ctrl.templateUrl"></div>',
1675    controller: function ($scope, $q, $timeout, Upload, $http,
1676            TradeModelService, TransactionService, FxService, FastCheckService,
1677            tbServices, Data, modalService, $location, AuthService, DocumentService,
1678            orgServices) {
1679
1680
1681        let self = this;
1682
1683        self.isRequestNow = false;
1684        self.isRequestNow = $location.path().includes('simple-request');
1685        self.isBroker = AuthService.User.is_acting_as_trader == false;
1686        $scope.isTrader = _.get(AuthService, 'User.role') !== 'provider';
1687
1688        self.currentUser = AuthService.User;
1689        const getMyOrg = async () => {
1690            self.userOrg = await orgServices.getMyOrganisation();
1691            if (self.userOrg && self.userOrg.organisation) {
1692                let shipment = self.trade.shipments[0];
1693                if (shipment.user_customs === 'export') {
1694                    if (!shipment.origin.exporter || !shipment.origin.exporter.organisation) {
1695                        shipment.origin.exporter = self.userOrg;
1696                        // Data.toast({status: "info", message: "Set exporter to user"});
1697                    }
1698                } else if (shipment.user_customs === 'import') {
1699                    if (!shipment.destination.importer || !shipment.destination.importer.organisation) {
1700                        shipment.destination.importer = self.userOrg;
1701                    }
1702                }
1703            }
1704        }
1705        getMyOrg();
1706        
1707
1708        self.view = {
1709            processingDocs: false,
1710            seller_is_exporter: true,
1711            consignor_is_exporter: true,
1712            buyer_is_importer: true,
1713            consignee_is_importer: true,
1714            carrier_address: {street: "", city: "", postcode: ""},
1715            item_notes_placeholder: {},
1716            // editItems: false,
1717            activeItemTab: 0,
1718            disableItemPack: {},
1719            address_format: {},
1720            item_prefs_updating: {},
1721            controls: [],
1722            hide_alt_methods: true,
1723            cover_note_placeholder: "Add an optional cover note"
1724        }
1725
1726        if (self.isRequestNow) {
1727            $scope.broker = {
1728                isEditMode: false,
1729                header_short_hand: {
1730                    nature_of_transaction: "",
1731                    requested_procedure: "",
1732                    additional_procedure: ""
1733                },
1734                consignment_ref_user_editable: "",
1735                consignment_ref_start: "CD1AAX",        // TODO: set this based on user's org and initials
1736                consignment_ref_is_duplicated: false,
1737            }
1738        }
1739        $scope.$on('broker-edit-mode', (event, data) => {
1740            $scope.broker.isEditMode = data;
1741        });
1742  
1743        $scope.scenarioId = 0;
1744        $scope.shipmentId = 0;
1745        
1746        $scope.incoterms = tbServices.service('delivery_terms').src('2020');  // TODO: bring in 2010 codes like DAT
1747
1748        self.countries = TradeModelService.ordered_countries_list;
1749        self.countries_ex_gb = TradeModelService.ordered_countries_list.filter((country) => { return country.alpha_2.toLowerCase() !== "gb"; });
1750        
1751        $scope.currencies = FxService.currencies;
1752
1753        self.CDS_locations = TradeModelService.CDS_locations_array;
1754        self.CDS_COL_locations = TradeModelService.CDS_COL_locations;
1755        self.CDS_previous_docs_codes = TradeModelService.CDS_previous_docs_codes;
1756                
1757        self.$onChanges = async function (changes) {
1758
1759            if (changes.templateUrl) {
1760                // add on the servicePages a visited count
1761                $scope.$parent.servicePages.forEach((el, index)=>{
1762                    
1763                    //self.templateUrl = changes.templateUrl.currentValue;
1764
1765                        let template = changes.templateUrl.currentValue.split('.')[0];
1766                        if(el.template == template){
1767                            $scope.$parent.servicePages[index].visited++;
1768                        }
1769                })
1770            }
1771
1772            
1773            if (self.templateUrl === '01-file-upload.html') loadSource();
1774
1775            if (self.templateUrl === 'delivery-carrier.html') {
1776                // await self.validateGBEORI('carrier_eori');
1777                const legacy_address = _.get(self.trade.shipments[0].carrier, "contact.address.in_full", "");
1778                const new_address = _.get(self.trade.shipments[0].carrier, "contact.extra_data.addresses.registered[0].in_full", false);
1779                if (new_address) {
1780                    self.view.carrier_address = splitAddress(new_address);
1781                } else {
1782                    self.view.carrier_address = splitAddress(legacy_address);
1783                }
1784            }
1785
1786            if (self.templateUrl === 'items-table.html' && self.trade.shipments[0].items.length == 0) {
1787                // Only create the first item after completing the prior steps, since they can affect the items
1788                self.addItem();
1789            }
1790
1791            if (self.isRequestNow && self.request) {
1792                $scope.broker.header_short_hand = populateBrokerShortHandViews(self.request);
1793            }
1794
1795            self.onChange(0, 'none');
1796
1797        }
1798        
1799        self.$onInit = function() {
1800
1801            self.onRouteChangeOff = $scope.$on('$locationChangeStart', routeChange);
1802
1803        }
1804
1805
1806        const deleteDraftRequest = async (draftDataForDelete) => {
1807            let draftTrade = await TransactionService.indexedDB_get('tradedraft');
1808
1809            const deleteRequestDraftKey = draftDataForDelete && draftDataForDelete.value;
1810            if (draftTrade && deleteRequestDraftKey && draftTrade[deleteRequestDraftKey]) {
1811                delete draftTrade[deleteRequestDraftKey];
1812            }
1813
1814            await TransactionService.indexedDB_set('tradedraft', draftTrade);
1815            
1816        };
1817
1818
1819        const populateBrokerShortHandViews = (request) => {
1820            let header_short_hand = {};
1821            let nature_of_transaction = [];
1822            let requested_procedure = [];
1823            let additional_procedure = [];
1824            request.shipments[0].commodity_group.groups.forEach((group) => {
1825                nature_of_transaction.push(group.country_fields.nature_of_transaction);
1826                requested_procedure.push(group.country_fields.requested_procedure);
1827                additional_procedure.push(_.get(group.country_fields, "additional_procedures[0].code", ""));
1828            });
1829            const nature_of_transaction_set = new Set(nature_of_transaction);
1830            const requested_procedure_set = new Set(requested_procedure);
1831            const additional_procedure_set = new Set(additional_procedure);
1832            if (nature_of_transaction_set.size === 1) {
1833                header_short_hand.nature_of_transaction = nature_of_transaction[0];
1834            }
1835            if (requested_procedure_set.size === 1) {
1836                header_short_hand.requested_procedure = requested_procedure[0];
1837            }
1838            if (additional_procedure_set.size === 1) {
1839                header_short_hand.additional_procedure = additional_procedure[0];
1840            }
1841            return header_short_hand;
1842        };
1843
1844
1845
1846        function routeChange(event, newUrl, oldUrl) {
1847
1848            if (newUrl.includes("/simple-filing/")) { // no need to catch and save if staying on simple interface
1849                return
1850            }
1851           
1852            // seems to be the basic initialisation state
1853            if (self.trade.type=='trade') {
1854
1855                // nothing save created at all, so don't create a indexDb entry or delete immediately
1856                deleteDraftRequest({value: self.trade.object_id});
1857                         
1858                return;
1859            }
1860
1861            
1862            if (!(self.trade.type=='import_filing' || self.trade.type=='export_filing') || !self.trade.shipments[0].origin.country || !self.trade.invoices[0].invoice_ref ) {
1863                        
1864                try { JL('Simple Interface').info({mx_event: "Leaving"}); } catch {}
1865
1866                var modalOptions = {
1867                    closeButtonText: 'Stay & complete',
1868                    actionButtonText: 'Discard',
1869                    headerText: 'Unsaved changes',
1870                    bodyText: 'To save this draft, please set export/import, invoice reference, and country.'
1871                };
1872
1873                modalService.showModal({}, modalOptions).then(function (result) {
1874
1875                    
1876                    if (result === 'ok') {
1877
1878                        // Discard this filing
1879                        
1880                        deleteDraftRequest({value: self.trade.object_id});
1881                        
1882                        self.onRouteChangeOff(); //Stop listening for location changes
1883
1884                        var newPath = newUrl.replace(/^(?:\/\/|[^\/]+)*\//, "");
1885                        $location.path("/" + newPath); // Go to page they're interested in
1886
1887                    }                
1888
1889
1890                }).catch(function(){
1891
1892                    //Return to enter more details 
1893
1894                });
1895
1896                // prevent navigation by default since we'll handle it
1897                // once the user selects a dialog option
1898                event.preventDefault();
1899
1900             } else {
1901
1902                // if (!justSubmitted) Data.toast({status:"info", message: "Draft saved - re-open to select"});
1903
1904             }
1905
1906          
1907
1908        }
1909   
1910       
1911        // $scope.gb_duty_pay_methods = TradeModelService.uk_duty_pay_methods;
1912        $scope.gb_duty_pay_methods = [
1913            {id: 5, code: 'E', desc: "Deferment account",},
1914            {id: 7, code: 'H', desc: "Pay by faster payment, CHAPS, BACS",},
1915            {id: 9, code: 'N', desc: "Pay from CDS Cash Account deposit",},
1916            {id: 11, code: 'P', desc: "Pay from CDS Cash Account",},
1917            {id: 15, code: 'U', desc: "Guarantee account - standing authority (direct/indirect representation)",},
1918            {id: 16, code: 'V', desc: "Guarantee account - individual/specific authority (direct/ indirect representation)",},
1919        ];
1920
1921
1922        self.toggleFilingType = function(shipmentId, val) {
1923            if (self.isRequestNow && !$scope.broker.isEditMode) return
1924            if (val === 'import') return
1925            if (self.trade.shipments[shipmentId].user_customs !== val) {
1926                self.trade.shipments[shipmentId].user_customs = val;
1927                if (val === "export") self.trade.buy_sell = "sell";
1928                if (val === "import") self.trade.buy_sell = "buy";
1929            } else {
1930                self.trade.shipments[shipmentId].user_customs = "";
1931                self.trade.buy_sell = "";
1932            }
1933            self.onChange(shipmentId, 'user_customs');
1934        };
1935
1936
1937        self.setToExporter = function(target_org_string) {
1938            if (self.isRequestNow && !$scope.broker.isEditMode) return
1939            if (target_org_string === 'seller') {
1940                if (!self.view.seller_is_exporter) return
1941                self.trade.contract.seller = self.trade.shipments[0].origin.exporter;
1942            } else if (target_org_string === 'consignor') {
1943                self.trade.shipments[0].consignor = self.trade.shipments[0].origin.exporter;
1944            }
1945        }
1946
1947        self.setToImporter = function(target_org_string) {
1948            if (self.isRequestNow && !$scope.broker.isEditMode) return
1949            if (target_org_string === 'buyer') {
1950                if (!self.view.buyer_is_importer) return
1951                self.trade.contract.buyer = self.trade.shipments[0].destination.importer;
1952            } else if (target_org_string === 'consignee') {
1953                self.trade.shipments[0].consignee = self.trade.shipments[0].destination.importer;
1954            }
1955        }
1956
1957
1958        self.validateGBEORI = async function(field) {
1959            if (self.isRequestNow && !$scope.broker.isEditMode) return
1960            if (field === 'carrier_eori') {
1961                const newEORI =
1962                self.trade.shipments[0].carrier.contact.extra_data.accounting_details.EORIno
1963                .replace(/\s+/g, '').trim().toUpperCase();
1964                if (!newEORI || newEORI.length < 9 || newEORI.length > 15) {
1965                    self.view.carrier_eori_valid = false;
1966                    return
1967                }
1968
1969                // set the EORI to the new value
1970                if (newEORI.slice(0,2) === "GB") {
1971                    self.trade.shipments[0].carrier.contact.extra_data.accounting_details.EORIno = newEORI;
1972                } else {
1973                    self.trade.shipments[0].carrier.contact.extra_data.accounting_details.EORIno = "GB"+newEORI;
1974                }
1975                
1976                const result = await FastCheckService.checkGBEORI(newEORI);
1977                const eori_valid = result.valid;
1978
1979                if (eori_valid) {       // set the nationality if not set
1980                    const existing_alpha_2 = _.get(self.trade.shipments[0], "carrier.contact.extra_data.addresses.registered[0].country.alpha_2", "");
1981                    const country_alpha_2 = self.trade.shipments[0].carrier.contact.extra_data.accounting_details.EORIno.slice(0,2);
1982                    if (existing_alpha_2 !== country_alpha_2) {
1983                        _.set(self.trade.shipments[0], "carrier.contact.extra_data.addresses.registered[0].country", 
1984                        self.countries.find((country) => { return country.alpha_2 === country_alpha_2 }));
1985                        Data.toast({status: "success", message: "Nationality updated to match EORI"});
1986                    }
1987                    // Populate (overwrite) the name and address if provided by HMRC
1988                    _.set(self.trade.shipments[0], "carrier.contact.organisation", result.companyDetails.traderName);
1989                    _.set(self, "view.carrier_address.street", result.companyDetails.address.streetAndNumber);
1990                    _.set(self, "view.carrier_address.city", result.companyDetails.address.cityName);
1991                    _.set(self, "view.carrier_address.postcode", result.companyDetails.address.postcode);
1992                }
1993
1994                self.view.carrier_eori_valid = eori_valid;
1995            }
1996        }
1997
1998
1999        self.addRouteCountry = function (shipmentId) {
2000            if (self.isRequestNow && !$scope.broker.isEditMode) return
2001            // we track by alpha_2, so don't allow more than one blank country
2002            if (self.trade.shipments[shipmentId].route.map((country) => { return country.alpha_2 }).includes("")) {
2003                Data.toast({status: "warning", message: "You can only have one blank country at a time in the route"});
2004                return
2005            }
2006            // insert a blank country at the penultimate position
2007            self.trade.shipments[shipmentId].route.splice(-1, 0, {alpha_2: "", name: "" });
2008        }
2009
2010        self.deleteRouteCountry = function (shipmentId, $index) {
2011            if (self.isRequestNow && !$scope.broker.isEditMode) return
2012            self.trade.shipments[shipmentId].route.splice($index, 1);
2013        }
2014
2015        self.setRouteCountry = function (shipmentId, $index, country) {
2016            if (self.isRequestNow && !$scope.broker.isEditMode) return
2017            self.trade.shipments[shipmentId].route[$index] = country;
2018            country = null;
2019        }
2020
2021
2022        self.addContainer = function (shipmentId) {
2023            if (self.isRequestNow && !$scope.broker.isEditMode) return
2024            self.trade.shipments[shipmentId].containers.push({ idnum: "" });
2025        }
2026
2027        self.deleteContainer = function (shipmentId, $index) {
2028            if (self.isRequestNow && !$scope.broker.isEditMode) return
2029            if (self.trade.shipments[shipmentId].containers.length==1) return;
2030            self.trade.shipments[shipmentId].containers.splice($index, 1);
2031        }
2032
2033        // Toggle between seals and unknown
2034        // Seals = qty>0
2035        // Unknown = qty==null && ids[0].text==""
2036        self.toggleSeals = function(shipmentId) {
2037            if (self.isRequestNow && !$scope.broker.isEditMode) return
2038            const qty = _.get(self.trade.shipments[0].containers[0], "seals.qty", 0);
2039            const firstID = _.get(self.trade.shipments[0].containers[0], "seals.ids[0].text", "");
2040            if (qty > 0) {  // set to unknown
2041                _.set(self.trade.shipments[0].containers[0], "seals.ids[0].text", "");
2042                _.set(self.trade.shipments[0].containers[0], "seals.qty", null);
2043            } else {  // set to 1 seal
2044                _.set(self.trade.shipments[0].containers[0], "seals.ids[0].text", "");
2045                _.set(self.trade.shipments[0].containers[0], "seals.qty", 1);
2046            }
2047        }
2048
2049        // Toggle between NOSEALS and unknown
2050        // NOSEALS = qty==0 && ids[0].text=="NOSEALS"
2051        // Unknown = qty==null && ids[0].text==""
2052        self.toggleNoSeals = function (shipmentId) {
2053            if (self.isRequestNow && !$scope.broker.isEditMode) return
2054            const qty = _.get(self.trade.shipments[0].containers[0], "seals.qty", 0);
2055            const firstID = _.get(self.trade.shipments[0].containers[0], "seals.ids[0].text", "");
2056            if (firstID === "NOSEALS") {  // set to unknown
2057                _.set(self.trade.shipments[0].containers[0], "seals.ids[0].text", "");
2058                _.set(self.trade.shipments[0].containers[0], "seals.qty", null);
2059            } else {        // set to NOSEALS
2060                self.trade.shipments[shipmentId].containers[0].seals = {qty: 0, ids: [{text: "NOSEALS"}]};
2061                _.set(self.trade.shipments[0].containers[0], "seals.qty", 0);
2062            }
2063        }
2064
2065        self.addSeal = function(shipmentId) {
2066            if (self.isRequestNow && !$scope.broker.isEditMode) return
2067            self.trade.shipments[shipmentId].containers[0].seals.ids.push({text: ""});
2068        }
2069
2070        self.deleteSeal = function(shipmentId, $index) {
2071            if (self.isRequestNow && !$scope.broker.isEditMode) return
2072            if (self.trade.shipments[shipmentId].containers[0].seals.ids.length > 1) {
2073                self.trade.shipments[shipmentId].containers[0].seals.ids.splice($index, 1);
2074            }
2075            // The final one gets filled in with "NOSEALS"
2076        }
2077
2078            
2079        self.buildAddressInFull = function (field) {
2080            if (self.isRequestNow && !$scope.broker.isEditMode) return
2081            if (field === 'carrier') {
2082                let address_in_full = self.view.carrier_address.street + "\n" + self.view.carrier_address.city + "\n" + self.view.carrier_address.postcode;
2083                _.set(self.trade.shipments[0], "carrier.contact.address.in_full", address_in_full);     // legacy
2084                _.set(self.trade.shipments[0], "carrier.contact.extra_data.addresses.registered[0].in_full", address_in_full);
2085                // TODO: validate address
2086            }
2087        }
2088
2089        var splitAddress = function (address_in_full) {
2090                
2091                let address = {
2092                    street: '',
2093                    city: '',
2094                    postcode: ''
2095                }
2096
2097                let address_parts;
2098                if (address_in_full.indexOf("\n") === -1) {
2099                    address_parts = address_in_full.split(",").map(p => p.trim());
2100                } else {
2101                    address_parts = address_in_full.split("\n").map(p => p.trim());
2102                }
2103
2104                if (address_parts.length === 0) {
2105                    return address;
2106                }
2107                if (address_parts.length === 1) {
2108                    address.street = address_parts[0];
2109                    return address;
2110                }
2111                if (address_parts.length === 2) {
2112                    address.street = address_parts[0];
2113                    address.city = address_parts[1];
2114                    return address;
2115                }
2116                if (address_parts.length >= 3) {
2117                    address.street = address_parts.slice(0, address_parts.length - 2).join(", ");
2118                    address.city = address_parts[address_parts.length - 2];
2119                    address.postcode = address_parts[address_parts.length - 1];
2120                    return address;
2121                }
2122        }
2123
2124        // Method of transport logic
2125        // Method codes
2126        // 1 = Sea
2127        // 2 = Rail
2128        // 3 = Road
2129        // 4 = Air
2130        // 5 = Postal
2131        // 6 = RoRo
2132        // 7 = Fixed transport installations
2133        // 8 = Inland waterway
2134        // 9 = Own propulsion
2135        const transportIDs = [
2136            { id: "10", desc: "IMO ship ID #", methods: ["1"] },
2137            { id: "11", desc: "Vessel name", methods: ["1"] },
2138            { id: "20", desc: "Wagon number", methods: ["2","3","6"] },
2139            { id: "30", desc: "Vehicle reg.", methods: ["3","6"] },
2140            { id: "40", desc: "IATA flight #", methods: ["4"] },
2141            { id: "41", desc: "Aircraft reg.", methods: ["4"] },
2142            { id: "80", desc: "ENI (European Vessel ID)", methods: ["8"] },
2143            { id: "81", desc: "Name of vessel", methods: ["8"] },
2144            { id: "Z", desc: "(other)", methods: ["5","7","9"] },
2145        ];
2146        $scope.relevantTransportIDs_INLAND = transportIDs;
2147        $scope.relevantTransportIDs_BORDER = transportIDs;
2148
2149        self.toggleShipMode = function(shipmentId, where, val) {
2150            if (self.isRequestNow && !$scope.broker.isEditMode) return
2151            let shipment = self.trade.shipments[shipmentId];
2152            if (where === 'import_border') {
2153                if (shipment.destination.customs.border_transport.mode.code !== val) {
2154                    shipment.destination.customs.border_transport.mode.code = val;
2155                } else {
2156                    shipment.destination.customs.border_transport.mode.code = "";
2157                }
2158            }
2159            self.onChange(shipmentId, where);
2160        }
2161
2162
2163        self.onBrokerChange = function(shipmentId=0, field) {
2164            const user_customs = self.request.shipments[shipmentId].user_customs;
2165            if (!user_customs || user_customs==="") return
2166
2167            // These fields apply one value to all items
2168            if (['nature_of_transaction','requested_procedure'].includes(field)) {
2169                self.request.shipments[shipmentId].items.forEach((item) => {
2170                    item.HS_code[user_customs].country_fields[field] = $scope.broker.header_short_hand[field];
2171                })
2172            }
2173
2174            // // Apply to all items, first sub-item array element
2175            if (['additional_procedure'].includes(field)) {
2176                const attribute_name = field+"s";
2177                self.request.shipments[shipmentId].items.forEach((item) => {
2178                    _.set(item.HS_code[user_customs].country_fields, attribute_name+"[0].code", $scope.broker.header_short_hand[field]);
2179                })
2180            }
2181
2182            // Apply to the active item only, first sub-item array element
2183            // if (['taric_add_national','taric_additional'].includes(field)) {
2184            //     const attribute_name = field+"s";
2185            //     self.request.shipments[shipmentId].items[self.view.activeItemTab]
2186            //     .HS_code[user_customs].country_fields[attribute_name][0] = $scope.broker[field];
2187            // }
2188
2189            // // Apply to all items, first sub-item array element, by attribute
2190            // const prev_doc_object = {'prev_doc_class':'class','prev_doc_type':'type','prev_doc_ref':'ref'};
2191            // if (Object.keys(prev_doc_object).includes(field)) {
2192            //     const attribute_name = prev_doc_object[field];
2193            //     self.request.shipments[shipmentId].items.forEach((item) => {
2194            //         item.HS_code[user_customs].country_fields.previous_docs[0][attribute_name] = $scope.broker[field];
2195            //     })
2196            // }
2197
2198            if (field === 'consignment_ref_user_editable') {
2199                const checkRef = (user_ref) => {
2200                    // TODO: bring in the checkRef function from SGS
2201                    // If used before, it sets self.view.consignment_ref_is_duplicated
2202                    // If not a valid reference, it sets self.view.consignment_ref_is_invalid
2203                }
2204                const makeLRN = () => {
2205                    return self.view.consignment_ref_start + self.view.consignment_ref_user_editable;
2206                }
2207                self.request.shipments[shipmentId].origin.customs.country_fields.cds.lrn = makeLRN();
2208            }
2209
2210
2211
2212            if (field === 'item') {
2213                console.log(self.view.activeItemTab);
2214            }
2215
2216
2217            self.request.shipments[shipmentId].anyItemPacks = TransactionService.arePacksUnderItems({
2218                items_array: self.request.shipments[shipmentId].items,
2219                import_export: self.request.shipments[shipmentId].user_customs
2220            });
2221            self.request = TransactionService.calcTxnSummary(self.request);
2222
2223
2224            $scope.$emit('request-change', self.request);
2225            return
2226        }
2227
2228
2229        self.onChange = async function (shipmentId, field) {
2230            // if (self.isRequestNow && !$scope.broker.isEditMode) return
2231
2232            let shipment = self.trade.shipments[shipmentId];
2233            if (!shipment.user_customs || shipment.user_customs==="") return
2234
2235            
2236            self.trade.contract.buy_sell = shipment.user_customs === 'export' ? 'sell' : 'buy';
2237            const border = (self.trade.contract.buy_sell === 'sell' ? "origin" : "destination");
2238
2239            if (shipment.user_customs === 'export') {
2240                if (!shipment.origin.exporter || !shipment.origin.exporter.organisation) {
2241                    self.userOrg = await orgServices.getMyOrganisation();
2242                    if (self.userOrg && self.userOrg.organisation) {
2243                        shipment.origin.exporter = self.userOrg;
2244                        // Data.toast({status: "info", message: "Set exporter to user"});
2245                    }
2246                }
2247            } else if (shipment.user_customs === 'import') {
2248                if (!shipment.destination.importer || !shipment.destination.importer.organisation) {
2249                    self.userOrg = await orgServices.getMyOrganisation();
2250                    if (self.userOrg && self.userOrg.organisation) {
2251                        shipment.destination.importer = self.userOrg;
2252                    }
2253                }
2254            }
2255
2256
2257            if (field === 'invoice_ref' || field === 'none') {
2258                if (!self.trade.invoices[0].invoice_ref) return
2259                if (self.trade.invoices[0].invoice_ref.length > 8) {
2260                    self.view.invoice_ref_hint = "Up to 8 characters from your reference can be used in the submission to CDS";
2261                }
2262                if (self.trade.invoices[0].invoice_ref.length > 0 && self.trade.shipments[0].items.length === 1) {
2263                    _.set(self.trade.shipments[0].items[0].HS_code[shipment.user_customs], "country_fields.previous_docs[0].ref", self.trade.invoices[0].invoice_ref);
2264                }
2265                // TODO: bring consignment_ref / lrn function and assign to trade
2266            }
2267
2268            if (field === "user_customs") {
2269                if (shipment.user_customs === 'export') {
2270                    shipment.origin.country = {alpha_2: "GB", name: "United Kingdom", code: 826};
2271                    // shipment.destination.country = {alpha_2: "", name: ""};  // would like to clear it like this, but it affects the org selection
2272                    self.view.buyer_is_importer = true;
2273                    self.view.seller_is_exporter = true;
2274                } else if (shipment.user_customs === 'import') {
2275                    shipment.destination.country = {alpha_2: "GB", name: "United Kingdom", code: 826};
2276                    // shipment.origin.country = {alpha_2: "", name: ""};  // would like to clear it like this, but it affects the org selection
2277                }
2278            }
2279
2280            if (true || field === "origin_country") {
2281                // self.updateOriginPorts(shipmentId);
2282                const origin_country = shipment.origin.country;
2283                _.set(self.trade.contract.seller, "extra_data.addresses.registered[0].country", origin_country);
2284                _.set(shipment.consignor, "extra_data.addresses.registered[0].country", origin_country);
2285                _.set(shipment.origin.exporter, "extra_data.addresses.registered[0].country", origin_country);
2286                _.set(shipment.consignor, "extra_data.addresses.registered[0].country", origin_country);
2287                _.set(shipment.origin, "customs.filing_country", origin_country);
2288                if (shipment.incoterm.PointCountry === "origin") {
2289                    shipment.handover.country = shipment[shipment.incoterm.PointCountry].country;
2290                }
2291                shipment.route[0] = origin_country;
2292            }
2293            if (true || field === "destination_country") {
2294                try {
2295                    // self.updateDestinationPorts(shipmentId);
2296                    const destination_country = shipment.destination.country;
2297                    if (_.get(self.trade, 'contract.buyer.extra_data.addresses.registered[0].country'))
2298                        self.trade.contract.buyer.extra_data.addresses.registered[0].country = destination_country;
2299                    
2300                    shipment.consignee.extra_data.addresses.registered[0].country = destination_country;
2301                    if (_.get(shipment, 'destination.importer.extra_data.addresses.registered[0].country'))
2302                        shipment.destination.importer.extra_data.addresses.registered[0].country = destination_country;
2303                    
2304                    shipment.consignee.extra_data.addresses.registered[0].country = destination_country;
2305                    shipment.destination.customs.filing_country = destination_country;
2306                    if (shipment.incoterm.PointCountry === "destination") {
2307                        shipment.handover.country = shipment[shipment.incoterm.PointCountry].country;
2308                    }
2309                    shipment.route[Math.max(1, shipment.route.length-1)] = destination_country;
2310                    // Data.toast({status: "info", message: "Destination country set to "+destination_country.name});
2311                } catch (error) {
2312                    Data.toast({status: "error", message: error.toString()});
2313                }
2314            }
2315
2316            if (field === 'documents') {
2317                self.documents = ['docref1','docref2'];
2318            }
2319
2320            if (field === 'incoterm') {
2321                // self.setShowHideFields(self.trade, shipmentId);
2322                shipment.handover.country = shipment[shipment.incoterm.PointCountry].country;
2323
2324                if (shipment.incoterm.code === "DDP" && border === "destination") {
2325                    Data.toast({message: "Are you sure? DDP suggests that the seller is responsible for import customs clearance and duties", status: "warning"});
2326                } else if (shipment.incoterm.code === "EXW" && border === "origin") {
2327                    Data.toast({message: "Are you sure? EXW suggests that the buyer is responsible for export customs clearance and duties", status: "warning"});
2328                }
2329            }
2330
2331            if (true || field === 'exporter') {
2332                try {
2333                    const exporter = JSON.parse(JSON.stringify(shipment.origin.exporter));
2334                    shipment.consignor = exporter;
2335                    if (self.view.seller_is_exporter) self.trade.contract.seller = exporter;
2336                    // Data.toast({status: "info", message: "Exporter set to "+exporter.organisation.toString()});
2337                } catch (error) {
2338                    Data.toast({status: "error", message: shipment.origin.exporter + " --- " + error.toString()});
2339                }
2340            }
2341
2342            if (true || field === 'importer') {
2343                let source_org;
2344                if (shipment.user_customs === 'import') {
2345                    source_org = shipment.destination.importer && JSON.parse(JSON.stringify(shipment.destination.importer));
2346                    shipment.consignee = source_org;
2347                } else {
2348                    source_org = JSON.parse(JSON.stringify(shipment.consignee));
2349                    shipment.destination.importer = source_org;
2350                }
2351                if (self.view.buyer_is_importer) self.trade.contract.buyer = source_org;
2352            }
2353
2354            if (field === 'carrier_country') {
2355                if (shipment.carrier.contact.extra_data.addresses.registered[0].country.alpha_2 !== "GB") {
2356                    shipment.carrier.contact.extra_data.accounting_details.EORIno = "";
2357                }
2358            }
2359
2360            if (field === 'export_inland') {
2361                if (shipment.origin.customs.inland_transport.mode.code === "") {
2362                    $scope.relevantTransportIDs_INLAND = transportIDs;
2363                } else {
2364                    $scope.relevantTransportIDs_INLAND = transportIDs.filter((row) => {
2365                        return row.methods.includes(shipment.origin.customs.inland_transport.mode.code)
2366                    });
2367                }
2368                if ($scope.relevantTransportIDs_INLAND.length === 1) {
2369                    shipment.origin.customs.inland_transport.id.type = $scope.relevantTransportIDs_INLAND[0].id;
2370                } else {
2371                    shipment.origin.customs.inland_transport.id.type = "";
2372                }
2373            }
2374            if (field === 'import_inland') {
2375                if (shipment.destination.customs.inland_transport.mode.code === "") {
2376                    $scope.relevantTransportIDs_INLAND = transportIDs;
2377                } else {
2378                    $scope.relevantTransportIDs_INLAND = transportIDs.filter((row) => {
2379                        return row.methods.includes(shipment.destination.customs.inland_transport.mode.code)
2380                    });
2381                }
2382                if ($scope.relevantTransportIDs_INLAND.length === 1) {
2383                    shipment.destination.customs.inland_transport.id.type = $scope.relevantTransportIDs_INLAND[0].id;
2384                } else {
2385                    shipment.destination.customs.inland_transport.id.type = "";
2386                }
2387            }
2388            if (field === 'export_border') {
2389                if (shipment.origin.customs.border_transport.mode.code === "") {
2390                    $scope.relevantTransportIDs_BORDER = transportIDs;
2391                } else {
2392                    $scope.relevantTransportIDs_BORDER = transportIDs.filter((row) => {
2393                        return row.methods.includes(shipment.origin.customs.border_transport.mode.code)
2394                    });
2395                }
2396                if ($scope.relevantTransportIDs_BORDER.length === 1) {
2397                    shipment.origin.customs.border_transport.id.type = $scope.relevantTransportIDs_BORDER[0].id;
2398                } else {
2399                    shipment.origin.customs.border_transport.id.type = "";
2400                }
2401            }
2402            if (field === 'import_border') {
2403                if (shipment.destination.customs.border_transport.mode.code === "") {
2404                    $scope.relevantTransportIDs_BORDER = transportIDs;
2405                } else {
2406                    $scope.relevantTransportIDs_BORDER = transportIDs.filter((row) => {
2407                        return row.methods.includes(shipment.destination.customs.border_transport.mode.code)
2408                    });
2409                }
2410                if ($scope.relevantTransportIDs_BORDER.length === 1) {
2411                    shipment.destination.customs.border_transport.id.type = $scope.relevantTransportIDs_BORDER[0].id;
2412                } else {
2413                    shipment.destination.customs.border_transport.id.type = "";
2414                }
2415            }
2416
2417            if (field === 'freight_amount') {
2418                if (shipment.costs.freight.amount < 0) {
2419                    shipment.costs.freight.amount = 0;
2420                }
2421            }
2422
2423            if (field === 'insurance_amount') {
2424                if (shipment.costs.insurance.amount < 0) {
2425                    shipment.costs.insurance.amount = 0;
2426                }
2427            }
2428
2429            if (field === 'item_amount') {
2430                shipment.items.forEach((item) => {
2431                    if (item.amount < 0) {
2432                        item.amount = 0;
2433                    }
2434                });
2435            }
2436
2437            if (field === 'ship_quantity') {
2438                if (shipment.quantity < 0) {
2439                    shipment.quantity = 0;
2440                }
2441            }
2442
2443            if (field === 'ship_net_weight') {
2444                if (shipment.net_weight < 0) {
2445                    shipment.net_weight = 0;
2446                }
2447            }
2448
2449            if (field === 'ship_gross_weight') {
2450                if (shipment.gross_weight < 0) {
2451                    shipment.gross_weight = 0;
2452                }
2453            }
2454
2455            if (field === 'seals') {
2456                if (shipment.origin.customs.country_fields.seals.qty < 0) {
2457                    shipment.origin.customs.country_fields.seals.qty = 0;
2458                }
2459                if (shipment.origin.customs.country_fields.seals.qty == 0) {
2460                    shipment.origin.customs.country_fields.seals.ids = [{text: "NOSEALS"}];
2461                } else {
2462                    if (shipment.origin.customs.country_fields.seals.ids[0].text === "NOSEALS") {
2463                        shipment.origin.customs.country_fields.seals.ids[0].text = "";
2464                    }
2465                }
2466            }
2467
2468            if (true || field === 'licenses') {
2469                let any_licenses = false;
2470                shipment.items.forEach((item) => {
2471                    if (item.HS_code[shipment.user_customs].country_fields.trader_controlled_goods_licenses !== 'No') {
2472                        any_licenses = true;
2473                    }
2474                });
2475                if (!any_licenses) {
2476                    self.view.cover_note_placeholder = 'You may need to state "No licenses required"';
2477                }
2478            }
2479
2480            // if (field === 'quota') {
2481            //     self.trade.shipments[0].items.forEach((item) => {
2482            //         let placeholder_text = '';
2483            //         if (item.HS_code.import.trader_trade_quota_claim) {
2484            //             placeholder_text = 'Enter quota details here';
2485            //         }
2486            //         document.getElementById('item-'+key+'-notes').placeholder = placeholder_text;
2487            //     });
2488            // }
2489
2490            if (field === "duty_pay_method") {   //  || field === "duty_acc_number"
2491                if (shipment.destination.customs.default_duty_pay_method === null) {
2492                    self.trade.feedback[0].duty_deferment_account_authorisation = 'no_auth';
2493                } else if (shipment.destination.customs.default_duty_pay_method === "E" &&
2494                    !self.trade.shipments[0].destination.customs.country_fields.vat_defer_acc_number) {
2495                    const user_deferment_acc = _.get(self.trade.contract.buyer.extra_data, "accounting_details.duty_acc_number", "");
2496                    self.trade.shipments[0].destination.customs.country_fields.duty_acc_number = user_deferment_acc;
2497                }
2498                // Fallback auto-fill ONLY if not already created. If there is ANYTHING there, doesn't overwrite behind the scenes.
2499                // if (shipment.destination.customs.country_fields.duty_pay_method === "trader" && shipment.destination.customs.country_fields.duty_acc_number) {
2500                //     if (shipment.destination.customs.country_fields.duty_acc_number.length > 0) {
2501                //         let uorg_details;
2502                //         if (self.trade.contract.buy_sell === "sell") {
2503                //             uorg_details = self.live_seller.extra_data.accounting_details;
2504                //         } else {
2505                //             uorg_details = self.live_buyer.extra_data.accounting_details;
2506                //         }
2507                //         if (uorg_details.duty_acc_number.length === 0) {
2508                //             uorg_details.duty_acc_number = shipment.destination.customs.country_fields.duty_acc_number;
2509                //         }
2510                //     }
2511                // }
2512            }
2513
2514            if (field === 'item_amount') {
2515                // self.trade.shipments.forEach((shipment) => {
2516                    _.set(shipment[border], "customs.official_fx", {
2517                        from: self.trade.contract.currency,
2518                        to: "GBP",          // SINGLE COUNTRY IMPLMENTATION
2519                        rate: FxService.convertOfficial({currency: "GBP", amount: 1}, self.trade.contract.currency, "GB").amount,
2520                    });
2521                    shipment.items.forEach((item) => {
2522                        item.HS_code[shipment.user_customs].country_fields.home_ccy_value = FxService.convertOfficial(item, "GBP", "GB");
2523                    })
2524                // });
2525            }
2526
2527            // if (shipment.commodity_group.groups.length === 0) {
2528            //     self.view.comGroupsAvailable = false;
2529            // } else if (shipment.commodity_group.groups[0].value === 0) {
2530            //     self.view.comGroupsAvailable = false;
2531            // } else {
2532            //     self.view.comGroupsAvailable = true;
2533            //     // shipment.commodity_group = TransactionService.groupByComcode(shipment, import_export, self.trade);
2534            // }
2535
2536            // anyItemPacks should always be true in the wizard
2537            self.trade.shipments[shipmentId].anyItemPacks = TransactionService.arePacksUnderItems({
2538                items_array: self.trade.shipments[shipmentId].items,
2539                import_export: self.trade.shipments[shipmentId].user_customs
2540            });
2541            const tmp_trade = TransactionService.calcTxnSummary(self.trade);
2542            
2543            self.trade.shipments[shipmentId].quantity = tmp_trade.shipments[shipmentId].quantity;
2544            self.trade.shipments[shipmentId].net_weight = tmp_trade.shipments[shipmentId].net_weight;
2545            self.trade.shipments[shipmentId].gross_weight = tmp_trade.shipments[shipmentId].gross_weight;
2546            self.trade.shipments[shipmentId].costs = tmp_trade.shipments[shipmentId].costs;
2547            self.trade.shipments[shipmentId].commodity_group = tmp_trade.shipments[shipmentId].commodity_group;
2548
2549            if (self.trade.contract.currency && self.trade.shipments[0].items.length > 0) {
2550                if (self.trade.contract.currency === self.trade.shipments[0].items[0].currency) {
2551                    self.trade.contract.contract_price = self.trade.shipments[0].items.reduce((acc, item) => { return acc + item.amount }, 0);
2552                } else {
2553                    try { JL("Wizard").info({mx_event: "currency_mismatch", contract_currency: self.trade.contract.currency, item_currency: self.trade.shipments[0].items[0].currency}); } catch {}
2554                    console.error("Currency mismatch between contract and item");
2555                }
2556            }
2557
2558            // self.view.any_header_docs_or_refs = updateAnyHeaderDocsOrRefs(shipmentId);
2559      
2560
2561        };
2562
2563        self.setItemLicenses = function(shipmentId, itemId, value) {
2564            if (self.isRequestNow && !$scope.broker.isEditMode) return
2565            _.set(self.trade.shipments[shipmentId].items[itemId].HS_code.export.country_fields, "trader_controlled_goods_licenses", value);
2566        }
2567
2568        self.setContainerised = function(shipmentId, value) {
2569            if (self.isRequestNow && !$scope.broker.isEditMode) return
2570            _.set(self.trade.shipments[shipmentId], "load_type.containerised", value);
2571        }
2572
2573        // self.orgUpdated = function(params) {
2574        //     console.log(params);
2575        // }
2576        
2577        
2578        self.refreshCDSLocResults = function (params) {
2579            return new Promise((resolve, reject) => {
2580                
2581                const user_input = params.search.toLowerCase();
2582                let shortList;
2583                const border = self.trade.shipments[0].user_customs==='export' ? 'origin' : 'destination';
2584                const gvms_only = self.trade.shipments[0][border].customs.country_fields.gvms_boolean;
2585                if (gvms_only == 'gvms') {
2586                    const gvmsList = TradeModelService.CDS_locations_array.filter((loc) => {
2587                        return loc.desc.contains('GVM')
2588                    });
2589                    shortList = gvmsList;
2590                } else {
2591                    shortList = TradeModelService.CDS_locations_array;
2592                }
2593
2594                const found = shortList.filter((loc) => {
2595
2596                    if (loc.code.toLowerCase().includes(user_input) || loc.desc.toLowerCase().includes(user_input)) {
2597                        return true
2598                    } else {
2599                        return false
2600                    }
2601                });
2602
2603                if (found.length <= 50) {
2604                    self.CDS_locations = found;
2605                } else {
2606                    const first50 = found.slice(0, 50);
2607                    self.CDS_locations = [...first50, {desc: "More available..."}];
2608                }
2609                
2610                // const new_post = {
2611                //     "isTag": true,
2612                //     "id": Date.now(), "type": "user input", "code": params.search, "desc": "(User-created code - don't forget to start with 'GBAU' or similar)"
2613                // };
2614                // self.CDS_locations.push(new_post);
2615                
2616                return resolve(found);
2617                
2618            });
2619        };
2620
2621        self.refreshCDSCOLocResults = function (params) {
2622            return new Promise((resolve, reject) => {
2623                
2624                const user_input = params.search.toLowerCase();
2625
2626                const found = TradeModelService.CDS_COL_locations.filter((loc) => {
2627                    if (loc.code.toLowerCase().includes(user_input) || loc.desc.toLowerCase().includes(user_input)) {
2628                        return true
2629                    } else {
2630                        return false
2631                    }
2632                });
2633                
2634                if (found.length <= 50) {
2635                    self.CDS_COL_locations = found;
2636                } else {
2637                    const first50 = found.slice(0, 50);
2638                    self.CDS_COL_locations = [...first50, {desc: "More available..."}];
2639                }
2640                // const new_post = {
2641                //     "isTag": true,
2642                //     "id": Date.now(), "type": "user input", "code": params.search, "desc": "(User-created code - don't forget to start with 'GBAU' or similar)"
2643                // };
2644                // self.CDS_COL_locations.push(new_post);
2645                
2646                return resolve(found);
2647                
2648            });
2649        }
2650
2651
2652
2653        $scope.setItemDefaultCurrency = function() {
2654            if (self.isRequestNow && !$scope.broker.isEditMode) return
2655            const user_customs = self.trade.shipments[0].user_customs;
2656            self.trade.shipments[0].items.forEach((item) => {
2657                item.currency = self.trade.contract.currency;
2658                item.HS_code[user_customs].country_fields.home_ccy_value = FxService.convertOfficial(item, "GBP", "GB");
2659            });
2660            const border = user_customs === 'export' ? 'origin' : 'destination';
2661            const new_rate = FxService.convertOfficial({currency: "GBP", amount: 1}, self.trade.contract.currency, "GB").amount;
2662            _.set(self.trade.shipments[0][border], "customs.official_fx", {
2663                rate: new_rate,
2664                from: self.trade.contract.currency,
2665                to: "GBP"
2666            });
2667
2668        }
2669
2670
2671        self.addItem = function () {
2672            if (self.isRequestNow && !$scope.broker.isEditMode) return
2673
2674            if (self.trade.shipments[0].items.length === 5) {
2675                Data.toast({"status": "info", "message":"If you need more items, use the full interface"});
2676                return
2677            }
2678
2679            const blankItem = _.merge({}, ItemClass);
2680            const origin_country = self.trade.shipments[0].origin.country;
2681            const destination_country = self.trade.shipments[0].destination.country;
2682            const invoice_ref = _.get(self.trade, 'invoices[0].invoice_ref', '');
2683
2684            let export_additional_info = [];
2685            if (self.trade.shipments[0].origin.customs.country_fields.transit.bool === true) {
2686                const exit_office_code = _.get(self.trade.shipments[0].origin, "customs.exit_office.code", "");
2687                const exit_office_desc = _.get(self.trade.shipments[0].origin, "customs.exit_office.desc", "");
2688                const populate_with = exit_office_code ? exit_office_code : exit_office_desc;
2689                export_additional_info.push({code: "TRANS", text: populate_with});
2690            }
2691
2692            const partialItem = {
2693                indexNo: self.trade.shipments[0].items.length,
2694                amount: 0,
2695                currency: self.trade.contract.currency,
2696                description: "",
2697                dutiable_weight: 0,
2698                HS_code: {
2699                    export: {
2700                        country: origin_country,
2701                        trader_controlled_goods_licenses: '',
2702                        country_fields: {
2703                            taric_add_nationals: [{code: ""}],   // , {code: "VATZ"}
2704                            previous_docs: [{class: "Z", type: "380", ref: invoice_ref}],
2705                            additional_info: export_additional_info
2706                        },
2707                    },
2708                    import: {
2709                        country: destination_country,
2710                        trader_controlled_goods_licenses: '',
2711                        country_fields: {
2712                            previous_docs: [{class: "Z", type: "380", ref: invoice_ref}],
2713                            additional_info: []
2714                        },
2715                    }
2716                },
2717                packs: [{kind: "", qty: 0, marks: "As per consignee"}]
2718            }
2719
2720            let newItemPack;
2721            if (partialItem.indexNo > 0) {
2722                const previousItem = self.trade.shipments[0].items[partialItem.indexNo-1];
2723                const previousItemPackKind = _.get(previousItem, "packs[0].kind", "");
2724                const previousItemPackMarks = _.get(previousItem, "packs[0].marks", "As per consignee");
2725                newItemPack = {packs: [{qty: 0, kind: previousItemPackKind, marks: previousItemPackMarks}]};
2726            }
2727            const newItem = _.merge(blankItem, partialItem, newItemPack);
2728
2729            self.trade.shipments[0].items.push(newItem);
2730            
2731            if (newItem.indexNo > 0) {
2732                self.view.disableItemPack[newItem.indexNo] = true;
2733            } else {
2734                self.view.disableItemPack[newItem.indexNo] = false;
2735            }
2736            $timeout(function(){
2737                self.view.activeItemTab = self.trade.shipments[0].items.length - 1;
2738            });
2739           
2740        }
2741
2742        self.deleteItem = function (idx) {
2743            if (self.isRequestNow && !$scope.broker.isEditMode) return
2744
2745            if (self.trade.shipments[0].items.length === 1){
2746                Data.toast({"status":"info","message":"You must have at least one item" } );
2747                return;
2748            }
2749
2750            const position = self.trade.shipments[0].items.findIndex((row) => { return row.indexNo === idx; });
2751            $timeout(function(){
2752                self.trade.shipments[0].items.splice(position, 1);
2753                self.trade.shipments[0].items.forEach((item, newIdx) => {
2754                    item.indexNo = newIdx;
2755                    if (newIdx === 0) {self.view.disableItemPack[newIdx] = false;}
2756                });
2757            },250); 
2758
2759            self.view.activeItemTab = idx-1;
2760       
2761        }
2762
2763        
2764
2765        self.openExportTariff = function (hs, partner, reporter) {
2766
2767            // exstend for import
2768            var level;
2769            if (hs.len != 6) level = 8
2770            //    https://www.macmap.org/en//query/results?reporter=699&partner=144&product=72254011&level=8
2771
2772            const exim = self.trade.buy_sell=='buy' ? 'import' : 'export';
2773            
2774            let reporter_country, lookup_code;
2775            if (!hs[exim]) {
2776                // not full resolved yet
2777                reporter_country = reporter.code;
2778                lookup_code = hs.Code;
2779
2780            } else {
2781                reporter_country = hs[exim].country.code;
2782                lookup_code = hs[exim].Code;
2783            }
2784
2785            let url, get;
2786
2787            get = lookup_code;
2788            
2789            if (partner.alpha_2 && reporter.alpha_2 != "GB") {
2790                get = lookup_code + "?country=" + partner.alpha_2 + "#" + exim;
2791            }
2792            else if (reporter.alpha_2 === "GB")  {
2793                if (lookup_code.length === 8) {
2794                    get = lookup_code + "00#" + exim;  // Todo: this is not necessarily the case (and might not work for loads). All the sub headings need to checked       
2795                } else if (lookup_code.length === 10) {
2796                    get = lookup_code + "#" + exim;
2797                } else {
2798                    Data.toast({status: "error", message: "Commodity code is not in a suitable format"});
2799                    return
2800                }
2801            }
2802
2803            if (reporter.alpha_2 == "GB") {
2804                url = "https://www.trade-tariff.service.gov.uk/commodities/" + get;
2805            } else if (TradeModelService.EU27.includes(reporter.alpha_2)) {
2806                url="https://ec.europa.eu/taxation_customs/dds2/taric/measures.jsp?Lang=en&Area="+partner.alpha_2 +"&Taric="+ lookup_code;      // alpha2 needs to be trading bloc
2807            } else {
2808                url ='https://www.macmap.org/en/query/results?reporter=' + reporter_country + '&partner=' + partner.code + '&product=' + lookup_code + '&level=' + level;
2809            }
2810
2811            window.open(url);
2812
2813        }
2814
2815        $scope.packSaveAsPreviousItem = function(idx) {
2816            if (self.isRequestNow && !$scope.broker.isEditMode) return
2817            const previousItemPackKind = _.get(self.trade.shipments[0].items[idx-1], "packs[0].kind", "");
2818            const previousItemPackMarks = _.get(self.trade.shipments[0].items[idx-1], "packs[0].marks", "As per consignee");
2819            _.set(self.trade.shipments[0], "items."+idx+".packs.0.qty", 0);
2820            _.set(self.trade.shipments[0], "items."+idx+".packs.0.kind", previousItemPackKind);
2821            _.set(self.trade.shipments[0], "items."+idx+".packs.0.marks", previousItemPackMarks);
2822        }
2823
2824
2825        self.getProductList = function (val, shipmentId, direction, itemId = 0) {
2826            console.log('getProductList');
2827            console.log(val, shipmentId, direction, itemId);
2828            val = val.split(" - ")[0];  //.replaceAll(" ", "");
2829            var codelen = 8;
2830            var shipment = self.trade.shipments[shipmentId];
2831
2832            if (direction === 'import') {
2833                var country = shipment.destination.customs.filing_country;
2834            } else if (direction === 'export') {
2835                var country = shipment.origin.customs.filing_country;
2836            }
2837            if (direction) shipment.items[itemId].HS_code[direction].valid_code = false;
2838
2839            return tbServices.service('code')
2840                .getMacmapProductList(val, country, codelen, direction)  // country object contains ISO3
2841                .then(function (validated_comcode_array) {
2842
2843                    if (validated_comcode_array.length === 0) {
2844
2845                        var hs_input = angular.element('#view-0-' + shipmentId + '-' + itemId + '-HS_code-' + direction);
2846                        hs_input.addClass('ng-invalid');
2847                        FastCheckService.add_message(
2848                            "Shipment " + (Number(shipmentId) + 1) + " item " + (Number(itemId) + 1) + " is invali
2848d",
2849                            "Import comcode", "warn", 1
2850                        );
2851
2852                    } else {
2853
2854                        if (direction === 'export') {
2855                            validated_comcode_array.forEach((found_code) => {
2856                                if (found_code['8digit']) {   // external API dependency
2857                                    found_code.Code = found_code['8digit'];
2858                                }
2859                            });
2860                        }
2861
2862                    }
2863
2864                    let total;
2865                    total = validated_comcode_array.length;
2866
2867                    let returnArray;
2868                    returnArray = validated_comcode_array.slice(0,4)
2869                    
2870                    // console.log("found "+validated_comcode_array.length.toString()+" codes");
2871                    // console.log(validated_comcode_array);
2872                    return validated_comcode_array;
2873                    //return {result: returnArray, total: total};
2874
2875                });
2876
2877        }
2878
2879        self.assignHScode = function (shipmentId, itemId, import_export) {
2880            if (self.isRequestNow && !$scope.broker.isEditMode) return
2881            try { JL('itembuilder').info("assignHScode(" + shipmentId + "-" + itemId.toString() + "-" + import_export + ")"); } catch {}
2882            if (import_export === undefined) {
2883                import_export = self.trade.buy_sell === 'buy' ? 'import' : 'export';
2884            }
2885            var shipment = self.trade.shipments[shipmentId];
2886            const NTL = 8;   //shipment.user_customs==='export' ? 8 : 10;
2887            var item = shipment.items[itemId];
2888            if (import_export === 'import') {
2889                if (!item.origin_country || !item.origin_country.alpha_2) {
2890                    Data.toast({status: "error", message: "Set the country of origin first"});
2891                    return
2892                }
2893            }
2894            // self.view.controls[0][shipmentId][itemId]["validating_"+import_export] = true;
2895            var hscode = item.HS_code;
2896
2897            if (!hscode.import) {
2898                hscode.import = angular.copy(ItemClass.HS_code.import);
2899            }
2900            if (!hscode.export) {
2901                hscode.export = angular.copy(ItemClass.HS_code.export);
2902            }
2903            if (!hscode.export.Code) {
2904                hscode.export.Code = "";
2905            }
2906
2907            // var filing_direction = TransactionService.customs_filing_side(self.trade, shipmentId);
2908            var direction_hscode = item.HS_code[import_export];
2909            if (!direction_hscode.country || !direction_hscode.country.alpha_2) {
2910                if (import_export === 'export') direction_hscode.country = shipment.origin.customs.filing_country;
2911                if (import_export === 'import') direction_hscode.country = shipment.destination.customs.filing_country;
2912            }
2913            if (!direction_hscode.country || !direction_hscode.country.alpha_2) {
2914                console.debug("Country not available. Will not lookup commodity code");
2915                return
2916            }
2917
2918            self.view.item_prefs_updating[item.indexNo] = true;
2919            tbServices.service('code')
2920                .getMacmapProductList(direction_hscode.Code, direction_hscode.country, NTL, import_export)  // country object contains ISO3
2921                .then(function (validated_comcode_array) {
2922                    if (validated_comcode_array.length === 0) {
2923
2924                        var hs_input = angular.element('#view-0-' + shipmentId + '-' + itemId + '-HS_code');
2925                        hs_input.addClass('ng-invalid');
2926                        FastCheckService.add_message(
2927                            "Shipment " + (Number(shipmentId) + 1) + " item " + (Number(itemId) + 1) + " is invalid",
2928                            "Import comcode", "warn", 1
2929                        );
2930                        console.error("Did not get any goods nomenclature entry for " + direction_hscode.Code + " in " + direction_hscode.country.name);
2931                        return
2932
2933                    } else if (validated_comcode_array.length > 1) {
2934                        console.error("Unexpectedly got more than one goods nomenclature entry for " + direction_hscode.Code + " in " + direction_hscode.country.name);
2935                    }
2936
2937                    if (import_export === 'export') {
2938                        validated_comcode_array.forEach((found_code) => {
2939                            if (found_code['8digit']) {   // external API dependency
2940                                found_code.Code = found_code['8digit'].replaceAll(" ", "");
2941                            }
2942                        });
2943                    }
2944                    selected_comcode = validated_comcode_array[0];
2945                    item.HS_code.Code = selected_comcode.Code.substr(0, 6).replaceAll(" ", "");
2946                    item.HS_code.Description = selected_comcode.Description;
2947                    item.HS_code.title = selected_comcode.view_value;  // for title
2948                    
2949                    direction_hscode.Code = selected_comcode.Code.replaceAll(" ", "");
2950                    direction_hscode.Description = selected_comcode.Description;
2951                    direction_hscode.country = selected_comcode.country;
2952                    direction_hscode.valid_code = selected_comcode.validated;  // validation sentinel
2953                    direction_hscode.title = selected_comcode.view_value;
2954
2955                    if (_.get(item.HS_code, "export.Code", "").substr(0, 4) != _.get(item.HS_code, "import.Code", "").substr(0, 4)) {
2956                        item.inconsistency = true;
2957                        // console.error("Need to tell user that item "+itemId+" export and import codes do not match even at 4 digit level");
2958                    } else {
2959                        item.inconsistency = false;
2960                    }
2961
2962                    shipment.commodity_group = TransactionService.groupByComcode(shipment, import_export, self.trade);
2963
2964
2965                    var hs_input = angular.element('#view-0-' + shipmentId + '-' + itemId + '-HS_code-' + import_export);
2966                    hs_input.removeClass('ng-invalid');
2967                    hs_input.blur();
2968
2969                    if (NTL !== 8) return
2970                    if (import_export === 'import' && item.HS_code.import.country.alpha_2.length > 0) {
2971
2972                        // copied this code to the product match routine
2973                        tbServices.service('newduty').getDuties(shipment, item, NTL, import_export)
2974                            .then((data) => {
2975                                item.HS_code[import_export].custom_duties = _.get(data, "CustomDuty", []);
2976                                item.HS_code[import_export].duty_message = data.error_message;
2977                                item.HS_code[import_export].duty_rate = '';
2978                                // unique regime on excel upload
2979                                if (item.HS_code[import_export].custom_duties.length === 1) {
2980                                    let regime = item.HS_code[import_export].custom_duties[0];
2981                                    item.HS_code[import_export].duty_rate = regime.TariffReported;
2982                                    item.HS_code[import_export].selected_regime = regime.TariffReported + " - " + regime.TariffRegime;
2983                                    item.HS_code[import_export].custom_duties[0].selected = true;
2984                                }
2985
2986                                // shipment = TransactionService.updateDutyTotals(shipment, self.trade.contract.currency);
2987                                // // SINGLE SHIPMENT IMPLEMENTATION
2988                                // self.trade.contract.costs.tariffs = {amount: shipment.costs.tariffs.amount, currency: shipment.costs.tariffs.currency};
2989                                
2990                            })
2991                            .catch((e) => {
2992                                console.error(e);
2993                                item.HS_code[import_export].duty_message = "Error unknown";
2994                                item.HS_code[import_export].custom_duties = [];
2995                                item.HS_code[import_export].duty_rate = '';
2996                                hs_input.addClass('ng-invalid');
2997                            })
2998                            .finally(() => {
2999                                // self.calcItemisedSummary(0, shipmentId, "item");
3000                                // self.view.controls[0][shipmentId][itemId]["validating_"+import_export] = false;
3001                            });
3002
3003                    } else {
3004                        // self.getDUCodes(item.HS_code.export)
3005                        //     .then((augmentedExportComcode) => {
3006                        //         console.log(augmentedExportComcode);
3007                        //         item.HS_code.export = augmentedExportComcode;
3008                        //     })
3009                        //     .catch((error) => {
3010                        //         console.warn("Error looking up DU code");
3011                        //     })
3012                        //     .finally(() => {
3013                        //         self.calcItemisedSummary(0, shipmentId, "item");
3014                        //         // self.view.controls[0][shipmentId][itemId]["validating_"+import_export] = false;
3015                        //     });
3016
3017                    }
3018
3019                })
3020                .finally(()=>{
3021                    $timeout(() => {
3022                        self.view.item_prefs_updating[item.indexNo] = false;
3023                    }, 500);
3024                });
3025
3026        }
3027
3028        self.selectRegime = function (shipmentId, itemId) {
3029            if (self.isRequestNow && !$scope.broker.isEditMode) return
3030
3031            if (!self.trade.shipments[shipmentId].items[itemId].HS_code.import.custom_duties) return
3032            self.trade.shipments[shipmentId].items[itemId].HS_code.import.duty_rate = "";
3033            self.trade.shipments[shipmentId].items[itemId].HS_code.import.custom_duties.forEac
3033h((regime) => {
3034                if (self.trade.shipments[shipmentId].items[itemId].HS_code.import.selected_regime === regime.TariffReported + " - " + regime.TariffRegime) {
3035                    regime.selected = true;
3036                    self.trade.shipments[shipmentId].items[itemId].HS_code.import.duty_rate = regime.TariffReported;
3037                } else {
3038                    regime.selected = false;
3039                }
3040            });            
3041            // self.calcItemisedSummary(0, shipmentId, '');
3042
3043        };
3044
3045        self.toggleDefermentAuth = function(shipmentId, val) {
3046            if (self.isRequestNow && !$scope.broker.isEditMode) return
3047            if (self.trade.feedback[0].duty_deferment_account_authorisation !== val) {
3048                self.trade.feedback[0].duty_deferment_account_authorisation = val;
3049            } else {
3050                self.trade.feedback[0].duty_deferment_account_authorisation = "";
3051            }
3052        };
3053
3054
3055
3056        $scope.calcShipmentTotals = function (field) {
3057            if (self.isRequestNow && !$scope.broker.isEditMode) return
3058            if (self.trade.shipments[0].calc_gross_wt) {   // field === 'gross_weight' && 
3059                let new_gross = 0;
3060                self.trade.shipments[0].items.forEach((item) => {
3061                    if (item.line_gross_weight< 0) {
3062                        item.line_gross_weight = 0;
3063                    }
3064                    new_gross += Number(item.line_gross_weight);
3065                });
3066                self.trade.shipments[0].gross_weight = new_gross;
3067            }
3068            if (self.trade.shipments[0].calc_net_wt) {   // field === 'net_weight' && 
3069                let new_net = 0;
3070                self.trade.shipments[0].items.forEach((item) => {
3071                    if (item.dutiable_weight < 0) {
3072                        item.dutiable_weight = 0;
3073                    }
3074                    new_net += Number(item.dutiable_weight);
3075                });
3076                self.trade.shipments[0].net_weight = new_net;
3077            }
3078            if (self.trade.shipments[0].calc_pack_qty) {  // field === 'quantity' && 
3079                let new_qty = 0;
3080                self.trade.shipments[0].items.forEach((item) => {
3081                    if (item.packs[0].qty < 0) {
3082                        item.packs[0].qty = 0;
3083                    }
3084                    new_qty += Number(_.get(item, "packs[0].qty", 0));
3085                });
3086                self.trade.shipments[0].quantity = new_qty;
3087            }
3088        }
3089
3090
3091        self.toggleValue = function(shipmentId, field, val) {
3092            if (self.isRequestNow && !$scope.broker.isEditMode) return
3093            if (field === 'export_gvms') {
3094                if (self.trade.shipments[shipmentId].origin.customs.country_fields.gvms_boolean !== val) {
3095                    self.trade.shipments[shipmentId].origin.customs.country_fields.gvms_boolean = val;
3096                } else {
3097                    self.trade.shipments[shipmentId].origin.customs.country_fields.gvms_boolean = "";
3098                }
3099            }
3100            if (field === 'transit') {
3101                if (self.trade.shipments[shipmentId].origin.customs.country_fields.transit.bool !== val) {
3102                    self.trade.shipments[shipmentId].origin.customs.country_fields.transit.bool = val;
3103                    // TODO: alert user if items are already present with HS_code.export.country_fields.additional_info
3104                } else {
3105                    self.trade.shipments[shipmentId].origin.customs.country_fields.transit.bool = "";
3106                    // TODO: alert user if items are already present with HS_code.export.country_fields.additional_info
3107                }
3108            }
3109        }
3110
3111        self.showHint = function(field) {
3112            if (field === 'import') self.view.show_import_hint = true;
3113            if (field === 'export') self.view.show_export_hint = true;
3114            if (field === 'no_auth') self.view.show_no_auth_hint = true;
3115            if (field === 'specific') self.view.show_specific_hint = true;
3116            if (field === 'standing') self.view.show_standing_hint = true;
3117        }
3118        self.hideHint = function(field) {
3119            if (field === 'import') self.view.show_import_hint = false;
3120            if (field === 'export') self.view.show_export_hint = false;
3121            if (field === 'no_auth') self.view.show_no_auth_hint = false;
3122            if (field === 'specific') self.view.show_specific_hint = false;
3123            if (field === 'standing') self.view.show_standing_hint = false;
3124        }
3125
3126
3127        let debounceWizard;
3128        $scope.$watch('$ctrl.trade',  (newVal, oldVal) => {
3129            
3130            if (debounceWizard) {
3131                $timeout.cancel(debounceWizard);
3132            }
3133            debounceWizard = $timeout(() => {
3134                if (self.isRequestNow && $scope.broker.isEditMode) {
3135                    $scope.$emit('request-change', newVal);
3136                } else {
3137                    $scope.$emit('trade-change', {trade: newVal, documents: self.docImages});
3138                }
3139            }, 500)
3140
3141            let differences = difference(newVal, oldVal);
3142            console.debug(differences);
3143            if (!_.isEmpty(differences)) {
3144                TransactionService.asyncLogTradeDiffs(differences);
3145            }
3146
3147            // run fast check again
3148
3149
3150
3151        }, true);
3152
3153        /**
3154         * Deep diff between two object, using lodash
3155         * @param  {Object} object Object compared
3156         * @param  {Object} base   Object to compare with
3157         * @return {Object}        Return a new object who represent the diff
3158         */
3159        function difference(object, base) {
3160            function changes(object, base) {
3161                return _.transform(object, function(result, value, key) {
3162                    if (!_.isEqual(value, base[key])) {
3163                        result[key] = (_.isObject(value) && _.isObject(base[key])) ? changes(value, base[key]) : value;
3164                    }
3165                });
3166            }
3167            return changes(object, base);
3168        }
3169
3170        $scope.$watch('$ctrl.trade.shipments[0].user_customs', async (newVal, oldVal) => {
3171
3172            if (self.userOrg && self.userOrg.organisation) {
3173                let shipment = self.trade.shipments[0];
3174                if (shipment.user_customs === 'export') {
3175                    if (!shipment.origin.exporter || !shipment.origin.exporter.organisation) {
3176                        shipment.origin.exporter = self.userOrg;
3177                        // Data.toast({status: "info", message: "Set exporter to user"});
3178                    }
3179                } else if (shipment.user_customs === 'import') {
3180                    if (!shipment.destination.importer || !shipment.destination.importer.organisation) {
3181                        shipment.destination.importer = self.userOrg;
3182                    }
3183                }
3184            }
3185
3186            await $scope.$emit('user-customs-change', {newVal: newVal, oldVal: oldVal});
3187
3188        });
3189
3190
3191        self.openDoc = function($index, file) {
3192            $scope.$emit('selected-document', $index+1);
3193        }
3194
3195        self.classifyDoc = function($index, file) {
3196        }
3197
3198
3199        //// DOCUMENT PROCESSING FUNCTION /////
3200
3201
3202        $scope.prompt_text = "What type of document is this? Normally ['tables'] will identify the items in this document. How many items are there in this document? What is the total value of the items? Return the result is JSON with \"document identified\", \"total_items\" and \"total_items_value\". Return also the items in the JSON as an array of items with the headings as key's in the object array.";
3203
3204        /// RE-INTERATING FROM CONTROLLER.JS
3205
3206        $scope.$watch('$ctrl.files', function (newVal, oldVal) {
3207
3208            if (!newVal) return;
3209            $scope.upload(self.files);
3210
3211        });
3212
3213        $scope.$watch('file', function (newVal, oldVal) {
3214            if (!newVal) return;
3215
3216            if ($scope.file != null) {
3217                $scope.files = [$scope.file];
3218            }
3219        });
3220
3221        let json_jumble;
3222
3223        $scope.log = '';
3224
3225        self.docNatives = [];
3226        $scope.upload = async function (files) {
3227
3228            // self.docPNGs = [];
3229            self.view.processingDocs = true;
3230            $scope.$emit('started-processing-docs');
3231            if (files && files.length) {
3232                if (!self.docImages) self.docImages = [];
3233
3234                let files_bd64 = await Upload.base64DataUrl(files).then(function (urls) {
3235                    return urls;
3236                });
3237                // self.docNatives = (files_bd64 && files_bd64.length > 0) ? files_bd64 : [];
3238                files.forEach((file, idx) => {
3239                    self.docNatives.push({
3240                        filename: file.name,
3241                        size: file.size,
3242                        type: file.type,
3243                        b64: files_bd64[idx]
3244                    })
3245                });
3246
3247                if (self.isRequestNow) {
3248
3249                    const response = await DocumentService.uploadDocumentForFormWizard(files, self.trade);
3250    
3251                    $timeout(async () => {
3252                        let documentsForTradeWithHistory = await DocumentService.getDocumentsForTradeWithHistory(self.trade.id, self.trade.object_id);
3253    
3254                        if (documentsForTradeWithHistory.length > 0) {
3255    
3256                            for (let i = 0; i < response.docPNGs.length; i++) {
3257                                let item = response.docPNGs[i];
3258                                let findItem = _.find(documentsForTradeWithHistory, { fldFilename: item.unique_id });
3259                                const index = documentsForTradeWithHistory.findIndex((objItem)=> objItem.fldtransactionDocId === findItem.fldtransactionDocId);
3260    
3261                                if (findItem.fldUrl && (findItem.fldUrl !== '' || findItem.fldUrl !== null)) {
3262                                    const pngFilesResult = await DocumentService.convertPDFToBase68PNGs(findItem.fldUrl);
3263                                    findItem = {...findItem, ...{ filename: item.unique_id, pages: pngFilesResult }};
3264    
3265                                    // documentsForTradeWithHistory[index] = findItem;
3266    
3267                                    self.docImages.push(findItem);
3268                                }
3269                            }
3270                        }
3271    
3272                        // self.docPNGs = documentsForTradeWithHistory;
3273                        self.docNatives = [...self.docNatives, ...response.docNatives];
3274                        $scope.$emit("added-document", {pngs_list: self.docImages, natives_list: self.docNatives, loadCropper: true});
3275    
3276                        self.view.processingDocs = false;
3277                        $scope.$emit('finished-processing-docs');
3278                        
3279                    }, 500);
3280
3281                } else {
3282
3283                    for (var i = 0; i < files.length; i++) {
3284    
3285                        let file = files[i];
3286    
3287                        if (!file.$error) {
3288                            self.generalPromptsToShow = [];
3289    
3290                            // convert multi-page PDF to array of PNG
3291                            let pngFiles;
3292                            if (files_bd64[i].startsWith('data:application/pdf;base64,')) {
3293                                const justB64 = files_bd64[i].split('data:application/pdf;base64,')[1];
3294                                const tmp = await DocumentService.convertPDFToPNGs(justB64);
3295                                pngFiles = tmp.map((rawb64) => { return 'data:image/png;base64,' + rawb64 });
3296                            } else {
3297                                pngFiles = [files_bd64[i]];
3298                            }
3299                            const randNum = Math.floor(Math.random()*89999+10000);
3300                            const currTime = Date.now();
3301                            const unique_id = `${currTime}_${randNum}.png`;
3302                            const pngObject = {filename: file.name, pages: pngFiles, unique_id: unique_id};
3303                            self.docImages.push(pngObject);
3304                            TransactionService.asyncLogTradeDiffs({mx_event: "Added document", filename: file.name});
3305                            $scope.$emit("added-document", {pngs_list: self.docImages, natives_list: self.docNatives});
3306    
3307                            // self.generalPromptsToShow.push({ path: "Number of pages", value: pngFiles.length });
3308    
3309                            const pngFile = pngFiles[0];
3310                            continue;
3311    
3312                            const queries_array = sourceTarget['general-prompts'].map((p) => { return p.prompt }).filter((p) => { return p.length > 0 });
3313    
3314                            await Upload.http({
3315                                // url: 'https://shybw1g4th.execute-api.eu-west-2.amazonaws.com/dev',
3316                                // url: 'https://api.exabler-uk.com/file_to_trade_1',
3317                                url: 'http://localhost:5000/ocr/sync/initial',
3318                                headers: {
3319                                    'Content-Type': 'application/json',
3320                                    'Authorization': 'Bearer on'
3321                                },
3322                                data: {
3323                                    file: pngFile,
3324                                    // features: ["tables", "forms"],
3325                                    prompt_text_array: queries_array   // [$scope.prompt_text]
3326                                }
3327    
3328                            }).then(async (resp) => {
3329    
3330                                if (resp.data.results.json_jumble.error) {
3331                                    alert("There was a JJ error: " + resp.data.json_jumble.error);
3332                                    self.view.process_general = false;
3333                                    return
3334                                }
3335    
3336                                try {
3337                                    json_jumble = resp.data.results.json_jumble;
3338                                } catch (e) { }
3339    
3340                                Data.toast({ status: "success", message: "Found some data, let's check it out" });
3341    
3342                                // await gatherFilingInformation('general-prompts', json_jumble.key_values);
3343    
3344                                showItemsWithDelay(
3345                                    json_jumble.answers
3346                                        .filter((qa) => {
3347                                            return qa.conf > 0
3348                                        }).map((qa) => {
3349                                        return { path: qa.q, value: qa.a };
3350                                    })
3351                                );
3352    
3353                                self.view.process_general = false;
3354                                self.view.process_complete = false;
3355                                self.view.process_complete_val = 0;
3356    
3357    
3358    
3359                                // $timeout(function () {
3360    
3361                                //   $scope.log = 'file: ' +
3362                                //     file.name +
3363                                //     ', Response: ' + JSON.stringify(resp.data) +
3364                                //     '\n' + $scope.log;
3365    
3366                                // });
3367                            }, function (result) {
3368    
3369                                self.showSimpleToast("There was an error: " + result.data.error);
3370                                self.view.process_general = false;
3371                                return;
3372    
3373                            }, function (evt) {
3374    
3375                                self.view.process_general = true;
3376    
3377                                var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
3378    
3379                                file.progressPercentage = progressPercentage;
3380    
3381                            });
3382                        }
3383                    }
3384
3385                    self.view.processingDocs = false;
3386                    $scope.$emit('finished-processing-docs');
3387                    console.log('finished-processing-docs');
3388
3389                }
3390            }
3391
3392            
3393        };
3394
3395        function cleanUpGeneralResponse(generalResponse) {
3396            const blocks = _.get(generalResponse, "Blocks", [])
3397                .map((block) => {
3398                    delete block.Id;
3399                    delete block.Relationships;
3400                    delete block.Geometry.BoundingBox;
3401                    return block;
3402                })
3403                .filter((block) => {
3404                    return block.Confidence && block.Confidence > 0.5 && block.Text && block.Text.length && block.Text.length > 0
3405                })
3406
3407            return cleaned_blocks
3408        }
3409
3410        self.getResponse = function(what) {
3411
3412            processInitialResponse(json_jumble);
3413
3414        }
3415
3416        /**
3417         * 
3418         * @param {*} key 
3419         * @param {*} json_query 
3420         * @param {*} label Chunk label, e.g. exporter, importer
3421         * @returns 
3422         */
3423        let gatherFilingInformation = function (key, json_query, label = null) {
3424
3425            return new Promise((resolve, reject) => {
3426
3427                var promises = [];
3428
3429                let groupedSource = chunkSource(key, label);
3430
3431                // goes through one-by-one right now
3432                Object.entries(groupedSource).forEach(function ([path, element], index) {
3433
3434                    //if (index >= 5) return    // for debug (groupSource is just one value)
3435                    
3436                    let chained_prompt, json_post;
3437                    if (label == 'item') {
3438
3439                        chained_prompt = "Map the values of this JSON object to the following:" + element.prompt + ". Return a JSON that follows the mapping: " + element.fields + " that to the original JSON.  If it doesn't map well, put (not sure). Ensure each value has \" \"."
3440                        //chained_prompt = "In the JSON, there is an array with a of headings which correspond to: " + element.prompt + ". Return JSON object with only these keys: " + element.fields + ". Values for these keys should be mapped to keys of the original JSON. Ensure each value has \" \".";
3441                        json_post = json_query.tableHeaders;
3442                    } else {
3443                        chained_prompt = "In the JSON: " + element.prompt + " Return as a JSON array. Each object has path and value where path is: " + element.fields;
3444                        json_post = json_query;
3445                    }
3446
3447                    const http_request = {
3448                        method: 'POST',
3449                        url: 'http://localhost:5000/jumble/sync/follow-up-queries',
3450                        data: { "prompt_text": chained_prompt, "json_jumble": json_post },
3451                        headers: {
3452                            'Content-Type': 'application/json',
3453                            'Authorization': 'Bearer on'
3454                        }
3455
3456                    };
3457
3458                    const promise = $http(http_request).then((result) => {
3459
3460                        // let tabs = [];
3461
3462                        try {
3463
3464                            if (label == 'item') {
3465
3466                                console.log("carrying out re-mapping")
3467                                let mapping = JSON.parse(result.data.results.choices[0].text)
3468                                const renamedArray = json_query.tableRows.map((item) => {
3469                                    const renamedItem = {};
3470                                    for (const key in item) {
3471                                        for (const prop in mapping.item) {
3472                                            if (mapping.item[prop] === key) {
3473                                                renamedItem[prop] = item[key];
3474                                                break;
3475                                            }
3476                                        }
3477                                    }
3478                                    return renamedItem;
3479                                });
3480
3481                                // if the openai test does find a good match the renamed array is an empty  {}
3482                                // we could just pass back and lets the user decide
3483                                self.tabs.push({ key: label, ai_value: renamedArray });
3484
3485                            } else {
3486
3487                                let parsed_resp;
3488                                let relabelled_array = [];
3489                                try {
3490                                    parsed_resp = JSON.parse(result.data.results.choices[0].text);
3491                                    if (Array.isArray(parsed_resp)) {
3492                                        parsed_resp.forEach((row) => {
3493                                            let nice_label;
3494                                            if (row.path) { nice_label = _.get(nice_names_map, row.path, row.path); }
3495                                            new_row = angular.copy(row);
3496                                            new_row.path = nice_label;
3497                                            relabelled_array.push(new_row)
3498                                        })
3499                                    }
3500                                } catch {
3501                                    relabelled_array = [{ path: 'error' }]
3502                                }
3503                                self.tabs.push({ key: label, ai_value: relabelled_array })
3504
3505
3506
3507                            }
3508                            //console.log(self.tabs[label]);
3509
3510                        } catch (e) {
3511
3512                            console.log(e);
3513                            console.log(result.data.results.choices[0].text);
3514
3515                        }
3516
3517                        return;
3518
3519                    });
3520
3521                    promises.push(promise);
3522
3523                });
3524
3525                Promise.all(promises).then(function (result) {
3526
3527                    return resolve();
3528
3529                });
3530
3531
3532            });
3533
3534
3535
3536
3537        }
3538
3539        /**
3540         * Sometimes textract can find several tables. 
3541         * The item table is not always the one it finds first
3542         * This procedure tells it to try again
3543         *  
3544         * */
3545        self.tryAnotherTable = async function (index = null) {
3546
3547            self.view.process_items = true;
3548
3549            if (!index) index = parseInt(self.view.table_selected);
3550
3551            // remove the old one
3552            _.remove(self.tabs, function (el) {
3553                return el.key == 'item';
3554            });
3555
3556
3557            let table = processTextractTable(json_jumble.tables, index)
3558            await gatherFilingInformation('user-create-trade-fields', table, 'item');
3559
3560            // in this case it is mapping, we use the text table feature as its better
3561            let tmp_array = self.tabs.filter((element) => {
3562                return element.key == 'item';
3563            })
3564
3565
3566            if (!_.isEmpty(tmp_array[0].ai_value[0])) {
3567
3568                self.items = tmp_array[0].ai_value;
3569                self.item_headers = Object.keys(self.items[0]);
3570
3571                // self.tryAnotherTable(index+1)
3572
3573            } else {
3574
3575                self.items = []
3576                self.item_headers = [];
3577
3578            }
3579
3580            self.view.process_items = false;
3581            return
3582
3583        }
3584
3585
3586        let processInitialResponse = async function (json_jumble) {
3587
3588            self.tabs = [];
3589
3590            self.view.process_header = true;
3591
3592            let cards = ['exporter', 'importer', 'consignee', 'consignor'] // to cycle the below
3593
3594            cards.forEach(async (card) => {
3595                await gatherFilingInformation('user-create-trade-fields', json_jumble.key_values, card);
3596            })
3597
3598
3599            self.view.process_header = false;
3600
3601
3602            // in this case we want open ai to have a go at the mapping
3603
3604            // self.view.number_of_tables = json_jumble.tables.length;
3605            // self.view.table_list = [];
3606
3607            // for (j = 0; j < json_jumble.tables.length; j++) {
3608            //     self.view.table_list.push(j);
3609            // }
3610
3611            // self.tryAnotherTable(self.view.table_selected)
3612
3613            // console.log(items);
3614
3615            self.view.process_complete = true;
3616            self.view.process_complete_val = 85 + 15 * Math.random();
3617
3618            $timeout(() => {
3619                setBarColor();
3620            }, 200)
3621
3622        }
3623
3624
3625        let setBarColor = function () {
3626
3627            let progressBar = document.getElementById('completion-bar');
3628            const value = self.view.process_complete_val; // Replace this with your desired value
3629
3630            if (value < 60) {
3631                progressBar.classList.remove('medium-value', 'high-value');
3632            } else if (value >= 60 && value <= 80) {
3633                progressBar.classList.remove('high-value');
3634                progressBar.classList.add('medium-value');
3635            } else {
3636                progressBar.classList.remove('medium-value');
3637                progressBar.classList.add('high-value');
3638            }
3639
3640        }
3641
3642
3643        self.addPrompt = function (tab, field, value) {
3644
3645            // creates a queue of promise to improve the accuracy of the field
3646            let prompt = "The field does not look right, try again";
3647
3648            let old_text = self.tabs[tab][field];
3649
3650            self.tabs[tab][field] = ''
3651
3652            $timeout(() => {
3653
3654                typeModel(old_text, tab, field)
3655
3656            }, 2000)
3657
3658
3659        }
3660
3661
3662        function typeModel(rationale, tab, field) {
3663
3664            var chars = rationale.split('');
3665            var typedRationale = '';
3666
3667            function typeNextChar(index) {
3668                if (index < chars.length) {
3669                    typedRationale += chars[index];
3670                    self.tabs[tab][field] = typedRationale;
3671                    $timeout(function () {
3672                        typeNextChar(index + 1);
3673                    }, 15); // Adjust the delay (in milliseconds) between each character typing
3674                }
3675            }
3676
3677            typeNextChar(0);
3678        }
3679
3680
3681        let chunkSource = function (key, label) {
3682
3683            let temp_array = [];
3684
3685            sourceTarget[key].forEach((element, index) => {
3686
3687                try {
3688
3689                    let path_array = element.path.split('.')
3690
3691                    if (element.path.includes(label)) {
3692                        // simple log to reduce to management set of cards/blocks -- to improve
3693                        element.single_block = label;
3694                        temp_array.push(element);
3695
3696                    } else {
3697                        //    sourceTarget[key][index].single_block = path_array[0] + "." + path_array[1]; //+ "." + path_array[1];
3698                    }
3699
3700
3701                } catch (e) {
3702
3703                    //     sourceTarget[key][index].single_block = 'general';
3704
3705                }
3706
3707            });
3708
3709            const groupedData = {};
3710
3711            for (const item of temp_array) {
3712                const { path, prompt, single_block } = item;
3713
3714                if (!groupedData[single_block]) {
3715                    groupedData[single_block] = {
3716                        prompt: prompt,
3717                        fields: path,
3718                    };
3719                } else {
3720                    groupedData[single_block].prompt += ` ${prompt}`;
3721                    groupedData[single_block].fields += `, ${path}`;
3722                }
3723            }
3724
3725            return groupedData;
3726
3727        }
3728
3729
3730
3731        ////
3732
3733
3734        $scope.log = '';
3735
3736
3737        let promptPromises = [];
3738
3739
3740        let processFileToTradeResponse = function (data) {
3741            if (data.results) {
3742                if (data.results.choices && Array.isArray(data.results.choices) && data.results.choices.length > 0) {
3743                    const raw_text = data.results.choices[0].text;
3744                    let intermediate, resulting_json;
3745                    try {
3746                        intermediate = raw_text.replaceAll("\n", "").replaceAll(" ", "");
3747                    } catch (e) {
3748                        return e.toString()
3749                    }
3750                    try {
3751                        resulting_json = JSON.parse(intermediate)
3752                    } catch (e) {
3753                        return e.toString()
3754                    }
3755                    return resulting_json
3756                }
3757            }
3758        }
3759
3760
3761        /**
3762         * 
3763         * @param {} index 
3764         * @param {*} key 
3765         * @returns 
3766         */
3767
3768        function showItemsWithDelay(data) {
3769
3770            var deferred = $q.defer();
3771            var promises = [];
3772
3773            angular.forEach(data, function (item, index) {
3774                promises.push($timeout(function () {
3775                    self.generalPromptsToShow.push(item);
3776
3777                    $('#docs_summarising').animate({
3778                        scrollTop: $("#docs_summarising").offset().top
3779                     });
3780
3781                }, 1000 * index));    // could add the typing animation also
3782            });
3783
3784            $q.all(promises).then(function () {
3785                deferred.resolve();
3786            });
3787
3788            return deferred.promise;
3789
3790        }
3791
3792
3793        var sourceTarget = {};
3794        var nice_names_map = {};
3795
3796        /**
3797         * Loading source files
3798         * This includes the prompts for each trade field
3799         */
3800        let loadSource = async function () {
3801
3802            const http_request = {
3803                method: 'get',
3804                url: '/api/v1/doc-ocr/invoice-prompts',
3805                dataType: 'json',
3806                contentType: "application/json"
3807            };
3808
3809            let result = await $http(http_request)
3810
3811            sourceTarget = result.data.standard;
3812
3813            var nice_names_array = [];
3814            try {
3815                const nice_names_response = await $http({
3816                    method: "GET",
3817                    url: "/api/v1/analytics/report/fields",
3818                    dataType: "json",
3819                    contentType: "application/json"
3820                });
3821                nice_names_array = _.get(nice_names_response, "data[0].fields", []);
3822            } catch (e) {
3823                nice_names_array = [
3824                    { "name": "shipment.origin.exporter.organisation", "nice_name": "Exporter Name" },
3825                    { "name": "shipment.origin.exporter.extra_data.accounting_details.EORIno", "nice_name": "Exporter EORI" },
3826                    { "name": "shipment.origin.exporter.extra_data.addresses.0.in_full", "nice_name": "Exporter Address" },
3827                    { "name": "shipment.origin.exporter.extra_data.addresses.0.country.alph
3827a_2", "nice_name": "Exporter Country Code" },
3828                    { "name": "trade.type", "nice_name": "Trade Type" },
3829                ];
3830            }
3831            nice_names_array.forEach((row) => {
3832                nice_names_map[row.name] = row.nice_name;
3833            });
3834
3835        }
3836
3837        let processTextractTable = function (data) {
3838            // expected response includes interesting key, values with confidence
3839            // array of tables
3840            let tables = data// .tables
3841            try {
3842                if (tables.length > 1) {
3843                    // Data.toast({ "status": "info", "message": "More than one table found, using the first one" });
3844                }
3845
3846                tables.forEach((table, index) => {
3847                    tables[index] = JSON.parse(table);
3848                }, tables)
3849
3850            } catch (e) {
3851
3852                console.log("Error parsing table");
3853                console.log(e);
3854
3855            }
3856
3857            // CAN TURN LOOP THROUGH MULTIPLE TABLES
3858            const headers = Object.keys(tables[0]);
3859            headers.forEach((key, index) => {
3860                headers[index] = key.trim();
3861            }, headers);
3862            const keys = Object.keys(tables[0][Object.keys(tables[0])[0]]);
3863            const rows = keys.map((key) => {
3864                const obj = {};
3865                for (const prop in tables[0]) {
3866                    const trimmedProp = prop.trim();
3867                    obj[trimmedProp] = tables[0][prop][key];
3868                    //obj[prop] = tables[0][prop][key];
3869                }
3870                return obj;
3871            });
3872
3873            return { tableHeaders: headers, tableRows: rows }
3874        }
3875
3876        // THIS IS FOR THE GENERAL DOC ANALYSER WITHOUT AWS TEXTRACT
3877        $scope.sendQuestion = function () {
3878
3879            questions.push($scope.question);
3880            var question = $scope.question;
3881
3882            $scope.messages.push('You: ' + question);
3883
3884            const http_request = {
3885                method: 'POST',
3886                url: "http://localhost:5000/filequery",
3887                data: {
3888                    "prompts": questions,
3889                    "file_key": $scope.file_value
3890                },
3891                headers: {
3892                    'Content-Type': 'application/json',
3893                    'Authorization': 'Bearer on'
3894                }
3895
3896            };
3897
3898            // Make an HTTP GET request to fetch the response from the server
3899            $http(http_request).then(function (response) {
3900
3901                $scope.question = '';
3902
3903                let index = response.data.results.length - 1;
3904
3905                var answer = response.data.results[index].answer.trim();
3906
3907                $scope.messages.push('Bot: ' + answer);
3908
3909            })
3910                .catch(function (error) {
3911                    console.error('Error:', error);
3912                });
3913
3914        }
3915
3916        function convertValue(value) {
3917            if (value.includes('%')) {
3918                return Number(value.replace('%', ''));
3919            } else if (/^\d{1,3}(,\d{3})*(\.\d+)?$/.test(value)) {
3920                return parseFloat(value.replace(/,/g, ''));
3921            } else {
3922                return value;
3923            }
3924        }
3925
3926        //// END OF DOCUMENT PROCESSING FUNCTION /////
3927
3928    }
3929
3930
3931});
3932
3933
3934
3935app.directive('uibTabButton', function() {
3936    return {
3937      restrict: 'EA',
3938      scope: {
3939        handler: '&',
3940        text:'@'
3941      },
3942      template: '<li class="uib-tab nav-item">' +
3943        '<a href="javascript:;" ng-click="handler()" class="nav-link" ng-bind="text"></a>' +
3944        '</li>',
3945      replace: true
3946    }
3947});
3948
3949
3950app.component('whatHappensComponent', {
3951    templateUrl: 'whatHappensTemplate.html',
3952    bindings: {
3953        resolve: '<',
3954        close: '&',
3955    //   dismiss: '&'
3956    },
3957    controller: function () {
3958        var $ctrl = this;
3959
3960        $ctrl.$onInit = function () {
3961        // $ctrl.items = $ctrl.resolve.items;
3962        // $ctrl.selected = {
3963        //   item: $ctrl.items[0]
3964        // };
3965        };
3966
3967        $ctrl.ok = function () {
3968            $ctrl.close({$value: 'close'});
3969        };
3970
3971    //   $ctrl.cancel = function () {
3972    //     $ctrl.dismiss({$value: 'cancel'});
3973    //   };
3974    }
3975});
3976
3977
3978app.component('statValueComponent', {
3979    templateUrl: 'statValueTemplate.html',
3980    bindings: {
3981        resolve: '<',
3982        close: '&',
3983        dismiss: '&'
3984    },
3985    controller: function () {
3986        $ctrl = this;
3987        var trade;
3988
3989        $ctrl.$onInit = function () {
3990            trade = $ctrl.resolve.trade;
3991            $ctrl.currency = $ctrl.resolve.trade.contract.currency;
3992            $ctrl.items = $ctrl.resolve.trade.shipments[0].items;
3993            $ctrl.current_rate = $ctrl.resolve.current_rate;
3994            $ctrl.saved_rate = $ctrl.resolve.saved_rate;
3995        }
3996
3997        $ctrl.ok = function () {
3998            const updated_items = $ctrl.items.map((item) => {
3999                const new_stat_value = item.amount / $ctrl.current_rate;
4000                return _.merge({}, item, {HS_code: {export: {country_fields: {home_ccy_value: {amount: new_stat_value}}}}});
4001            });
4002            const updated_trade = _.merge({}, trade, {shipments: [{ origin: {customs: {official_fx: {rate: $ctrl.current_rate}} }, items: updated_items}]});
4003            $ctrl.close({$value: updated_trade});
4004        };
4005
4006        $ctrl.cancel = function () {
4007            $ctrl.dismiss({$value: 'cancel'});
4008        };
4009
4010    }
4011})
4012
4013app.component('sendModalComponent', {
4014    // templateUrl: 'app/components/ts_hub/captive_start_modal.html',
4015    // templateUrl: 'app/components/trade_assist/trade.assist.fees.confirm.html',
4016    templateUrl: 'sendModalTemplate.html',
4017    bindings: {
4018        resolve: '<',
4019        close: '&',
4020        dismiss: '&'
4021    },
4022    controller: function() {
4023        $ctrl = this;
4024        
4025        $ctrl.$onInit = function () {
4026            $ctrl.payload = $ctrl.resolve.payload;
4027            $ctrl.sponsor = $ctrl.resolve.sponsor;
4028            $ctrl.purpose = "customs brokerage";
4029        };
4030
4031        $ctrl.ok = function () {
4032            $ctrl.close({$value: 'ok'});
4033        };
4034
4035        $ctrl.cancel = function () {
4036            $ctrl.dismiss({$value: 'cancel'});
4037        };
4038
4039    }
4040})
4041
4042
4043
4044/**
4045 * AngularJS default filter with the following expression:
4046 * "person in people | filter: {name: $select.search, age: $select.search}"
4047 * performs an AND between 'name: $select.search' and 'age: $select.search'.
4048 * We want to perform an OR.
4049 */
4050app.filter('propsFilter', function() {
4051    return function(items, props) {
4052      var out = [];
4053  
4054      if (angular.isArray(items)) {
4055        var keys = Object.keys(props);
4056  
4057        items.forEach(function(item) {
4058          var itemMatches = false;
4059  
4060          for (var i = 0; i < keys.length; i++) {
4061            var prop = keys[i];
4062            var text = props[prop].toLowerCase();
4063            if (item[prop].toString().toLowerCase().indexOf(text) !== -1) {
4064              itemMatches = true;
4065              break;
4066            }
4067          }
4068  
4069          if (itemMatches) {
4070            out.push(item);
4071          }
4072        });
4073      } else {
4074        // Let the output be the input untouched
4075        out = items;
4076      }
4077  
4078      return out;
4079    };
4080  });
4081
4082  (function ($) {
4083    'use strict';
4084    $.fn.bsPeekabooLabel = function () {
4085      var input = $(this),
4086        control = input.closest('[class*="col-"]'),
4087        label = control.siblings('.control-label'),
4088        show = false,
4089        place = function (d) {
4090          var m;
4091          if ($(control).css('float') === 'left') {
4092            m = '0 0 0 ' + label.outerWidth() + 'px';
4093            label.animate({ margin: show ? 0 : m, opacity: show ? 1 : 0 }, d);
4094            control.animate({ margin: m }, d);
4095          } else {
4096            label.animate({ margin: 0, opacity: 1 }, d);
4097            control.animate({ margin: show ? label.outerHeight() + 'px 0 0' : 0 }, d);
4098          }
4099        };
4100      label.css({ position: 'absolute' });
4101      place(0);
4102      input.keyup(function () {
4103        if (show === !input.val()) {
4104          show = !show;
4105          place(400);
4106        }
4107      });
4108      $(window).resize(function () { place(0); });
4109    };
4110  }(jQuery));
4111
4112  $('.form-control[placeholder]', '.form-horizontal').each(function () {
4113    $(this).bsPeekabooLabel();
4114  });

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.