diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 72edcd926..98a6e9492 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -11536,6 +11536,27 @@ + + viewallocations + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + @@ -11892,6 +11913,27 @@ + + accounting-payables + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + accounting-receivables false diff --git a/client/src/components/accounting-receivables-table/accounting-receivables-table.component.jsx b/client/src/components/accounting-receivables-table/accounting-receivables-table.component.jsx index 6af18698b..67f1e1bd2 100644 --- a/client/src/components/accounting-receivables-table/accounting-receivables-table.component.jsx +++ b/client/src/components/accounting-receivables-table/accounting-receivables-table.component.jsx @@ -1,4 +1,4 @@ -import { Input, Table } from "antd"; +import { Input, Table, Button } from "antd"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; @@ -123,13 +123,14 @@ export default function AccountingReceivablesTableComponent({ loading, jobs }) { sorter: (a, b) => a.clm_total - b.clm_total, render: (text, record) => ( -
+
- - {JSON.stringify(record.date_exported)} + + +
), }, diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index 85ae5c631..17c538f9f 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -229,6 +229,11 @@ function Header({ {t("menus.header.accounting-receivables")} + + + {t("menus.header.accounting-payables")} + + diff --git a/client/src/components/invoice-export-button/invoice-export-button.component.jsx b/client/src/components/invoice-export-button/invoice-export-button.component.jsx index e9ee6f068..724bd5a0b 100644 --- a/client/src/components/invoice-export-button/invoice-export-button.component.jsx +++ b/client/src/components/invoice-export-button/invoice-export-button.component.jsx @@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { auth } from "../../firebase/firebase.utils"; -import { UPDATE_INVOICE } from "../../graphql/invoices.queries"; +import { UPDATE_INVOICES } from "../../graphql/invoices.queries"; import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -19,7 +19,7 @@ export function InvoiceExportButton({ loadingCallback, }) { const { t } = useTranslation(); - const [updateInvoice] = useMutation(UPDATE_INVOICE); + const [updateInvoice] = useMutation(UPDATE_INVOICES); const [loading, setLoading] = useState(false); const handleQbxml = async () => { @@ -51,6 +51,7 @@ export function InvoiceExportButton({ } let PartnerResponse; + try { PartnerResponse = await axios.post( "http://e9c5a8ed9079.ngrok.io/qb/", @@ -66,26 +67,42 @@ export function InvoiceExportButton({ return; } - const invoiceUpdateResponse = await updateInvoice({ - variables: { - invoiceId: invoiceId, - invoice: { - exported: true, - exported_at: new Date(), + console.log("handleQbxml -> PartnerResponse", PartnerResponse); + const failedTransactions = PartnerResponse.data.filter((r) => !r.success); + const successfulTransactions = PartnerResponse.data.filter( + (r) => r.success + ); + if (failedTransactions.length > 0) { + //Uh oh. At least one was no good. + failedTransactions.map((ft) => + notification["error"]({ + message: t("invoices.errors.exporting", { + error: ft.errorMessage || "", + }), + }) + ); + } + if (successfulTransactions.length > 0) { + const invoiceUpdateResponse = await updateInvoice({ + variables: { + invoiceIdList: successfulTransactions.map((st) => st.id), + invoice: { + exported: true, + exported_at: new Date(), + }, }, - }, - }); - - if (!!!invoiceUpdateResponse.errors) { - notification["success"]({ - message: t("jobs.successes.exported"), - }); - } else { - notification["error"]({ - message: t("jobs.errors.exporting", { - error: JSON.stringify(invoiceUpdateResponse.error), - }), }); + if (!!!invoiceUpdateResponse.errors) { + notification["success"]({ + message: t("jobs.successes.exported"), + }); + } else { + notification["error"]({ + message: t("jobs.errors.exporting", { + error: JSON.stringify(invoiceUpdateResponse.error), + }), + }); + } } if (!!loadingCallback) loadingCallback(false); 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 405e9c877..cbaaff6bf 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 @@ -44,7 +44,7 @@ export function JobsCloseExportButton({ bodyshop, jobId, disabled }) { let PartnerResponse; try { PartnerResponse = await axios.post( - "http://localhost:1337/qb/", + "http://e9c5a8ed9079.ngrok.io/qb/", QbXmlResponse.data, { headers: { @@ -62,26 +62,40 @@ export function JobsCloseExportButton({ bodyshop, jobId, disabled }) { } console.log("PartnerResponse", PartnerResponse); - 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"), - }); + //Check to see if any of them failed. If they didn't don't execute the update. + const failedTransactions = PartnerResponse.data.filter((r) => !r.success); + if (failedTransactions.length > 0) { + //Uh oh. At least one was no good. + failedTransactions.map((ft) => + notification["error"]({ + message: t("jobs.errors.exporting", { + error: ft.errorMessage || "", + }), + }) + ); } 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/graphql/invoices.queries.js b/client/src/graphql/invoices.queries.js index 539a95339..456ef02be 100644 --- a/client/src/graphql/invoices.queries.js +++ b/client/src/graphql/invoices.queries.js @@ -135,3 +135,17 @@ export const UPDATE_INVOICE = gql` } } `; +export const UPDATE_INVOICES = gql` + mutation UPDATE_INVOICES( + $invoiceIdList: [uuid!]! + $invoice: invoices_set_input! + ) { + update_invoices(where: { id: { _in: $invoiceIdList } }, _set: $invoice) { + returning { + id + exported + exported_at + } + } + } +`; diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 1ecccb897..0226308ce 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -706,7 +706,8 @@ "suspense": "Suspense", "total_repairs": "Total Repairs", "totals": "Totals", - "vehicle_info": "Vehicle" + "vehicle_info": "Vehicle", + "viewallocations": "View Allocations" }, "successes": { "addedtoproduction": "Job added to production board.", @@ -731,6 +732,7 @@ }, "header": { "accounting": "Accounting", + "accounting-payables": "Payables", "accounting-receivables": "Receivables", "activejobs": "Active Jobs", "alljobs": "All Jobs", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 123be1be9..8ad37cc7d 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -706,7 +706,8 @@ "suspense": "", "total_repairs": "", "totals": "", - "vehicle_info": "Vehículo" + "vehicle_info": "Vehículo", + "viewallocations": "" }, "successes": { "addedtoproduction": "", @@ -731,6 +732,7 @@ }, "header": { "accounting": "", + "accounting-payables": "", "accounting-receivables": "", "activejobs": "Empleos activos", "alljobs": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index cbb57cfb5..b5f824dab 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -706,7 +706,8 @@ "suspense": "", "total_repairs": "", "totals": "", - "vehicle_info": "Véhicule" + "vehicle_info": "Véhicule", + "viewallocations": "" }, "successes": { "addedtoproduction": "", @@ -731,6 +732,7 @@ }, "header": { "accounting": "", + "accounting-payables": "", "accounting-receivables": "", "activejobs": "Emplois actifs", "alljobs": "", diff --git a/server/accounting/qbxml/qbxml-payables.js b/server/accounting/qbxml/qbxml-payables.js index 478e61987..edcf34180 100644 --- a/server/accounting/qbxml/qbxml-payables.js +++ b/server/accounting/qbxml/qbxml-payables.js @@ -32,7 +32,11 @@ exports.default = async (req, res) => { const QbXmlToExecute = []; invoices.map((i) => { - QbXmlToExecute.push(generateBill(i)); + QbXmlToExecute.push({ + id: i.id, + okStatusCodes: ["0"], + qbxml: generateBill(i), + }); }); //For each invoice. diff --git a/server/accounting/qbxml/qbxml-receivables.js b/server/accounting/qbxml/qbxml-receivables.js index e65fd1d47..1c16f5da7 100644 --- a/server/accounting/qbxml/qbxml-receivables.js +++ b/server/accounting/qbxml/qbxml-receivables.js @@ -33,14 +33,30 @@ exports.default = async (req, res) => { //Is this a two tier, or 3 tier setup? const isThreeTier = bodyshop.accountingconfig.tiers === 3; if (isThreeTier) { - QbXmlToExecute.push( - generateSourceCustomerQbxml(jobs_by_pk, bodyshop) // Create the source customer. - ); + QbXmlToExecute.push({ + id: jobId, + okStatusCodes: ["0", "3100"], + qbxml: generateSourceCustomerQbxml(jobs_by_pk, bodyshop), // Create the source customer. + }); } - QbXmlToExecute.push(generateJobQbxml(jobs_by_pk, bodyshop, isThreeTier, 2)); - QbXmlToExecute.push(generateJobQbxml(jobs_by_pk, bodyshop, isThreeTier, 3)); + + QbXmlToExecute.push({ + id: jobId, + okStatusCodes: ["0", "3100"], + qbxml: generateJobQbxml(jobs_by_pk, bodyshop, isThreeTier, 2), + }); + + QbXmlToExecute.push({ + id: jobId, + okStatusCodes: ["0", "3100"], + qbxml: generateJobQbxml(jobs_by_pk, bodyshop, isThreeTier, 3), + }); //Generate the actual invoice. - QbXmlToExecute.push(generateInvoiceQbxml(jobs_by_pk, bodyshop)); + QbXmlToExecute.push({ + id: jobId, + okStatusCodes: ["0"], + qbxml: generateInvoiceQbxml(jobs_by_pk, bodyshop), + }); res.status(200).json(QbXmlToExecute); } catch (error) { @@ -196,7 +212,6 @@ const generateInvoiceQbxml = (jobs_by_pk, bodyshop) => { FullName: bodyshop.md_responsibility_centers.taxes.federal.accountitem, }, Desc: bodyshop.md_responsibility_centers.taxes.federal.accountdesc, - //Quantity: 1, Amount: federal_tax.toFormat(DineroQbFormat), }); } @@ -207,7 +222,6 @@ const generateInvoiceQbxml = (jobs_by_pk, bodyshop) => { FullName: bodyshop.md_responsibility_centers.taxes.state.accountitem, }, Desc: bodyshop.md_responsibility_centers.taxes.state.accountdesc, - //Quantity: 1, Amount: state_tax.toFormat(DineroQbFormat), }); } @@ -218,7 +232,6 @@ const generateInvoiceQbxml = (jobs_by_pk, bodyshop) => { FullName: bodyshop.md_responsibility_centers.taxes.local.accountitem, }, Desc: bodyshop.md_responsibility_centers.taxes.local.accountdesc, - //Quantity: 1, Amount: local_tax.toFormat(DineroQbFormat), }); } diff --git a/server/accounting/qbxml/qbxmlObject.json b/server/accounting/qbxml/qbxmlObject.json new file mode 100644 index 000000000..85d61be8c --- /dev/null +++ b/server/accounting/qbxml/qbxmlObject.json @@ -0,0 +1,5 @@ +{ + "id": "12345", + "okStatusCodes": ["0", "31400"], + "qbxml": "the qbxml string" +}