From 25bc343027f14947a1b18d9e698f51c15e7866d8 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Mon, 17 Jan 2022 09:26:38 -0800 Subject: [PATCH 01/19] IO-1643 Add shop name to bread crumb. --- .../src/components/breadcrumbs/breadcrumbs.component.jsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/src/components/breadcrumbs/breadcrumbs.component.jsx b/client/src/components/breadcrumbs/breadcrumbs.component.jsx index ed3331da1..ee0d51bf6 100644 --- a/client/src/components/breadcrumbs/breadcrumbs.component.jsx +++ b/client/src/components/breadcrumbs/breadcrumbs.component.jsx @@ -5,21 +5,25 @@ import { connect } from "react-redux"; import { Link } from "react-router-dom"; import { createStructuredSelector } from "reselect"; import { selectBreadcrumbs } from "../../redux/application/application.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; import GlobalSearch from "../global-search/global-search.component"; import "./breadcrumbs.styles.scss"; const mapStateToProps = createStructuredSelector({ breadcrumbs: selectBreadcrumbs, + bodyshop: selectBodyshop, }); -export function BreadCrumbs({ breadcrumbs }) { +export function BreadCrumbs({ breadcrumbs, bodyshop }) { return ( - + {" "} + {(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) || + ""} {breadcrumbs.map((item) => From 933e0a62fb88d4d8d611901da1ece3d4ea177033 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Mon, 17 Jan 2022 10:18:07 -0800 Subject: [PATCH 02/19] IO-1573 CDK Job costing updates --- server/graphql-client/queries.js | 8 +++ server/job/job-costing.js | 119 ++++++++++++++++++++----------- 2 files changed, 87 insertions(+), 40 deletions(-) diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 3ed050f80..e9ba2d511 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -1030,6 +1030,7 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid! status ca_bc_pvrt ca_customer_gst + dms_allocation joblines(where: { removed: { _eq: false } }) { id db_ref @@ -1073,11 +1074,14 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid! actualhrs productivehrs flat_rate + ciecacode } bodyshop{ id md_responsibility_centers jc_hourly_rates + cdk_dealerid + pbs_serialnumber } } }`; @@ -1133,6 +1137,7 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT status ca_bc_pvrt ca_customer_gst + dms_allocation joblines(where: {removed: {_eq: false}}) { id db_ref @@ -1176,11 +1181,14 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT actualhrs productivehrs flat_rate + ciecacode } bodyshop { id md_responsibility_centers jc_hourly_rates + cdk_dealerid + pbs_serialnumber } } } diff --git a/server/job/job-costing.js b/server/job/job-costing.js index 7756810f6..4a7ebadb4 100644 --- a/server/job/job-costing.js +++ b/server/job/job-costing.js @@ -261,7 +261,7 @@ function GenerateCostingData(job) { val.profitcenter_labor || defaultProfits[val.mod_lbr_ty] || "?"; if (laborProfitCenter === "?") - console.log("Unknown type", val.mod_lbr_ty); + console.log("Unknown type", val.line_desc, val.mod_lbr_ty); const rateName = `rate_${(val.mod_lbr_ty || "").toLowerCase()}`; const laborAmount = Dinero({ @@ -285,11 +285,12 @@ function GenerateCostingData(job) { val.profitcenter_part || defaultProfits[val.part_type] || "?"; if (partsProfitCenter === "?") - console.log("Unknown type", val.part_type); + console.log("Unknown type", val.line_desc, val.part_type); if (!partsProfitCenter) console.log( "Unknown cost/profit center mapping for parts.", + val.line_desc, val.part_type ); const partsAmount = Dinero({ @@ -298,13 +299,13 @@ function GenerateCostingData(job) { .multiply(val.part_qty || 1) .add( val.prt_dsmk_m && val.prt_dsmk_m !== 0 - ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) - : Dinero({ - amount: Math.round(val.act_price * 100), - }) - .multiply(val.part_qty || 0) - .percentage(Math.abs(val.prt_dsmk_p || 0)) - .multiply(val.prt_dsmk_p > 0 ? 1 : -1) + ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(val.act_price * 100), + }) + .multiply(val.part_qty || 0) + .percentage(Math.abs(val.prt_dsmk_p || 0)) + .multiply(val.prt_dsmk_p > 0 ? 1 : -1) ); if (!acc.parts[partsProfitCenter]) acc.parts[partsProfitCenter] = Dinero(); @@ -322,7 +323,7 @@ function GenerateCostingData(job) { "?"; if (partsProfitCenter === "?") { - console.log("Unknown type", val.part_type); + console.log("Unknown type", val.line_desc, val.part_type); } else { const partsAmount = Dinero({ amount: Math.round((val.act_price || 0) * 100), @@ -330,13 +331,13 @@ function GenerateCostingData(job) { .multiply(val.part_qty || 1) .add( val.prt_dsmk_m && val.prt_dsmk_m !== 0 - ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) - : Dinero({ - amount: Math.round(val.act_price * 100), - }) - .multiply(val.part_qty || 0) - .percentage(Math.abs(val.prt_dsmk_p || 0)) - .multiply(val.prt_dsmk_p > 0 ? 1 : -1) + ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(val.act_price * 100), + }) + .multiply(val.part_qty || 0) + .percentage(Math.abs(val.prt_dsmk_p || 0)) + .multiply(val.prt_dsmk_p > 0 ? 1 : -1) ); if (!acc.parts[partsProfitCenter]) @@ -372,21 +373,41 @@ function GenerateCostingData(job) { ); } + //Is it a DMS Setup? + const selectedDmsAllocationConfig = + job.bodyshop.md_responsibility_centers.dms_defaults.find( + (d) => d.name === job.dms_allocation + ) || job.bodyshop.md_responsibility_centers.defaults; + const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => { //At the bill level. bill_val.billlines.map((line_val) => { //At the bill line level. - //console.log("JobCostingPartsTable -> line_val", line_val); - if (!bill_acc[line_val.cost_center]) - bill_acc[line_val.cost_center] = Dinero(); + if (job.bodyshop.pbs_serialnumber || job.bodyshop.cdk_dealerid) { + if (!bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]]) + bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] = + Dinero(); - bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add( - Dinero({ - amount: Math.round((line_val.actual_cost || 0) * 100), - }) - .multiply(line_val.quantity) - .multiply(bill_val.is_credit_memo ? -1 : 1) - ); + bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] = + bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]].add( + Dinero({ + amount: Math.round((line_val.actual_cost || 0) * 100), + }) + .multiply(line_val.quantity) + .multiply(bill_val.is_credit_memo ? -1 : 1) + ); + } else { + if (!bill_acc[line_val.cost_center]) + bill_acc[line_val.cost_center] = Dinero(); + + bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add( + Dinero({ + amount: Math.round((line_val.actual_cost || 0) * 100), + }) + .multiply(line_val.quantity) + .multiply(bill_val.is_credit_memo ? -1 : 1) + ); + } return null; }); @@ -443,20 +464,38 @@ function GenerateCostingData(job) { const ticketTotalsByCostCenter = job.timetickets.reduce( (ticket_acc, ticket_val) => { //At the invoice level. - if (!ticket_acc[ticket_val.cost_center]) - ticket_acc[ticket_val.cost_center] = Dinero(); - ticket_acc[ticket_val.cost_center] = ticket_acc[ - ticket_val.cost_center - ].add( - Dinero({ - amount: Math.round((ticket_val.rate || 0) * 100), - }).multiply( - ticket_val.flat_rate - ? ticket_val.productivehrs || ticket_val.actualhrs || 0 - : ticket_val.actualhrs || ticket_val.productivehrs || 0 - ) //Should base this on the employee. - ); + if (job.bodyshop.pbs_serialnumber || job.bodyshop.cdk_dealerid) { + if (!ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]]) + ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]] = + Dinero(); + + ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]] = + ticket_acc[selectedDmsAllocationConfig.costs[ticket_val.ciecacode]].add( + Dinero({ + amount: Math.round((ticket_val.rate || 0) * 100), + }).multiply( + ticket_val.flat_rate + ? ticket_val.productivehrs || ticket_val.actualhrs || 0 + : ticket_val.actualhrs || ticket_val.productivehrs || 0 + ) //Should base this on the employee. + ); + } else { + if (!ticket_acc[ticket_val.cost_center]) + ticket_acc[ticket_val.cost_center] = Dinero(); + + ticket_acc[ticket_val.cost_center] = ticket_acc[ + ticket_val.cost_center + ].add( + Dinero({ + amount: Math.round((ticket_val.rate || 0) * 100), + }).multiply( + ticket_val.flat_rate + ? ticket_val.productivehrs || ticket_val.actualhrs || 0 + : ticket_val.actualhrs || ticket_val.productivehrs || 0 + ) //Should base this on the employee. + ); + } return ticket_acc; }, From 821b044515e8e1e050fd40ee21384687b41020c7 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Mon, 17 Jan 2022 11:48:12 -0800 Subject: [PATCH 03/19] IO-1606 Include additional costs in reconciliation. --- .../job-bills-total.component.jsx | 6 +++--- .../job-reconciliation-modal.component.jsx | 20 ++++++++++++++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/client/src/components/job-bills-total/job-bills-total.component.jsx b/client/src/components/job-bills-total/job-bills-total.component.jsx index 8f8ba31e6..f4dbb80ea 100644 --- a/client/src/components/job-bills-total/job-bills-total.component.jsx +++ b/client/src/components/job-bills-total/job-bills-total.component.jsx @@ -64,9 +64,9 @@ export default function JobBillsTotalComponent({ }) ); - const totalPartsSublet = Dinero(totals.parts.parts.total).add( - Dinero(totals.parts.sublets.total) - ); + const totalPartsSublet = Dinero(totals.parts.parts.total) + .add(Dinero(totals.parts.sublets.total)) + .add(Dinero(totals.additional.additionalCosts)); const discrepancy = totalPartsSublet.subtract(billTotals); diff --git a/client/src/components/job-reconciliation-modal/job-reconciliation-modal.component.jsx b/client/src/components/job-reconciliation-modal/job-reconciliation-modal.component.jsx index f817a4e73..39da47462 100644 --- a/client/src/components/job-reconciliation-modal/job-reconciliation-modal.component.jsx +++ b/client/src/components/job-reconciliation-modal/job-reconciliation-modal.component.jsx @@ -18,7 +18,8 @@ export default function JobReconciliationModalComponent({ job, bills }) { .flat() || []; const jobLineData = job.joblines.filter( - (j) => j.part_type !== null && j.part_type !== "PAE" + (j) => + (j.part_type !== null && j.part_type !== "PAE") || IsAdditionalCost(j) ); return ( @@ -50,3 +51,20 @@ export default function JobReconciliationModalComponent({ job, bills }) { ); } + +function IsAdditionalCost(jobLine) { + //May be able to use db_ref here to help. + //936012 is Haz Waste Dispoal + //936008 is Paint/Materials + //936007 is Shop/Materials + + //Remove paint and shop mat lines. They're calculated under rates. + const isPaintOrShopMat = + jobLine.db_ref === "936008" || jobLine.db_ref === "936007"; + + return ( + (jobLine.lbr_op === "OP13" || //Added to resolve manual job lines coming into other totals because they have no reference. + (jobLine.db_ref && jobLine.db_ref.startsWith("9360"))) && + !isPaintOrShopMat + ); +} From e2941bfe847c3d0bb479f0f645cffbe9c8b10ee1 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Mon, 17 Jan 2022 12:08:09 -0800 Subject: [PATCH 04/19] IO-1558 Add color to dates that are today. --- client/src/App/App.styles.scss | 13 +++++++------ .../production-list-columns.date.component.jsx | 10 +++++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/client/src/App/App.styles.scss b/client/src/App/App.styles.scss index ccaf3b89f..c188f8a14 100644 --- a/client/src/App/App.styles.scss +++ b/client/src/App/App.styles.scss @@ -92,13 +92,14 @@ } .production-completion-1 { - animation: production-completion-1-blinker 5s linear infinite; -} -@keyframes production-completion-1-blinker { - 50% { - background: rgba(207, 12, 12, 0.555); - } + color: rgba(207, 12, 12, 0.8); + +// animation: production-completion-1-blinker 1s linear infinite; } +// @keyframes production-completion-1-blinker { +// 50% { +// } +// } .react-resizable { position: relative; diff --git a/client/src/components/production-list-columns/production-list-columns.date.component.jsx b/client/src/components/production-list-columns/production-list-columns.date.component.jsx index 51999c288..59b59e420 100644 --- a/client/src/components/production-list-columns/production-list-columns.date.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.date.component.jsx @@ -72,14 +72,14 @@ export default function ProductionListDate({ record, field, time }) { style={{ height: "19px", }} + className={ + !!record[field] && moment().isSame(moment(record[field]), "day") + ? "production-completion-1" + : "" + } > {record[field]} From 34ae2c56b78e8fa6d977f0b9e84c4146737103a1 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Mon, 17 Jan 2022 12:35:43 -0800 Subject: [PATCH 05/19] IO-1642 Resolve delivery checklist not including dates. --- .../job-checklist-form.component.jsx | 36 ++++++++++++------- ...production-list-columns.date.component.jsx | 9 +---- client/src/graphql/bodyshop.queries.js | 3 ++ .../jobs-delivery.page.container.jsx | 1 + 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/client/src/components/job-checklist/components/job-checklist-form/job-checklist-form.component.jsx b/client/src/components/job-checklist/components/job-checklist-form/job-checklist-form.component.jsx index a08464db1..049fa58dc 100644 --- a/client/src/components/job-checklist/components/job-checklist-form/job-checklist-form.component.jsx +++ b/client/src/components/job-checklist/components/job-checklist-form/job-checklist-form.component.jsx @@ -171,7 +171,12 @@ export function JobChecklistForm({ }); } }; - + console.log(job,{ + removeFromProduction: true, + actual_completion: + job && job.actual_completion && moment(job.actual_completion), + actual_delivery: job && job.actual_delivery && moment(job.actual_delivery), + }); return ( fi.value) diff --git a/client/src/components/production-list-columns/production-list-columns.date.component.jsx b/client/src/components/production-list-columns/production-list-columns.date.component.jsx index 59b59e420..4381d3b52 100644 --- a/client/src/components/production-list-columns/production-list-columns.date.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.date.component.jsx @@ -8,9 +8,6 @@ import { DateFormatter } from "../../utils/DateFormatter"; import { useTranslation } from "react-i18next"; -const OneCalendarDay = 60 * 60 * 24 * 1000; -const Now = new Date(); - export default function ProductionListDate({ record, field, time }) { const [updateAlert] = useMutation(UPDATE_JOB); const [visible, setVisible] = useState(false); @@ -78,11 +75,7 @@ export default function ProductionListDate({ record, field, time }) { : "" } > - - {record[field]} - + {record[field]} diff --git a/client/src/graphql/bodyshop.queries.js b/client/src/graphql/bodyshop.queries.js index 878f3a3e9..23905296a 100644 --- a/client/src/graphql/bodyshop.queries.js +++ b/client/src/graphql/bodyshop.queries.js @@ -262,6 +262,9 @@ export const QUERY_DELIVER_CHECKLIST = gql` jobs_by_pk(id: $jobId) { id ro_number + actual_completion + actual_delivery + } } `; diff --git a/client/src/pages/jobs-deliver/jobs-delivery.page.container.jsx b/client/src/pages/jobs-deliver/jobs-delivery.page.container.jsx index ff81b90ba..a87038f58 100644 --- a/client/src/pages/jobs-deliver/jobs-delivery.page.container.jsx +++ b/client/src/pages/jobs-deliver/jobs-delivery.page.container.jsx @@ -69,6 +69,7 @@ export function JobsDeliverContainer({ checklistConfig={ (data && data.bodyshops_by_pk.deliverchecklist) || {} } + job={data ? data.jobs_by_pk : {}} /> From bf81c6dd065761c845b85d120cd78041e7d3bc5a Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 19 Jan 2022 08:38:03 -0800 Subject: [PATCH 06/19] ARMS Updates --- server/data/arms.js | 357 ++++++++++++++++--------------- server/graphql-client/queries.js | 2 + 2 files changed, 191 insertions(+), 168 deletions(-) diff --git a/server/data/arms.js b/server/data/arms.js index 7124c07ad..2d23ab8d3 100644 --- a/server/data/arms.js +++ b/server/data/arms.js @@ -2,6 +2,7 @@ const path = require("path"); const queries = require("../graphql-client/queries"); const Dinero = require("dinero.js"); const moment = require("moment"); +const fs = require("fs"); const _ = require("lodash"); const logger = require("../utils/logger"); @@ -28,7 +29,6 @@ exports.default = async (req, res) => { logger.log("arms-start", "DEBUG", "api", null, null); const { bodyshops } = await client.request(queries.GET_ENTEGRAL_SHOPS); - const allxmlsToUpload = []; const allErrors = []; try { for (const bodyshop of bodyshops) { @@ -40,10 +40,12 @@ exports.default = async (req, res) => { const { jobs } = await client.request(queries.ENTEGRAL_EXPORT, { bodyshopid: bodyshop.id, }); - const ret = jobs.map((job) => { + const jobsToPush = []; + + jobs.forEach((job) => { const transId = uuid(); // Can this actually be the job id? - return { + let obj = { RqUID: transId, DocumentInfo: { BMSVer: "4.0.0", @@ -55,16 +57,18 @@ exports.default = async (req, res) => { TransmitDateTime: moment().format(momentFormat), // Omitted from ARMS docs }, EventInfo: { - AssignmentEvent: { - CreateDateTime: - job.asgn_date && moment(job.asgn_date).format(momentFormat), - }, - EstimateEvent: { - UploadDateTime: moment().format(momentFormat), - }, + // AssignmentEvent: { + // CreateDateTime: + // job.asgn_date && moment(job.asgn_date).format(momentFormat), + // }, + // EstimateEvent: { + // UploadDateTime: moment().format(momentFormat), + // }, RepairEvent: { - CreatedDateTime: - job.date_open && moment(job.date_open).format(momentFormat), + CreatedDateTime: (job.date_open + ? moment(job.date_open) + : moment() + ).format(momentFormat), ArrivalDateTime: job.actual_in && moment(job.actual_in).format(momentFormat), ArrivalOdometerReading: job.kmin, @@ -92,35 +96,35 @@ exports.default = async (req, res) => { IDQualifierCode: "US", IDNum: 44, // ** Not sure where to get this entegral ID from? }, - Communications: [ - { - CommQualifier: "WA", - Address: { - Address1: job.ins_addr1, - Address2: job.ins_addr2, - City: job.ins_city, - StateProvince: job.ins_st, - PostalCode: job.ins_zip, - CountryCode: job.ins_ctry, - }, - }, - { - CommQualifier: "WP", - CommPhone: job.ins_ph1, - }, - { - CommQualifier: "WF", - CommPhone: job.ins_ph2, - }, - ], - }, - ContactInfo: { - ContactJobTitle: "Adjuster", - ContactName: { - FirstName: job.est_ct_fn, - LastName: job.est_ct_ln, - }, + // Communications: [ + // { + // CommQualifier: "WA", + // Address: { + // Address1: job.ins_addr1, + // Address2: job.ins_addr2, + // City: job.ins_city, + // StateProvince: job.ins_st, + // PostalCode: job.ins_zip, + // CountryCode: job.ins_ctry, + // }, + // }, + // { + // CommQualifier: "WP", + // CommPhone: job.ins_ph1, + // }, + // { + // CommQualifier: "WF", + // CommPhone: job.ins_ph2, + // }, + // ], }, + // ContactInfo: { + // ContactJobTitle: "Adjuster", + // ContactName: { + // FirstName: job.est_ct_fn, + // LastName: job.est_ct_ln, + // }, + // }, }, }, // InsuranceAgent: { @@ -141,16 +145,16 @@ exports.default = async (req, res) => { // }, // }, // }, - Insured: { - Party: { - PersonInfo: { - PersonName: { - FirstName: job.insd_fn, - LastName: job.insd_ln, - }, - }, - }, - }, + // Insured: { + // Party: { + // PersonInfo: { + // PersonName: { + // FirstName: job.insd_fn, + // LastName: job.insd_ln, + // }, + // }, + // }, + // }, Owner: { Party: { PersonInfo: { @@ -158,68 +162,70 @@ exports.default = async (req, res) => { FirstName: job.ownr_fn, LastName: job.ownr_ln, }, - Communications: [ - { - CommQualifier: "HA", - Address: { - Address1: job.ownr_addr1, + // Communications: [ + // { + // CommQualifier: "HA", + // Address: { + // Address1: job.ownr_addr1, - City: job.ownr_city, - StateProvince: job.ownr_st, - PostalCode: job.ownr_zip, - CountryCode: job.ownr_ctry, - }, - }, - { - CommQualifier: "HP", - CommPhone: job.ownr_ph1, - }, - { - CommQualifier: "WP", - CommPhone: job.ownr_ph2, - }, - { - CommQualifier: "CP", - CommPhone: job.ownr_ph1, - }, - { - CommQualifier: "EM", - CommEmail: job.ownr_ea, - }, - ], - }, - }, - }, - Claimant: { - Party: { - PersonInfo: { - PersonName: { - FirstName: job.clm_ct_fn, - LastName: job.clm_ct_ln, - }, - }, - }, - OwnerInd: true, - }, - Estimator: { - Party: { - PersonInfo: { - PersonName: { - FirstName: job.est_ct_fn, - LastName: job.est_ct_ln, - }, - // IDInfo: { - // IDQualifierCode: "US", - // IDNum: 2941, - // }, + // City: job.ownr_city, + // StateProvince: job.ownr_st, + // PostalCode: job.ownr_zip, + // CountryCode: job.ownr_ctry, + // }, + // }, + // { + // CommQualifier: "HP", + // CommPhone: job.ownr_ph1, + // }, + // { + // CommQualifier: "WP", + // CommPhone: job.ownr_ph2, + // }, + // { + // CommQualifier: "CP", + // CommPhone: job.ownr_ph1, + // }, + // { + // CommQualifier: "EM", + // CommEmail: job.ownr_ea, + // }, + // ], }, }, }, + // Claimant: { + // Party: { + // PersonInfo: { + // PersonName: { + // FirstName: job.clm_ct_fn, + // LastName: job.clm_ct_ln, + // }, + // }, + // }, + // OwnerInd: true, + // }, + // Estimator: { + // Party: { + // PersonInfo: { + // PersonName: { + // FirstName: job.est_ct_fn, + // LastName: job.est_ct_ln, + // }, + // // IDInfo: { + // // IDQualifierCode: "US", + // // IDNum: 2941, + // // }, + // }, + // }, + // }, RepairFacility: { - //This section not in documentation. Party: { OrgInfo: { - CompanyName: bodyshop.shopname, + CompanyName: + process.env.NODE_ENV === "production" + ? bodyshop.shopname + : "IMEX Test Shop", IDInfo: { IDQualifierCode: "US", IDNum: bodyshop.entegral_id, @@ -233,7 +239,7 @@ exports.default = async (req, res) => { // VendorCode: "C", // EstimateDocumentID: "1223HJ76", }, - //RepairOrderType: "DRP", + RepairOrderType: "DirectRepairProgram", //Need to get from Entegral //ReferralSourceType: "Yellow Pages", VehicleInfo: { VINInfo: { @@ -241,9 +247,9 @@ exports.default = async (req, res) => { VINNum: job.v_vin, }, }, - License: { - LicensePlateNum: job.plate_no, - }, + // License: { + // LicensePlateNum: job.plate_no, + // }, VehicleDesc: { //ProductionDate: "2009-10", ModelYear: @@ -255,23 +261,23 @@ exports.default = async (req, res) => { MakeDesc: job.v_make_desc, ModelName: job.v_model_desc, }, - Paint: { - Exterior: { - Color: { - ColorName: job.v_color, - // OEMColorCode: "1M3", - }, - }, - }, + // Paint: { + // Exterior: { + // Color: { + // ColorName: job.v_color, + // // OEMColorCode: "1M3", + // }, + // }, + // }, // Body: { // BodyStyle: "2 Door Convertible", // Trim: { // TrimCode: "1B3", // }, // }, - Condition: { - DrivableInd: job.driveable ? "Y" : "N", - }, + // Condition: { + // DrivableInd: job.driveable ? "Y" : "N", + // }, }, ClaimInfo: { ClaimNum: job.clm_no, @@ -296,7 +302,7 @@ exports.default = async (req, res) => { }, }, ProfileInfo: { - //ProfileName: "Shop Standard Rates", + ProfileName: "ImEX", RateInfo: [ { RateType: "PA", @@ -306,7 +312,7 @@ exports.default = async (req, res) => { TaxableInd: true, TaxTierInfo: { TierNum: 1, - Percentage: 0, //TODO Find the best place to take the tax rates for parts. + Percentage: job.parts_tax_rates.PAN.prt_tax_rt * 100, //TODO Find the best place to take the tax rates for parts. }, }, }, @@ -318,7 +324,7 @@ exports.default = async (req, res) => { TaxableInd: true, TaxTierInfo: { TierNum: 1, - Percentage: 0, //TODO Find the best place to take the tax rates for labor. + Percentage: job.parts_tax_rates.PAN.prt_tax_rt * 100, //TODO Find the best place to take the tax rates for labor. }, }, }, @@ -495,56 +501,56 @@ exports.default = async (req, res) => { TotalType: "PAA", TotalTypeDesc: "Aftermarket Parts", TotalAmt: Dinero( - job.job_totals.parts.parts.list.paa && - job.job_totals.parts.parts.list.paa.total + job.job_totals.parts.parts.list.PAA && + job.job_totals.parts.parts.list.PAA.total ).toFormat("0.00"), }, { TotalType: "PAC", TotalTypeDesc: "Re-Chromed Parts", TotalAmt: Dinero( - job.job_totals.parts.parts.list.pac && - job.job_totals.parts.parts.list.pac.total + job.job_totals.parts.parts.list.PAC && + job.job_totals.parts.parts.list.PAC.total ).toFormat("0.00"), }, { TotalType: "PAG", TotalTypeDesc: "Glass Parts", TotalAmt: Dinero( - job.job_totals.parts.parts.list.pag && - job.job_totals.parts.parts.list.pag.total + job.job_totals.parts.parts.list.PAG && + job.job_totals.parts.parts.list.PAG.total ).toFormat("0.00"), }, { TotalType: "PAL", TotalTypeDesc: "LKQ/Used Parts", TotalAmt: Dinero( - job.job_totals.parts.parts.list.pal && - job.job_totals.parts.parts.list.pal.total + job.job_totals.parts.parts.list.PAL && + job.job_totals.parts.parts.list.PAL.total ).toFormat("0.00"), }, { TotalType: "PAM", TotalTypeDesc: "Remanufactured Parts", TotalAmt: Dinero( - job.job_totals.parts.parts.list.pam && - job.job_totals.parts.parts.list.pam.total + job.job_totals.parts.parts.list.PAM && + job.job_totals.parts.parts.list.PAM.total ).toFormat("0.00"), }, { TotalType: "PAN", TotalTypeDesc: "New Parts", TotalAmt: Dinero( - job.job_totals.parts.parts.list.pan && - job.job_totals.parts.parts.list.pan.total + job.job_totals.parts.parts.list.PAN && + job.job_totals.parts.parts.list.PAN.total ).toFormat("0.00"), }, { TotalType: "PAR", TotalTypeDesc: "Recored Parts", TotalAmt: Dinero( - job.job_totals.parts.parts.list.par && - job.job_totals.parts.parts.list.par.total + job.job_totals.parts.parts.list.PAR && + job.job_totals.parts.parts.list.PAR.total ).toFormat("0.00"), }, ], @@ -616,6 +622,14 @@ exports.default = async (req, res) => { "0.00" ), }, + { + TotalType: "TOT", + TotalSubType: "SM", + TotalTypeDesc: "Supplement Total", + TotalAmt: job.cieca_ttl + ? job.cieca_ttl.data.supp_amt + : Dinero().toFormat("0.00"), + }, { TotalType: "TOT", TotalSubType: "F7", @@ -632,12 +646,14 @@ exports.default = async (req, res) => { "0.00" ), }, - // { - // TotalType: "TOT", - // TotalSubType: "SM", - // TotalTypeDesc: "Supplement Total", - // TotalAmt: 0, - // }, + { + TotalType: "TOT", + TotalSubType: "D8", + TotalTypeDesc: "Bottom Line Discount", + TotalAmt: Dinero( + job.job_totals.additional.adjustments + ).toFormat("0.00"), + }, { TotalType: "TOT", TotalSubType: "D2", @@ -658,15 +674,13 @@ exports.default = async (req, res) => { TotalType: "TOT", TotalSubType: "AA", TotalTypeDesc: "Appearance Allowance", - TotalAmt: 0, + TotalAmt: Dinero().toFormat("0.00"), }, { TotalType: "TOT", - TotalSubType: "D8", - TotalTypeDesc: "Bottom Line Discount", - TotalAmt: Dinero( - job.job_totals.additional.adjustments - ).toFormat("0.00"), + TotalSubType: "DEPOSIT", + TotalTypeDesc: "Deposit", + TotalAmt: Dinero().toFormat("0.00"), }, { TotalType: "TOT", @@ -676,12 +690,6 @@ exports.default = async (req, res) => { .subtract(Dinero(job.job_totals.totals.custPayable.total)) .toFormat("0.00"), }, - // { - // TotalType: "TOT", - // TotalSubType: "DEPOSIT", - // TotalTypeDesc: "Deposit", - // TotalAmt: 0, - // }, { TotalType: "TOT", TotalSubType: "CUST", @@ -691,7 +699,7 @@ exports.default = async (req, res) => { ).toFormat("0.00"), }, ], - RepairTotalsType: 1, + // RepairTotalsType: 1, }, // RepairLabor: { // LaborAllocations: { @@ -741,7 +749,7 @@ exports.default = async (req, res) => { // }, ProductionStatus: { ProductionStage: { - ProductionStageCode: GetProductionStageCode(job), + ProductionStageCode: GetProductionStageCode(job, bodyshop), ProductionStageDateTime: moment().format(momentFormat), // ProductionStageStatusComment: // "Going to be painted this afternoon", @@ -777,6 +785,9 @@ exports.default = async (req, res) => { // }, // }, }; + + deleteNullKeys(obj); + jobsToPush.push(obj); }); if (erroredJobs.length > 0) { @@ -784,13 +795,12 @@ exports.default = async (req, res) => { count: erroredJobs.length, jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)), }); + allErrors = [...allErrors, ...erroredJobs]; } logger.log("arms-end-shop-extract", "DEBUG", "api", bodyshop.id, { shopname: bodyshop.shopname, }); - const abc = ret[1]; - deleteNullKeys(abc); try { const entegralSoapClient = await soap.createClientAsync( @@ -798,7 +808,7 @@ exports.default = async (req, res) => { { ignoredNamespaces: true, wsdl_options: { - useEmptyTag: true, + // useEmptyTag: true, }, wsdl_headers: { Authorization: `Basic ${new Buffer.from( @@ -816,14 +826,20 @@ exports.default = async (req, res) => { ); const entegralResponse = - await entegralSoapClient.RepairOrderFolderAddRqAsync(abc); + await entegralSoapClient.RepairOrderFolderAddRqAsync( + jobsToPush, + function (err, result, rawResponse, soapHeader, rawRequest) { + fs.writeFileSync(`./logs/arms-request.xml`, rawRequest); + fs.writeFileSync(`./logs/arms-response.xml`, rawResponse); + + res.json(err || result); + } + ); const [result, rawResponse, , rawRequest] = entegralResponse; - console.log("🚀 ~ file: arms.js ~ line 806 ~ result", result); - res.json({ result, obj: abc }); } catch (error) { + fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml); console.log(error); - res.json(error); } } catch (error) { //Error at the shop level. @@ -846,15 +862,18 @@ exports.default = async (req, res) => { } } - // res.sendStatus(200); + res.sendStatus(200); } catch (error) { res.status(200).json(error); } }; function GetSupplementNumber(joblines) { - return 0; - return _.max(joblines.map((jl) => jl.line_ind)); + const max = _.max( + joblines.map((jl) => parseInt((jl.line_ind || "0").replace(/[^\d.-]/g, ""))) + ); + + return max || 0; } function GetDocumentstatus(job, bodyshop) { @@ -873,7 +892,9 @@ function GetDocumentstatus(job, bodyshop) { function GetRepairStatusCode(job) { return "25"; } -function GetProductionStageCode(job) { +function GetProductionStageCode(job, bodyshop) { + if (bodyshop.md_ro_statuses.post_production_statuses.includes(job.status)) + return "8D"; return "33"; } diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index e9ba2d511..1393e1b4d 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -783,6 +783,8 @@ query ENTEGRAL_EXPORT($bodyshopid: uuid!) { rate_matd job_totals ded_amt + cieca_ttl + adjustment_bottom_line } } `; From 4a7eb9b37310fa997f153ee33df0d9acecea13d0 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 19 Jan 2022 11:46:49 -0800 Subject: [PATCH 07/19] User email case sensitivity on login. --- client/src/redux/user/user.sagas.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/redux/user/user.sagas.js b/client/src/redux/user/user.sagas.js index 46d07443f..2daf7a907 100644 --- a/client/src/redux/user/user.sagas.js +++ b/client/src/redux/user/user.sagas.js @@ -270,7 +270,7 @@ export function* SetAuthLevelFromShopDetails({ payload }) { factory.client(payload.imexshopid); const authRecord = payload.associations.filter( - (a) => a.useremail === userEmail + (a) => a.useremail.toLowerCase() === userEmail.toLowerCase() ); yield put(setAuthlevel(authRecord[0] ? authRecord[0].authlevel : 0)); From 4deed41a12021869f973eee4808a9bca800702bd Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 19 Jan 2022 11:50:14 -0800 Subject: [PATCH 08/19] Revert "IO-1606 Include additional costs in reconciliation." This reverts commit 821b044515e8e1e050fd40ee21384687b41020c7. --- .../job-bills-total.component.jsx | 6 +++--- .../job-reconciliation-modal.component.jsx | 20 +------------------ 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/client/src/components/job-bills-total/job-bills-total.component.jsx b/client/src/components/job-bills-total/job-bills-total.component.jsx index f4dbb80ea..8f8ba31e6 100644 --- a/client/src/components/job-bills-total/job-bills-total.component.jsx +++ b/client/src/components/job-bills-total/job-bills-total.component.jsx @@ -64,9 +64,9 @@ export default function JobBillsTotalComponent({ }) ); - const totalPartsSublet = Dinero(totals.parts.parts.total) - .add(Dinero(totals.parts.sublets.total)) - .add(Dinero(totals.additional.additionalCosts)); + const totalPartsSublet = Dinero(totals.parts.parts.total).add( + Dinero(totals.parts.sublets.total) + ); const discrepancy = totalPartsSublet.subtract(billTotals); diff --git a/client/src/components/job-reconciliation-modal/job-reconciliation-modal.component.jsx b/client/src/components/job-reconciliation-modal/job-reconciliation-modal.component.jsx index 39da47462..f817a4e73 100644 --- a/client/src/components/job-reconciliation-modal/job-reconciliation-modal.component.jsx +++ b/client/src/components/job-reconciliation-modal/job-reconciliation-modal.component.jsx @@ -18,8 +18,7 @@ export default function JobReconciliationModalComponent({ job, bills }) { .flat() || []; const jobLineData = job.joblines.filter( - (j) => - (j.part_type !== null && j.part_type !== "PAE") || IsAdditionalCost(j) + (j) => j.part_type !== null && j.part_type !== "PAE" ); return ( @@ -51,20 +50,3 @@ export default function JobReconciliationModalComponent({ job, bills }) { ); } - -function IsAdditionalCost(jobLine) { - //May be able to use db_ref here to help. - //936012 is Haz Waste Dispoal - //936008 is Paint/Materials - //936007 is Shop/Materials - - //Remove paint and shop mat lines. They're calculated under rates. - const isPaintOrShopMat = - jobLine.db_ref === "936008" || jobLine.db_ref === "936007"; - - return ( - (jobLine.lbr_op === "OP13" || //Added to resolve manual job lines coming into other totals because they have no reference. - (jobLine.db_ref && jobLine.db_ref.startsWith("9360"))) && - !isPaintOrShopMat - ); -} From abf9d0b7f455a676b13c00519a00a1cf69dab574 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 19 Jan 2022 12:03:30 -0800 Subject: [PATCH 09/19] IO-1558 --- client/src/App/App.styles.scss | 15 ++++++------- .../production-list-columns.data.js | 4 ++-- ...production-list-columns.date.component.jsx | 22 ++++++++++++++----- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/client/src/App/App.styles.scss b/client/src/App/App.styles.scss index c188f8a14..e7243044d 100644 --- a/client/src/App/App.styles.scss +++ b/client/src/App/App.styles.scss @@ -91,15 +91,14 @@ color: blue; } -.production-completion-1 { - color: rgba(207, 12, 12, 0.8); - -// animation: production-completion-1-blinker 1s linear infinite; +.production-completion-soon { + color: rgba(255, 140, 0, 0.8); + font-weight: bold; +} +.production-completion-past { + color: rgba(255, 0, 0, 0.8); + font-weight: bold; } -// @keyframes production-completion-1-blinker { -// 50% { -// } -// } .react-resizable { position: relative; diff --git a/client/src/components/production-list-columns/production-list-columns.data.js b/client/src/components/production-list-columns/production-list-columns.data.js index 00f279a66..d8d547665 100644 --- a/client/src/components/production-list-columns/production-list-columns.data.js +++ b/client/src/components/production-list-columns/production-list-columns.data.js @@ -109,7 +109,7 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => { state.sortedInfo.columnKey === "scheduled_completion" && state.sortedInfo.order, render: (text, record) => ( - + ), }, { @@ -156,7 +156,7 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => { state.sortedInfo.columnKey === "scheduled_delivery" && state.sortedInfo.order, render: (text, record) => ( - + ), }, { diff --git a/client/src/components/production-list-columns/production-list-columns.date.component.jsx b/client/src/components/production-list-columns/production-list-columns.date.component.jsx index 4381d3b52..1b201032e 100644 --- a/client/src/components/production-list-columns/production-list-columns.date.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.date.component.jsx @@ -8,7 +8,12 @@ import { DateFormatter } from "../../utils/DateFormatter"; import { useTranslation } from "react-i18next"; -export default function ProductionListDate({ record, field, time }) { +export default function ProductionListDate({ + record, + field, + time, + pastIndicator, +}) { const [updateAlert] = useMutation(UPDATE_JOB); const [visible, setVisible] = useState(false); const { t } = useTranslation(); @@ -31,6 +36,15 @@ export default function ProductionListDate({ record, field, time }) { }); }; + let className = ""; + if (pastIndicator) { + className = + !!record[field] && + ((moment().isSameOrAfter(moment(record[field]), "day") && + "production-completion-past") || + (moment().add(1, "day").isSame(moment(record[field]), "day") && + "production-completion-soon")); + } return (
{record[field]}
From 413400fa7164bc152225e67d7452a6985a37b327 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 19 Jan 2022 12:45:16 -0800 Subject: [PATCH 10/19] IO-1652 Printing tickets from tech console. --- .../tech-job-clock-in-form.container.jsx | 16 ++- .../tech-job-print-tickets.component.jsx | 125 ++++++++++++++++++ 2 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 client/src/components/tech-job-print-tickets/tech-job-print-tickets.component.jsx diff --git a/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.container.jsx b/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.container.jsx index 499f40af1..eeff6d2de 100644 --- a/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.container.jsx +++ b/client/src/components/tech-job-clock-in-form/tech-job-clock-in-form.container.jsx @@ -1,5 +1,5 @@ import { useMutation } from "@apollo/client"; -import { Button, Card, Form, notification } from "antd"; +import { Button, Card, Form, notification, Space } from "antd"; import axios from "axios"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; @@ -9,6 +9,7 @@ import { INSERT_NEW_TIME_TICKET } from "../../graphql/timetickets.queries"; import { selectTechnician } from "../../redux/tech/tech.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors"; import TechClockInComponent from "./tech-job-clock-in-form.component"; +import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component"; const mapStateToProps = createStructuredSelector({ technician: selectTechnician, @@ -71,9 +72,16 @@ export function TechClockInContainer({ technician, bodyshop }) { form.submit()} loading={loading}> - {t("timetickets.actions.clockin")} - + + + + } >
diff --git a/client/src/components/tech-job-print-tickets/tech-job-print-tickets.component.jsx b/client/src/components/tech-job-print-tickets/tech-job-print-tickets.component.jsx new file mode 100644 index 000000000..e09691a65 --- /dev/null +++ b/client/src/components/tech-job-print-tickets/tech-job-print-tickets.component.jsx @@ -0,0 +1,125 @@ +import { Button, Card, DatePicker, Form, Popover, Space } from "antd"; +import moment from "moment"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; +import DatePIckerRanges from "../../utils/DatePickerRanges"; +import { GenerateDocument } from "../../utils/RenderTemplate"; +import { TemplateList } from "../../utils/TemplateConstants"; + +const mapStateToProps = createStructuredSelector({ + technician: selectTechnician, +}); +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); +export default connect( + mapStateToProps, + mapDispatchToProps +)(TechJobPrintTickets); + +export function TechJobPrintTickets({ technician, event }) { + const { t } = useTranslation(); + + const [loading, setLoading] = useState(false); + const [form] = Form.useForm(); + const [visibility, setVisibility] = useState(false); + + useEffect(() => { + if (visibility && event) { + form.setFieldsValue(event); + } + }, [visibility, form, event]); + + const handleFinish = async (values) => { + logImEXEvent("schedule_manual_event"); + + setLoading(true); + const start = values.dates[0]; + const end = values.dates[1]; + + try { + await GenerateDocument( + { + name: TemplateList().timetickets_employee.key, + variables: { + ...(start + ? { start: moment(start).startOf("day").format("YYYY-MM-DD") } + : {}), + ...(end + ? { end: moment(end).endOf("day").format("YYYY-MM-DD") } + : {}), + ...(start ? { starttz: moment(start).startOf("day") } : {}), + ...(end ? { endtz: moment(end).endOf("day") } : {}), + + id: technician.id, + }, + }, + { + to: technician.email, + subject: TemplateList().timetickets_employee.subject, + }, + "p" + ); + } catch (error) { + console.log(error); + } finally { + setLoading(false); + setVisibility(false); + form.resetFields(); + } + }; + + const overlay = ( + +
+ + + + + + + + + + +
+
+ ); + + const handleClick = (e) => { + setVisibility(true); + }; + + return ( + + + + ); +} From 1966e91463e829a8dbc12564ea39b848946a0677 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 19 Jan 2022 14:09:24 -0800 Subject: [PATCH 11/19] IO-1644 Allow invoice date to be set on job closure. --- bodyshop_translations.babel | 21 +++++++++++++ client/src/graphql/jobs.queries.js | 1 + .../pages/jobs-close/jobs-close.component.jsx | 31 ++++++++++++++++++- client/src/translations/en_us/common.json | 1 + client/src/translations/es/common.json | 1 + client/src/translations/fr/common.json | 1 + 6 files changed, 55 insertions(+), 1 deletion(-) diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index f292acf4f..8de0a69f7 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -25469,6 +25469,27 @@ + + invoicedatefuture + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + kmoutnotgreaterthankmin false diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index 999d56224..8f927d4ff 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -1878,6 +1878,7 @@ export const QUERY_JOB_CLOSE_DETAILS = gql` scheduled_delivery actual_delivery scheduled_in + date_invoiced actual_in kmin kmout diff --git a/client/src/pages/jobs-close/jobs-close.component.jsx b/client/src/pages/jobs-close/jobs-close.component.jsx index 71d961c99..697211ee6 100644 --- a/client/src/pages/jobs-close/jobs-close.component.jsx +++ b/client/src/pages/jobs-close/jobs-close.component.jsx @@ -56,7 +56,7 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) { jobId: job.id, job: { status: bodyshop.md_ro_statuses.default_invoiced || "", - date_invoiced: new Date(), + date_invoiced: values.date_invoiced, actual_in: values.actual_in, actual_completion: values.actual_completion, actual_delivery: values.actual_delivery, @@ -119,6 +119,9 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) { actual_delivery: job.actual_delivery ? moment(job.actual_delivery) : job.scheduled_delivery && moment(job.scheduled_delivery), + date_invoiced: job.date_invoiced + ? moment(job.date_invoiced) + : moment(), kmin: job.kmin, kmout: job.kmout, dms_allocation: job.dms_allocation, @@ -219,6 +222,32 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) { > + ({ + validator(_, value) { + if (!bodyshop.cdk_dealerid) return Promise.resolve(); + if (!value || moment(value).isSameOrAfter(moment(), "day")) { + return Promise.resolve(); + } + + return Promise.reject( + new Error(t("jobs.labels.dms.invoicedatefuture")) + ); + }, + }), + ]} + > + + {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && ( Date: Wed, 19 Jan 2022 16:12:16 -0800 Subject: [PATCH 12/19] IO-1653 Include Other images when downloading --- .../jobs-document-gallery.download.component.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/components/jobs-documents-gallery/jobs-document-gallery.download.component.jsx b/client/src/components/jobs-documents-gallery/jobs-document-gallery.download.component.jsx index 957fe3344..4384118d8 100644 --- a/client/src/components/jobs-documents-gallery/jobs-document-gallery.download.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-document-gallery.download.component.jsx @@ -36,7 +36,7 @@ export function JobsDocumentsDownloadButton({ ); const imagesToDownload = [ ...galleryImages.images.filter((image) => image.isSelected), - // ...galleryImages.other.filter((image) => image.isSelected), + ...galleryImages.other.filter((image) => image.isSelected), ]; function downloadProgress(progressEvent) { @@ -123,6 +123,7 @@ export function JobsDocumentsDownloadButton({ a.click(); } }; + console.log("🚀 ~ file: jobs-document-gallery.download.component.jsx ~ line 131 ~ imagesToDownload", imagesToDownload) return ( <>