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.