diff --git a/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx b/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx index 3691379a4..d86aefc00 100644 --- a/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx +++ b/client/src/components/accounting-payables-table/accounting-payables-table.component.jsx @@ -39,7 +39,8 @@ export default function AccountingPayablesTableComponent({ to={{ pathname: `/manage/shop/vendors`, search: queryString.stringify({ selectedvendor: record.vendor.id }), - }}> + }} + > {record.vendor.name} ), @@ -60,7 +61,8 @@ export default function AccountingPayablesTableComponent({ invoiceid: record.id, vendorid: record.vendor.id, }), - }}> + }} + > {record.invoice_number} ), @@ -160,7 +162,7 @@ export default function AccountingPayablesTableComponent({ /> @@ -168,10 +170,10 @@ export default function AccountingPayablesTableComponent({ ); }} dataSource={dataSource} - size='small' + size="small" pagination={{ position: "top", pageSize: 50 }} columns={columns} - rowKey='id' + rowKey="id" onChange={handleTableChange} rowSelection={{ onSelectAll: (selected, selectedRows) => 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 5e62f7b57..23237e69d 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 @@ -6,9 +6,12 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter"; import { alphaSort } from "../../utils/sorters"; import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component"; import { logImEXEvent } from "../../firebase/firebase.utils"; +import { JobsExportAllButton } from "../jobs-export-all-button/jobs-export-all-button.component"; export default function AccountingReceivablesTableComponent({ loading, jobs }) { const { t } = useTranslation(); + const [selectedJobs, setSelectedJobs] = useState([]); + const [transInProgress, setTransInProgress] = useState(false); const [state, setState] = useState({ sortedInfo: {}, @@ -178,8 +181,15 @@ export default function AccountingReceivablesTableComponent({ loading, jobs }) { loading={loading} title={() => { return ( -
- + + + setSelectedJobs(selectedRows.map((i) => i.id)), + onSelect: (record, selected, selectedRows, nativeEvent) => { + setSelectedJobs(selectedRows.map((i) => i.id)); + }, + getCheckboxProps: (record) => ({ + disabled: record.exported, + }), + selectedRowKeys: selectedJobs, + type: "checkbox", + }} />
); 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 f863c3f9d..f5a4a74ad 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 @@ -26,7 +26,7 @@ export function JobsCloseExportButton({ bodyshop, jobId, disabled }) { try { QbXmlResponse = await axios.post( "/accounting/qbxml/receivables", - { jobId: jobId }, + { jobIds: [jobId] }, { headers: { Authorization: `Bearer ${await auth.currentUser.getIdToken(true)}`, diff --git a/client/src/components/jobs-export-all-button/jobs-export-all-button.component.jsx b/client/src/components/jobs-export-all-button/jobs-export-all-button.component.jsx new file mode 100644 index 000000000..f945260d7 --- /dev/null +++ b/client/src/components/jobs-export-all-button/jobs-export-all-button.component.jsx @@ -0,0 +1,133 @@ +import { useMutation } from "@apollo/react-hooks"; +import { Button, notification } from "antd"; +import axios from "axios"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { auth } from "../../firebase/firebase.utils"; +import { UPDATE_JOB, UPDATE_JOBS } from "../../graphql/jobs.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { logImEXEvent } from "../../firebase/firebase.utils"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); + +export function JobsExportAllButton({ + bodyshop, + jobIds, + disabled, + loadingCallback, + completedCallback, +}) { + const { t } = useTranslation(); + const [updateJob] = useMutation(UPDATE_JOBS); + const [loading, setLoading] = useState(false); + const handleQbxml = async () => { + logImEXEvent("jobs_export_all"); + + setLoading(true); + let QbXmlResponse; + try { + QbXmlResponse = await axios.post( + "/accounting/qbxml/receivables", + { jobIds: jobIds }, + { + headers: { + Authorization: `Bearer ${await auth.currentUser.getIdToken(true)}`, + }, + } + ); + console.log("handle -> XML", QbXmlResponse); + } catch (error) { + console.log("Error getting QBXML from Server.", error); + notification["error"]({ + message: t("jobs.errors.exporting", { + error: "Unable to retrieve QBXML. " + JSON.stringify(error.message), + }), + }); + setLoading(false); + return; + } + + let PartnerResponse; + try { + PartnerResponse = await axios.post( + "http://localhost:1337/qb/", + // "http://609feaeae986.ngrok.io/qb/", + QbXmlResponse.data, + { + headers: { + Authorization: `Bearer ${await auth.currentUser.getIdToken(true)}`, + }, + } + ); + } catch (error) { + console.log("Error connecting to quickbooks or partner.", error); + notification["error"]({ + message: t("jobs.errors.exporting-partner"), + }); + setLoading(false); + return; + } + + console.log("PartnerResponse", PartnerResponse); + + //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); + 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("jobs.errors.exporting", { + error: ft.errorMessage || "", + }), + }) + ); + } + if (successfulTransactions.length > 0) { + const jobUpdateResponse = await updateJob({ + variables: { + jobIds: successfulTransactions.map((st) => st.id), + 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), + }), + }); + } + } + //Set the list of selected invoices to be nothing. + if (!!completedCallback) completedCallback([]); + if (!!loadingCallback) loadingCallback(false); + setLoading(false); + }; + + return ( + + ); +} + +export default connect(mapStateToProps, null)(JobsExportAllButton); diff --git a/server/accounting/qbxml/qbxml-receivables.js b/server/accounting/qbxml/qbxml-receivables.js index a46de76a1..b5e5efbcf 100644 --- a/server/accounting/qbxml/qbxml-receivables.js +++ b/server/accounting/qbxml/qbxml-receivables.js @@ -16,7 +16,7 @@ const { generateJobTier, generateOwnerTier, generateSourceTier } = QbXmlUtils; exports.default = async (req, res) => { const BearerToken = req.headers.authorization; - const { jobId } = req.body; + const { jobIds } = req.body; const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { headers: { @@ -27,51 +27,56 @@ exports.default = async (req, res) => { try { const result = await client .setHeaders({ Authorization: BearerToken }) - .request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, { id: jobId }); - const { jobs_by_pk } = result; - const { bodyshop } = jobs_by_pk; + .request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, { ids: jobIds }); + console.log("result", result); + const { jobs } = result; + const { bodyshops } = result; const QbXmlToExecute = []; - //Is this a two tier, or 3 tier setup? - const isThreeTier = bodyshop.accountingconfig.tiers === 3; - const twoTierPref = bodyshop.accountingconfig.twotierpref; + const bodyshop = bodyshops[0]; + + jobs.map((jobs_by_pk) => { + //Is this a two tier, or 3 tier setup? + const isThreeTier = bodyshop.accountingconfig.tiers === 3; + const twoTierPref = bodyshop.accountingconfig.twotierpref; + + if (isThreeTier) { + QbXmlToExecute.push({ + id: jobs_by_pk.id, + okStatusCodes: ["0", "3100"], + qbxml: generateSourceCustomerQbxml(jobs_by_pk, bodyshop), // Create the source customer. + }); + } - if (isThreeTier) { QbXmlToExecute.push({ - id: jobId, + id: jobs_by_pk.id, okStatusCodes: ["0", "3100"], - qbxml: generateSourceCustomerQbxml(jobs_by_pk, bodyshop), // Create the source customer. + qbxml: generateJobQbxml( + jobs_by_pk, + bodyshop, + isThreeTier, + 2, + twoTierPref + ), }); - } - QbXmlToExecute.push({ - id: jobId, - okStatusCodes: ["0", "3100"], - qbxml: generateJobQbxml( - jobs_by_pk, - bodyshop, - isThreeTier, - 2, - twoTierPref - ), - }); - - QbXmlToExecute.push({ - id: jobId, - okStatusCodes: ["0", "3100"], - qbxml: generateJobQbxml( - jobs_by_pk, - bodyshop, - isThreeTier, - 3, - twoTierPref - ), - }); - //Generate the actual invoice. - QbXmlToExecute.push({ - id: jobId, - okStatusCodes: ["0"], - qbxml: generateInvoiceQbxml(jobs_by_pk, bodyshop), + QbXmlToExecute.push({ + id: jobs_by_pk.id, + okStatusCodes: ["0", "3100"], + qbxml: generateJobQbxml( + jobs_by_pk, + bodyshop, + isThreeTier, + 3, + twoTierPref + ), + }); + //Generate the actual invoice. + QbXmlToExecute.push({ + id: jobs_by_pk.id, + okStatusCodes: ["0"], + qbxml: generateInvoiceQbxml(jobs_by_pk, bodyshop), + }); }); res.status(200).json(QbXmlToExecute); diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index e4f379443..caff3d08d 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -41,8 +41,8 @@ mutation UPDATE_MESSAGE($msid: String!, $fields: messages_set_input!) { `; exports.QUERY_JOBS_FOR_RECEIVABLES_EXPORT = ` -query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($id: uuid!) { - jobs_by_pk(id: $id) { +query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) { + jobs(where: {id: {_in: $ids}}) { id job_totals date_invoiced @@ -59,14 +59,14 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($id: uuid!) { ownr_city ownr_st ins_co_nm - owner{ + owner { accountingid } - bodyshop { - id - md_responsibility_centers - accountingconfig - } + } + bodyshops(where: {associations: {active: {_eq: true}}}) { + id + md_responsibility_centers + accountingconfig } } `;