diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 523753f92..bda94d8f7 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -20162,6 +20162,32 @@ payments + + errors + + + exporting + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + fields diff --git a/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx b/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx index dd0de0185..c2a1f9702 100644 --- a/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx +++ b/client/src/components/accounting-payments-table/accounting-payments-table.component.jsx @@ -153,19 +153,20 @@ export default function AccountingPayablesTableComponent({ loading={loading} title={() => { return ( -
+
+ -
); }} diff --git a/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.container.jsx b/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.container.jsx index 8db6c5182..c7442188d 100644 --- a/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.container.jsx +++ b/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.container.jsx @@ -38,6 +38,7 @@ function JobLinesUpsertModalContainer({ .then((r) => { if (jobLineEditModal.actions.refetch) jobLineEditModal.actions.refetch(); + //Need to recalcuate totals. toggleModalVisible(); notification["success"]({ message: t("joblines.successes.created"), diff --git a/client/src/components/jobs-close-export-button/jobs-close-export-button.component.jsx b/client/src/components/jobs-close-export-button/jobs-close-export-button.component.jsx index f5a4a74ad..ab3d9c53d 100644 --- a/client/src/components/jobs-close-export-button/jobs-close-export-button.component.jsx +++ b/client/src/components/jobs-close-export-button/jobs-close-export-button.component.jsx @@ -80,27 +80,32 @@ export function JobsCloseExportButton({ bodyshop, jobId, disabled }) { }) ); } else { - const jobUpdateResponse = await updateJob({ - variables: { - jobId: jobId, - job: { - status: bodyshop.md_ro_statuses.default_exported || "Exported*", - date_exported: new Date(), - }, - }, - }); - if (!!!jobUpdateResponse.errors) { - notification["success"]({ - message: t("jobs.successes.exported"), - }); - } else { - notification["error"]({ - message: t("jobs.errors.exporting", { - error: JSON.stringify(jobUpdateResponse.error), - }), - }); - } + + // const jobUpdateResponse = await updateJob({ + // variables: { + // jobId: jobId, + // job: { + // status: bodyshop.md_ro_statuses.default_exported || "Exported*", + // date_exported: new Date(), + // }, + // }, + // }); + + // if (!!!jobUpdateResponse.errors) { + // notification["success"]({ + // message: t("jobs.successes.exported"), + // }); + // } else { + // notification["error"]({ + // message: t("jobs.errors.exporting", { + // error: JSON.stringify(jobUpdateResponse.error), + // }), + // }); + // } + + + } setLoading(false); diff --git a/client/src/components/jobs-close-lines/jobs-close-lines.component.jsx b/client/src/components/jobs-close-lines/jobs-close-lines.component.jsx index 2d7353dbe..13be564b4 100644 --- a/client/src/components/jobs-close-lines/jobs-close-lines.component.jsx +++ b/client/src/components/jobs-close-lines/jobs-close-lines.component.jsx @@ -73,14 +73,21 @@ export function JobsCloseLines({ bodyshop, joblines }) { name={[field.name, "profitcenter_part"]} rules={[ { - required: - !!joblines[index].part_type && - !!joblines[index].act_price, + required: !!joblines[index].act_price, message: t("general.validation.required"), }, ]} > - + option.children + .toLowerCase() + .indexOf(input.toLowerCase()) >= 0 + } + > {bodyshop.md_responsibility_centers.profits.map((p) => ( {p.name} @@ -99,7 +106,16 @@ export function JobsCloseLines({ bodyshop, joblines }) { }, ]} > - + option.children + .toLowerCase() + .indexOf(input.toLowerCase()) >= 0 + } + > {bodyshop.md_responsibility_centers.profits.map((p) => ( {p.name} diff --git a/client/src/graphql/jobs-lines.queries.js b/client/src/graphql/jobs-lines.queries.js index 2f4d849e3..1af20d349 100644 --- a/client/src/graphql/jobs-lines.queries.js +++ b/client/src/graphql/jobs-lines.queries.js @@ -151,7 +151,6 @@ export const GET_JOB_LINES_TO_ENTER_INVOICE = gql` // } export const generateJobLinesUpdatesForInvoicing = (joblines) => { - console.log("generateJobLinesUpdatesForInvoicing -> joblines", joblines); const updates = joblines.reduce((acc, jl, idx) => { return ( acc + diff --git a/client/src/pages/jobs-close/jobs-close.component.jsx b/client/src/pages/jobs-close/jobs-close.component.jsx index 7543d2da5..3850307e7 100644 --- a/client/src/pages/jobs-close/jobs-close.component.jsx +++ b/client/src/pages/jobs-close/jobs-close.component.jsx @@ -1,5 +1,5 @@ import { Button, Form, Space, notification, Popconfirm } from "antd"; -import React from "react"; +import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -24,22 +24,33 @@ export function JobsCloseComponent({ job, bodyshop }) { const client = useApolloClient(); const history = useHistory(); const [closeJob] = useMutation(UPDATE_JOB); + const [loading, setLoading] = useState(false); // useEffect(() => { // //if (job && form) form.setFields({ joblines: job.joblines }); // }, [job, form]); const handleFinish = async (values) => { - console.log(values); - + setLoading(true); const result = await client.mutate({ mutation: generateJobLinesUpdatesForInvoicing(values.joblines), }); - console.log("result.data", result.data); + if (!!!result.errors) { + notification["success"]({ message: t("job.successes.saved") }); + form.resetFields(); + } else { + notification["error"]({ + message: t("job.errors.saving", { + error: JSON.stringify(result.errors), + }), + }); + } form.resetFields(); form.resetFields(); + setLoading(false); }; const handleClose = async () => { + setLoading(true); const result = await closeJob({ variables: { jobId: job.id, @@ -54,6 +65,7 @@ export function JobsCloseComponent({ job, bodyshop }) { notification["success"]({ message: t("job.successes.closed") }); history.push(`/manage/jobs/${job.id}`); } else { + setLoading(false); notification["error"]({ message: t("job.errors.closing", { error: JSON.stringify(result.errors), @@ -69,6 +81,7 @@ export function JobsCloseComponent({ job, bodyshop }) { form={form} onFinish={handleFinish} initialValues={{ joblines: job.joblines }} + scrollToFirstError > - @@ -87,7 +100,9 @@ export function JobsCloseComponent({ job, bodyshop }) { cancelText={t("general.labels.no")} title={t("jobs.labels.closeconfirm")} > - + diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index dad225565..69afdcf50 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1242,6 +1242,9 @@ } }, "payments": { + "errors": { + "exporting": "Error exporting payment(s). {{error}}" + }, "fields": { "amount": "Amount", "created_at": "Created At", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 26837db3e..93ded7d72 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -1242,6 +1242,9 @@ } }, "payments": { + "errors": { + "exporting": "" + }, "fields": { "amount": "", "created_at": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index c558890bf..5dfc24669 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -1242,6 +1242,9 @@ } }, "payments": { + "errors": { + "exporting": "" + }, "fields": { "amount": "", "created_at": "", diff --git a/server/accounting/qbxml/qbxml-payments.js b/server/accounting/qbxml/qbxml-payments.js index 857348463..ab1548bb2 100644 --- a/server/accounting/qbxml/qbxml-payments.js +++ b/server/accounting/qbxml/qbxml-payments.js @@ -76,11 +76,14 @@ const generatePayment = (payment) => { TotalAmount: Dinero({ amount: Math.round(payment.amount * 100), }).toFormat(DineroQbFormat), + PaymentMethodRef: { + FullName: payment.type, + }, Memo: `RO ${payment.job.ro_number || ""} OWNER ${ payment.job.ownr_fn || "" } ${payment.job.ownr_ln || ""} ${payment.job.ownr_co_nm || ""} ${ - payment.stripeid - }`, + payment.stripeid || "" + } ${payment.payer ? ` PAID BY ${payment.payer}` : ""}`, IsAutoApply: true, // AppliedToTxnAdd:{ // T diff --git a/server/accounting/qbxml/qbxml-receivables.js b/server/accounting/qbxml/qbxml-receivables.js index b5e5efbcf..9b09c4238 100644 --- a/server/accounting/qbxml/qbxml-receivables.js +++ b/server/accounting/qbxml/qbxml-receivables.js @@ -28,7 +28,6 @@ exports.default = async (req, res) => { const result = await client .setHeaders({ Authorization: BearerToken }) .request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, { ids: jobIds }); - console.log("result", result); const { jobs } = result; const { bodyshops } = result; const QbXmlToExecute = []; @@ -174,44 +173,74 @@ const generateJobQbxml = ( const generateInvoiceQbxml = (jobs_by_pk, bodyshop) => { //Build the Invoice XML file. const InvoiceLineAdd = []; - const invoice_allocation = jobs_by_pk.invoice_allocation; - Object.keys(invoice_allocation.partsAllocations).forEach( - (partsAllocationKey) => { - if ( - !!!invoice_allocation.partsAllocations[partsAllocationKey].allocations - ) - return; - invoice_allocation.partsAllocations[ - partsAllocationKey - ].allocations.forEach((alloc) => { - InvoiceLineAdd.push( - generateInvoiceLine( - jobs_by_pk, - alloc, - bodyshop.md_responsibility_centers - ) - ); + const responsibilityCenters = bodyshop.md_responsibility_centers; + + jobs_by_pk.joblines.map((jobline) => { + if (jobline.profitcenter_part && jobline.act_price) { + const DineroAmount = Dinero({ + amount: Math.round(jobline.act_price * 100), }); - } - ); - Object.keys(invoice_allocation.labMatAllocations).forEach((AllocationKey) => { - if (!!!invoice_allocation.labMatAllocations[AllocationKey].allocations) - return; - invoice_allocation.labMatAllocations[AllocationKey].allocations.forEach( - (alloc) => { - InvoiceLineAdd.push( - generateInvoiceLine( - jobs_by_pk, - alloc, - bodyshop.md_responsibility_centers - ) + const account = responsibilityCenters.profits.find( + (i) => jobline.profitcenter_part.toLowerCase() === i.name.toLowerCase() + ); + + if (!!!account) { + throw new Error( + `A matching account does not exist for the allocation. Center: ${center}` ); } - ); + + InvoiceLineAdd.push({ + ItemRef: { FullName: account.accountitem }, + Desc: `${account.accountdesc} - ${jobline.line_desc}`, + Quantity: jobline.part_qty, + Rate: DineroAmount.toFormat(DineroQbFormat), + //Amount: DineroAmount.toFormat(DineroQbFormat), + SalesTaxCodeRef: { + FullName: "E", + }, + }); + } + if ( + jobline.profitcenter_labor && + jobline.mod_lb_hrs && + jobline.mod_lb_hrs > 0 + ) { + const DineroAmount = Dinero({ + amount: Math.round( + jobs_by_pk[`rate_${jobline.mod_lbr_ty.toLowerCase()}`] * 100 + ), + }); + console.log( + "Rate", + jobline.mod_lbr_ty, + jobs_by_pk[`rate_${jobline.mod_lbr_ty.toLowerCase()}`] + ); + const account = responsibilityCenters.profits.find( + (i) => jobline.profitcenter_labor.toLowerCase() === i.name.toLowerCase() + ); + + if (!!!account) { + throw new Error( + `A matching account does not exist for the allocation. Center: ${center}` + ); + } + + InvoiceLineAdd.push({ + ItemRef: { FullName: account.accountitem }, + Desc: `${account.accountdesc} - ${jobline.op_code_desc} ${jobline.line_desc}`, + Quantity: jobline.mod_lb_hrs, + Rate: DineroAmount.toFormat(DineroQbFormat), + //Amount: DineroAmount.toFormat(DineroQbFormat), + SalesTaxCodeRef: { + FullName: "E", + }, + }); + } }); //Add tax lines - const job_totals = JSON.parse(jobs_by_pk.job_totals); + const job_totals = jobs_by_pk.job_totals; const federal_tax = Dinero(job_totals.totals.federal_tax); const state_tax = Dinero(job_totals.totals.state_tax); @@ -288,31 +317,32 @@ const generateInvoiceQbxml = (jobs_by_pk, bodyshop) => { .end({ pretty: true }); const invoiceQbxml_Full = QbXmlUtils.addQbxmlHeader(invoiceQbxml_partial); + console.log("invoiceQbxml_Full", invoiceQbxml_Full); return invoiceQbxml_Full; }; -const generateInvoiceLine = (job, allocation, responsibilityCenters) => { - const { amount, center } = allocation; - const DineroAmount = Dinero(amount); - const account = responsibilityCenters.profits.find( - (i) => i.name.toLowerCase() === center.toLowerCase() - ); +// const generateInvoiceLine = (job, allocation, responsibilityCenters) => { +// const { amount, center } = allocation; +// const DineroAmount = Dinero(amount); +// const account = responsibilityCenters.profits.find( +// (i) => i.name.toLowerCase() === center.toLowerCase() +// ); - if (!!!account) { - throw new Error( - `A matching account does not exist for the allocation. Center: ${center}` - ); - } +// if (!!!account) { +// throw new Error( +// `A matching account does not exist for the allocation. Center: ${center}` +// ); +// } - return { - ItemRef: { FullName: account.accountitem }, - Desc: account.accountdesc, - Quantity: 1, - //Rate: 100, - Amount: DineroAmount.toFormat(DineroQbFormat), - SalesTaxCodeRef: { - FullName: "E", - }, - }; -}; +// return { +// ItemRef: { FullName: account.accountitem }, +// Desc: account.accountdesc, +// Quantity: 1, +// //Rate: 100, +// Amount: DineroAmount.toFormat(DineroQbFormat), +// SalesTaxCodeRef: { +// FullName: "E", +// }, +// }; +// }; diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index caff3d08d..a0e5b4135 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -59,9 +59,45 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) { ownr_city ownr_st ins_co_nm + job_totals + rate_la1 + rate_la2 + rate_la3 + rate_la4 + rate_laa + rate_lab + rate_lad + rate_lae + rate_laf + rate_lag + rate_lam + rate_lar + rate_las + rate_lau + rate_ma2s + rate_ma2t + rate_ma3s + rate_mabl + rate_macs + rate_mahw + rate_mapa + rate_mash + rate_matd owner { accountingid } + joblines{ + id + line_desc + part_type + act_price + mod_lb_hrs + mod_lbr_ty + part_qty + op_code_desc + profitcenter_labor + profitcenter_part + } } bodyshops(where: {associations: {active: {_eq: true}}}) { id @@ -136,6 +172,8 @@ exports.QUERY_PAYMENTS_FOR_EXPORT = ` stripeid exportedat stripeid + type + payer } } `;