(function () { var exports = window.sharedCodeDb = {} function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } // appConfig.sharedCodeDb /* global SHARED, exports, ejs, moment */ exports.paymentTermsForPrincipal = function (job) { var principalContactId = job.principalContactIdParentContactId || job.principalContactId; switch (principalContactId) { case 1008: // Danaos Shipping case 737: // Global Seaways case 894: // Seanergy case 658: // Thenamaris Conbulk case 671: // TMS Bulkers case 1110: // Trust Bulkers case 727: // Tsakos Conbulk return 30; case 985: // NGM Energy return 60; default: return 7; // principalContactId + ' - ' + job.principalContactId + ' - ' + job.principalContactIdParentContactId } }; // ***************************************************************** // CHECKLIST FUNCTIONS // ***************************************************************** // Make checklist "email" icon disappear in some cases (e.g. there is no buyer) exports.validateChecklistAction = function (checklistItem, emailItem, job, invoiceGroup, item) { /* * Returns { disabled: true | false, reason: } for any conditions that would disable checklist email buttons * Return `false` by default, meaning the action is not disabled and should be available for the user. * */ // Disable any actions with buyerUpdates fragment if job.notifUpdatesBuyerEmail is not set // if (emailItem.checklistTemplateIdEmailTo.indexOf('buyerUpdates') !== -1 && !job.notifUpdatesBuyerEmail) { var emailTo = emailItem && emailItem.checklistTemplateIdEmailTo; if (emailTo && emailTo.indexOf('buyerUpdates') !== -1 && !job.notifUpdatesBuyerEmail) { return { disabled: true, reason: 'Buyer Updates Email is not set in job.' }; } /* To be deleted after 15/6/2026, it's only protecting existing jobs after a change of checklist */ // if (checklistItem.checklistTemplateId === 274 && job.portId !== 4) return { disabled: true, reason: "Email only valid for Esperance" } if (checklistItem.checklistTemplateId === 274 || checklistItem.checklistTemplateId === 556) { if (job.portId !== 4 && job.portId !== 7) return { disabled: true, reason: "Email not valid for this port" }; } return false; }; // Prevent users from marking items as done/obsolete and/or sending emails exports.satisfyChecklistItemCondition = function (itemId, job, invoiceGroup, item, op) { var warnings; var agencyInvoices, invoices, total, hasAgencyCharge, artificialTotalAugmenter; var mainChartersChecklist; var invoiceGroupItems, itemCreateCloseEstimate, itemReceiveInvoices, crucialAndUnchecked; var isMainChecklist = job.mainInvoiceGroupId === invoiceGroup.id; // let invoiceGroup var jobConfig = SHARED.jobConfigObject(job.jobConfig); function unsilencedWarnings(job, warnings) { var silencedWarnings; try { silencedWarnings = JSON.parse(job.silencedWarnings); } catch (_unused) { silencedWarnings = []; } silencedWarnings = silencedWarnings || []; return warnings.filter(function (w) { return !silencedWarnings[w.name]; }); } var allWarnings = SHARED.workoutWarnings(job); // Can't do ANYTHING if there are fields that need to be recalculated if (allWarnings.find(function (w) { return w.name === 'fieldsMustBeRecalculated'; })) { return "Must recalculate all pending fields in the Master Sheet."; } var screensHash = {}; // If a checklist item has the words "FORM 44" in them, then the abfOrQuarantineEmailMissing warning // MUST BE either not present or dismissed if (item.description.toUpperCase().includes('FORM 44')) { if (unsilencedWarnings(job, allWarnings).find(function (w) { return w.name === 'abfOrQuarantineEmailMissing'; })) { return "ABF or DAFF email missing, rectify before sending this email. Add the email address in the port page"; } } switch (itemId) { case 425: if (unsilencedWarnings(job, allWarnings).find(function (w) { return w.name === 'abfOrQuarantineEmailMissing'; })) { return "ABF or DAFF email missing, rectify before sending this email. Add the email address in the port page"; } break; case 133: case 520: if (unsilencedWarnings(job, allWarnings).find(function (w) { return w.name === 'sofForbiddenWords'; })) { return "There are forbidden words in the SOF. You must address this problem before ticking this item"; } if ((!job.sofAiCheck || !job.sofAiCheck.isCompleted) && (itemId === 520 || itemId === 133 && job.shipperContactId === 363 && job.portId !== 131)) { return 'SOF “Check with AI” required before progressing.'; } if (itemId === 520 && job.sofAiCheck && !job.sofAiCheck.isCompleted) { return 'SOF “Check with AI” required before emailing the final draft SOF or completing this item.'; } break; case 223: if (!job.principalContactIdFreightTaxExempted && !job.principalContactIdFreightTaxComments) return "Principal must either be marked as 'Freight Tax exempted' OR have a comment on its Freight Tax status"; break; case 220: if (job.shipperContactId !== 369 && op !== 'notRelevant') return "This checklist item only ever applies to CBH!"; break; case 204: // Receive the email from Operations advising job ready for Accounts FDA processing if (!SHARED.isDone(job, 207, invoiceGroup.id)) return 'You can only do this once accounts has been advised that job is ready for FDA processing'; break; case 399: // Receive the email from Operations advising job ready for Accounts FDA processing / NON MAIN JOB if (!SHARED.isDone(job, 386, invoiceGroup.id)) return 'You can only do this once accounts has been advised that job is ready for FDA processing'; break; // Checklist items for FDA received, invoiced, sent // Next 2 item removed as of ticket #559 // case 204: // Receive the email from Operations advising job ready for Accounts FDA processing // case 399: // Receive the email from Operations advising job ready for Accounts FDA processing / NON MAIN JOB case 205: // Invoice the FDA case 400: // Invoice the FDA / NON MAIN JOB case 206: // Send the FDA case 389: // Send the FDA / NON MAIN JOB if ((op === 'check' || op === 'email') && !invoiceGroup.fdaCreated) return 'You can only do this once the FDA has been created'; break; // Advise Accounts the job is ready for FDA processing case 207: case 386: // mainChartersChecklist = isMainChecklist && !job.isOwnersJob; if (mainChartersChecklist) { if (!invoiceGroup.fdaCalculated) { return 'FDA must be calculated before sending it off to Accounts'; } // Check that recoveries were calculated by operator, if applicable if (job.recoveries && !job.recoveriesCalculated) { return 'Cannot send off a job with itemised recoveries without calculating them'; } // Check that voucher numbers were calculated by operator if (!job.voucherNumbersCalculated) { return 'Cannot send off a job without calculating voucher numbers and checking FDA first'; } } // Check that there are no warnings left // Note that if a warning is invoiceGroup-specific, it will be ignored unless it's for this very invoice Group. // This will ensure for example that a warning for a B job won't prevent a main job from being sent to accounts warnings = unsilencedWarnings(job, allWarnings).filter(function (w) { return !w.invoiceGroupId || w.invoiceGroupId === invoiceGroup.id; }); if (warnings.length) { warnings.forEach(function (w) { return screensHash[w.screen] = true; }); var screensWithWarnings = Object.keys(screensHash).join(', '); return "Cannot send off a job with warnings (screens: ".concat(screensWithWarnings, ")"); } if (isMainChecklist) { // // Check if Arrived, All Fast and Sailed times are set if (!job.timeArrived) { return '"Arrived" needs to be set in Vessel times.'; } if (!job.timeAllFast && !job.didNotBerth) { return '"All fast" or "Did not berth" needs to be set in Vessel times.'; } if (!job.timeSailed) { return '"Sailed" needs to be set in Vessel times.'; } } if (job.jobConfig != 'ownersprotective') { // Crucial checklist items are unticked: do not allow to progress to accounts // For charter jobs, the blocking ones are all of them EXCEPT the ones between "create close estimate" and "receive invoices" // For owners jobs, the blocking ones are all of the non-billing ones, and the billing ones before the current one. (The billing ones are the autoImport ones.) invoiceGroupItems = job.checklists.filter(function (i) { return !i.description.startsWith('_') && i.invoiceGroupId === invoiceGroup.id; }); if (jobConfig.importDefaultChecklist && !job.isOwnersJob && invoiceGroup.main) { itemCreateCloseEstimate = invoiceGroupItems.find(function (i) { return i.checklistTemplateId === 212; }); itemReceiveInvoices = invoiceGroupItems.find(function (i) { return i.checklistTemplateId === 150; }); crucialAndUnchecked = invoiceGroupItems.filter(function (i) { return (i.position >= itemCreateCloseEstimate.position || i.position <= itemReceiveInvoices.position) && !i.isHeading && i.position < item.position && !i.isDone && !i.isObsolete; }); } else { crucialAndUnchecked = invoiceGroupItems.filter(function (i) { return (!i.autoImport || i.autoImport && i.position < item.position) && !i.isHeading && !i.isDone && !i.isObsolete; }); } //if (job.checklists.filter(i => !i.description.startsWith('_') && i.invoiceGroupId === invoiceGroup.id && !i.isHeading && i.position < item.position && (!i.isDone && !i.isObsolete)).length) { if (crucialAndUnchecked.length) { return 'Crucial checklist items unticked, cannot progress job to accounts ' + crucialAndUnchecked.map(function (el) { return el.id; }).join(','); } } // END OF CHECK IF IT'S OWNERSPROTECTIVE if (!SHARED.isSupplementaryDa(invoiceGroup.name)) { invoices = job.invoices.filter(function (i) { return i.invoiceGroupId === item.invoiceGroupId; }); if (!invoices.length) return 'There are no FDA invoices'; agencyInvoices = invoices.filter(function (t) { return t.documentTemplateId === 19; }); if (!agencyInvoices.length) return 'There are no invoices which are Agency Fee invoices in the FDA. Add one to continue'; hasAgencyCharge = false; artificialTotalAugmenter = 0; agencyInvoices.forEach(function (invoice) { // Allow cases where the agency fee is not applicable if (invoice.transactions.find(function (t) { return t.description1Fda && t.description1Fda.toUpperCase().includes('NOT APPLIC'); })) artificialTotalAugmenter++; if (invoice.transactions.find(function (t) { return t.description1Fda && t.description1Fda.toUpperCase().includes('N/A'); })) artificialTotalAugmenter++; total = artificialTotalAugmenter + invoice.transactions.reduce(function (prev, curr) { return prev + Number(curr.costFda); }, 0); if (total) hasAgencyCharge = true; }); if (!hasAgencyCharge) return "There must be at least an agency invoice with non-zero amount, or N/A."; } return ''; /* case 206: // invoiceGroup = job.invoiceGroups.find(ig => ig.id === item.invoiceGroupId) if (!SHARED.invoiceGroupHasFda(job, invoiceGroup.id)) { return 'Cannot send email without creating an FDA first' } break */ case 62: // AGAIN, Check that recoveries were calculated by operator, if applicable if (job.recoveries && !job.recoveriesCalculated) { return 'Cannot tick this checklist item without actually calculating recoveries'; } return ''; /* // Obsoleted 02/05/25 11:51 Since we no longer check that cargo names are unique; original reason behind this check/warning unclear case 35: case 175: if (allWarnings.find(w => w.name === 'cargoDuplicates')){ return `Cargoes with duplicate names. Make sure each cargo has a different name in the Master Sheet before proceeding. Note: look at the load sequence to decide alternate and appropriate names` } break */ // Only allow emailing of B/Ls if and only IF the second operator has double checked case 66: case 65: if (!SHARED.isDone(job, 168)) { return "You MUST get a second operator to check the B/Ls before progressing"; } break; default: return ''; } return ''; }; // Prevent importing of checklists in some cases (e.g. it doesn't apply to a job) exports.checklistFilter = function (job, id, checklistTemplate, invoiceGroup) { var notInDampier = [21, 28, 143, 9, 108, 119, 144, 133, 129, 102, 94, 130, 27, 138, 148, 65]; var notForLng = [137, 171, 172, 173, 142, 208, 209, 210, 45, 108, 166, 241, 244, 245, 246, 247, 248, 57, 102, 103]; // ETA UPDATES, BERTHING UPDATES, BERTHING REPORT, COMMENCE LOADING, PROGRESS REPORT, DEPARTURE REPORT var principalContactId = job.principalContactIdParentContactId || job.principalContactId; // autoImport checklists: billing entries will only go to invoiceGroups that need an FDA, and B-JOB checklists // will only go to invoiceGroups that do NOT need an FDA if (checklistTemplate.autoImport) { var isMJob = invoiceGroup.doesNotNeedFda; if (isMJob && (checklistTemplate.checklistTypeId === 18 || checklistTemplate.checklistTypeId === 9)) return false; // Owners Job Setup and Post Departure / Accounts if (!isMJob && (checklistTemplate.checklistTypeId === 20 || checklistTemplate.checklistTypeId === 16)) return false; // M Job Setup and M Jobs Tasks } // Filter out "Email proposed load sequence to [[marineSurveyorContactIdNickNameOrCompanyName]]" // This is the opposite of the filter of item 83 (Check with MRL Shipping...) // So will filter it out if it's an MRL ship in ASHBURTON, ESPERANCE and PORT HEDLAND if (id === 90 && job.shipperContactId === 363 && (job.portId === 131 || job.portId === 4 || job.portId === 9)) return false; // Deleted 3/1/2024. // Why on earth was this here?!? This should be handled by filters in the checklist template. // if (id === 414 && principalContactId !== 629) return false // LNG ships will have different checklist items/email templates for ETA UPDATES, BERTHING UPDATES, BERTHING REPORT, COMMENCE LOADING, PROGRESS REPORT, DEPARTURE REPORT if (SHARED.hasCargo(job, 9) && notForLng.indexOf(id) !== -1) return false; // Hide non-Dampier items from Dampier if (job.portId === 3 && notInDampier.indexOf(id) !== -1) return false; // Hide checklist items from Dampier/LNG if (job.portId === 3 && SHARED.hasCargo(job, 9)) { // Email final draft B/L to principal // Email Pre-Arrival Message(s) and Questionnaire to Vessel // Arrange delivery of original B/L if (id === 64 || id === 42 || id === 188) return false; } // No recoveries items if the job has no recoveries if (!job.recoveries && id === 62) return false; // No "Make arrangements for original B/L delivery" for MRL if (job.shipperContactId === 363 && id === 188) return false; // Taken out, from now on email icons will only appear if locals are there // if (job.portId === 9 && id === 172) return false // NO ETA updates for locals in Port Hedland // if (job.portId === 9 && id === 209) return false // NO Berthing updates for locals in Port Hedland if (SHARED.mergeLocalsEmail(job) && id === 172) return false; // ETA updates, LOCALS, NOT included as locals merged with shipper if (SHARED.mergeLocalsEmail(job) && id === 209) return false; // BERTHING updates, LOCALS, NOT included as locals merged with shipper if (job.principalContactId === 91 && id === 266) return false; // Ocean21 does NOT get Email [[principalContactIdName]] to establish hold inspection requirements // Commented out, as per Geoff's instructions, 14/05/2025 // if ((job.principalContactId === 93 && job.shipperContactId === 363) && (id === 64 || id === 151)) return false // If MRL is shipper, Oldendorff does NOT get Email final draft B/L to [[principalContactIdName]] NOR Receive [[principalContactIdName]]'s approval to sign and release original B/L to shippers // Only allow "Submit the FDA in the accounting manager's system (if applicable)" for jobs with account managers if (!job.accountingManagerContactId && id === 460) return false; // Add the Bald Hill checklist item ONLY for "baldhill" vessels if (id === 468 && job.customField1 !== 'baldhill') return false; // Old and to be deleted // if (id === 23 && baldHill) return true // The default checklist item will ALWAYS apply // ONE OF 479 and 51 (Port Clearance Templates), one for SA and one for anybody else if (id == 51 && job.portIdState === 'SA') return false; if (id == 479 && job.portIdState !== 'SA') return false; // We don't want "Create Port Clearance template and email to ABF" for Geraldton nor Dampier if (id === 51 && (job.portId == 3 || job.portId == 6)) return false; // Only owners jobs get 485 and 484 if (!job.isOwnersJob && (id === 485 || id === 484 || id === 514 || id === 515)) return false; // Email Complete Loading Report -- only for grain OR CBH if (id == 217 || id == 201 || id == 218) { if (job.shipperContactId === 369 || job.shipperContactId === 968) return true; if (SHARED.isGrain(job)) return true; return false; } // ********************** // ASHBURTON EXCEPTIONS // ********************** // No "Receive completed Questionnaire from Master" for ASHBURTON if (job.portId === 131 && id === 159) return false; // No "Receive Master's proposed load sequence for ASHBURTON if (job.portId === 131 && id === 28) return false; // No "Enter intended arrival and departure drafts into conditions" for ASHBURTON if (job.portId === 131 && id === 143) return false; // No "Check with MRL Shipping (iron ore or spodumene team, depending on cargo) for approval of proposed load sequence" for ASHBURTON if (job.portId === 131 && id === 83) return false; // No "Email approved Load Sequence to Terminal and [[shipperContactIdNickNameOrCompanyName]]" for ASHBURTON if (job.portId === 131 && id === 35) return false; // No "Contact surveyor(s) to confirm expected <%=job.portId == 131 ? 'loading' : 'berthing'%> time (if applicable)" for ASHBURTON if (job.portId === 131 && id === 9) return false; // No "Enter berthing times into Master sheet" nor "Enter berthing conditions into Master sheet" for ASHBURTON if (job.portId === 131 && (id === 116 || id === 118)) return false; // No berthing report checklist items for ASHBURTON if (job.portId === 131 && checklistTemplate.description && checklistTemplate.description.toLowerCase().indexOf("berthing report") !== -1) return false; return true; }; // Custom fields exports.customFieldValues = function (job) { var r = {}; if (!job || job && job.portId === 4) { r.customField1 = { choices: [{ id: 'baldhill', name: 'Bald Hill ship' }] }; } return r; }; exports.resolveLabel = function (label, type) { switch (label) { case 'Employee': return "SAS Employee"; case 'Custom Field 1': return "Type of ship"; } return label; }; // ***************************************************************** // OTHER FUNCTIONS // ***************************************************************** exports.anchoragePositions = function (job) { if (job.portId === 131) { return ['NW5', 'NW6', 'NW7', 'NW8', 'NW9', 'NW10', 'NW11', 'NW12']; } return null; }; exports.templateParser_PLEASE_FIND_ATTACHED = function (_ref) { var context = _ref.context; return context.externalDocumentsIdsSelected.length ? '\nPlease find attached:\n\n' : ''; }; exports.extraFdaEmailAddresses = function (job) { var principalContactId = job.principalContactIdParentContactId || job.principalContactId; /* // Obsolete as of 12/08/2026, correct addresses are now in contacts if (principalContactId === 591) { // const extras = job.principalContactId + '@debug.com' const extras = SHARED.isTanker(job) ? 'invs-tankers@navios.com' : 'invs@navios.com' return extras }*/ return ''; }; exports.jobConfigOptions = function () { return [{ id: '', name: 'Default' }, { id: 'receiversprotective', name: 'Receivers Protective' }, { id: 'shippersprotective', name: 'Shippers Protective' }, { id: 'ownersprotective', name: 'Owners Protective' }]; }; exports.jobConfigObject = function (jobConfig) { switch (jobConfig) { case 'receiversprotective': return { importDefaultChecklist: false, importDefaultTransactions: false, operationMustBe: 'DISCHARGE' }; case 'shippersprotective': return { importDefaultChecklist: false, importDefaultTransactions: false, operationMustBe: 'LOAD' }; case 'ownersprotective': return { importDefaultChecklist: false, importDefaultTransactions: false, operationMustBe: false // No condition set: it can be anything }; default: return { importDefaultChecklist: true, importDefaultTransactions: true, operationMustBe: false // No condition set: it can be anything }; } }; exports.calculateDoesNotBerth = function (record) { return record.portId == 131; }; exports.extraEditorButtons = function (context) { return [ /* { label: 'A to B ', action: () => { context.input.innerHTML = context.input.innerHTML.replace(/a/, 'b') }, condition: () => context.job.portId === 9 } */ ]; }; exports.extraPrintPreviewButtons = function (context) { // FDA, SAS Invoices, FDA Cover letter, Close estimate, PDA var docsWithNoMasterHeadButton = [13, 28, 19, 26, 18, 14]; return [{ id: 'no_master_head', label: 'Remove vessel from address', action: function no_master_head() { var contents = document.getElementById('contents-body'); contents.innerHTML = contents.innerHTML.replace(/The Master & Owners of the:
[\s\S]*?
/, ''); }, // Only and only for specific documents condition: function condition() { return context.documentTemplatesForDoc.some(function (obj) { return docsWithNoMasterHeadButton.includes(obj.id); }); } // context.documentTemplatesForDoc.find(d => d.id === 13) && context.job.portId === 9 }]; }; exports.terminalName = function (job) { if (SHARED.isGrain(job)) { if (job.portIdState === 'WA') return 'CBH ';else if (job.portIdState === 'SA') return 'Viterra '; } return ''; }; exports.manipulateEmailTemplate = function (job, item, text) { if (!job.typeOfOperation || job.typeOfOperation === 'LOAD') return text; var replaceWords = function replaceWords(from, to, text) { var pattern = new RegExp("(^|\\s|[.,;:])".concat(from, "(?=\\s|[.,;:]|$)"), 'g'); return text.replace(pattern, "$1".concat(to)); }; var changedText = text; changedText = replaceWords('loading', 'discharging', changedText); changedText = replaceWords('Loading', 'Discharging', changedText); changedText = replaceWords('LOADING', 'DISCHARGING', changedText); changedText = replaceWords('loaded', 'discharged', changedText); changedText = replaceWords('Loaded', 'Discharged', changedText); changedText = replaceWords('LOADED', 'DISCHARGED', changedText); changedText = replaceWords('load port agent', 'discharge port agent', changedText); // Just for checklist template 344 return changedText; }; exports.portHedlandTowageDirect = function (job) { var principalContactId = job.principalContactIdParentContactId || job.principalContactId; switch (principalContactId) { // billTo is used for the "Bill to" field in the Shipping Confirmation doc // billToShort is used for the name of the principal in the transaction (it's used by the calculator) // COSCO SHIPPING BULK case 629: return { billTo: SHARED.upper(job.principalContactIdParentContactIdCompanyName || job.principalContactIdName), billToShort: 'Cosco' }; // SWISSMARINE PTE LTD // case 110: // return { // billTo: SHARED.upper(job.principalContactIdName), // billToShort: 'Swissmarine' // } // LAURITZEN BULKERS A/S case 956: // Was: 540 (J. LAURITZEN SINGAPORE PTE LTD) return { billTo: SHARED.upper(job.principalContactIdName), billToShort: 'Lauritzen' }; // DAMPSKIBSSELSKABET NORDEN A/S case 67: return { billTo: SHARED.upper(job.principalContactIdName), billToShort: 'Norden' }; // BBC Chartering Carriers GmbH & Co. KG case 939: return { billTo: SHARED.upper(job.principalContactIdName), billToShort: 'BBC Chartering' }; // Hanseatic Unity Handysize Pool (HUHP) Management Pte. Ltd. case 952: return { billTo: SHARED.upper(job.principalContactIdName), billToShort: 'Hanseatic Unity' }; // PAN OCEAN CO LTD case 96: return { billTo: SHARED.upper(job.principalContactIdName), billToShort: 'Pan Ocean' }; } }; exports.dataChangeOnChecklistAction = function (item, job, invoiceGroup) { var modifications = []; if (item.isDone) { var isMain = job.mainInvoiceGroupId === invoiceGroup.id; var now = new Date().getTime(); if ((item.checklistTemplateId == 207 || item.checklistTemplateId === 386) && !invoiceGroup.timeFdaReceived) { modifications.push({ store: 'invoiceGroups', patches: { timeFdaReceived: now } }); if (isMain) modifications.push({ store: 'jobs', patches: { timeFdaReceived: now } }); } if ((item.checklistTemplateId == 205 || item.checklistTemplateId === 400) && !invoiceGroup.timeFdaInvoiced) { modifications.push({ store: 'invoiceGroups', patches: { timeFdaInvoiced: now } }); if (isMain) modifications.push({ store: 'jobs', patches: { timeFdaInvoiced: now } }); } if ((item.checklistTemplateId == 206 || item.checklistTemplateId === 389) && !invoiceGroup.timeFdaSent) { modifications.push({ store: 'invoiceGroups', patches: { timeFdaSent: now } }); if (isMain) modifications.push({ store: 'jobs', patches: { timeFdaSent: now } }); } } return modifications; }; exports.jobPostSaveModifications = function (job, originalJob, originalBody) { var patches = {}; if (typeof originalBody.customField1 !== 'undefined') { if (job.customField1 !== originalJob.customField1 && job.customField1 === 'baldhill') patches.mustReimportChecklist = 1; } // if (job.portId == 131) patches.sofWaitingSailing = "Waiting sailing due to vessel preparing for sea" return patches; }; exports.goToReportMakerOrFinanceScreen = function (whichDate, job, invoiceGroup) { if (invoiceGroup.main) { return "/page-report-maker?fields=".concat(whichDate, "&jobId=").concat(job.id); } else { return "/add-edit-job-invoice-groups/".concat(job.id, "/").concat(invoiceGroup.id); } }; exports.emailInSignature = function (data, portIdMailFrom, userEmail) { if (data && data.masterItem.checklistTemplateIdEmailFrom) { var tplemailFrom = data.masterItem.checklistTemplateIdEmailFrom; var match = tplemailFrom.match(/^.*<(.*?)>.*$/); if (match && match[1]) return match[1]; } return portIdMailFrom || userEmail; }; exports.resolveInputLabel = function (type, field) { if (type === 'job') { switch (field) { case 'principalContactId': return 'Lu principale'; default: return ''; } } if (type === 'contact') { switch (field) { default: return ''; } } }; exports.shippingNominationsForWaGrain = function (job) { // If it's grain in Geraldton, Kwinana, Albany or Esperance, then return 'Shipping.Nominations@cbh.com.au' if (SHARED.isGrain(job)) { if (job.portId === 6 || job.portId === 7 || job.portId === 1 || job.portId === 4) return 'Shipping.Nominations@cbh.com.au'; } // UAN from Kwinana, also return 'Shipping.Nominations@cbh.com.au' if (SHARED.hasCargo(job, 28) && job.portId === 7) { return 'Shipping.Nominations@cbh.com.au'; } return ''; }; exports.norRecipients = function (job) { var to = job.notifShipperEmail; // SPECIAL CASES if (job.shipperContactId == 363 && job.portId == 131) { // MRL IN ASHBURTON to = job.notifShipperEmail + ";" + 'DL-CSIOIOpsIntegratedScheduling.Team@piha.com.au'; } // 26/3/2025: Geoff believes it is unclear why we are using Notif Load Sequence Email instead of Shipper Email All Updates Including Cargo Docs if (job.shipperContactId == 363 && (job.portId == 9 || job.portId == 7)) { // MRL IN PORT HEDLAND OR KWINANA to = job.notifLoadSequenceEmail; } if (job.shipperContactId == 364 && job.portId == 7) { // ALCOA IN KWINANA to = job.notifLoadSequenceEmail; } return to; }; exports.isNewcastlemax = function (job) { return job.shipLoa > 295 || job.shipBeam > 45; }; exports.cargoLine = function (job, k) { var line = ''; if (!job["cargoId".concat(k)]) return; // Contract tonnage as text: just display that line += (job["cargoDescription".concat(k)] || job["cargoId".concat(k, "Name")]) + ' '; if (job["contractTonnageToleranceAsText".concat(k)]) { line += job["contractTonnageToleranceAsText".concat(k)] + ' '; // NO contract tonnage as text: display tolerance and description } else { line += (job["contractTonnage".concat(k)] ? SHARED.addCommas(job["contractTonnage".concat(k)]) + ' MT' : '[[OPERATOR: Fill in the contract tonnage]]') + ' '; line += (job["contractTonnageTolerance".concat(k)] ? SHARED.addCommas(job["contractTonnageTolerance".concat(k)]) : '[[OPERATOR: Fill in the contract tonnage tolerance]]') + ' '; } line += "Masters stow plan and load sequence is for "; line += job["tonnageToBeLoaded".concat(k)] ? SHARED.addCommas(job["tonnageToBeLoaded".concat(k)]) + ' MT' : '[[OPERATOR: Fill in the contract tonnage to be loaded]]'; return line; }; exports.form175Cc = function (job) { switch (job.portId) { // GERALDTON // DAFF AO Survey is conducted alongside, and therefore the AO surveyor doesn't need to be notified about the launch case 6: return SHARED.joinEmail(job.launchContactIdEmailCc, job.notifMarineSurveyorEmail, job.portIdPortAuthorityEmail); // KWINANA // The port doesn't need to be notified. The marine and AO surveyor both attend on the same launch case 7: return SHARED.joinEmail(job.launchContactIdEmailCc, job.notifMarineSurveyorEmail, job.notifAoSurveyorEmail); // ESPERANCE // The hard-coded email addresses represent the port. The port provides the launch - therefore the is no separate launch email // 'Lydia.vanderWalt@southernports.com.au', 'TerminalSupervisors@southernports.com.au' deleted and replaced by 'Shipping.Operations@southernports.com.au' on 15/03/24 case 4: return SHARED.joinEmail('Shipping.Operations@southernports.com.au', job.notifMarineSurveyorEmail, job.notifAoSurveyorEmail); // ALBANY // This email is usually sent by the sub agent Halls Shipping case 1: return SHARED.joinEmail(job.launchContactIdEmailCc, job.notifMarineSurveyorEmail, job.notifAoSurveyorEmail); } }; exports.sendFdaAccountingManagerExtraSentence = function (job, invoiceGroup) { // Only ever work on the main invoice group if (!invoiceGroup.main) return ''; // THIS FUNCTION WILL ADD A SENTENCE TO THE "SEND FDA" EMAIL // NO ACCOUNTING MANAGER, NOTHING TO DO if (!job.accountingManagerContactId) return ''; var name = job.accountingManagerContactIdName; // WARNING!!! IF YOU CHANGE THINGS HERE, YOU PROBABLY ALSO WANT TO CHANGE THEM IN sendCloseEstimateAccountingManagerExtraSentence if (name.toUpperCase().includes('MOL SHIPPING')) return " and Statement of Ship’s Disbursement Account in MOL format";else if (name.toUpperCase().includes('DA DESK')) return " which has been submitted in the ".concat(job.accountingManagerContactIdName, " system");else if (name.toUpperCase().includes('DIABOS')) return " which has been submitted in the ".concat(job.accountingManagerContactIdName, " system");else if (name.toUpperCase().includes('HARBOR')) return " which has been submitted in the ".concat(job.accountingManagerContactIdName, " system");else if (name.toUpperCase().includes('INCHCAPE')) return " which has been submitted in the ISS HMM Hub Finance system"; // OTHER ACCOUNTING MANAGERS WITHOUT A SYSTEM (E.G. FIVE STAR) else return ''; }; exports.sendCloseEstimateAccountingManagerExtraSentence = function (job) { // THIS FUNCTION WILL ADD A SENTENCE TO THE "SEND CLOSE ESTIMATE" EMAIL // NO ACCOUNTING MANAGER, NOTHING TO DO if (!job.accountingManagerContactId) return ''; var name = job.accountingManagerContactIdName; // WARNING!!! IF YOU CHANGE THINGS HERE, YOU PROBABLY ALSO WANT TO CHANGE THEM IN sendFdaAccountingManagerExtraSentence if (name.includes('MOL SHIPPING')) return "";else if (name.toUpperCase().includes('DA DESK')) return " submit it in the ".concat(job.accountingManagerContactIdName, " system and");else if (name.toUpperCase().includes('DIABOS')) return " submit it in the ".concat(job.accountingManagerContactIdName, " system and");else if (name.toUpperCase().includes('HARBOR')) return " submit it in the ".concat(job.accountingManagerContactIdName, " system and");else if (name.toUpperCase().includes('INCHCAPE')) return " submit it in the ISS HMM Hub Finance system and"; // OTHER ACCOUNTING MANAGERS WITHOUT A SYSTEM (E.G. FIVE STAR) else return ''; }; exports.sgsEmailAddress = function (job) { var to = ''; if (job.portId == 9) to = 'AU.Minerals.Trade@sgs.com; AU.PortHedland.Trade@sgs.com'; if (job.portId == 7) to = 'AU.Minerals.Trade@sgs.com'; if (job.portId == 4) to = 'AU.Minerals.Trade@sgs.com; Rodney.La\'Brooy@sgs.com'; return to; }; exports.duration = function (d1, d2) { var p = Duration.fromObject({ hours: 0, minutes: 0, seconds: 0, milliseconds: Math.abs(d2 - d1) }).normalize().toObject(); // var p = moment.duration(d1 - d2) // var p = moment.duration(d1, d2) return Math.floor(p.hours) + 'hrs ' + p.minutes + 'mins'; }; /* // DELETE ME LATER, this was commented out 17/9/2025 exports.mrlShipping = (job) => { // This will return a SUBSET, minimal email addresses specifically for the // load sequence. This is so that if an email is sent to Nathalie/Gus about the load sequence, but if they are not // in the office, somebody else will pick it up -- but, without sending it to the whole of MRL ops. switch (job.portId) { case 4: return 'esperanceshipping@mrl.com.au; esperanceportops@mrl.com.au' case 7: return 'fremantleshipping@mineralresources.com.au' case 9: return 'Shipping-Ops@mrl.com.au' default: return '' } } */ exports.grainOrSand = function (job) { return SHARED.isGrain(job) || SHARED.hasCargo(job, 25) || SHARED.hasCargo(job, 28); }; exports.mergeLocalsEmail = function (job) { // if (job.portId === 131) return false // return SHARED.isGrain(job) || SHARED.hasCargo(job, 25) || SHARED.hasCargo(job, 28) || SHARED.hasCargo(job, 19); // GRAIN or SAND or UAN or FERTILIZER }; exports.extraJobValidationErrors = function (job) { var errors = []; // Estimated departure draft required for Kwinana, only when adding // a new job. NOTE: the field estimatedDepartureDraft is deleted from the // body, and transposed to departureDraftFwd, departureDraftMid and departureDraftAft. // So, to check that an estimated departure draft was actually entered, the field // departureDraftFwd is checked. This field is NOT available in the master sheet. So, if it's // set, it MUST be because estimatedDepartureDraft was set (and departureDraftFwd was set // automatically as a consequence) if (!job.id && job.portId === 7 && !job.isOwnersJob && !job.departureDraftFwd) { errors.push({ field: 'estimatedDepartureDraft', message: 'Estimated Departure Draft required for Kwinana' }); } // IF SHIPPER IS NOT MRL, AND THERE IS SHIPPER REFERENCE, "Check if shipper reference is required." if (job.shipperContactId !== 363 && job.shipperReference) { errors.push({ field: 'shipperReference', message: 'Check if shipper reference is required' }); } if (!job.principalEmail && job.notifUpdatesPrincipalEmail && !job.isOwnersJob) { errors.push({ field: 'principalEmail', message: 'Principal email must be set if the computed principal email is set' }); } if (!job.isOwnersJob && !job.typeOfOperation) { errors.push({ field: 'typeOfOperation', message: 'Operation must be set for charters jobs' }); } if (job.isOwnersJob && job.typeOfOperation && job.jobConfig != 'ownersprotective') { // TODO URGENT DO NOT FORGET: UNCOMMENT THIS AFTER DEPLOYENT //errors.push({ // field: 'typeOfOperation', // message: 'Operation must NOT be set for owners jobs (except for Owners Protective)' + job.jobConfig //}) } if (!job.isOwnersJob && job.portId === 115 && !job.estimatedHoursAtBerth) { errors.push({ field: 'estimatedHoursAtBerth', message: 'Estimated hours at berth required for Adelaide jobs' }); } if (!job.isOwnersJob && job.portId === 9 && !job.berthId) { errors.push({ field: 'berthId', message: 'Port Hedland jobs must have berths set' }); } if (job.portId === 107 && !job.berthId) { errors.push({ field: 'berthId', message: 'Must select berth for Hay Point jobs' }); } if (job.portId === 131 && job.berthId === 116 && job.timeAllFast) { errors.push({ field: 'timeAllFast', message: 'All Fast cannot be set for Ashburton jobs using Anchorage Berth' }); } return errors; }; exports.setCustomPilotLabels = function (job) { // Initial filters if (job.isOwnersJob) return false; // Allow custom pilot labels for CBH jobs if (job.shipperContactId === 369) return true; return false; }; exports.shouldBeDomestic = function (job, invoiceGroup) { if (!job || !Object.keys(job).length) return null; if (!job.principalContactId) return null; // CASE #1: MAIN INVOICE GROUP. CHECK WITH ACCOUNTING MANAGER INSTEAD IF PRESENT if (!invoiceGroup || invoiceGroup.id == job.mainInvoiceGroupId) { if (job.accountingManagerContactId) { return job.accountingManagerContactIdCountryId === 13; } else { return job.principalContactIdCountryId === 13; } // CASE 2: OTHER INVOICE GROUPS } else { return invoiceGroup.principalContactIdCountryId === 13; } }; exports.workoutWarnings = function (job, invoiceGroupId) { var warnings = []; var isMrl = job.shipperContactId === 363; var showSofWarnings = Boolean(job.timeAllFast && !job.timeAllFastIsEstimate || job.portId === 131 && job.timeCommencedLoading && !job.timeCommencedLoadingIsEstimate); var sofCheck = showSofWarnings && isMrl ? SHARED.sofProgrammaticCheck(job) : null; var sofProblems = sofCheck && Array.isArray(sofCheck.problems) ? sofCheck.problems : []; var _iterator = _createForOfIteratorHelper(sofProblems), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var problem = _step.value; var warningName = problem.warningName || "sofRule_".concat(problem.ruleId); var screens = Array.isArray(problem.screens) && problem.screens.length ? problem.screens : ['Statement Of Facts']; var _iterator5 = _createForOfIteratorHelper(screens), _step5; try { for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) { var screen = _step5.value; warnings.push({ name: warningName, screen: screen, message: problem.message, dismissable: problem.dismissable !== false }); } } catch (err) { _iterator5.e(err); } finally { _iterator5.f(); } } // CONFIGURED_PROGRAMMATIC_SOF_WARNINGS_END } catch (err) { _iterator.e(err); } finally { _iterator.f(); } function hasFact(particulars) { if (!job.sof || !job.sof.length) return; return job.sof.find(function (o) { return (o.particulars || '').toUpperCase().startsWith((particulars || '').toUpperCase()); }); } function checkSofBalance(firstPrefixes, secondPrefixes) { var normalise = function normalise(value) { return (value || '').toLowerCase(); }; var count = function count(prefixes) { prefixes = (Array.isArray(prefixes) ? prefixes : [prefixes]).map(normalise); var rows = (job.sof || []).filter(function (s) { return !s.isObsolete && prefixes.some(function (prefix) { return normalise(s.particulars).startsWith(prefix); }); }).sort(function (a, b) { return a.delaysTimestampA - b.delaysTimestampA; }); return rows.filter(function (row, i) { var previous = rows[i - 1]; var continuation = previous && row.delaysTimestampA && row.delaysTimestampA === previous.delaysTimestampB && normalise(row.particulars) === normalise(previous.particulars); return !continuation; }).length; }; return [count(firstPrefixes), count(secondPrefixes)]; } function sofForbiddenWords() { var words = ["maintenance", "maintainance", "maintanance", "maintanence", "maintainence", "mentainance", "mentanance", "maintainanse", "maintanens", "maintainans", "maintenence"]; if (job.sof && job.sof.length) { var forbiddenList = []; var _iterator2 = _createForOfIteratorHelper(job.sof), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var entry = _step2.value; if (entry.isObsolete) continue; var _iterator3 = _createForOfIteratorHelper(words), _step3; try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { var word = _step3.value; var regex = new RegExp("\\b".concat(word, "\\b"), 'i'); if (regex.test(entry.particulars) && !forbiddenList.includes(word)) { forbiddenList.push(word); } } } catch (err) { _iterator3.e(err); } finally { _iterator3.f(); } } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } return forbiddenList.join(", "); } return ""; } var sofResult = sofForbiddenWords(); if (job.shipperContactId === 363 && sofResult) { warnings.push({ name: 'sofForbiddenWords', screen: 'Statement Of Facts', message: "Forbidden word in SOF: ".concat(sofResult, ". You MUST seek Ops Manager's approval."), dismissable: true }); warnings.push({ name: 'sofForbiddenWords', screen: 'Master Sheet', message: "Forbidden word in SOF: ".concat(sofResult, ". You MUST seek Ops Manager's approval."), dismissable: true }); } function tonnageMismatch(job) { var totalLoaded = job.tonnageLoaded || 0; if (!job || !Object.keys(job).length) return false; var manifestTonnages = [job.manifestTonnageLoaded1, job.manifestTonnageLoaded2, job.manifestTonnageLoaded3, job.manifestTonnageLoaded4, job.manifestTonnageLoaded5, job.manifestTonnageLoaded6, job.manifestTonnageLoaded7, job.manifestTonnageLoaded8, job.manifestTonnageLoaded9, job.manifestTonnageLoaded10]; var manifestSum = manifestTonnages.filter(function (i) { return !!i; }).map(function (i) { return i.replace(/,/g, ''); }).reduce(function (total, curr) { return total + Number(curr); }, 0); return totalLoaded.toFixed(2) !== manifestSum.toFixed(2); } function agencyFeeInTransactionsMismatch(transactions, jobAmount) { // Loop through the transactions array var _iterator4 = _createForOfIteratorHelper(transactions), _step4; try { for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { var transaction = _step4.value; var description1Pda = transaction.description1Pda; // Check if the current transaction meets the criteria var transactionAmount = transaction.costFda || transaction.costClose || transaction.costPda || 0; // if ((/agency/i.test(description1Pda) || /coordinat/i.test(description1Pda)) && /fee/i.test(description1Pda) && !/not applicable/i.test(description1Pda) && !/N\/A/i.test(description1Pda)) { if (/agency/i.test(description1Pda) && /fee/i.test(description1Pda)) { return transactionAmount !== jobAmount ? transactionAmount : false; } } } catch (err) { _iterator4.e(err); } finally { _iterator4.f(); } return false; } function getTransactionsForInvoiceGroup(job, invoiceGroupId) { var invoices = job.invoices || []; return invoices.filter(function (invoice) { return invoice.invoiceGroupId === invoiceGroupId; }).flatMap(function (invoice) { return invoice.transactions || []; }); } if (!job.portIdAbfEmail || !job.portIdQuarantineEmail) { warnings.push({ name: 'abfOrQuarantineEmailMissing', screen: 'Master Sheet', message: 'ABF or DAFF email missing from port. Add the email address in the port page', dismissable: true, invoiceGroupId: job.mainInvoiceGroupId }); } if (!job.isOwnersJob && job.recoveries && !job.recoveriesImported && job.timeSailed) { warnings.push({ name: 'recoveriesNotImported', screen: 'Master Sheet', message: 'Main DA has itemised recoveries but recoveries were not imported', dismissable: true, invoiceGroupId: job.mainInvoiceGroupId }); warnings.push({ name: 'recoveriesNotImported', screen: 'Transactions', message: 'Main DA has itemised recoveries but recoveries were not imported', dismissable: true, invoiceGroupId: job.mainInvoiceGroupId }); } if (job && Object.keys(job).length) { // var tr = getTransactionsForInvoiceGroup(job, job.mainInvoiceGroupId); var mismatch = agencyFeeInTransactionsMismatch(tr, job.agencyFee); if (!job.isOwnersJob && mismatch) { var message = "Agency fee in transactions (".concat(mismatch, ") does NOT match the agency fee in the master sheet (").concat(job.agencyFee, ")"); warnings.push({ name: 'agencyFeeWrongInTransactions', screen: 'Master Sheet', message: message, dismissable: true, invoiceGroupId: job.mainInvoiceGroupId }); warnings.push({ name: 'agencyFeeWrongInTransactions', screen: 'Transactions', message: message, dismissable: true, invoiceGroupId: job.mainInvoiceGroupId }); } if (!job.contractTonnageOverall && job.timeAllFast && !job.isOwnersJob && !job.jobConfig && job.typeOfOperation === 'LOAD') { warnings.push({ name: 'contractTonnage', screen: 'Master Sheet', message: 'Contract Tonnage Not Set', dismissable: true, invoiceGroupId: job.mainInvoiceGroupId }); } if (!job.contractTonnageToleranceOverall && job.timeAllFast && !job.isOwnersJob && !job.jobConfig && job.typeOfOperation === 'LOAD') { warnings.push({ name: 'contractTonnageTolerance', screen: 'Master Sheet', message: 'Contract Tonnage Tolerance Not Set', dismissable: true, invoiceGroupId: job.mainInvoiceGroupId }); } if (!job.agencyFee && job.principalContactId && !job.isEnquiry && !job.isOwnersJob) { warnings.push({ name: 'agencyFee', screen: 'Master Sheet', message: 'Agency fee is not set', dismissable: true, invoiceGroupId: job.mainInvoiceGroupId }); } if (!job.principalContactId && !job.isEnquiry) { warnings.push({ name: 'principalNotSet', screen: 'Master Sheet', message: 'Principal is not set', dismissable: true, invoiceGroupId: job.mainInvoiceGroupId }); } if (!job.fdaInstructions) { warnings.push({ name: 'fdaInstructions', screen: 'Master Sheet', message: 'FDA instructions are not set!', dismissable: true, invoiceGroupId: job.mainInvoiceGroupId }); } var expandFieldName = function expandFieldName(fieldName) { var capitaliseFirstLetter = function capitaliseFirstLetter(str) { return str.charAt(0).toUpperCase() + str.slice(1); }; if (fieldName.startsWith('notifUpdates')) { fieldName = "".concat(fieldName.replace('notifUpdates', ''), " (Updates Email)"); } else if (fieldName.startsWith('notif')) { fieldName = "".concat(fieldName.replace('notif', ''), " (Email)"); } else if (fieldName.endsWith('ContactId')) { fieldName = "".concat(fieldName.replace('ContactId', ''), " (Key Contact)"); } fieldName = fieldName.replace(/([a-z])([A-Z])/g, '$1 $2').trim(); return capitaliseFirstLetter(fieldName); }; if (job.recalcFieldsToCheck && job.recalcFieldsToCheck !== '[]') { warnings.push({ name: 'fieldsMustBeRecalculated', screen: 'Master Sheet', message: 'Some fields must be recalculated: ' + JSON.parse(job.recalcFieldsToCheck).map(function (n) { return expandFieldName(n); }).join(', '), dismissable: false, invoiceGroupId: job.mainInvoiceGroupId }); } // Warnings for all InvoiceGroups if (job.invoiceGroups && job.invoiceGroups.length) { var _loop = function _loop() { var invoiceGroup = job.invoiceGroups[i]; var shouldBeDomestic = SHARED.shouldBeDomestic(job, invoiceGroup); if (invoiceGroup.isObsolete) return 1; // continue // Warning for GST mismatch if (invoiceGroup.principalContactId && !invoiceGroup.doesNotNeedFda && (shouldBeDomestic && !invoiceGroup.gst || !shouldBeDomestic && invoiceGroup.gst)) { var inMainInvoiceGroup = invoiceGroup.id === job.mainInvoiceGroupId; warnings.push({ name: "GSTMismatch_".concat(invoiceGroup.name), screen: inMainInvoiceGroup ? 'Master Sheet' : 'Transactions', message: inMainInvoiceGroup ? 'GST status does NOT match principal/accounting manager' : "GST status for sub-job does NOT match principal ".concat(invoiceGroup.name), dismissable: true, invoiceGroupId: invoiceGroup.id }); } job.invoices = job.invoices || []; var invoices = job.invoices.filter(function (inv) { return inv.invoiceGroupId === invoiceGroup.id; }); var hasAgencyFee = false; for (var ii = 0; ii < invoices.length; ii++) { var invoice = invoices[ii]; if (invoice.isTransfer) continue; invoice.transactions = invoice.transactions || []; for (var it = 0; it < invoice.transactions.length; it++) { var transaction = invoice.transactions[it]; if (transaction.documentTemplateId === 19) hasAgencyFee = true; var gstOnTransaction = SHARED.gstOnTransaction(invoiceGroup, invoice, transaction); for (var _i = 0, _arr = ['Pda', 'Close', 'Fda']; _i < _arr.length; _i++) { var field = _arr[_i]; var transactionDescription = transaction["description1".concat(field)]; transactionDescription = transactionDescription.length <= 20 ? transactionDescription : transactionDescription.substring(0, 20) + '...'; if (field !== 'Pda' && !invoiceGroup.doesNotNeedFda && gstOnTransaction && transaction["cost".concat(field)] && !transaction["gst".concat(field)]) { var warning = { name: "transaction_gst_error_".concat(transaction.id, "_").concat(invoiceGroup.name), message: "GST is required but not present for ".concat(transactionDescription, " (").concat(field, " in ").concat(invoiceGroup.name, ")"), dismissable: true, invoiceGroupId: invoiceGroup.id }; warnings.push(_objectSpread(_objectSpread({}, warning), {}, { screen: "Transactions" })); warnings.push(_objectSpread(_objectSpread({}, warning), {}, { screen: "Master Sheet" })); } if (!invoiceGroup.doesNotNeedFda && !gstOnTransaction && transaction["cost".concat(field)] && transaction["gst".concat(field)]) { var _warning = { name: "transaction_gst_error_".concat(transaction.id, "_").concat(invoiceGroup.name), message: "GST shouldn't be there and yet it's present for ".concat(transactionDescription, " (").concat(field, " in ").concat(invoiceGroup.name, ")"), dismissable: true, invoiceGroupId: invoiceGroup.id }; warnings.push(_objectSpread(_objectSpread({}, _warning), {}, { screen: "Transactions" })); warnings.push(_objectSpread(_objectSpread({}, _warning), {}, { screen: "Master Sheet" })); } } } } if (!SHARED.isSupplementaryDa(invoiceGroup.name) && !hasAgencyFee && !invoiceGroup.doesNotNeedFda) { warnings.push({ name: "NO_AGENCY_FEE_MS".concat(invoiceGroup.name), screen: 'Master Sheet', message: "Agency fee missing (".concat(invoiceGroup.name, ")"), dismissable: true, invoiceGroupId: invoiceGroup.id }); warnings.push({ name: "NO_AGENCY_FEE_TR".concat(invoiceGroup.name), screen: 'Transactions', message: "Agency fee missing (".concat(invoiceGroup.name, ")"), dismissable: true, invoiceGroupId: invoiceGroup.id }); } // Warning for missing FDA dates if (invoiceGroup.fdaCreated && (!invoiceGroup.timeFdaReceived || !invoiceGroup.timeFdaInvoiced || !invoiceGroup.timeFdaSent)) { warnings.push({ name: "FDA_CREATED_BUT_NO_DATES_".concat(invoiceGroup.name), screen: 'Master Sheet', message: "FDA created but dates (time FDA received, invoiced or sent) not set (".concat(invoiceGroup.name, ")"), dismissable: true, invoiceGroupId: invoiceGroup.id }); } }; for (var i = 0; i < job.invoiceGroups.length; i++) { if (_loop()) continue; } } /* // Obsoleted 02/05/25 11:51 Since we no longer check that cargo names are unique; original reason behind this check/warning unclear // Do not allow duplicate cargoId/description pairs for (let i = 0; i < 6; i++) { if (typeof job[`cargoDescription${i}`] !== 'undefined') { for (let j = i + 1; j < 6; j++) { if (!job[`cargoDescription${i}`]) continue // Ignore empty fields if ( job[`cargoDescription${i}`] === job[`cargoDescription${j}`] && job[`cargoId${i}`] === job[`cargoId${j}`] ) { warnings.push({ name: `cargoDuplicates${i}`, screen: 'Master Sheet', message: 'Duplicate cargo names', dismissable: false }) } } } } */ for (var _i2 = 0, _arr2 = [1, 2, 3, 4, 5]; _i2 < _arr2.length; _i2++) { var _i3 = _arr2[_i2]; // if (!job["cargoId".concat(_i3)]) continue; if (job["cargoId".concat(_i3)] && !job["cargoDescription".concat(_i3)] && !job.isOwnersJob) { warnings.push({ name: "cargoMissing".concat(_i3), screen: 'Master Sheet', message: "Cargo ".concat(_i3, " description missing"), dismissable: true }); } if (SHARED.authorityToLoadWarning(job)) { var tonnage = Number(job["tonnageToBeLoaded".concat(_i3)]); var min = Number(job["minAuthorityToLoad".concat(_i3)]); var max = Number(job["maxAuthorityToLoad".concat(_i3)]); if (!min || !max) { warnings.push({ name: "authorityToLoadNotSet".concat(_i3), screen: 'Master Sheet', message: "Authority to load ".concat(_i3, " not set"), dismissable: true }); } // If any value is not set, skip if (isNaN(tonnage) || isNaN(min) || isNaN(max)) continue; var msg = ''; if (min && tonnage < min) { msg = "Authority to load ".concat(_i3, " below minimum authority to load"); } else if (min && tonnage < min * 1.005) { msg = "Authority to load ".concat(_i3, " close to minimum authority to load"); } else if (max && tonnage > max) { msg = "Authority to load ".concat(_i3, " above maximum authority to load"); } else if (max && tonnage > max * 0.995) { msg = "Authority to load ".concat(_i3, " close to maximum authority to load"); } if (msg) { warnings.push({ name: "authorityToLoad".concat(_i3), screen: 'Master Sheet', message: msg, dismissable: true }); } } } // If job.jobConfig is '', it's a default job. A configured ruleset may replace these legacy warnings. if (showSofWarnings && !isMrl && !job.isOwnersJob && !job.jobConfig) { // if (!job.isOwnersJob && !job.jobConfig) { if (!hasFact('Vessel arrived')) { warnings.push({ name: 'vesselArrived', screen: 'Statement Of Facts', message: 'Vessel arrived missing', dismissable: true }); } if (!hasFact('Commence loading')) { warnings.push({ name: 'commenceLoading', screen: 'Statement Of Facts', message: 'Commence loading missing', dismissable: true }); } if (!hasFact('Complete loading')) { warnings.push({ name: 'completeLoading', screen: 'Statement Of Facts', message: 'Complete loading missing', dismissable: true }); } var _checkSofBalance = checkSofBalance('final ', 'intermediate '), _checkSofBalance2 = _slicedToArray(_checkSofBalance, 2), finals = _checkSofBalance2[0], inters = _checkSofBalance2[1]; if (finals !== inters) { warnings.push({ name: 'draftSurvey', screen: 'Statement Of Facts', message: "Draft survey count mismatch: ".concat(inters, " intermediate, ").concat(finals, " final. Check the SOF entries."), dismissable: true }); } var _checkSofBalance3 = checkSofBalance(['commence loading', 'commenced loading', 'recommence loading', 'recommenced loading', 'start loading', 'started loading'], ['complete loading', 'completed loading', 'stop loading', 'stopped loading', 'cease loading', 'ceased loading', 'pause loading', 'paused loading']), _checkSofBalance4 = _slicedToArray(_checkSofBalance3, 2), commencedLoading = _checkSofBalance4[0], completedLoading = _checkSofBalance4[1]; if (commencedLoading !== completedLoading) { warnings.push({ name: 'loadingBalance', screen: 'Statement Of Facts', message: "Loading event count mismatch: ".concat(commencedLoading, " commence loading, ").concat(completedLoading, " complete loading. Check the SOF entries."), dismissable: true }); } var _checkSofBalance5 = checkSofBalance('mooring to ogv', 'unmooring from ogv'), _checkSofBalance6 = _slicedToArray(_checkSofBalance5, 2), mooredToOgv = _checkSofBalance6[0], unmooredFromOgv = _checkSofBalance6[1]; if (mooredToOgv !== unmooredFromOgv) { warnings.push({ name: 'ogvMooringBalance', screen: 'Statement Of Facts', message: "OGV mooring event count mismatch: ".concat(mooredToOgv, " mooring to OGV, ").concat(unmooredFromOgv, " unmooring from OGV. Check the SOF entries."), dismissable: true }); } var hasInitialDraftSurvey = (job.sof || []).some(function (st) { return !st.isObsolete && (st.particulars || '').toLowerCase().startsWith('initial draft '); }); if (!SHARED.isGrain(job) && finals && !hasInitialDraftSurvey) { warnings.push({ name: 'initialDraftSurvey', screen: 'Statement Of Facts', message: 'Initial draft survey missing from the SOF', dismissable: true }); } } if (tonnageMismatch(job)) { warnings.push({ name: 'tonnageMismatch', screen: 'B/L Manifest', message: 'Master Sheet and B/L Manifest tonnage loaded don\'t match', dismissable: false }); } } // Missing head owner — jobs created from 10 September 2026, Perth time if (!job.isOwnersJob && !job.isCancelled && !job.isEnquiry && !job.isArchived && job.added >= Date.parse('2026-09-10T00:00:00+08:00') && !job.headOwnerContactId) { warnings.push({ name: 'headOwnerNotSet', screen: 'Master Sheet', message: 'Head owner is not set', dismissable: true, invoiceGroupId: job.mainInvoiceGroupId }); } return warnings; }; exports.portPilotStation = function (job) { switch (job.portId) { case 7: return "FREMANTLE Inner Pilot Station"; case 131: return "ASHBURTON Anchorage"; default: return "".concat(job.portIdName, " Pilot Station"); } }; exports.valuerPreProcess = function (klass, job, r) { switch (klass) { case 'defaultJobContacts': // VERY unsure about this. If uncommented, a recalculation will overwrite with "null" // a set field. This is not good, because these are contacts and if one is set, it's // normally for a reason. r.brokerContactId = null; r.headOwnerContactId = null; r.stevedoreContactId = null; r.towageContactId = null; r.launchContactId = null; r.agencyContactId = null; r.fumigatorContactId = null; r.hatchSealerContactId = null; r.marineSurveyorContactId = null; r.aoSurveyorContactId = null; r.boarderContactId = null; return r; case 'fees': // DEFAULT FEES return { fee: null, recoveries: false, prefunding: 100, prefundingFreeThreshold: 0, prefundingGst: true, fdaPaymentTermsDays: 7, longTermDeal: 0 }; case 'notifEmails': // ALERTS EMAILS r.notifUpdatesPrincipalEmail = job.principalEmail ? job.principalEmail + ';' : ''; r.notifUpdatesShipperEmail = job.shipperContactId && job.shipperContactIdEmailCc ? job.shipperContactIdEmailCc + ';' : ''; r.notifUpdatesLocalsEmail = ''; r.notifUpdatesBuyerEmail = ''; // OTHER EMAILS r.notifShipperEmail = job.shipperContactId && job.shipperContactIdEmailCc ? job.shipperContactIdEmailCc + ';' : ''; r.notifLoadSequenceEmail = ''; r.notifStevedoreEmail = job.stevedoreContactId && job.stevedoreContactIdEmailCc ? job.stevedoreContactIdEmailCc + ';' : ''; r.notifAoSurveyorEmail = job.aoSurveyorContactId && job.aoSurveyorContactIdEmailCc ? job.aoSurveyorContactIdEmailCc + ';' : ''; r.notifMarineSurveyorEmail = job.marineSurveyorContactId && job.marineSurveyorContactIdEmailCc ? job.marineSurveyorContactIdEmailCc + ';' : ''; r.notifBoarderEmail = job.boarderContactId && job.boarderContactIdEmailCc ? job.boarderContactIdEmailCc + ';' : ''; r.notifAgencyEmail = job.agencyContactId && job.agencyContactIdEmailCc ? job.agencyContactIdEmailCc + ';' : ''; return r; default: return r; } }; exports.valuerPostProcess = function (klass, job, r) { switch (klass) { case 'defaultJobContacts': return r; case 'fees': r.fixedRecoveries = r.fixedRecoveries || null; // non-mandatory r.prefundingDiscount = r.prefundingDiscount || 0; // non-mandatory r.prefundingDiscountReason = r.prefundingDiscountReason || null; // non-mandatory r.longTermDeal = r.longTermDeal || false; // non-mandatory /* // This no longer applies 26/8/24 // // SAND IN GERALDTON: Fee is 6800 if (job.portId === 6 && SHARED.hasCargo(job, 25)) { r.fee = 6800 // Enquiry: zap the fee regardless } else if (job.isEnquiry) { // It is an enquiry: agency fee is 0 (unless it's sand in Geraldton) r.fee = 0 } */ // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // If the principal is Australian, OR if the third party agency is Australian, // then prefunding of GST is mandatory regardless // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if (job.accountingManagerContactId) { if (job.accountingManagerContactIdCountryId === 13) r.prefundingGst = true; } else { if (job.principalContactId && job.principalContactIdCountryId === 13) r.prefundingGst = true; } // You CANNOT __CANNOT__ have itemised recoveries AND fixed recoveries if (r.fixedRecoveries) r.recoveries = false; // Return the object return r; case 'notifEmails': // PRINCIPAL IS NOT SET: ASSIGN EYE-CATCHING DEFAULT (DIFFERENT DEFAULTS FOR OCEAN 21) if (!r.notifUpdatesPrincipalEmail) { if (job.principalId === 91) { r.notifUpdatesPrincipalEmail = 'OPERATOR_AT_OCEAN21@ocean-21.com; OCEAN21_GROUP_EMAIL_FOR_SHIP@ssmship.com;'; } else { r.notifUpdatesPrincipalEmail = ''; } } // If shipperEmail or updatesShipperEmail are not set, ASSIGN EYE-CATCHING DEFAULT // r.notifUpdatesShipperEmail = r.notifUpdatesShipperEmail // || 'SHIPPER@example.com3;' r.notifShipperEmail = r.notifShipperEmail || 'SHIPPER@example.com4;'; /* // MOVED TO APP CONFIG, 25/11/2023 // WA PORTS ONLY -- ALBANY, ESPERANCE, KWINANA, GERALDTON if (job.portId === 1 || job.portId === 4 || job.portId === 6 || job.portId === 7) { // EXTRA ADDRESSES FOR GRAIN OR UAN if (SHARED.isGrain(job) || SHARED.hasCargo(job, 28)) { r.notifUpdatesLocalsEmail += ' Shipping.Nominations@cbh.com.au;' r.notifLoadSequenceEmail += ' Shipping.Nominations@cbh.com.au;' } if (SHARED.hasCargo(job, 28)) { // r.notifUpdatesLocalsEmail += ' fertiliser@cbh.com.au;' // Locals option; not used but here if it needs changed 9/11/23 // r.notifShipperEmail += ' fertiliser@cbh.com.au;' // global shipper email; not used but here if it needs changed 9/11/23 r.notifUpdatesShipperEmail += ' fertiliser@cbh.com.au;' } } */ // SHIPPERS AGENT: SHIPPER EMAIL IS SHIPPERS AGENT'S EMAIL if (job.agencyContactId) { r.notifShipperEmail = r.notifAgencyEmail; r.notifUpdatesShipperEmail = r.notifAgencyEmail; } // Delete double semicolons for (var f in r) { if (typeof r[f] === 'string') r[f] = r[f].replace(/;+/g, ';'); } // Monson's exception for Geraldton // MOVED TO RULES // if (job.portId === 6 && job.agencyContactId === 412) r.notifAgencyEmail = 'geraldton@monson.com.au' // WA Mercantile -- as surveyors // MOVED TO RULES // if (job.marineSurveyorContactId === 494) r.notifMarineSurveyorEmail = 'surveyor@wamercantile.com.au' // WA Mercantile return r; case '': return r; } }; exports.draftOLD = function (n, allowThreePlaces) { if (n === null) return ''; if (!allowThreePlaces) return Number(n).toFixed(2);else return Number(n).toFixed(3); // .replace(/0$/, '') }; exports.draft = function (number) { var showWholeNumberAsIs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (number === null) return ''; number = Number(number); if (isNaN(number)) return ''; // Check if the number is a whole number if (showWholeNumberAsIs && Number.isInteger(number)) { return number.toString(); // Return whole number as is } // 8.120 becomes 8.12 number = Number(number).toFixed(3); if (number.split('.')[1].length == 3 && number.endsWith('0')) number = Number(number).toFixed(2); return number; }; exports.remoteZoneAllowanceExempt = function (job) { var principalContactId = job.principalContactIdParentContactId || job.principalContactId; return principalContactId === 67 || // DAMPSKIBSSELSKABET NORDEN A/S principalContactId === 110 || // SWISSMARINE SERVICES principalContactId === 479 || // CARGILL INTERNATIONAL SA principalContactId === 83 && job.nominatorContactId === 363 || // LOUIS DREYFUS COMPANY FREIGHT ASIA PTE LTD (ONLY MRL) principalContactId === 93 && job.nominatorContactId === 363 && SHARED.hasCargo(job, 14); // OLDENDORFF (ONLY MRL + SPODUMENE) }; exports.getStowplan = function (job, field) { if (!job.stowPlanData) return null; var stowplan = JSON.parse(job.stowPlanData); function calcTotalOverall(stowplan) { var total = 0; if (!stowplan) return ''; for (var i = 1; i <= 9; i++) { var current = Number(stowplan["stow".concat(i, "Total")]); total += isNaN(current) ? 0 : Number(stowplan["stow".concat(i, "Total")]); } return isNaN(total) ? 0 : total; } if (field === 'totalOverall') { return stowplan.totalOverall || calcTotalOverall(stowplan); } else if (field) { return stowplan[field]; } return stowplan; }; exports.locals = function (job) { // Some ports (Dampier, Kwinana, Port Hedland) get less locals than others return SHARED.oneOf(job.portId, 3, 7, 9) ? SHARED.someLocals(job) : SHARED.allLocals(job); }; exports.allLocals = function (job) { return SHARED.joinEmail(job.portId === 7 ? '' : job.portIdPortAuthorityEmail, job.notifStevedoreEmail, job.towageContactIdEmailCc, job.portIdAbfEmail, job.portIdQuarantineEmail, job.notifMarineSurveyorEmail, job.notifAoSurveyorEmail, job.notifAgencyEmail, job.notifBoarderEmail, job.fumigatorContactIdEmailCc, job.hatchSealerContactIdEmailCc, job.notifUpdatesLocalsEmail); }; exports.someLocals = function (job) { return SHARED.joinEmail(job.portId === 7 ? '' : job.portIdPortAuthorityEmail, job.notifStevedoreEmail, /* this is left blank so that the length of this matches the list above */job.notifMarineSurveyorEmail, job.notifAoSurveyorEmail, job.notifAgencyEmail, job.notifBoarderEmail, job.fumigatorContactIdEmailCc, job.hatchSealerContactIdEmailCc, job.notifUpdatesLocalsEmail); /* eslint-disable-line no-multi-spaces */ }; exports.invoiceSuffix = function (invoiceGroupInfo) { // Main invoice group: no suffix if (invoiceGroupInfo.main) return ''; // It's a supplementary DA: have the parent's (B, C, D, E) plus S plus the supplementary DA's number if (invoiceGroupInfo.parentInvoiceGroupId) { var maybeLetter = invoiceGroupInfo.parentInvoiceGroupIdName[0]; if (maybeLetter == 'M') maybeLetter = ''; var maybeNumber = invoiceGroupInfo.name.slice(-1); if (!maybeNumber.match(/[0-9]/)) maybeNumber = ''; return maybeLetter + 'S' + maybeNumber; } if (invoiceGroupInfo.name[0] === 'M') { var _maybeNumber = invoiceGroupInfo.name[1] || ''; return invoiceGroupInfo.name[0] + _maybeNumber; } // Any other case return invoiceGroupInfo.name[0]; }; exports.invoiceHeader = function (job, f, title, taxInvoice, showExtraDetails, voucherNumber, params) { var invoiceLine; if (taxInvoice) { invoiceLine = '
Tax invoice
'; } else { invoiceLine = '
'; } var address; if (params.isCoverLetter) { address = params.invoiceGroupInfo.invoiceAddress || ''; } else { address = params.invoiceGroupInfo.address || ''; } var firstLine = address.slice(0, address.indexOf('\n') + 1); var otherLines; if (firstLine === '') { firstLine = address; otherLines = ''; } else { otherLines = address.slice(address.indexOf('\n') + 1); } // Addresses are shown as
 -- necessarily so.
  // The trouble is that an empty line is translated, later in the pipeline, as a 
. // This causes addresses to have THREE spaces for each enter in them. // This "fixes" it -- each empty line is transformed into a line with a space in it otherLines = otherLines.split('\n').map(function (l) { return l === '' ? ' ' : l; }).join('\n'); return "\n \n \n \n
\n \n

ABN: 69 148 177 431

\n\n
\n \n \n \n \n
\n

").concat(title, "

\n ").concat(invoiceLine, "\n
\n
\n\n\n

Ship Agency Services Pty Ltd

\n

78 Discovery Drive

\n

Bibra Lake WA 6163

\n

Ph: +61 8 9494 1198

\n

Fx: +61 8 6316 1414

\n
\n
\n
\n\n \n \n \n \n \n
To:\n The Master & Owners of the:
\n ").concat(job.addressAs, " ").concat(job.shipName, "
\n ").concat(firstLine, "").concat(otherLines, "\n
\n
\n \n \n \n \n \n \n ").concat(job.agencyReference ? '' : '', "\n ").concat(job.agencyReference ? "") : '', "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Date:").concat(SHARED.dateINTERNATIONAL(new Date()), "
Invoice #:".concat(job.agencyReference).concat(SHARED.invoiceSuffix(params.invoiceGroupInfo), "
Vessel:").concat(job.shipName, "
SDWT:").concat(job.shipDwt, "
GRT:").concat(job.shipGrt, "
NRT:").concat(job.shipNrt, "
LOA:").concat(job.shipLoa, "
VOUCHER:
").concat(voucherNumber, "
\n
\n
\n

\n "); }; exports.invoiceFooter = function () { return '
PLEASE NOTE ALL BUSINESS IS CONDUCTED IN LINE WITH OUR TRADING TERMS AND CONDITIONS AVAILABLE ON REQUEST
'; }; exports.invoiceBreaker = function () { var fda = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; return "
\n \n \n ".concat(fda ? "" : "", "\n \n \n \n \n "); }; exports.progressReportEmailManipulateRecipient = function (job, toType, email) { if (job.shipperContactId == 363 && toType == 'shipper') email += 'augustin.couani@mrl.com.au; chartering@mineralresources.com.au;'; return email; }; exports.progressReportEmail = function (job, recipient, report) { var extrasForMrl = function extrasForMrl() { var result = ''; if (report.recipientType === 'shipper' && job.nominatorContactId === 363) { if (job.portId === 9) { // Port Hedland result += "\nTotal delays: X.XXhrs\n GLR: XXXX MT/HR\n NLR: XXXX MT/HR\n"; } if (job.portId === 4) { // Esperance result += '\nTotal delays: X.XXhrs\n'; } } if (job.portId === 4) { // Esperance result += '\nTotal loading time: <%=report.loadingTime%>\n'; } if (job.portId === 131) { // Ashburton result += '\n[[***OPERATOR TO EDIT : INSERT LOAD MASTER REPORT COMMENTS***]]\n'; } return result; }; var extrasForEsperance = function extrasForEsperance() { var result = ''; var next = new Date(report.progressTime).getHours() + 6; if (next > 20) next = 6; next = (next < 10 ? '0' : '') + next + '00'; console.log(next); if (job.portId === 4 && (SHARED.hasCargo(job, 7) || SHARED.hasCargo(job, 8))) { // Esperance, iron ore or iron ore fines // result += `\nBefore the next vessel update (to be issued at ${next} hrs) the lowest predicted tide expected is X.XXm.\nThe port is currently experiencing XXcm of residuals.\nTherefore the maximum allowable draft until the next update will be XX.XXm.` result += "\nThe lowest predicted tide within 2.5 hours from POB is X.XXm at HHMMhrs DD/MM/YYYY.\nThe port is currently experiencing XXcm of residuals.\nTherefore the maximum allowable draft for channel transit will be XX.XXm.\n"; } return result; }; function _accumDuration(acc) { var hours = (acc / 60 / 60 / 1000).toFixed(2); return "".concat(hours, " hours"); } var subjectTo; if (job.portId === 9) subjectTo = 'ETC and port program';else if (job.portId === 131) subjectTo = "review";else subjectTo = 'ETC and pilot/tug availability'; var totalTimeAt; if (job.portId !== 131) totalTimeAt = "\nTotal time alongside: <%=report.timeAlongside%>";else totalTimeAt = 'XX,XXXMT loaded since HHMMhrs DD/MM/20XX\n'; return "Good day,\n\nPlease find below Progress Report for <%-report.shipName%> at <%=report.portIdName%>: As at <%=date(report.progressTime, 'm')%>hrs <%=date(report.progressTime, 'd')%>.\n\nCargo loaded: <%=SHARED.addCommas(report.cargoLoaded)%>MT\nRemaining cargo: <%=SHARED.addCommas(report.cargoRemaining)%>MT (based on <%=SHARED.addCommas(report.tonnageToBeLoaded)%>MT / Dept Draft <%=report.deptDraft%>m)\n".concat(totalTimeAt).concat(extrasForMrl(), "\nETComp: <%=report.ETComp%> (subject to ").concat(subjectTo, ")\nETD: <%=report.etd.trim()%> (subject to ").concat(subjectTo, ")\n").concat(extrasForEsperance(), "\nReverting with updates.\n "); }; exports.localsButtonOnLoadingReport = function (job) { return job.portId !== 9; }; exports.isDone = function (job, checklistTemplateId, invoiceGroupId) { var trueIfNotPresent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; // Set the invoiceGroupId as the job's main if it wasn't passed if (!invoiceGroupId) invoiceGroupId = job.mainInvoiceGroupId; // Checklist item not found: assume it's all good var item = false; for (var i = 0; i < job.checklists.length; i++) { var el = job.checklists[i]; if (el.invoiceGroupId === invoiceGroupId && el.checklistTemplateId === checklistTemplateId) { item = el; break; } } if (!item) { if (trueIfNotPresent) return true;else return false; } if (item.isNotRelevant) return true; // Return isDone (true or false) return !!item.isDone; }; exports.makeAlerts = function (jobs) { function hoursAgo(hours) { var d = new Date(); d.setHours(d.getHours() - hours); return d; } function daysAgo(days) { var hours = days * 24; return hoursAgo(hours); } function pushAlert(alerts, alert, job) { alert.type = 'job'; alert.operatorContactId = job.operatorContactId; alert.operatorContactIdName = job.operatorContactIdName; alert.jobId = job.id; alert.alertName = job.shipName; alert.agencyReference = job.agencyReference; alerts.push(alert); } var isDone = SHARED.isDone; /* eslint-disable no-unused-vars */ var today = daysAgo(0); var yesterday = daysAgo(1); var oneDayAgo = daysAgo(1); var oneAndAHalfDaysAgo = daysAgo(1.5); var twoDaysAgo = daysAgo(2); var twoAndAHalfDaysAgo = daysAgo(2.5); var threeDaysAgo = daysAgo(3); var fourDaysAgo = daysAgo(4); var fourAndAHalfDaysAgo = daysAgo(4.5); var fiveDaysAgo = daysAgo(5); var sixDaysAgo = daysAgo(6); var tomorrow = daysAgo(-1); var inOneDay = daysAgo(-1); var inOneAndAHalfDays = daysAgo(-1.5); var inTwoDays = daysAgo(-2); var inTwoAndAHalfDays = daysAgo(-2.5); var inThreeDays = daysAgo(-3); var inFourDays = daysAgo(-4); var inFourAndAHalfDays = daysAgo(-4.5); var inFiveDays = daysAgo(-5); var inSixDays = daysAgo(-6); var sevenDaysAgo = daysAgo(7); var tenDaysAgo = daysAgo(10); var twelveDaysAgo = daysAgo(12); var fourteenDaysAgo = daysAgo(14); /* eslint-enable no-unused-vars */ var alerts = []; var _iterator6 = _createForOfIteratorHelper(jobs), _step6; try { for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) { var job = _step6.value; var alert = void 0; alert = null; if (!job.isCancelled && !job.timeArrived && job.eta <= today) { var _oneDayAgo = daysAgo(1); alert = { type: 'alert', title: "Vessel's ETA is in the past. Check whether it has arrived", description: '' }; if (job.eta >= _oneDayAgo) { // ETA passed within the last 24h alert.severity = 2; } else if (!job.isOwnersJob) { // ETA passed more than 24h ago alert.severity = 3; } } if (alert) pushAlert(alerts, alert, job); // Prefunding alert (only if checklist item 24 not ticked) alert = null; if (!job.isCancelled && job.prefunding && !isDone(job, 24)) { if (!job.timeArrived && job.eta < inThreeDays) { // Alert 1: within 3 days of arrival alert = { type: 'alert', title: 'Prefunding required before vessel arrival', description: '', severity: 2 }; } else if (job.timeArrived) { // Alert 2: vessel has arrived and prefunding still required alert = { type: 'alert', title: 'Prefunding required – vessel HAS ARRIVED', description: '', severity: 2 }; } } if (alert) pushAlert(alerts, alert, job); // Create alert for ETD (sailingSetFor) passed today alert = null; if (!job.isCancelled && !job.timeSailed && job.timeSailingSetFor && job.timeSailingSetFor <= today) { alert = { type: 'alert', title: 'Vessel\'s ETD is in the past. Check whether it has sailed', description: '' }; alert.severity = 2; } if (alert) pushAlert(alerts, alert, job); // Create alert for Arrival Report alert = null; if (!job.isCancelled && !isDone(job, 29) && job.timeArrived) { alert = { type: 'alert', title: 'Lodge Sea Actual Arrival Report in ICS', description: '', actionUrl: 'https://www.ccf.customs.gov.au/' }; if (job.timeArrived > hoursAgo(24)) alert.severity = 2;else alert.severity = 3; } if (alert) pushAlert(alerts, alert, job); // Create alert for ABF form 13 alert = null; if (!job.isCancelled && !isDone(job, 15) && job.eta < inSixDays) { alert = { type: 'alert', title: 'Submit ABF Forms 13, 3B and 5(IV)', description: '', actionUrl: '' }; if (job.eta < inFourAndAHalfDays) alert.severity = 3;else if (job.eta < inFiveDays) alert.severity = 2;else alert.severity = 1; } if (alert) pushAlert(alerts, alert, job); // pushAlert(alerts, { type: 'alert', title: 'DUMMY TEST ALERT', description: '', actionUrl: '' }, job) // Create Sea Impending arrival report alert = null; if (!job.isCancelled && !isDone(job, 13) && job.eta < inSixDays) { alert = { type: 'alert', title: 'Lodge Sea Impending Arrival Report in ICS', description: '', actionUrl: '' }; if (job.eta < inFourAndAHalfDays) alert.severity = 3;else if (job.eta < inFiveDays) alert.severity = 2;else alert.severity = 1; } if (alert) pushAlert(alerts, alert, job); // Create alert for DAFF alert = null; if (!job.isCancelled && !isDone(job, 6) && job.eta < inFourDays) { alert = { type: 'alert', title: 'Receive DAFF Pre-Arrival Report', description: '', actionUrl: 'https://online.agriculture.gov.au/selfservice/faces/oracle/webcenter/portalapp/pages/public/login.jspx;' }; if (job.eta < inOneDay) alert.severity = 3;else if (job.eta < inThreeDays) alert.severity = 2;else alert.severity = 1; } if (alert) pushAlert(alerts, alert, job); // Create alert for Shippers Declarations alert = null; var declarationDueTime = job.portId === 131 ? job.timeCommencedLoading : job.timeAllFast; var giveWarning = !job.isCancelled && !isDone(job, 141) && declarationDueTime && declarationDueTime < inOneAndAHalfDays; if (giveWarning) { 55555; alert = { type: 'alert', title: 'Receive Shippers Declaration', description: '' }; if (declarationDueTime < hoursAgo(-6)) alert.severity = 3;else alert.severity = 2; } if (alert) pushAlert(alerts, alert, job); // Create alert for main mainfest not submitted to ICS alert = null; if (!job.isCancelled && !isDone(job, 60) && job.timeSailed && today > job.timeSailed) { alert = { type: 'alert', title: 'Enter main manifest in ICS ', description: '', actionUrl: '' }; if (job.timeSailed < twoDaysAgo) alert.severity = 3;else if (job.timeSailed < yesterday) alert.severity = 2;else alert.severity = 1; // alert.severity = 3 } if (alert) pushAlert(alerts, alert, job); // If it sailed more that X days ago, // then check that FDA has been dealt with if (job.timeSailed && job.timeSailed < twelveDaysAgo) { var _iterator7 = _createForOfIteratorHelper(job.invoiceGroups), _step7; try { for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) { var invoiceGroup = _step7.value; // Unsailed ships: don't bother with the warming // if (!job.timeSailed || today < job.timeSailed) continue // The ID to check is different for the main invoice group than it is for sub-jobs // if (!isDone(job, invoiceGroup.id === job.mainInvoiceGroupId ? 206 : 389, invoiceGroup.id)) { var lastInvoiceReceivedTemplateId = void 0; var fdaSentChecklistTemplateId = void 0; var found = false; if (invoiceGroup.id === job.mainInvoiceGroupId) { if (isDone(job, 206, invoiceGroup.id, false) !== 'undefined') { fdaSentChecklistTemplateId = 206; lastInvoiceReceivedTemplateId = 207; found = true; } } else { if (isDone(job, 389, invoiceGroup.id, false) !== 'undefined') { fdaSentChecklistTemplateId = 389; lastInvoiceReceivedTemplateId = 386; found = true; } } // No checklist items to check -- checklist probably wasn't imported if (!found) continue; // FDA sent. No warning to send if (isDone(job, fdaSentChecklistTemplateId, invoiceGroup.id)) continue; var text = void 0; if (!isDone(job, lastInvoiceReceivedTemplateId, invoiceGroup.id)) text = "Operations prepare FDA for Accounts - ";else text = "Accounts send FDA - "; var _alert = { type: 'alert', title: text + invoiceGroup.name, description: '', actionUrl: '', endOfList: true }; _alert.severity = 1; if (job.timeSailed < fourteenDaysAgo) _alert.severity = 2; pushAlert(alerts, _alert, job); } } catch (err) { _iterator7.e(err); } finally { _iterator7.f(); } } } } catch (err) { _iterator6.e(err); } finally { _iterator6.f(); } return alerts; }; exports.independentMarineSurveyForGrain = function (job) { // No grain, do not display (thanks to the "null" in the cost) if (!SHARED.isGrain(job)) return { cost: null, description1: 'Independent Marine Survey for grain - Only applicable for grain' }; // Ocean21 gets invoiced direct, hence "0" cost (which will still display) if (job.principalContactId === 91) { return { cost: 0, description1: 'Independent Marine Survey for grain - Amspec to invoice Ocean 21 direct' }; // Everybody else pays } else { var description1 = 'Independent Marine Survey for grain'; var cost = 0; var holds = job.shipHolds || 5; switch (job.portId) { case 1: // Albany case 6: // Geraldton if (holds === 5) cost = 1500 + 500; // Extra for travel expenses if (holds === 7) cost = 1850 + 500; // Extra for travel expenses break; case 4: // Esperance if (holds === 5) cost = 1600 + 950; // Extra for travel expenses if (holds === 7) cost = 1850 + 950; // Extra for travel expenses break; case 7: // Kwinana if (holds === 5) cost = 1500; if (holds === 7) cost = 1850; break; case 2: // Bunbury (assumed) if (holds === 5) cost = 1500 + 350; // Extra for travel expenses if (holds === 7) cost = 1850 + 350; // Extra for travel expenses break; case 115: // Adelaide cost = 1750; break; case 118: // Port Lincoln case 128: // Port Giles cost = 2500; // Inc. travel fee break; case 142: // Thevenard cost = 3000; break; default: cost = 1; } return { description1: description1, cost: cost }; } }; exports.followUpIndependentMarineSurveyForGrain = function (job) { // No grain, do not display (thanks to the "null" in the cost) if (!SHARED.isGrain(job)) return { cost: null, description1: 'Follow-up Marine Survey of Ballast Hold - Only applicable for grain' }; // Ocean21 gets invoiced direct, hence "0" cost (which will still display) if (job.principalContactId === 91) { return { cost: 0, description1: 'Follow-up Marine Survey of Ballast Hold - Amspec to invoice Ocean 21 direct' }; // Everybody else pays } else { var description1 = 'Follow-up Marine Survey of Ballast Hold'; var cost = 0; switch (job.portId) { case 1: // Albany case 6: // Geraldton cost = 750; break; case 4: // Esperance cost = 850; break; case 7: // Kwinana cost = 750; break; case 2: // Bunbury (assumed) cost = 750; break; case 115: // Adelaide case 118: // Port Lincoln case 128: // Port Giles case 142: // Thevenard cost = 1000; break; default: cost = 1; } return { description1: description1, cost: cost }; } }; exports.quarantineSurveyForGrain = function (job) { // No grain, do not display (thanks to the "null" in the cost) if (!SHARED.isGrain(job)) return { cost: null, description1: 'Quarantine Survey for grain - Only applicable for grain' }; if (job.principalContactId === 91) { // OCEAN 21 var description = 'AO Quarantine Survey for grain'; var cost; switch (job.portId) { case 1: // Albany case 6: // Geraldton // cost = 2750 + 850 + 200 // as of 1/10/19 // cost = 2300 + 850 + 350 // Before ballast hold split 21/10/19 cost = 2300 + 850; // Travel costs updated 12/06/26 description = description + ' - inc. travel costs (if applicable)'; break; case 4: // Esperance // cost = 2850 + 950 + 200 // as of 1/10/19 // cost = 2300 + 950 + 650 // Before ballast hold split 21/10/19 cost = 2300 + 1450; // Travel costs updated 12/06/26 description = description + ' - inc. travel costs (if applicable)'; break; case 7: // Kwinana // cost = 2300 + 600 // Before ballast hold split 21/10/19 cost = 2300; break; case 2: // Bunbury (assumed) // cost = 2500 + 600 + 150 // as of 1/10/19 // cost = 2300 + 600 + 150 // Before ballast hold split 21/10/19 cost = 2300 + 500; // Travel costs updated 12/06/26 description = description + ' - inc. travel costs (if applicable)'; break; case 115: // Adelaide cost = 3000; break; case 142: // Thevenard cost = 5000; break; case 118: // Port Lincoln case 128: // Port Giles cost = 4000; break; default: cost = 1; } return { description1: description, cost: cost }; } else { var _description = 'Authorised Officer Quarantine Survey for grain'; var _cost; switch (job.portId) { case 1: // Albany case 6: // Geraldton _cost = 2750 + 750; // Extra for travel expenses break; case 4: // Esperance _cost = 2950 + 950; // Extra for travel expenses break; case 7: // Kwinana _cost = 2750; break; case 2: // Bunbury (assumed) _cost = 2500 + 500; // Extra for travel expenses break; case 115: // Adelaide _cost = 3000; break; case 118: // Port Lincoln case 128: // Port Giles _cost = 4000; break; case 142: // Thevenard _cost = 5000; break; default: _cost = 1; } return { description1: _description, cost: _cost }; } }; exports.followUpQuarantineSurveyForGrain = function (job) { // No grain, do not display (thanks to the "null" in the cost) if (!SHARED.isGrain(job)) return { cost: null, description1: 'Follow-up Authorised Officer Quarantine Survey of Ballast Hold - Only applicable for grain' }; var description1 = 'Follow-up Authorised Officer Quarantine Survey of Ballast Hold (if required)'; var cost = 0; switch (job.portId) { case 1: // Albany case 6: // Geraldton case 4: // Esperance case 7: // Kwinana case 2: // Bunbury (assumed) case 115: // Adelaide cost = 950; break; case 118: // Port Lincoln case 128: // Port Giles cost = 1200; break; case 142: // Thevenard cost = 1500; break; default: cost = 1; } return { description1: description1, cost: cost }; }; exports.calculateRecoveryInvoices = function () { var recoveryInvoices = []; recoveryInvoices.push(251); recoveryInvoices.push(252); recoveryInvoices.push(253); recoveryInvoices.push(254); recoveryInvoices.push(255); recoveryInvoices.push(14395); // Phone calls recoveryInvoices.push(256); recoveryInvoices.push(16151); // FDA despatch fee return recoveryInvoices; }; exports.defaultRecoveriesLine = function (job, what) { var sandInGeraldton = job.portId === 6 && SHARED.hasCargo(job, 25); // It's a PDA for an enquiry: return the default writing 0 estimate if (job.isEnquiry && what === 'pda') { if (job.recoveries) { var description1; if (sandInGeraldton) description1 = "Recoveries - Not Applicable";else description1 = 'Estimated Recoveries - To be confirmed in Final DA'; return { description1: description1, cost: 0 }; } else { return { description1: "Recoveries - Not Applicable", cost: null }; } } // FIXED RECOVERIES: Just print the right amount for ALL cases (PDA, close estimate, FDA) if (job.fixedRecoveries) { return { description1: 'Fixed Recoveries', cost: job.fixedRecoveries }; } // If sandInGeraldton is true, return "Recoveries Not Applicable" if (sandInGeraldton) { return { description1: 'Recoveries - Not Applicable', cost: 0 }; } // GRAIN: Make recoveries line completely invisible if (SHARED.isGrain(job)) { return { description1: 'Recoveries - Not Applicable to grain vessels, line will not appear', cost: null, amountCheckedByDefault: true }; // } else { // NO ITEMISED RECOVERIES: SHOW "Not applicable" if (!job.recoveries) { return { description1: 'Recoveries - Not Applicable', cost: what === 'fda' ? null : 0, amountCheckedByDefault: true }; // YES ITEMISED RECOVERIES: Only show $2000 estimate, and HIDE for FDA (which will have itemised ones) } else { if (what === 'fda') { return { cost: null, description1: 'Recoveries are itemised, this will be invisible in FDA', amountCheckedByDefault: true, amountCheckedByDefaultReason: "Recoveries itemised in FDA" }; } else { return { cost: 2000, description1: 'Estimated Recoveries - To be confirmed in Final DA', amountCheckedByDefault: true, amountCheckedByDefaultReason: "Recoveries itemised in FDA" }; } } } }; exports.daysBetween = function (a, b) { var aZ = new Date(a); aZ = new Date(Number(aZ) + aZ.getTimezoneOffset() * 60 * 1000 + 1000 * 60 * 60 * 8); aZ.setHours(0); aZ.setMinutes(0); var bZ = new Date(b); bZ = new Date(Number(bZ) + bZ.getTimezoneOffset() * 60 * 1000 + 1000 * 60 * 60 * 8); bZ.setHours(0); bZ.setMinutes(0); return Math.abs((aZ - bZ) / 1000 / 60 / 60 / 24) + 1; }; exports.round = function (n) { var d = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 2; var x = n * Math.pow(10, d); var r = Math.round(x); var br = Math.abs(x) % 1 === 0.5 ? r % 2 === 0 ? r : r - 1 : r; return br / Math.pow(10, d); }; exports.mnl = function (job) { var nrt = job.shipNrt; var c = 0; if (nrt <= 5000) c = nrt * 0.405;else if (nrt > 5000 && nrt <= 20000) c = 2025 + (nrt - 5000) * 0.291;else if (nrt > 20000 && nrt <= 50000) c = 6390 + (nrt - 20000) * 0.24;else c = 13590 + (nrt - 50000) * 0.18; return c; }; exports.psl = function (job) { var nrt = job.shipNrt; return nrt * 0.1125; }; exports.mnlPsl = function (job) { var c = SHARED.mnl(job); var p = SHARED.psl(job); return SHARED.round(c) + SHARED.round(p); }; exports.calculateMnl = function (job) { // Permit is valid on arrival. Return 0, end of story! if (job.originalMnlPsl === 'VALID') { return { cost: 0, description1: 'Marine Navigation/Protection of the Sea Levies - Valid' }; } var t = SHARED.round(SHARED.mnlPsl(job)); return { cost: t, description1: 'Marine Navigation/Protection of the Sea Levies' }; }; exports.calculateGstAdminFee = function (job, what) { // If GST is included in the FDA, then no GST is claimed back, // so no GST admin fee is charged if (job.gst) { return { cost: null, description1: 'GST Admin Fee not applicable to this job' }; } // NON-MRL SHIP: MAYBE IT WILL CHARGE GST ADMIN FEE if (job.nominatorContactId !== 363) { var exceptions = [83, // LOUIS DREYFUS 93, // OLDENDORFF 371, // PACIFIC BASIN 96, // PAN OCEAN 479, // CARGILL 91 // OCEAN 21 ]; if (exceptions.indexOf(job.principalContactId) !== -1 || exceptions.indexOf(job.principalContactIdParentContactId) !== -1) { return { cost: null, description1: 'GST Admin Fee not applicable to this principal' }; } else { return { cost: 180, description1: 'GST Admin Fee' }; } // IF IT'S MRL, THEN NO GST ADMIN FEE SINCE IT WILL BE IN RECOVERIES } else { return { cost: null, description1: 'GST Admin Fee charged through recoveries for MRL vessels' }; } }; exports.calculateTugs = function (job) { switch (Number(job.portId)) { // PORT HEDLAND case 9: // Case #1: very small ship if (job.shipLoa < 260 && job.shipDwt < 90000) { return { tugsIn: 2, tugsOut: 2, tugsReason: '2 Tugs In & 2 Tugs Out - Less than 260m LOA and/or less than 90,000 DWT' }; // Case #2: very big ship } else if (job.shipDwt > 150000) { return { tugsIn: 4, tugsOut: 4, tugsReason: '4 Tugs In & 4 Tugs Out - more than 150,000 DWT' }; // Case #3: average ship (will include 2 cases) } else { // Average ship case #3a: longer than 280M if (job.shipLoa > 280) { return { tugsIn: 4, tugsOut: 3, tugsReason: '4 Tugs In & 3 Tugs Out - LOA more than 280m and from 90,000 to 150,000 DWT' }; // Average ship case #3b: average, but shorter than 280M } else { return { tugsIn: 3, tugsOut: 3, tugsReason: '3 Tugs In & 3 Tugs Out - LOA more than 260M and less than 280M, or from 90,000 to 150,000 DWT' }; } } // ESPERANCE case 4: // if (SHARED.isNewcastlemax(job)) { // Changed on the 12/12/2022 as per new rules from port if (job.shipLoa >= 270) { return { tugsIn: 3, tugsOut: 3, // tugsReason: '3 Tugs in/out - LOA more than 295m or beam more than 45m (Newcastlemax)' // Changed on the 12/12/2022 as per new rules from port tugsReason: '3 Tugs in/out - LOA equal or greater than 270m' }; } else { return { tugsIn: 2, tugsOut: 2, tugsReason: '2 Tugs in/out' }; } // EVERY OTHER PORT default: return { tugsIn: 2, tugsOut: 2, tugsReason: '2 Tugs in/out' }; } }; exports.loadRate = function (job) { switch (job.portId) { case 9: // Port hedland if (SHARED.hasCargo(job, 7) || SHARED.hasCargo(job, 8)) return 5000; // Iron-ore or iron-ore fines else if (SHARED.hasCargo(job, 14)) return 300; // Spodumene is 300 break; case 3: // Dampier if (SHARED.hasCargo(job, 9)) return 5000; // LIQUEFIED NATURAL GAS break; case 6: // Geraldton if (SHARED.isGrain(job)) return 1000;else if (SHARED.hasCargo(job, 7) || SHARED.hasCargo(job, 8)) return 5000; // Iron ore or iron-ore fines break; case 7: // Kwinana if (SHARED.isGrain(job)) return 1000;else if (SHARED.hasCargo(job, 7) || SHARED.hasCargo(job, 8)) return 2600; // Iron ore or iron-ore fines else if (SHARED.hasCargo(job, 3)) return 1500; // Bauxite else if (SHARED.hasCargo(job, 14)) return 875; // Spodumene break; case 2: // Bunbury (assumed) if (SHARED.hasCargo(job, 20)) return 2250; // Alumina else if (SHARED.hasCargo(job, 3)) return 2000; // Bauxite break; case 1: // Albany if (SHARED.isGrain(job)) return 1000; break; case 4: // Esperance if (SHARED.isGrain(job)) return 1000;else if (SHARED.hasCargo(job, 7) || SHARED.hasCargo(job, 8)) return 2400; // Iron ore or iron-ore fines else if (SHARED.hasCargo(job, 14)) return 2500; // Spodumene // FIXME: THIS IS TO BE SET break; case 115: // Adelaide case 118: // Port Lincoln case 142: // Thevenard case 128: // Port GIles if (SHARED.isGrain(job)) return 400; break; } // Default return 500; }; exports.loadHoursPerDay = function (job) { if (job.portId === 4 && SHARED.isGrain(job)) return 16; // ESPERANCE return 24; }; exports.rightTonnage = function (job, what) { // PDA: take EITHER the tonnageToBeLoaded, OR // the contract tonnage multiplied by the multiplier var tonnage; if (what === 'pda') { if (job.tonnageToBeLoaded) tonnage = job.tonnageToBeLoaded;else { var tolerance = SHARED.multiplier(SHARED.extractTolerance(job.contractTonnageToleranceOverall)); tonnage = job.contractTonnageOverall * tolerance; } // CLOSE ESTIMATE: use the tonnage loaded } else if (what === 'close') { tonnage = job.tonnageLoaded; } return tonnage; }; exports.estimateLoadInfo = function (job) { var tonnageToBeLoaded = (job.tonnageToBeLoaded1 || 0) + (job.tonnageToBeLoaded2 || 0) + (job.tonnageToBeLoaded3 || 0) + (job.tonnageToBeLoaded4 || 0) + (job.tonnageToBeLoaded5 || 0) + (job.tonnageToBeLoaded6 || 0); var tonnage = tonnageToBeLoaded || job.contractTonnageOverall || 0; var loadRate = SHARED.loadRate(job); var loadHoursPerDay = SHARED.loadHoursPerDay(job); // That 24/loadHoursPerDay is 1.5 for Esperance, which will INCREASE the number of hours var estimatedHoursAtBerth = Math.ceil(tonnage / loadRate) * (24 / loadHoursPerDay); // EXCEPTIONS // PORT HEDLAND... if (job.portId === 9) { // MRL LOADING SPODUMENE: 110 if (SHARED.hasCargo(job, 14) && job.shipperContactId == 363) estimatedHoursAtBerth = 110;else { // Hours at berth are always 40 // **NOTE: MARAN DRY MANAGEMENT GETS 32 HOURS if (job.principalContactId === 521) estimatedHoursAtBerth = 32;else estimatedHoursAtBerth = 40; } } // EXCEPTION for Esperance, returning specific values depending on the ship's size if (job.portId === 4 && !SHARED.isGrain(job)) { if (job.shipDwt < 90000) estimatedHoursAtBerth = 48;else if (job.shipDwt < 140000) estimatedHoursAtBerth = 72;else estimatedHoursAtBerth = 96; } return { estimatedHoursAtBerth: estimatedHoursAtBerth, loadRate: loadRate, loadHoursPerDay: loadHoursPerDay }; }; exports.calculateHoursAtBerth = function (job) { // Calculate the hours var timeSailedSeconds = job.timeSailed / 1000; var timeFirstLineSeconds = job.timeFirstLine / 1000; if (!timeSailedSeconds || !timeFirstLineSeconds) return 0; return ((timeSailedSeconds || 0) - (timeFirstLineSeconds || 0)) / 60 / 60; }; exports.extractTolerance = function (s) { if (typeof s !== 'string') return 0; var m = s.match(/(\d+)%/); return Number(m && m[1] || 0); }; exports.contractTonnageAsRange = function (tonnage, toleranceAsString) { var tolerancePercentage = SHARED.extractTolerance(toleranceAsString); if (!tolerancePercentage) return ''; var toleranceAsAmount = Math.floor(tonnage * tolerancePercentage / 100); return "".concat(tonnage - toleranceAsAmount, "-").concat(tonnage + toleranceAsAmount); }; exports.multiplier = function (percentage) { if (Number(percentage) === 0) return 1;else return percentage / 100 + 1; }; exports.isGrainSeason = function (job) { if (!SHARED.isGrain(job)) return false; var date = new Date(job.eta); var month; if (date) month = date.getMonth(); var seasonMonths = [10, 11, 0, 1, 2, 3, 4, 5]; return seasonMonths.indexOf(month) !== -1; }; exports.arrivalDraft = function (job) { return Math.max(job.arrivalDraftFwd || 0, job.arrivalDraftMid || 0, job.arrivalDraftAft || 0); }; exports.departureDraft = function (job) { return Math.max(job.departureDraftFwd || 0, job.departureDraftMid || 0, job.departureDraftAft || 0); }; exports.isFieldMandatoryOwnersJobs = function (fieldName) { switch (fieldName) { case 'timeArrived': case 'timeAllFast': case 'timeSailed': return true; default: return false; } }; exports.resolveTimeFieldLabel = function (job, fieldName, labelValue) { if (labelValue === undefined) return false; switch (fieldName) { // Only show if it's a grain cargo case 'timeCommQuarantineSurvey': case 'timePassedQuarantineSurvey': case 'timeCommMarineSurvey': case 'timePassedMarineSurvey': if (SHARED.isGrain(job)) return labelValue; break; case 'timeRequestedPobForSailing': if (job.shipperContactId === 369) return labelValue;else return false; break; // Optional Custom label defined by the port case 'timePilotBookedLabel': case 'timePilotRequestedLabel': // Optional Custom label defined by the port case 'timePilotBooked': case 'timePilotRequested': if (job.shipperContactId === 369) return labelValue;else return false; break; case 'timePilotBoardedLabel': if (job.shipperContactId === 369) return job[fieldName + 'Label'] || labelValue;else return false; break; // DO NOT show if it's a grain cargo or tanker ship case 'timeCommHoldsSurvey': case 'timePassedHoldsSurvey': case 'timeDraftSurveyCompleted': if (SHARED.isGrain(job) || SHARED.isTanker(job)) return false; return labelValue; // Only for Tankers case 'timeLoadingArmsConnected': case 'timeLoadingArmsDisconnected': if (SHARED.isTanker(job)) return labelValue; break; // The default case, which just returns the `labelValue`, will behave as // need for these two special cases, because they get their `labelValue` // from DB fields, which might be empty, resulting in hidden fields. case 'timePilotBookedInitial': case 'timePilotRequestedInitial': case 'timePilotBoardedInitial': if (job.shipperContactId === 369) return labelValue;else return false; case 'timeLeftAnchorage': if (job.portId === 7) return 'Left Anchorage To Berth';else return labelValue; default: return labelValue; } return false; }; exports.formatTime = function (d, format) { if (!d) return 'N/A'; d = Number(d); // return moment.tz(d, 'Australia/Perth').format(format) return DateTime.fromMillis(d, { zone: "Australia/Perth" }).toFormat(format); }; exports.portAuthorityEmailForLoadSequence = function (job) { switch (job.portId) { case 9: return 'manifests@pilbaraports.com.au'; case 131: return ''; default: return job.portIdPortAuthorityEmail; } }; /* exports.date = (d) => { return SHARED.formatTime(d, 'DD/MM/YYYY HH:mm') } exports.dateSHORT = (d) => { return SHARED.formatTime(d, 'DD/MM/YYYY') } exports.dateINTERNATIONAL = (d) => { return SHARED.formatTime(d, 'DD-MMM-YYYY') } exports.dateINTERNATIONALND = (d) => { return SHARED.formatTime(d, 'DD MMM YYYY') } exports.dateFORMAL = (d) => { return SHARED.formatTime(d, 'HHmm \\h\\r\\s \\o\\n dddd DD MMMM YYYY') } exports.dateSTATDEC = (d) => { return SHARED.formatTime(d, 'dddd, DD MMMM YYYY') } exports.dateHHMM = (d) => { return SHARED.formatTime(d, 'HHmm') } exports.dateHH_MM = (d) => { return SHARED.formatTime(d, 'HH:mm') } exports.dateWEEKDAY = (d) => { return SHARED.formatTime(d, 'dddd') } exports.dateDATE = (d) => { return SHARED.formatTime(d, 'DD MMMM YYYY') } */ // LUXON COMPATIBLE STRINGS exports.date = function (d) { return SHARED.formatTime(d, 'dd/MM/yyyy HH:mm'); }; exports.dateSHORT = function (d) { var isEstimate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (isEstimate) return 'N/A'; return SHARED.formatTime(d, 'dd/MM/yyyy'); }; exports.dateFULL = function (d) { var isEstimate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (isEstimate) return 'N/A'; return SHARED.formatTime(d, "dd/MM/yyyy HH:mm"); }; exports.dateINTERNATIONAL = function (d) { var isEstimate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (isEstimate) return 'N/A'; return SHARED.formatTime(d, 'dd-MMM-yyyy'); }; exports.dateINTERNATIONALND = function (d) { var isEstimate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (isEstimate) return 'N/A'; return SHARED.formatTime(d, 'dd MMM yyyy'); }; exports.dateFORMAL = function (d) { var isEstimate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (isEstimate) return 'N/A'; return SHARED.formatTime(d, "HHmm'hrs on' cccc dd MMMM yyyy"); }; exports.dateSTATDEC = function (d) { var isEstimate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (isEstimate) return 'N/A'; return SHARED.formatTime(d, 'cccc, dd MMMM yyyy'); }; exports.dateHHMM = function (d) { var isEstimate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (isEstimate) return 'N/A'; return SHARED.formatTime(d, 'HHmm'); }; exports.dateHH_MM = function (d) { var isEstimate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (isEstimate) return 'N/A'; return SHARED.formatTime(d, 'HH:mm'); }; exports.dateWEEKDAY = function (d) { var isEstimate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (isEstimate) return 'N/A'; return SHARED.formatTime(d, 'cccc'); }; exports.dateDATE = function (d) { var isEstimate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (isEstimate) return 'N/A'; return SHARED.formatTime(d, 'dd MMMM yyyy'); }; exports.datePRETTY = function (d) { var isEstimate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; // TODO: WTF IS THIS?!? if (!d) return 'N/A'; var _d$split = d.split('/'), _d$split2 = _slicedToArray(_d$split, 3), day = _d$split2[0], month = _d$split2[1], year = _d$split2[2]; year = Number(year) >= 1900 ? year : Number(year) + 2000; // const dateStr = `${year}-${month}-${day}` return DateTime.fromISO("".concat(year, "-").concat(month, "-").concat(day, "T00:00:00+00:00")).toFormat('dd MMM yyyy (ccc)'); // return moment.tz(dateStr, 'Australia/Perth').format('DD MMM YYYY (ddd)') || 'N/A' }; // Concatenate an address from a person's object // (Allowing for a prefix) exports.concatAddress = function (o, prefix) { prefix = prefix || ''; // Utility functions var capitaliseFirstLetter = function capitaliseFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1); }; var mc = function mc(s) { return prefix ? capitaliseFirstLetter(s) : s; }; var ent = function ent(s) { return s ? "".concat(s, "\n") : s; }; // Note: MC stands for "maybe capitalise". If there is // a prefix, then 'address' will become 'prefixAddress' var name = ''; name = o[prefix + mc('billingName')] || o[prefix + mc('name')] || ''; if (name === 'PRINCIPAL NOT APPLICABLE') name = ''; var address = o[prefix + mc('address')] || ''; var state = o[prefix + mc('state')] || ''; var country = o[prefix + mc('countryIdName')] || ''; var postcode = o[prefix + mc('postcode')] || ''; if (country === state) country = ''; if (state) { state = "".concat(state, " ").concat(postcode); } else { address = "".concat(address, " ").concat(postcode); } return ent(name.trim()) + ent(address.trim()) + ent(state.trim()) + country; }; exports.concatStrings = function () { var reducer = function reducer(res, v) { if (v && v !== res[res.length - 1]) res.push(v); return res; }; for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return args.reduce(reducer, []).join(', '); }; exports.money = function (m) { m = Number(m); return m.toLocaleString ? m.toLocaleString('en-AU', { style: 'currency', currency: 'AUD', maximumFractionDigits: 2 }) : '[[MONEY INVALID]]'; }; exports.addCommas = function (s) { if (!s) return 0; return String(s).replace(/\B(?=(\d{3})+(?!\d))/g, ','); }; exports.wholeDays = function (hours) { var days = hours / 24; if (days % 1 !== 0) days = Math.floor(days) + 1; return days; }; exports.cargoString = function (job) { var field = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'contractTonnage'; var longDescriptions = arguments.length > 2 ? arguments[2] : undefined; var multiline = arguments.length > 3 ? arguments[3] : undefined; var bullets = arguments.length > 4 ? arguments[4] : undefined; var stowFactor = arguments.length > 5 ? arguments[5] : undefined; var cargoStrings = []; var prevName = ''; var prevTolerance = ''; var atLeastOneTonnage = false; var separator; if (multiline) separator = '\n';else separator = ', '; var howManyCargos = 0; for (var _i4 = 0, _arr3 = [1, 2, 3, 4, 5]; _i4 < _arr3.length; _i4++) { var k = _arr3[_i4]; if (job["cargoId".concat(k)]) { howManyCargos++; var name = void 0; if (!longDescriptions) name = job["cargoId".concat(k, "Name")];else name = job["cargoDescription".concat(k)] || job["cargoId".concat(k, "Name")]; // Set the tonnage. var tonnage = job["".concat(field).concat(k)]; // DELETED: If `contractTonnage` and there is no tonnage AND there is only 1 cargo, // revert to the overall one // if (!tonnage && field === 'contractTonnage' && !job.cargoId2) tonnage = job.contractTonnageOverall // This will be used right at the end to see if tonnages should be // shown or not if (tonnage) atLeastOneTonnage = true; var tolerance = ''; if (field === 'contractTonnage') { tolerance = job["contractTonnageToleranceAsText".concat(k)] || job["contractTonnageTolerance".concat(k)] || job.contractTonnageToleranceOverall; } // If everything is the same as the previous one, don't add an entry, // but simply add tonnage (if any) to the previous one if (name === prevName && tolerance === prevTolerance && cargoStrings.length) { var tmp = cargoStrings[cargoStrings.length - 1]; tmp.tonnage = Number(tmp.tonnage) + Number(tonnage); } else { cargoStrings.push({ name: name, tonnage: tonnage, tolerance: tolerance }); } // Set the "prev" fields for the next iteraction prevName = name; prevTolerance = tolerance; } } var stowFactorString = stowFactor ? ' S/F X.XX to Y.YY M3/MT (without guarantee)' : ''; // At least one tonnage is set: print it out if (atLeastOneTonnage) { return cargoStrings.reduce(function (res, v) { res.push("".concat(bullets ? '\t* ' : '').concat(v.name, ": ").concat(v.tonnage ? SHARED.addCommas(v.tonnage) + 'MT' : '').concat(v.tolerance ? ' ' + v.tolerance : '').concat(stowFactorString)); return res; }, []).join(separator); // NO tonnage set: } else { var r = cargoStrings.reduce(function (res, v) { res.push("".concat(bullets ? '\t* ' : '').concat(v.name)); return res; }, []).join(separator); // If no tonnage was set, and `contractTonnage`, then add (if available) // the overall tonnage and tolerance values if (field === 'contractTonnage' && job.contractTonnageOverall) { var extraNewline = ''; if (multiline) extraNewline = '\n\t'; // Fixed. Check #1336 to see if it works fine if you change this if (howManyCargos > 1) r += "".concat(extraNewline, " Overall Tolerance: "); r += " ".concat(SHARED.addCommas(job.contractTonnageOverall), "MT"); if (job.contractTonnageToleranceOverall) r += ' ' + job.contractTonnageToleranceOverall; r += stowFactorString; } return r; } }; exports.cargoStringShort = function (job) { var a = []; var prevId = ''; for (var _i5 = 0, _arr4 = [1, 2, 3, 4, 5]; _i5 < _arr4.length; _i5++) { var k = _arr4[_i5]; var name = job["cargoId".concat(k)]; if (name && name !== prevId) { a.push(job["cargoId".concat(k, "Name")]); } prevId = name; } // https://stackoverflow.com/questions/53879088/split-an-array-with-commas-except-the-last-item-with-and-e-g-one-two return a.reduce(function (res, v, i) { return i === a.length - 2 ? res + v + ' and ' : res + v + (i === a.length - 1 ? '' : ', '); }, ''); }; exports.cargoStringManifest = function (job) { var cargoStrings = []; for (var _i6 = 0, _arr5 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; _i6 < _arr5.length; _i6++) { var k = _arr5[_i6]; var name = job["manifestCargoDescription".concat(k)]; if (name) name = name + ' (' + job["manifestTonnageLoaded".concat(k)] + ' ' + job.manifestWeightMeasure + ')'; if (name) cargoStrings.push(name); } return cargoStrings.join(', '); }; exports.cargoStringManifestShort = function (job) { var cargoStrings = []; var prevId = ''; for (var _i7 = 0, _arr6 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; _i7 < _arr6.length; _i7++) { var k = _arr6[_i7]; var name = job["manifestCargoDescription".concat(k)]; if (name && name !== prevId) { if (name) cargoStrings.push(name); } prevId = name; } return cargoStrings.join(', '); }; // DELETE AFTER DEPLOYMENT, NOW IN MAIN SOURCE exports.hasCargo = function (job, cargoId) { if (!job) return false; return job.cargoId1 === cargoId || job.cargoId2 === cargoId || job.cargoId3 === cargoId || job.cargoId4 === cargoId || job.cargoId5 === cargoId; }; exports.isGrainCargoIds = function () { return [2, 4, 10, 11, 13, 15]; }; exports.isGrain = function (job) { if (!job) return false; var grainCargoIds = SHARED.isGrainCargoIds(); var a = ['cargoId1', 'cargoId2', 'cargoId3', 'cargoId4', 'cargoId5']; for (var i = 0; i < a.length; i++) { var key = a[i]; var cid = Number(job[key]); if (grainCargoIds.indexOf(cid) !== -1) return true; } return false; }; exports.isTanker = function (job) { if (!job) return false; if (job.addressAs && job.addressAs.startsWith('MT')) return true; var a = ['cargoId1', 'cargoId2', 'cargoId3', 'cargoId4', 'cargoId5']; for (var i = 0; i < a.length; i++) { var key = a[i]; var cid = Number(job[key]); if (cid === 5 || cid === 6 || cid === 9 || cid === 28 || cid === 30 || cid === 33) return true; } return false; }; exports.draftMax = function (job) { if (job.typeOfOperation === 'DISCHARGE') { return Math.max(job.arrivalDraftFwd || 0, job.arrivalDraftMid || 0, job.arrivalDraftAft || 0); } else { return Math.max(job.departureDraftFwd || 0, job.departureDraftMid || 0, job.departureDraftAft || 0); } }; exports.authorityToLoadWarning = function (job) { if (job.portId === 4 && job.shipperContactId === 363 && job.timeAllFast) return true; return false; }; exports.checklistDone = function (job, id) { if (!job.checklists) return false; for (var i = 0, l = job.checklists.length; i < l; i++) { var item = job.checklists[i]; if (item.checklistTemplateId === id && item.isDone) return true; } return false; }; exports.findInvoice = function (invoices, documentTemplateId) { return invoices.find(function (invoice) { return Number(invoice.documentTemplateId) === Number(documentTemplateId); }) || { transactions: [] }; }; exports.calculateInvoice = function (job, what, params) { var _funded = 0; var _prefunded = 0; var _subtotal = 0; var _taxtotal = 0; var _total = 0; var _balance = 0; var cost; if (!params || !params.invoiceGroupInfo) { params = params || {}; params.invoiceGroupInfo = { id: job.mainInvoiceGroupId, name: 'Main DA', principalContactId: job.principalContactId, principalContactIdName: job.principalContactIdName, gst: job.gst, main: 1, address: job.principalAddress }; } var paramsGroupId = params.invoiceGroupInfo.id; var paramsGst = params.invoiceGroupInfo.gst; var _iterator8 = _createForOfIteratorHelper(job.invoices), _step8; try { for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) { var invoice = _step8.value; // Do not even consider the invoices that are not part of this group var invoiceGroupId = invoice.invoiceGroupId || 0; if (Number(invoiceGroupId) !== Number(paramsGroupId)) continue; var _iterator9 = _createForOfIteratorHelper(invoice.transactions || []), _step9; try { for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) { var transaction = _step9.value; // // Not a transfer: this is part of the quote. Take the right column, // depending on "what", and add it up if (!invoice.isTransfer) { cost = transaction["cost".concat(what)]; var gst = transaction["gst".concat(what)]; if (cost) _subtotal += cost; if (gst && (paramsGst || what !== 'Fda')) _taxtotal += gst; // Calculate _balance and _total _balance += cost; if (what !== 'Fda' || paramsGst) _balance += gst; // GST in balance only for PDA and close estimate _total += cost; if (what !== 'Fda') _total += gst; // GST in total only for PDA and close estimate // It's a transfer: it will always be taken from the Fda column, // depending on whether it's prefunding } else { cost = transaction.costFda; if (cost && !invoice.isPrefunding) _funded += cost; if (cost && invoice.isPrefunding) _prefunded += cost; _balance += cost; // Always updated } } } catch (err) { _iterator9.e(err); } finally { _iterator9.f(); } } } catch (err) { _iterator8.e(err); } finally { _iterator8.f(); } _balance = parseFloat(_balance.toFixed(2)); _funded = parseFloat(_funded.toFixed(2)); _prefunded = parseFloat(_prefunded.toFixed(2)); _subtotal = parseFloat(_subtotal.toFixed(2)); _total = parseFloat(_total.toFixed(2)); _balance = parseFloat(_balance.toFixed(2)); return { _funded: _funded, _prefunded: _prefunded, _subtotal: _subtotal, _taxtotal: _taxtotal, _total: _total, _balance: _balance }; }; exports.paymentInfo = function () { return "
Banking details:
\n National Australia Bank, 100 St Georges Terrace, Perth WA 6000
\n Account Name: Ship Agency Services Pty Ltd
\n BSB: 086 905
\n AUD Account: 188805117
\n Swift #: NATAAU3303M
"; }; exports.isSupplementaryDa = function (name) { return name.startsWith('Supplementary FDA'); }; exports.canBeParentInvoiceGroup = function (name) { return name.toLowerCase().startsWith('main') || !name.toLowerCase().startsWith('m') && !SHARED.isSupplementaryDa(name); }; exports.sofDate = function () { var s = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; // Inspired by https://stackoverflow.com/a/5812341/829771 with added rebustness checks. // I could have used moment(), but I would much rather use plain vanilla Javascript; this // solution is very simple, elegant, and it's a 3-liner. var format = 'Format: DD/MM/YY'; if (s === null) return; //if (s.trim().includes(' ')) return format if (!s.split) return format + ' 1 ' + JSON.stringify(s); var bits = s.split('/'); if (bits.length !== 3) return format + '2'; var d = new Date(bits[2], bits[1] - 1, bits[0]); if (bits[2].length !== 2) return format + '3'; if (d && d.getMonth() + 1 === Number(bits[1])) return; return format + '4'; }; exports.sofTime = function () { var s = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; function validHour(s) { if (s === null || s === '') return true; if (!s.split) return false; if (s.length !== 4) return false; var h = parseInt(s.substr(0, 2)); var m = parseInt(s.substr(2, 2)); if (h === 24 && m !== 0) return false; // 24:00 is OK, 24:01 isn't return h >= 0 && h <= 24 && m >= 0 && m < 60; } var format = 'Format: HHMM-HHMM'; // Attempt to zero all spaces if (typeof s === 'string') s = s.replace(/\s/g, ''); // s = s.replace(/\s/g, '') // The hour alone is fine if (validHour(s)) return; if (s === null) return; if (!s.split) return format; var bits = s.split('-'); if (bits.length !== 2 || bits[0].length !== 4 || bits[1].length !== 4) return format; if (validHour(bits[0]) && validHour(bits[1]) && parseInt(bits[0]) <= parseInt(bits[1])) return; return format; }; exports.formatPhoneNumber = function (s) { // Assume the string is complete, if not, do nothing if (s.startsWith('+') && s.length === 12) { s = s.trim().replace(/(\+\d{2})(\d{3})(\d{3})(\d{3})/, "$1 $2 $3 $4"); } return s; }; exports.formatLandLineNumber = function (s) { // Assume the string is complete, if not, do nothing if (s.startsWith('+') && s.length === 12) { s = s.trim().replace(/(\+\d{2})(\d{1})(\d{4})(\d{4})/, "$1 $2 $3 $4"); } return s; }; exports.parseRecipientsLabels = function (recipients, job, invoiceGroup, customRoleLabels, doNotResolveLabels) { if (doNotResolveLabels) return recipients; // The 'recipients' variable is a list of email addresses and labels. For example // it could be `berthAndStevedore; something@domain.com; someOtherLabel` // This function will split the various chunks; it will leave email addresses untouched; // however, it will if (!recipients) return ''; var recipientsArray = recipients.split(';').map(function (label) { label = label.trim(); var emails; if (label.includes('@')) return label; // Check if it's a custom label. If it is, it will get proprity: it will // be rendered in EJS and the result will be treated as a bunch of emails var labelTemplate = customRoleLabels.find(function (o) { return o.label === label; }); if (labelTemplate) { emails = ejs.render(labelTemplate.template, { job: job, invoiceGroup: invoiceGroup, f: SHARED, SHARED: SHARED }, { escape: function escape(v) { return v; } }); return emails.split(';').map(function (el) { return "".concat(el.trim()); }).join(';'); // Look for it in the job, looking up the right label } else { var roleForJob = SHARED.rolesFromJob(label); if (!roleForJob) return label; if (!job[roleForJob.path]) return label; return job[roleForJob.path].split(';').map(function (el) { return "<".concat(el, ">"); }).join(';'); } }); return recipientsArray.join(';'); }; exports.applyEmailTemplates = function (contents, emailTemplates) { return contents.replace(/^\{\{ *template *: *"(.*?)" *}}$/gm, function (match, templateName) { var tpl = emailTemplates.find(function (it) { return it.name === templateName; }); return tpl ? tpl.contents : "TEMPLATE NOT FOUND: ".concat(templateName); }); }; exports.applyEmailFragments = exports.applyEmailTemplates; exports.addEjsSpans = function (template) { var paramEscape = function paramEscape(param) { return param.replaceAll('<', '<').replaceAll('>', '>').replaceAll('&', '&').replaceAll('"', '"'); }; var value = template.replace(/<%=(.*?)%>/g, function (match, ejsString) { return "<%=").concat(ejsString, "%>"); }); return value; }; exports.updateBodyWithJobData = function (body, job, invoiceGroup) { var bodyWithRestoredEJS = body.replace(/< *span(.*?)ejs="(.*?)"(.*?)>.*?<\/span>/g, function (match, preAttr, ejsString, postAttr) { var paramUnescape = function paramUnescape(param) { return param.replaceAll('<', '<').replaceAll('>', '>').replaceAll('&', '&').replaceAll('"', '"'); }; return "<%=").concat(paramUnescape(ejsString), "%>"); }); return ejs.render(bodyWithRestoredEJS, { job: job, invoiceGroup: invoiceGroup, f: SHARED, SHARED: SHARED }); }; exports.deleteAllTags = function (body) { return body.replace(/< *span .*?>(.*?)<\/span>/g, function (match, tagContents) { return "".concat(tagContents); }); }; exports.deleteEJSTags = function (body) { return body.replace(/ejs=".*?"/g, ''); }; exports.renderEmail = function (body, from, to, cc, subject, user, info, invoiceGroup, customRoleLabels, emailTemplates, doNotResolveLabels) { // DEPLOYMENT info = Object.assign({}, info); invoiceGroup = invoiceGroup || {}; var makeSubject = function makeSubject(job, subject) { // Make up ref, taking into account P: or S: var ref; if (subject.startsWith('P:')) { ref = job.principalReference ? job.principalReference.toUpperCase() : ''; if (!ref) ref = job.principalVoyage ? job.principalVoyage.toUpperCase() : ''; // if (!ref) ref = job.shipperReference ? job.shipperReference.toUpperCase() : '' subject = subject.slice(2); // Clean up the subject } if (subject.startsWith('S:')) { ref = job.shipperReference ? job.shipperReference.toUpperCase() : ''; subject = subject.slice(2); // Clean up the subject // if (!ref) ref = job.principalReference ? job.principalReference.toUpperCase() : '' } // if (!ref) ref = job.principalVoyage ? job.principalVoyage.toUpperCase() : '' if (ref) ref = ' ' + ref + ' ';else ref = ' '; // Return the subject return job.shipName.toUpperCase() + ref + '// ' + job.portIdName + ' ' + subject; }; for (var k in info) if (info[k] === null) info[k] = ''; // const lines = contents.split(/\r?\n\r?/) || [] // const subject = lines.shift() var resolvedSubject = makeSubject(info, subject); resolvedSubject = ejs.render(resolvedSubject, { job: info, invoiceGroup: invoiceGroup, f: SHARED, SHARED: SHARED }); // const body = lines.join('\n').trim() var resolvedBody = SHARED.applyEmailTemplates(body, emailTemplates); // DEPLOYMENT resolvedBody = ejs.render(resolvedBody, { job: info, invoiceGroup: invoiceGroup, f: SHARED, SHARED: SHARED }).trim(); // for (var i in lines) lines[i] = lines[i].replace(/\r?\n|\r/, '') var resolvedFrom = from || "".concat(info.portIdMailFromName || "".concat(user.firstName, " ").concat(user.lastName), " <").concat(info.portIdMailFrom || user.email, ">"); var resolvedTo = SHARED.parseRecipientsLabels(to, info, invoiceGroup, customRoleLabels, doNotResolveLabels); var resolvedCc = SHARED.parseRecipientsLabels(cc, info, invoiceGroup, customRoleLabels, doNotResolveLabels) + ';' + (info.portIdMailFrom || 'fremantle@shipagency.com.au'); return { from: resolvedFrom, to: resolvedTo, cc: resolvedCc, subject: resolvedSubject, body: resolvedBody }; }; exports.rolesFromJob = function (key) { var jobContacts = { principal: { path: 'principalEmail', label: 'Principal' }, updatesPrincipal: { path: 'notifUpdatesPrincipalEmail', label: 'Principal (Updates)' }, updatesShipper: { path: 'notifUpdatesShipperEmail', label: 'Shipper (Updates)' }, updatesLocals: { path: 'notifUpdatesLocalsEmail', label: 'Locals (Updates) - Extra Addresses' }, updatesBuyer: { path: 'notifUpdatesBuyerEmail', label: 'Buyer (Updates)' }, notifShipper: { path: 'notifShipperEmail', label: 'Shipper' }, notifLoadSequence: { path: 'notifLoadSequenceEmail', label: 'Load Sequence' }, notifMarineSurveyor: { path: 'notifMarineSurveyorEmail', label: 'Marine Surveyor' }, notifAoSurveyor: { path: 'notifAoSurveyorEmail', label: 'AO Surveyor' }, notifStevedore: { path: 'notifStevedoreEmail', label: 'Stevedore' }, notifBoarder: { path: 'notifBoarderEmail', label: 'Boarder' }, notifAgency: { path: 'notifAgencyEmail', label: 'Agency' }, principalOperator: { path: 'principalOperatorContactIdEmailCc', label: 'Principal Operator' }, principal2: { path: 'principal2ContactIdEmailCc', label: 'Principal 2' }, nominator: { path: 'nominatorContactIdEmailCc', label: 'Nominator' }, shipperOperator: { path: 'shipperOperatorContactIdEmailCc', label: 'Shipper Operator' }, shipper2: { path: 'shipper2ContactIdEmailCc', label: 'Shipper 2' }, broker: { path: 'brokerContactIdEmailCc', label: 'Broker' }, brokerOperator: { path: 'brokerOperatorContactIdEmailCc', label: 'Broker Operator' }, headOwner: { path: 'headOwnerContactIdEmailCc', label: 'Head Owner' }, towage: { path: 'towageContactIdEmailCc', label: 'Towage' }, launch: { path: 'launchContactIdEmailCc', label: 'Launch' }, accountingManager: { path: 'accountingManagerContactIdEmailCc', label: 'Accounting Manager' }, fumigator: { path: 'fumigatorContactIdEmailCc', label: 'Fumigator' }, hatchSealer: { path: 'hatchSealerContactIdEmailCc', label: 'Hatch Sealer' } // shipper: { path: 'shipperContactIdEmailCc', label: 'Shipper' }, // stevedore: { path: 'stevedoreContactIdEmailCc', label: 'stevedore' }, // agency: { path: 'agencyContactIdEmailCc', label: 'agency' }, // marineSurveyor: { path: 'marineSurveyorContactIdEmailCc', label: 'marineSurveyor' }, // aoSurveyor: { path: 'aoSurveyorContactIdEmailCc', label: 'aoSurveyor' }, // boarder: { path: 'boarderContactIdEmailCc', label: 'boarder' } }; return key ? jobContacts[key] : jobContacts; }; exports.parseRoles = function (s) { // Parse and group email addresses from the same roles var parts = s.split(';').sort(function (a, b) { return a < b ? -1 : a === b ? 0 : 1; }); var currentRole = ''; var result = ''; for (var i = 0; i < parts.length; i++) { var item = parts[i]; var nameAndEmail = item.split('<'); if (nameAndEmail[0] === currentRole) { result += "".concat(result.length ? ',' : '', "<").concat(nameAndEmail[1]); } else { currentRole = nameAndEmail[0]; result += "".concat(result.length ? '; ' : '').concat(currentRole, " <").concat(nameAndEmail[1]); } } return result; }; exports.renderDocument = function (docs, contentsHeader, isLandscape, info, params) { info = Object.assign({}, info); if (!Array.isArray(docs)) docs = [docs]; // Make up the ONE doc's header var orientation = 'portrait'; if (Number(isLandscape)) orientation = 'landscape'; var docHeader = "\n\n\n \n \n Document writer\n \n\n\n
\n ".concat(contentsHeader, "\n
"); var docFfooter = "\n\n"; // Change NULL into emopty strings to make coding of templates easier for (var k in info) if (info[k] === null) info[k] = ''; // Go through each doc var fullBody = ''; var maybePagebreak = ''; var _iterator10 = _createForOfIteratorHelper(docs), _step10; try { for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) { var doc = _step10.value; var code = doc.code, css = doc.css, contents = doc.contents, documentTemplateId = doc.documentTemplateId, invoice = doc.invoice; // Empty documents are skipped if (doc.contents.trim() === '') continue; code = "<%\n Number.prototype.money = function(){\n return this.toLocaleString('en-AU', { minimumFractionDigits: 2, maximumFractionDigits: 2})\n // this.toLocaleString(undefined, { style: 'currency', currency: 'AUD', maximumFractionDigits: 2 })\n }\n ".concat(code, ";\n %>"); if (!params || !params.invoiceGroupInfo) { params = {}; var job = info; params.invoiceGroupInfo = { id: 0, name: 'Main FDA', principalContactId: job.principalContactId, principalContactIdName: job.principalContactIdName, gst: job.gst, main: 1, address: job.alternatePrincipalAddress }; } var body = ejs.render(code + '\n' + contents, { job: info, f: SHARED, documentTemplateId: documentTemplateId, invoice: invoice, params: params }, { context: {} }); body = body.replace(/^$/gm, '
'); // This is needed because, beginning in Chrome 91, we started seeing empty cell rendered with no height. // It is not clear wether the recent change to the line above revealed a quirk about table rendering or if the browser behavior changed // There are mentions of an update to Chrome's table rendering engine, which might be related to our issue. // As a workaround, the following line adds a break tag to any empty table cells in the template, while preserving it's attributes body = body.replace(/<\/td>/gm, '
'); css = "\n html {\n all: revert;\n }\n @page {\n size: A4 ".concat(orientation, ";\n margin-top: 0.0cm;\n margin-bottom: 0.0cm;\n margin-left: 0.5cm;\n margin-right: 0.5cm; }\n @media screen {\n #contents-body {\n margin-top: 0.0cm;\n margin-bottom: 0.0cm;\n margin-left: 0.5cm;\n margin-right: 0.5cm;\n }\n }\n html *\n {\n font-family: \"Century Gothic\", CenturyGothic, AppleGothic, sans-serif;\n letter-spacing: 0.0025em;\n }\n *[smaller] {\n font-size: smaller;\n }\n #error {\n color: red\n }\n .indent {\n margin-left: 2em\n }\n .centre {\n text-align: center;\n margin-left: auto;\n margin-right: auto;\n display: block;\n }\n /* ************************ */\n /* DEFAULT TABLES AND CELLS */\n /* ************************ */\n table, th, td {\n border-collapse: collapse;\n border: 1px solid grey;\n border-width: thin;\n }\n table {\n width: 100%;\n }\n .borderless table, .borderless th, .borderless td {\n border: 0;\n }\n td {\n text-align: left;\n vertical-align: top;\n padding: 1px 2px;\n }\n td[right] {\n text-align: right;\n }\n td[centre] {\n text-align: center;\n }\n td[left] {\n text-align: left;\n }\n [centre] {\n text-align: center;\n }\n /* ************************ */\n /* ALTERNATE TABLE STYLES */\n /* ************************ */\n table[no-border], th[no-border], tr[no-border], td[no-border] {\n border: 0;\n }\n table[no-border-top], th[no-border-top], tr[no-border-top], td[no-border-top] {\n border-top: 0;\n }\n table[no-border-right], th[no-border-right], tr[no-border-right], td[no-border-right] {\n border-right: 0;\n }\n table[no-border-bottom], th[no-border-bottom], tr[no-border-bottom], td[no-border-bottom] {\n border-bottom: 0;\n }\n table[no-border-left], th[no-border-left], tr[no-border-left], td[no-border-left] {\n border-left: 0;\n }\n table[bordered], th[bordered], tr[bordered], td[bordered] {\n border: 1px solid grey\n }\n /* ************************ */\n /* CSS FOR INVOICES */\n /* ************************ */\n td {\n position: relative;\n }\n td[m] {\n width: 8em;\n max-width: 8em;\n }\n td[m]::before {\n content: \"$\";\n position: absolute;\n left: 4px;\n }\n [fit] {\n width: 1%;\n white-space: nowrap;\n padding-left: 5px;\n padding-right: 5px;\n }\n /* ***************** */\n /* AUXILIARY STYLING */\n /* ***************** */\n [inline] {\n display: inline;\n }\n *[float-right] {\n float: right;\n }\n *[float-left] {\n float: left;\n }\n [x-width] {\n -webkit-flex-grow: 1;\n flex-grow: 1;\n }\n [x2-width] {\n -webkit-flex-grow: 2;\n flex-grow: 2;\n }\n [x3-width] {\n -webkit-flex-grow: 3;\n flex-grow: 3;\n } // Math.round(n * 0.1 * 100) / 100\n }\n [x4-width] {\n -webkit-flex-grow: 4;\n flex-grow: 4;\n }\n [x5-width] {\n -webkit-flex-grow: 5;\n flex-grow: 5;\n }\n [x6-width] {\n -webkit-flex-grow: 6;\n flex-grow: 6;\n }\n /* ***************** */\n /* SIGNATURES STYLING */\n /* ***************** */\n .signature-container {\n position: relative;\n height: 130px;\n \t display: flex;\n \t align-items: baseline;\n padding: 0 30px;\n margin-bottom: -10px;\n }\n .signature-container[hidden] {\n\t\tvisibility: hidden;\n\t }\n .signature, .stamp {\n position: absolute;\n\t cursor: grab;\n }\n .signature {\n max-width: 180px;\n \t margin-right: -30px;\n }\n .stamp {\n margin-left: 100px;\n max-height: 130px;\n }\n ").concat(css, "\n "); fullBody += "\n\t \n \n ").concat(maybePagebreak, "\n
\n ").concat(body, "\n
\n "); // Note: this is assigned AFTER the first document is rendered maybePagebreak = '
'; } } catch (err) { _iterator10.e(err); } finally { _iterator10.f(); } return "".concat(docHeader).concat(fullBody).concat(docFfooter); }; // Use regex to sanitize an email address, taking out characters not allowed at the beginning or end exports.sanitizeEmail = function (email) { var regex = /^[\s!#$%&'*+\-/=?^_`{|}~.]+|[\s!#$%&'*+\-/=?^_`{|}~.]+$/g; return email.replace(regex, ''); }; exports.findEmailsFdaInstructions = function (invoiceGroup) { // Set the correct regular expression var re; re = /(( ?; ?)?([a-zA-Z0-9._+-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+))+/ig; // Run it var res = (invoiceGroup.fdaInstructions || '').match(re); // Sanitise result and get everything ready to compare. // Principal emails will not be added if already present. if (!res || !res.length) res = []; // Prepare the data as arrays res = res.join(';').split(';').map(function (pe) { return pe.trim(); }).filter(function (el) { return el; }); var baseEmail = invoiceGroup.fdaEmail || invoiceGroup.principalEmail || ''; var pe = baseEmail.split(';').map(function (pe) { return pe.trim(); }).filter(function (pe) { return pe; }); // Filter the "bad" ones out res = res.filter(function (em) { return !pe.find(function (pe) { return pe === em; }); }).map(function (pe) { return SHARED.sanitizeEmail(pe.trim()); }); // Return the list of email addresses separated by ";" return res.join(';'); }; exports.mrlSubjectExtras = function (job) { function t(id, s) { if (id == 7) return 'LUMP'; if (id == 8) return 'FINES'; if (id == 14) return 'LITHIUM ORE'; return s; } var mrlExtras = ''; if (job.shipperContactId == 363 && job.portId === 9) { // IRON ORE -> LUMP, IRON ORE FINES -> FINES, SPODUMENE -> LITHIUM ORE mrlExtras = t(job.cargoId1, job.cargoId1Name) + ' '; if (job.cargoId2) mrlExtras = mrlExtras + 'AND ' + t(job.cargoId2, job.cargoId2Name) + ' '; if (job.cargoId3) mrlExtras = mrlExtras + 'AND ' + t(job.cargoId3, job.cargoId3Name) + ' '; if (job.cargoId4) mrlExtras = mrlExtras + 'AND ' + t(job.cargoId4, job.cargoId4Name) + ' '; if (job.cargoId5) mrlExtras = mrlExtras + 'AND ' + t(job.cargoId5, job.cargoId5Name) + ' '; mrlExtras = "(".concat(mrlExtras.trim(), ") "); } return mrlExtras; }; exports.makeSubjectForLoading = function (job, subject, recipientType) { var ref = recipientType.toLowerCase() === 'shipper' ? job.shipperReference : ''; if (!ref) ref = job.principalReference; if (!ref) ref = job.principalVoyage; if (ref) ref = ' ' + ref + ' ';else ref = ' '; return job.shipName.toUpperCase() + ref + '// ' + job.portIdName + ' ' + SHARED.mrlSubjectExtras(job) + subject; }; exports.workoutJobStatus = function () { var j = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var status; if (j.timeSailed) status = 'sailed';else if (j.timeAllFast && !j.timeAllFastIsEstimate) status = 'alongside';else if (j.timeArrived) status = 'anchored';else status = 'enroute'; return status; }; exports.gstOnTransaction = function (invoiceGroup, invoice, transaction) { // // Initial filter: if the transaction is GST exempt or it's a transafer, // then definitely not GST if (transaction.isGstExempt) return false; // Transaction is GST exempt if (invoice.isTransfer) return false; // Transaction is GST exempt // Actual check: if the invoiceGroup has GST, *or* if it's an external invoice // then DO charge GST if (invoiceGroup.gst || !invoice.documentTemplateId) return true; // Otherwise, no GST return false; }; exports.gstCalculator = function (n) { // return roundDown(n * 0.1, 2) // return SHARED.roundDown(n, 2) * 0.1 // Calculation AFTER rouding if (n === null) return null; return SHARED.round(n * 0.1, 2); // Using new round() function }; exports.roundUp = function (x, nd) { var rup = Math.pow(10, nd); var rdwn = Math.pow(10, -nd); return Number((Math.round(x * rup) * rdwn).toFixed(nd)); }; exports.roundDown = function (x, nd) { var rup = Math.pow(10, nd); var rdwn = Math.pow(10, -nd); return Number((Math.round(x * -rup) * -rdwn).toFixed(nd)); }; exports.intendedLoadQuantity = function (job) { // If there is tonnage to be loaded, return '' // If there is tonnage to be loaded // -- add the sentence // -- IF any one of the departure drafts are set, add "on ..." onwards // SENTENCE: ' – intended load quantity : about XX,XXXMT on XX.XXm draft' if (!job.tonnageToBeLoaded) return ''; var firstBit = " \u2013 intended ".concat(job.typeOfOperation === "LOAD" ? "load" : "discharge", " quantity: about ").concat(SHARED.addCommas(job.tonnageToBeLoaded), "MT"); var draftMax = SHARED.draftMax(job); var secondBit = ''; if (draftMax) { secondBit = " on ".concat(SHARED.draft(draftMax), "M draft"); } return "".concat(firstBit).concat(secondBit); }; exports.logo = "data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAMoAAACaCAMAAADfNSdtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ\nbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdp\nbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6\neD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEz\nNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJo\ndHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlw\ndGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAv\nIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RS\nZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpD\ncmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlE\nPSJ4bXAuaWlkOkZBQkYxNDhENkYwMDExRTI4NUU1QzgzQkIxNjdBMzJEIiB4bXBNTTpEb2N1bWVu\ndElEPSJ4bXAuZGlkOkZBQkYxNDhFNkYwMDExRTI4NUU1QzgzQkIxNjdBMzJEIj4gPHhtcE1NOkRl\ncml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6RkFCRjE0OEI2RjAwMTFFMjg1RTVD\nODNCQjE2N0EzMkQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6RkFCRjE0OEM2RjAwMTFFMjg1\nRTVDODNCQjE2N0EzMkQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1l\ndGE+IDw/eHBhY2tldCBlbmQ9InIiPz7iynEqAAADAFBMVEVkYWKwr6+mpaWenJwxLS6SkJH94+Hr\n6+vvPjT/+vqGhIWko6PR0NGKiIiQjo5raGlYVVaUkpP0enT+9/aEgYLe3t63tbZycHCcmprOzc2O\njIzp6OiIhoZ5d3fwRj319PX81NJ8enrxUko1MTLc3NzvNSxhXl/k5OSBf39VUlN6eHhsamr4qqaa\nmJnW1tb3lpDwTUTDwsP6wb73m5X2kYujoaH5ubWWlJSqqKiCgID6xsTyZV3yWlLyYFn2jolfXFzz\naGEmIiP1hYDzcWr+9PT+7+/6vbrxVk31iILyXlfvOTBBPj/7+/tmZGQlISJyb3DW1dX9/f1/fX7t\n7e1JRkZSTk/y8vLx8fGXlpa9vL3n5uf+7u03MzR1cnPzbWZMSEkzMDFua2w4NDUsKCk5NjdoZWZw\nbW7yY1xRTk7+8/J3dHUoJCUqJicnIyQpJSY7ODh+fH0+Ojt4dXZ0cnLj4+NGQkP8+/w/OzwkICH6\n+vpHREVLR0hjYGH8/PxaV1hAPT49OTpnZGVEQEFWU1Tv7+9TUFFKR0cvKyw5NTZCPz9cWVpDQED2\n9vYsKSpxbm9pZmc6NzhPTE3Qz88rJyjw8PDU09NdWlszLzDY19i6ubmMiov39/cuKiv29fU/PD1b\nWFnT0tKpp6hQTU2LiYpvbG3Ix8dIRUbv7u5eW1vu7e6xsLDc29tiX2BFQUItKiqgnp/5+fnb2tqv\nra6Ni4zs7OxNSkvh4ODo5+dOS0yhn6BXVFTm5ua5uLi0s7PEw8RqZ2hST1C9u7zCwcH96OfZ2Nna\n2dl+e3zFxMTHxsbg3+BMSUqrqqrz8/OurK2ysbE8ODmopqesq6uRj4/V1NS7urrBwMDj4uL4+PjL\nysuZl5iYlpe4t7dGQ0T++PfS0dLGxcW/vr7JyMjKycnKycr+8vF2c3Ti4eGFg4T//fzAv7/+/v7z\na2S8uru+vb21s7TuMSf7y8itrKyzsrL5s6/1fnj4r6vMy8vi4eL3oZz3n5v819X4pKDuLiQjHyD/\n///////iCYljAAABAHRSTlP/////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////////////////\n//////////////////////////////////////////////////////////8AU/cHJQAAEG5JREFU\neNrs3Hd8FFUeAPBHdhENSTbBQGgBcyuBAIqoKJ4ll7OcZ8IGQ1mi2UuyG4pJNmXTGwmEEiB0CKB0\nkHZ0QUW6IF1Q7OXsnv30zuuXH+/em7I7dXdz7syG+/j+kJk3O+U78+pvJiKsQ2re0kGHsyAdzjE2\nASwd/i8oY8cA6GFB+kj0sCCdJDpYkF4S7S1IN4nmFqSfRGsL0lGisQXpKdHWgnSVaGpB+kq0tCCd\nJRpakN4S7SxId4lmFqS/RCsLCoJEIwsKhkQbCwqKRBMLCo5ECwsKkkQDCwqWJPAWFDRJwC1IF4lR\nDwvSQzJi7AgdLEgPScqVFB0sSBfJFT0sSB+JHhakk0QHC9JLor0F6SbxYhnejih+SdQt0LndUPyU\naGwJBGXUQj8l2loCQBk12m+Jphakr0RLC9JZoqEF6S3RzoJ0l2hmQXpIrrv9CR0sSA/J71rv0cGC\ndJG06mFB+kj0sCCdJDpYkF4S7S1IN4nmFqSfRGsL0lGisQXpKdHWgnSVaGpB+kq0tCCdJRpakN4S\n7SxId4lmFhQYyaA2SLSyoIBIUh9ti6S19RePa2BBwZDILYMCYEFBkcgsj6b+dAsKjkQLCwqSRAML\nCpYk8BYUNIn/FkNAKVpI/LeMCyBFG0mALSiYksBa/KE8p5kkoBY/KI8laycJpAUFWRJACwqQ5Ppv\nWluDbEEBkkxsbQ22BQVfEigLageSAFlQe5AExoLahSQgFqSlpN8tv72/y8NDhtz2zyFD7vt1l1/9\n4lotLajNki++9kPS55vbf7zhQbJZdEjS+1zzw+CH7+7jh+XrYXPbaEFtk9ij/3HFh+SRX932wwM+\nmvgHb719Yj8flitV3Y4WtMWC2iBxzHux7opXyff3/uvfX/o5A/ryhtvu/d6r5Qoe++qaSr8tyG/J\nmFf60iKiLnmkyx2ftTFA+NlDXR7xasG4tGO9nxbkn2TmgOVsYVeTfH//HX/4n17VXHerQKNkwfj0\nIKc/FuSPpCZkFfYq+c3gJ37CS/Prf7zXuwXvG1fo24J8S9DH7gZIUdLvrzf85M+XrrnvEa8WXH54\n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\nrkJggg=="; exports.bhpLogo = "data:image/png;base64,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"; exports.mrlLogo = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAlgCWAAD/4QCMRX\nhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAA\nABAAIAAIdpAAQAAAABAAAAWgAAAAAAAACWAAAAAQAAAJYAAAABAAOgAQADAAAAAQABAACgAgAEAA\nAAAQAAAKCgAwAEAAAAAQAAAFUAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJTQ\nQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/AABEIAFUAoAMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQ\nAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHIn\nEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2\nRlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyM\nnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBA\nUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIz\nNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dn\nd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2d\nri4+Tl5ufo6ery8/T19vf4+fr/2wBDAAMDAwMDAwUDAwUIBQUFCAoICAgICg0KCgoKCg0QDQ0NDQ\n0NEBAQEBAQEBATExMTExMWFhYWFhkZGRkZGRkZGRn/2wBDAQQEBAYGBgsGBgsaEg8SGhoaGhoaGh\noaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhr/3QAEAAr/2gAMAwEAAh\nEDEQA/AP09udMmnYtHfXEGf7hTH/jyNXN33hbxFcZ+x+Kr+1/3YrR//Q4DXc0VEqae/wCbOmli6l\nPWNvmk/wA0zyS88G/E3aRpvjl1PYz6fbyfns8uvgL9sL9pa90iw/4Un4T1WS8vbUIutalH+5Z5Rh\n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"; // --------------------------------------------- // LEGACY CALLS, MAY BE USED BY OLD CALCULATORS // --------------------------------------------- exports.followUpMarineSurveyForGrain = function (job) { // LEAVE THIS HERE FOR LEGACY JOBS return SHARED.followUpIndependentMarineSurveyForGrain(job); }; exports.independentMarineSurveyForGrainBEFORECHANGES = function (job) { // BEFORE CHANGES ADVISED BY MUNAF 03/02/2022 // No grain, do not display (thanks to the "null" in the cost) if (!SHARED.isGrain(job)) return { cost: null, description1: 'Independent Marine Survey for grain - Only applicable for grain' }; // Ocean21 gets invoiced direct, hence "0" cost (which will still display) if (job.principalContactId === 91) { return { cost: 0, description1: 'Independent Marine Survey for grain - Amspec to invoice Ocean 21 direct' }; // Everybody else pays } else { var description1 = 'Independent Marine Survey for grain'; var cost = 0; var holds = job.shipHolds || 5; switch (job.portId) { case 1: // Albany case 6: // Geraldton if (holds === 5) cost = 1500; if (holds === 7) cost = 1750; // cost = cost + 750 break; case 4: // Esperance if (holds === 5) cost = 1600; if (holds === 7) cost = 1850; // cost = cost + 850 break; case 7: // Kwinana if (holds === 5) cost = 1250; if (holds === 7) cost = 1500; // cost = cost + 500 break; case 2: // Bunbury (assumed) if (holds === 5) cost = 1250; if (holds === 7) cost = 1500; // cost = cost + 500 cost = cost + 150; break; default: cost = 1; } return { description1: description1, cost: cost }; } }; exports.calculateFeesAndPrefundingTODELETE = function (job) { function r(o) { // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // If the principal is Australian, OR if the third party agency is Australian, // then prefunding of GST is mandatory regardless // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if (job.accountingManagerContactId) { if (job.accountingManagerContactIdCountryId === 13) o.prefundingGst = true; } else { if (job.principalContactId && job.principalContactIdCountryId === 13) o.prefundingGst = true; } // You CANNOT __CANNOT__ have itemised recoveries AND fixed recoveries if (o.fixedRecoveries) o.recoveries = false; // Return the object return o; } // DEFAULT FEES var fees = { fee: null, recoveries: false, prefunding: 100, prefundingGst: true, longTermDeal: 0 }; var principalContactId = job.principalContactIdParentContactId || job.principalContactId; // Real job, and there is no principal, this is it -- no calculations if (!principalContactId & !job.isEnquiry) return fees; var sandInGeraldton = job.portId === 6 && SHARED.hasCargo(job, 25); // SAND IN GERALDTON: Fee is 6800. Business is from ASI (606) if (sandInGeraldton) { fees.fee = 6800; } // It is an enquiry: agency fee is 0 (unless it's sand in Geraldton) if (job.isEnquiry) { // If it's an enquiry, set the fees to 0 (unless it's sand in Geraldton, // which is an exception) if (!sandInGeraldton) fees.fee = 0; return fees; } // FIRST OF ALL: DEALS WITH PRINCIPALS THAT HAVE ABSOLUTE PRIORITY // COMPARED TO ANY OTHER DEALS // ///////////////////////////////////////////////////// // PRINCIPAL: OCEAN21 // ///////////////////////////////////////////////////// if (principalContactId === 91) { return r({ fee: 2250, recoveries: false, prefunding: 0, prefundingGst: false, longTermDeal: true }); } // ////////////////////////////////////////////////////////////////////////////////////// // PRINCIPAL: OSLO BULK, SHIPPING AMMONIUM NITRATE // ////////////////////////////////////////////////////////////////////////////////////// if (principalContactId === 94 && SHARED.hasCargo(job, 17)) { return r({ fee: 4000, recoveries: false, prefunding: 80, prefundingGst: false, longTermDeal: true }); } // ///////////////////////////////////////////////////// // PRINCIPAL: WOODSIDE ENERGY (SINGAPORE) // ///////////////////////////////////////////////////// if (principalContactId === 449) { if (job.berthId === 19 || job.berthId === 20 || job.berthId === 21) { // WITHNELL BAY TERMINAL ALL BERTHS return r({ fee: 2000, recoveries: false, prefunding: 100, prefundingGst: false, longTermDeal: true }); } } // ///////////////////////////////////////////////////// // NOMINATORS SECTIONS // THEY WILL SET FEES BY CHANGING ATTRIBUTES OF "fees" // (An attribute might be "gst", "prefunding", etc. // THE DEFAULT "fees" IS USED AS A STARTING POINT // ///////////////////////////////////////////////////// // ///////////////////////////////////////////////////// // NOMINATOR: AEL // NOT CURRENT // ///////////////////////////////////////////////////// /* if (job.nominatorContactId === XX){ // AEL fees.fee = 3500 fees.recoveries = false fees.longTermDeal = true } */ // ///////////////////////////////////////////////////// // NOMINATOR: AUSTRALASIAN SANDS INTERNATIONAL // ///////////////////////////////////////////////////// if (job.nominatorContactId === 606) { // AUSTRALASIAN SANDS INTERNATIONAL fees.fee = 6800; fees.recoveries = false; // fees.longTermDeal = true } // ///////////////////////////////////////////////////// // NOMINATOR: CBH // ///////////////////////////////////////////////////// if (job.nominatorContactId === 369) { // CBH // STANDARD FEES ARE SET BELOW // EXCEPTIONS TO "SPECIAL" PRINCIPALS fees.fee = 4000; // ////////////////////////////////////////////////////////////// // CBH: SPECIAL DEALS FOR SPECIAL PRINCIPALS // ////////////////////////////////////////////////////////////// // OLDENDORFF (ALL PORTS) if (principalContactId === 93) { fees.prefundingDiscount = fees.fee; fees.prefundingDiscountReason = 'agency fee'; fees.longTermDeal = true; } } // OLDENDORFF // ///////////////////////////////////////////////////// // NOMINATOR: WOODSIDE ENERGY (AUSTRALIA) // ///////////////////////////////////////////////////// if (job.nominatorContactId === 450) { // WOODSIDE ENERGY (Australia) if (job.berthId === 19 || job.berthId === 20 || job.berthId === 21) { // WITHNELL BAY TERMINAL ALL BERTHS fees.fee = 2000; fees.prefundingGst = false; } } // ///////////////////////////////////////////////////// // NOMINATOR: ALCOA // ///////////////////////////////////////////////////// if (job.nominatorContactId === 364) { // ALCOA AS NOMINATOR // CARGO IS BAUXITE if (SHARED.hasCargo(job, 3)) { fees.fee = 4500; fees.recoveries = false; } // PERFECT BULK SHIPPING BAUXITE: SPECIAL DEAL if (principalContactId === 370 && SHARED.hasCargo(job, 3)) { fees.prefundingGst = false; fees.recoveries = false; fees.fee = 3500; } // NOBLE CHARTERING, DOESN'T FUND GST if (principalContactId === 89) { fees.prefundingGst = false; } // NOBLE CHARTERING/ALCOA AS SHIPPER IN KWINANA, SPODUMENE if (principalContactId === 89 && job.portId === 7 && SHARED.hasCargo(job, 14)) { fees.fee = 4500; fees.recoveries = false; fees.prefundingGst = false; fees.longTermDeal = true; } } // ///////////////////////////////////////////////////// // NOMINATOR: MRL // ///////////////////////////////////////////////////// if (job.nominatorContactId === 363) { // MRL AS NOMINATOR (NOMINATION) // ///////////////////////////////////////////////////// // FOR MRL JOBS, CHARGE RECOVERIES // ///////////////////////////////////////////////////// fees.recoveries = true; // STANDARD FEES ARE SET BELOW DEPENDING ON PORT // EXCEPTIONS TO "SPECIAL" PRINCIPALS // AT THE END OF THIS SECTION // ///////////////////////////////////////////////////// // MRL ESPERANCE: CHARGE 6800 // ///////////////////////////////////////////////////// if (job.portId === 4) { // SPODUMENE IN ESPERANCE GETS A CHEAPER FEE -- NOT ANYMORE, MAYBE LATER if (SHARED.hasCargo(job, 14)) fees.fee = 6800; // SAME FOR NOW // EVERY OTHER CARGO, PAYS 6800 else fees.fee = 6800; } // ////////////////////////////////////////////////////////////////////////// // MRL KWINANA: CHARGE 4500 FOR SPODUMENE // ////////////////////////////////////////////////////////////////////////// if (job.portId === 7) { if (SHARED.hasCargo(job, 14)) fees.fee = 4500; // SPODUMENE else fees.fee = 6800; // ANY OTHER } // ///////////////////////////////////////////////////// // MRL PORT HEDLAND // ///////////////////////////////////////////////////// if (job.portId === 9) { // SPECIAL PRINCIPALS SHIPPING MRL CARGO if (principalContactId === 58) fees.fee = 5500; // BYZANTINE MARITIME CORP // PORT HEDLAND: ALWAYS CHARGE 6800, WITH RECOVERIES fees.fee = 6800; // PH } // ////////////////////////////////////////////////////////////// // MRL: SPECIAL DEALS FOR SPECIAL PRINCIPALS // ////////////////////////////////////////////////////////////// // AUSCA SHIPPING HK LIMITED IN KWINANA if (principalContactId === 475 && job.portId === 7) { fees.fee = 5500; fees.fixedRecoveries = 2000; fees.recoveries = false; fees.longTermDeal = true; } // BUNGE S.A. ALL PORTS if (principalContactId === 476) { fees.prefunding = 80; fees.longTermDeal = true; } // BYZANTINE MARITIME CORP IN KWINANA if (principalContactId === 58 && job.portId === 7) { fees.fee = 5500; fees.longTermDeal = true; } // CARGILL INTERNATIONAL SA IN KWINANA, SPODUMENE ONLY // OBSOLETED. No current business from Kwinana, and if there is, fee to be adjusted // if (principalContactId === 479 && job.portId === 7 && SHARED.hasCargo(job, 14)) { // fees.fee = 6500 // fees.prefundingGst = false // fees.recoveries = false // fees.longTermDeal = true // } // CARGILL INTERNATIONAL SA IN ESPERANCE if (principalContactId === 479 && job.portId === 4) { fees.fee = 8800; fees.prefundingGst = false; fees.recoveries = false; fees.longTermDeal = true; } // CARGILL INTERNATIONAL SA IN PORT HEDLAND if (principalContactId === 479 && job.portId === 9) { fees.fee = 9790; fees.prefundingGst = false; fees.recoveries = false; fees.longTermDeal = true; } // BERGE BULK IN ESPERANCE if (principalContactId === 573 && job.portId === 4) { fees.fee = 7600; fees.recoveries = false; fees.longTermDeal = true; } // CLDN COBELFREIGHT IN PORT HEDLAND if (principalContactId === 63 && job.portId === 9) { fees.fee = 8800; fees.prefundingGst = false; fees.recoveries = false; fees.longTermDeal = true; } // DAMPSKIBSSELSKABET NORDEN A/S IN PORT HEDLAND if (principalContactId === 67 && job.portId === 9) { fees.fee = 8500; fees.recoveries = false; fees.prefunding = 90; fees.longTermDeal = true; } // DAMPSKIBSSELSKABET NORDEN A/S IN ESPERANCE if (principalContactId === 67 && job.portId === 4) { fees.fee = 8500; fees.recoveries = false; fees.prefunding = 90; fees.longTermDeal = true; } // LOUIS DREYFUS COMPANY FREIGHT ASIA PTE LTD ALL PORTS if (principalContactId === 83) { fees.prefunding = 90; fees.prefundingGst = false; fees.longTermDeal = true; // PORT HEDLAND AND ESPERANCE ONLY: all-in fee. // NOTE: In transaction templates for Port Hedland and Esperance, there // is a special provision in Agency Fee for this particular principal if (job.portId === 9 || job.portId === 4) { fees.fee = 8790; fees.recoveries = false; } } // OLDENDORFF (ALL PORTS) /* NOTE: IT USED TO HAVE THIS EXTRA CODE: // SMALL SHIP IN PORT HEDLAND if (job.portId === 9 && job.shipDwt < 90000) { fees.fee = 7990 fees.recoveries = false fees.prefundingGst = false fees.prefundingDiscount = fees.fee fees.prefundingDiscountReason = 'agency fee' // ANY OTHER PORT } else { */ // YOU ARE HERE if (principalContactId === 93) { fees.longTermDeal = true; fees.prefundingGst = false; if (job.portId === 9) { fees.prefundingDiscount = 2000 + 990 + fees.fee; fees.prefundingDiscountReason = 'agency fee, recoveries and remote zone allowance'; } else { fees.prefundingDiscount = 2000 + fees.fee; fees.prefundingDiscountReason = 'agency fee and recoveries'; } } // SWISSMARINE SERVICES (PARENT) OR SWISSMARINE PTE LTD: PORT HEDLAND OR ESPERANCE if (principalContactId === 110 && (job.portId === 9 || job.portId === 4)) { fees.prefundingGst = false; fees.recoveries = false; fees.longTermDeal = true; // SWISS MARINE IN PORT HEDLAND if (job.portId === 9) { // SMALL SHIP if (job.shipDwt < 90000) { fees.fee = 8500; // NORMAL SHIP } else { fees.fee = 9500; } // ADD DISCOUNT fees.prefundingDiscount = fees.fee; fees.prefundingDiscountReason = 'agency fee'; } // SWISS MARINE IN ESPERANCE if (job.portId === 4) { fees.fee = 8800; // ADD DISCOUNT fees.prefundingDiscount = fees.fee; fees.prefundingDiscountReason = 'agency fee'; } } // WORLDWIDE BULK SHIPPING IN PORT HEDLAND if (principalContactId === 485 && job.portId === 9) { fees.fee = 6800; fees.fixedRecoveries = 1000; fees.recoveries = false; fees.longTermDeal = true; } // FURNESS WITHY IN KWINANA if (principalContactId === 72 && job.portId === 7) { fees.fee = 5500; fees.fixedRecoveries = 2000; fees.recoveries = false; fees.longTermDeal = true; } // GEARBULK IN KWINANA if (principalContactId === 73 && job.portId === 7) { // fees.fee = 5500 // NO MORE as per Geoff's email, 3/6/21 fees.fixedRecoveries = 2000; fees.recoveries = false; fees.prefunding = 80; fees.longTermDeal = true; } // GOLDBEAM IN KWINANA if (principalContactId === 486 && job.portId === 7) { fees.fee = 5500; fees.fixedRecoveries = 2000; fees.recoveries = false; fees.longTermDeal = true; } // MITSUI OSK LINE IN KWINANA/PORT HEDLAND if (principalContactId === 86 && (job.portId === 7 || job.portId === 9)) { fees.fee = 6800; fees.longTermDeal = true; } // //////////////////////////////////////////////// // END OF MRL MODIFIER BLOCK // //////////////////////////////////////////////// } // ////////////////////////////////////////////////////////////////////////////////////// // PRINCIPALS WITH MODIFIERS, ALL NOMINATORS // Best NOT to change the fees here. // ////////////////////////////////////////////////////////////////////////////////////// // FORTUNE OCEAN MARINE PTE LTD if (principalContactId === 642) { fees.prefundingGst = false; fees.longTermDeal = true; } // SINCERE NAVIGATION CORPORATION if (principalContactId === 530) { fees.prefundingGst = false; fees.longTermDeal = true; } // DAMPSKIBSSELSKABET NORDEN A/S if (principalContactId === 67) { fees.prefunding = 90; fees.longTermDeal = true; } // QCOAST SHIPPING AMMONIUM NITRATE FROM KWINANA if (principalContactId === 98 && job.portId === 7 && SHARED.hasCargo(job, 17)) { fees.recoveries = false; fees.longTermDeal = true; } // POLARIS SHIPPING if (principalContactId === 430) { fees.prefundingGst = false; fees.longTermDeal = true; } // PAN OCEAN if (principalContactId === 96) { fees.prefundingGst = false; fees.prefunding = 90; fees.longTermDeal = true; } // SWIRE BULK PTE LTD if (principalContactId === 652) { fees.prefunding = 85; fees.longTermDeal = true; } // HYUNDAI GLOVIS if (principalContactId === 517) { fees.prefundingGst = false; fees.prefunding = 90; fees.longTermDeal = true; } // DHL PROJECT & CHARTERING LTD if (principalContactId === 421) { fees.prefundingGst = false; fees.prefunding = 90; fees.longTermDeal = false; } // IMC SHIPPING CO. PTE LTD if (principalContactId === 644) { fees.prefundingGst = false; fees.prefunding = 95; fees.longTermDeal = false; } // MARAN DRY MANAGEMENT INC if (principalContactId === 521) { fees.prefundingGst = false; fees.longTermDeal = true; } // CARGILL INTERNATIONAL SA if (principalContactId === 479) { fees.prefundingGst = false; fees.longTermDeal = true; } // CLDN COBELFREIGHT if (principalContactId === 63) { fees.prefundingGst = false; fees.recoveries = false; fees.longTermDeal = true; } // CLIPPER GROUP (SINGAPORE) PTE LTD if (principalContactId === 64) { fees.prefunding = 90; fees.longTermDeal = true; } // J. LAURITZEN SINGAPORE PTE LTD if (principalContactId === 540) { fees.prefunding = 100; fees.prefundingGst = false; fees.longTermDeal = true; } // GOLDEN OCEAN SHIPPING CO PTE. LTD if (principalContactId === 542) { fees.prefunding = 90; fees.longTermDeal = true; } // WESTERN BULK PTE LTD if (principalContactId === 115) { fees.prefunding = 80; fees.longTermDeal = true; } // OAK MARITIME (CANADA) INC. if (principalContactId === 553) { fees.prefundingGst = false; fees.longTermDeal = true; } // HOSCO HONG KONG if (principalContactId === 445) { fees.prefundingGst = false; fees.fixedRecoveries = 2000; fees.recoveries = false; fees.longTermDeal = true; } // LOUIS DREYFUS COMPANY FREIGHT ASIA PTE LTD if (principalContactId === 83) { fees.prefunding = 90; fees.prefundingGst = false; fees.longTermDeal = true; } // NYK LINE if (principalContactId === 90) { fees.prefundingGst = false; fees.longTermDeal = true; } // OLDENDORFF (ALL PORTS) -- NOT MRL if (principalContactId === 93 && job.nominatorContactId !== 363) { fees.longTermDeal = true; fees.prefundingGst = false; fees.prefundingDiscount = fees.fee; fees.prefundingDiscountReason = 'agency fee'; } // SWISSMARINE SERVICES (PARENT) OR SWISSMARINE PTE LTD if (principalContactId === 110) { fees.prefundingGst = false; fees.recoveries = false; fees.longTermDeal = true; } // WORLDWIDE BULK SHIPPING if (principalContactId === 485) { if (fees.recoveries) { fees.fixedRecoveries = 1000; fees.recoveries = false; fees.longTermDeal = true; } } // FURNESS WITHY if (principalContactId === 72) { if (fees.recoveries) { fees.fixedRecoveries = 2000; fees.recoveries = false; fees.longTermDeal = true; } } // GEARBULK if (principalContactId === 73) { if (fees.recoveries) { fees.fixedRecoveries = 2000; fees.recoveries = false; } fees.prefunding = 80; fees.longTermDeal = true; } // GOLDBEAM if (principalContactId === 486) { if (fees.recoveries) { fees.fixedRecoveries = 2000; fees.recoveries = false; fees.longTermDeal = true; } } // MITSUI OSK LINE IN KWINANA/PORT HEDLAND if (principalContactId === 86) { fees.longTermDeal = true; } // BERGE BULK SHIPPING PTE. LTD. if (principalContactId === 573) { fees.prefunding = 90; fees.longTermDeal = true; } // CARA SHIPPING PTE. LIMITED if (principalContactId === 576) { fees.prefundingGst = false; fees.longTermDeal = true; } // ULTRABULK if (principalContactId === 637) { fees.prefundingGst = false; fees.longTermDeal = true; } // RICHLAND BULK PTE LTD if (principalContactId === 622) { fees.prefundingGst = false; fees.longTermDeal = true; } // KCC CHARTERING AS if (principalContactId === 480) { // KLAVENESS... / MRL IN PORT HEDLAND: PRE-EXISTING SPECIAL DEAL if (job.nominatorContactId === 363 && job.portId === 9) { fees.fee = 9790; fees.recoveries = false; fees.prefunding = 85; fees.longTermDeal = true; // KLAVENESS... / OTHER PORTS } else { fees.prefunding = 85; fees.longTermDeal = true; } } // EASTERN PACIFIC SHIPPING PTE LTD if (principalContactId === 586) { fees.prefundingGst = false; fees.longTermDeal = true; } // CLASSIC MARITIME if (principalContactId === 529) { fees.prefundingGst = false; fees.longTermDeal = true; } // HENGDA SHIPPING GROUP LTD if (principalContactId === 583) { fees.prefundingGst = false; fees.longTermDeal = true; } // LANDMARK LINE CO LTD if (principalContactId === 605) { fees.prefundingGst = false; fees.longTermDeal = false; fees.fee = 6200; } // PCL (SHIPPING) PTE LTD if (principalContactId === 95) { fees.prefundingGst = false; fees.prefunding = 95; } // PACIFIC BASIN (UK) LIMITED if (principalContactId === 371) { fees.prefundingGst = false; fees.longTermDeal = true; if (SHARED.hasCargo(job, 14) && job.portId === 4) {// SPODUMENE IN PORT HEDLAND // Possibly revised agency fee for Esperance spodumene, but I need to check this. } } // G2 OCEAN (NOTE: RELATED TO GEARBULK) if (principalContactId === 610) { if (fees.recoveries) { fees.fixedRecoveries = 2000; fees.recoveries = false; } fees.prefunding = 80; fees.longTermDeal = true; } // WECO BULK ASIA PTE LTD if (principalContactId === 620) { fees.prefunding = 95; fees.longTermDeal = true; } // MUR SHIPPING B.V. if (principalContactId === 543) { fees.prefunding = 90; fees.longTermDeal = true; } // HUDSON SHIPPING LINES INC if (principalContactId === 78) { fees.prefundingGst = false; fees.longTermDeal = true; } // GOLDEN UNION if (principalContactId === 575) { // NOTE: TYPE "1" IS OWNERS JOBS if (job.type === 1) { fees.prefundingGst = false; fees.prefunding = 0; } else { fees.prefundingGst = false; } fees.longTermDeal = true; } // STARBULK SA if (principalContactId === 571) { fees.prefundingGst = false; fees.longTermDeal = true; } // BALTNAV SINGAPORE if (principalContactId === 452) { fees.prefunding = 90; fees.prefundingGst = false; fees.longTermDeal = true; } // ********************************************************************** // JACK FEE UP FOR MRL/ESPERANCE JOBS, FROM 6800, FOR CERTAIN PRINCIPALS // ********************************************************************** if (job.nominatorContactId === 363 && job.portId === 4) { if (principalContactId === 622) fees.fee = 7500; // RICHLAND BULK PTE LTD if (principalContactId === 575) fees.fee = 7500; // GOLDEN UNION /* // No longer needed since Golden Union is a parent company for all of these, // and the principalContactId will be fixed accordingly if (principalContactId === 538 || principalContactId === 565 || principalContactId === 567 || principalContactId === 600) { fees.fee = 7500 // GOLDEN UNION } */ } return r(fees); }; // appConfig.generalSofProgrammaticChecks // General programmatic SOF rules. This definition is always evaluated before contact rules. exports.sofProgrammaticRules = function () { return { version: '2026-09-03.1', rules: [] }; }; exports.sofProgrammaticCheck = function () { return { problems: [] }; }; })()
VoucherService/DescriptionNETGSTGROSS