From fff9073f9d02c9a338333f7610f37e13206bae4c Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Tue, 12 Oct 2021 16:54:28 -0700 Subject: [PATCH 01/23] IO-256 Add vendor credits. --- server/accounting/qbo/qbo-payables.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/server/accounting/qbo/qbo-payables.js b/server/accounting/qbo/qbo-payables.js index ffe60b373..c68531b49 100644 --- a/server/accounting/qbo/qbo-payables.js +++ b/server/accounting/qbo/qbo-payables.js @@ -72,6 +72,7 @@ exports.default = async (req, res) => { bill, vendorRecord ); + ret.push({ billid: bill.id, success: true }); } catch (error) { ret.push({ @@ -112,7 +113,7 @@ async function QueryVendorRecord(oauthClient, req, bill) { ); } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { - error, + error: JSON.stringify(error), method: "QueryVendorRecord", }); throw error; @@ -135,7 +136,7 @@ async function InsertVendorRecord(oauthClient, req, bill) { return result && result.Vendor; } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { - error, + error: JSON.stringify(error), method: "InsertVendorRecord", }); throw error; @@ -150,7 +151,7 @@ async function InsertBill(oauthClient, req, bill, vendor) { value: vendor.Id, }, TxnDate: moment(bill.date).format("YYYY-MM-DD"), - DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"), + //DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"), DocNumber: bill.invoice_number, ...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}), @@ -170,7 +171,10 @@ async function InsertBill(oauthClient, req, bill, vendor) { }; try { const result = await oauthClient.makeApiCall({ - url: urlBuilder(req.cookies.qbo_realmId, "bill"), + url: urlBuilder( + req.cookies.qbo_realmId, + bill.is_credit_memo ? "vendorcredit" : "bill" + ), method: "POST", headers: { "Content-Type": "application/json", @@ -181,7 +185,7 @@ async function InsertBill(oauthClient, req, bill, vendor) { return result && result.Bill; } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { - error, + error: JSON.stringify(error), method: "InsertBill", }); throw error; @@ -231,6 +235,7 @@ const generateBillLine = ( .toFormat(DineroQbFormat), }; }; + async function QueryMetaData(oauthClient, req) { const accounts = await oauthClient.makeApiCall({ url: urlBuilder( From 4d52a5c44a9bfcde871975a013da5d0ef2d69f07 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Tue, 12 Oct 2021 19:07:43 -0700 Subject: [PATCH 02/23] IO-256 Exporting of payments --- .../accounting-payments-table.component.jsx | 23 +- .../owner-find-modal.container.jsx | 4 +- .../payment-export-button.component.jsx | 102 ++++--- .../payments-export-all-button.component.jsx | 72 +++-- server.js | 1 + server/accounting/qbo/qbo-payables.js | 9 +- server/accounting/qbo/qbo-payments.js | 274 ++++++++++++++++++ server/accounting/qbo/qbo-receivables.js | 20 +- server/accounting/qbo/qbo.js | 1 + server/accounting/qbxml/qbxml-payments.js | 3 - 10 files changed, 415 insertions(+), 94 deletions(-) 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 2edc4a0a4..53a51bdae 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 @@ -8,8 +8,26 @@ import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter"; import { alphaSort, dateSort } from "../../utils/sorters"; import PaymentExportButton from "../payment-export-button/payment-export-button.component"; import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component"; +import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; -export default function AccountingPayablesTableComponent({ +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); + +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); + +export default connect( + mapStateToProps, + mapDispatchToProps +)(AccountingPayablesTableComponent); + +export function AccountingPayablesTableComponent({ + bodyshop, loading, payments, }) { @@ -163,6 +181,9 @@ export default function AccountingPayablesTableComponent({ loadingCallback={setTransInProgress} completedCallback={setSelectedPayments} /> + {bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && ( + + )} { logImEXEvent("accounting_payment_export"); - setLoading(true); - if (!!loadingCallback) loadingCallback(true); - - let QbXmlResponse; - try { - QbXmlResponse = await axios.post( - "/accounting/qbxml/payments", - { payments: [paymentId] }, - { - headers: { - Authorization: `Bearer ${await auth.currentUser.getIdToken()}`, - }, - } - ); - console.log("handle -> XML", QbXmlResponse); - } catch (error) { - console.log("Error getting QBXML from Server.", error); - notification["error"]({ - message: t("payments.errors.exporting", { - error: "Unable to retrieve QBXML. " + JSON.stringify(error.message), - }), - }); - if (loadingCallback) loadingCallback(false); - setLoading(false); - return; - } - + //Check if it's a QBO Setup. let PartnerResponse; - - try { - PartnerResponse = await axios.post( - "http://localhost:1337/qb/", - //"http://609feaeae986.ngrok.io/qb/", - QbXmlResponse.data - ); - } catch (error) { - console.log("Error connecting to quickbooks or partner.", error); - notification["error"]({ - message: t("payments.errors.exporting-partner"), + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { + PartnerResponse = await axios.post(`/qbo/payments`, { + withCredentials: true, + payments: [paymentId], }); - if (!!loadingCallback) loadingCallback(false); - setLoading(false); - return; + } else { + //Default is QBD + + if (!!loadingCallback) loadingCallback(true); + + let QbXmlResponse; + try { + QbXmlResponse = await axios.post( + "/accounting/qbxml/payments", + { payments: [paymentId] }, + { + headers: { + Authorization: `Bearer ${await auth.currentUser.getIdToken()}`, + }, + } + ); + console.log("handle -> XML", QbXmlResponse); + } catch (error) { + console.log("Error getting QBXML from Server.", error); + notification["error"]({ + message: t("payments.errors.exporting", { + error: "Unable to retrieve QBXML. " + JSON.stringify(error.message), + }), + }); + if (loadingCallback) loadingCallback(false); + setLoading(false); + return; + } + + try { + PartnerResponse = await axios.post( + "http://localhost:1337/qb/", + QbXmlResponse.data + ); + } catch (error) { + console.log("Error connecting to quickbooks or partner.", error); + notification["error"]({ + message: t("payments.errors.exporting-partner"), + }); + if (!!loadingCallback) loadingCallback(false); + setLoading(false); + return; + } } 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) => @@ -123,7 +132,14 @@ export function PaymentExportButton({ const paymentUpdateResponse = await updatePayment({ variables: { - paymentIdList: [paymentId], + paymentIdList: successfulTransactions.map( + (st) => + st[ + bodyshop.accountingconfig && bodyshop.accountingconfig.qbo + ? "paymentid" + : "id" + ] + ), payment: { exportedat: new Date(), }, diff --git a/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx b/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx index 693a14339..3e4085c44 100644 --- a/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx +++ b/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx @@ -33,42 +33,50 @@ export function PaymentsExportAllButton({ const handleQbxml = async () => { setLoading(true); if (!!loadingCallback) loadingCallback(true); - - let QbXmlResponse; - try { - QbXmlResponse = await axios.post("/accounting/qbxml/payments", { + let PartnerResponse; + if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { + PartnerResponse = await axios.post(`/qbo/payments`, { + withCredentials: true, payments: paymentIds, }); - } catch (error) { - console.log("Error getting QBXML from Server.", error); - notification["error"]({ - message: t("payments.errors.exporting", { - error: "Unable to retrieve QBXML. " + JSON.stringify(error.message), - }), - }); - if (loadingCallback) loadingCallback(false); - setLoading(false); - return; + } else { + let QbXmlResponse; + try { + QbXmlResponse = await axios.post("/accounting/qbxml/payments", { + payments: paymentIds, + }); + } catch (error) { + console.log("Error getting QBXML from Server.", error); + notification["error"]({ + message: t("payments.errors.exporting", { + error: "Unable to retrieve QBXML. " + JSON.stringify(error.message), + }), + }); + if (loadingCallback) loadingCallback(false); + setLoading(false); + return; + } + + try { + PartnerResponse = await axios.post( + "http://localhost:1337/qb/", + QbXmlResponse.data + ); + } catch (error) { + console.log("Error connecting to quickbooks or partner.", error); + notification["error"]({ + message: t("payments.errors.exporting-partner"), + }); + if (!!loadingCallback) loadingCallback(false); + setLoading(false); + return; + } } - let PartnerResponse; - - try { - PartnerResponse = await axios.post( - "http://localhost:1337/qb/", - QbXmlResponse.data - ); - } catch (error) { - console.log("Error connecting to quickbooks or partner.", error); - notification["error"]({ - message: t("payments.errors.exporting-partner"), - }); - if (!!loadingCallback) loadingCallback(false); - setLoading(false); - return; - } - - const groupedData = _.groupBy(PartnerResponse.data, "id"); + const groupedData = _.groupBy( + PartnerResponse.data, + bodyshop.accountingconfig.qbo ? "paymentid" : "id" + ); const proms = []; Object.keys(groupedData).forEach((key) => { proms.push( diff --git a/server.js b/server.js index 368fdf632..28a7da70a 100644 --- a/server.js +++ b/server.js @@ -150,6 +150,7 @@ app.post("/qbo/authorize", fb.validateFirebaseIdToken, qbo.authorize); app.get("/qbo/callback", qbo.callback); app.post("/qbo/receivables", fb.validateFirebaseIdToken, qbo.receivables); app.post("/qbo/payables", fb.validateFirebaseIdToken, qbo.payables); +app.post("/qbo/payments", fb.validateFirebaseIdToken, qbo.payments); var data = require("./server/data/data"); app.post("/data/ah", data.autohouse); diff --git a/server/accounting/qbo/qbo-payables.js b/server/accounting/qbo/qbo-payables.js index c68531b49..57d357c50 100644 --- a/server/accounting/qbo/qbo-payables.js +++ b/server/accounting/qbo/qbo-payables.js @@ -78,7 +78,8 @@ exports.default = async (req, res) => { ret.push({ billid: bill.id, success: false, - errorMessage: error.message, + errorMessage: + (error && error.authResponse.body) || JSON.stringify(error), }); } } @@ -113,7 +114,7 @@ async function QueryVendorRecord(oauthClient, req, bill) { ); } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { - error: JSON.stringify(error), + error: (error && error.authResponse.body) || JSON.stringify(error), method: "QueryVendorRecord", }); throw error; @@ -136,7 +137,7 @@ async function InsertVendorRecord(oauthClient, req, bill) { return result && result.Vendor; } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { - error: JSON.stringify(error), + error: (error && error.authResponse.body) || JSON.stringify(error), method: "InsertVendorRecord", }); throw error; @@ -185,7 +186,7 @@ async function InsertBill(oauthClient, req, bill, vendor) { return result && result.Bill; } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { - error: JSON.stringify(error), + error: (error && error.authResponse.body) || JSON.stringify(error), method: "InsertBill", }); throw error; diff --git a/server/accounting/qbo/qbo-payments.js b/server/accounting/qbo/qbo-payments.js index e69de29bb..99ae12fc9 100644 --- a/server/accounting/qbo/qbo-payments.js +++ b/server/accounting/qbo/qbo-payments.js @@ -0,0 +1,274 @@ +const path = require("path"); +require("dotenv").config({ + path: path.resolve( + process.cwd(), + `.env.${process.env.NODE_ENV || "development"}` + ), +}); +const logger = require("../../utils/logger"); +const Dinero = require("dinero.js"); + +const apiGqlClient = require("../../graphql-client/graphql-client").client; +const queries = require("../../graphql-client/queries"); +const { + refresh: refreshOauthToken, + setNewRefreshToken, +} = require("./qbo-callback"); +const OAuthClient = require("intuit-oauth"); +const moment = require("moment"); +const GraphQLClient = require("graphql-request").GraphQLClient; +const { + QueryInsuranceCo, + InsertInsuranceCo, + InsertJob, + InsertOwner, + QueryJob, + QueryOwner, +} = require("../qbo/qbo-receivables"); +const { urlBuilder } = require("./qbo"); +const { DineroQbFormat } = require("../accounting-constants"); + +exports.default = async (req, res) => { + const oauthClient = new OAuthClient({ + clientId: process.env.QBO_CLIENT_ID, + clientSecret: process.env.QBO_SECRET, + environment: + process.env.NODE_ENV === "production" ? "production" : "sandbox", + redirectUri: process.env.QBO_REDIRECT_URI, + logging: true, + }); + try { + //Fetch the API Access Tokens & Set them for the session. + const response = await apiGqlClient.request(queries.GET_QBO_AUTH, { + email: req.user.email, + }); + + oauthClient.setToken(response.associations[0].qbo_auth); + + await refreshOauthToken(oauthClient, req); + + const BearerToken = req.headers.authorization; + const { payments: paymentsToQuery } = req.body; + //Query Job Info + const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { + headers: { + Authorization: BearerToken, + }, + }); + logger.log("qbo-payment-create", "DEBUG", req.user.email, paymentsToQuery); + const result = await client + .setHeaders({ Authorization: BearerToken }) + .request(queries.QUERY_PAYMENTS_FOR_EXPORT, { + payments: paymentsToQuery, + }); + + const { payments, bodyshops } = result; + const bodyshop = bodyshops[0]; + + const ret = []; + + for (const payment of payments) { + try { + const isThreeTier = bodyshop.accountingconfig.tiers === 3; + const twoTierPref = bodyshop.accountingconfig.twotierpref; + + //Replace this with a for-each loop to check every single Job that's included in the list. + + let insCoCustomerTier, ownerCustomerTier, jobTier; + if (isThreeTier || twoTierPref === "source") { + //Insert the insurance company tier. + //Query for top level customer, the insurance company name. + insCoCustomerTier = await QueryInsuranceCo( + oauthClient, + req, + payment.job + ); + if (!insCoCustomerTier) { + //Creating the Insurance Customer. + insCoCustomerTier = await InsertInsuranceCo( + oauthClient, + req, + payment.job, + bodyshop + ); + } + } + + if (isThreeTier || twoTierPref === "name") { + //Insert the name/owner and account for whether the source should be the ins co in 3 tier.. + ownerCustomerTier = await QueryOwner(oauthClient, req, payment.job); + //Query for the owner itself. + if (!ownerCustomerTier) { + ownerCustomerTier = await InsertOwner( + oauthClient, + req, + payment.job, + isThreeTier, + insCoCustomerTier + ); + } + } + + //Query for the Job or Create it. + jobTier = await QueryJob(oauthClient, req, payment.job); + + // Need to validate that the job tier is associated to the right individual? + + if (!jobTier) { + jobTier = await InsertJob( + oauthClient, + req, + payment.job, + isThreeTier, + ownerCustomerTier + ); + } + + await InsertPayment(oauthClient, req, payment, jobTier); + ret.push({ paymentid: payment.id, success: true }); + } catch (error) { + logger.log("qbo-payment-create-error", "ERROR", req.user.email, { + error: (error && error.authResponse.body) || JSON.stringify(error), + }); + + ret.push({ + paymentid: payment.id, + success: false, + errorMessage: + (error && error.authResponse.body) || JSON.stringify(error), + }); + } + } + + res.status(200).json(ret); + } catch (error) { + console.log(error); + logger.log("qbo-payment-create-error", "ERROR", req.user.email, { error }); + res.status(400).json(error); + } +}; + +async function InsertPayment(oauthClient, req, payment, parentRef) { + const { paymentMethods, invoices } = await QueryMetaData( + oauthClient, + req, + payment.job.ro_number + ); + + if (invoices.length !== 1) { + throw new Error( + `More than 1 invoice with DocNumber ${payment.ro_number} found.` + ); + } + + const paymentQbo = { + CustomerRef: { + value: parentRef.Id, + }, + TxnDate: moment(payment.date).format("YYYY-MM-DD"), + //DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"), + DocNumber: payment.paymentnum, + TotalAmt: Dinero({ + amount: Math.round(payment.amount * 100), + }).toFormat(DineroQbFormat), + PaymentMethodRef: { + value: paymentMethods[payment.type], + }, + Line: [ + { + Amount: Dinero({ + amount: Math.round(payment.amount * 100), + }).toFormat(DineroQbFormat), + LinkedTxn: [ + { + TxnId: invoices[0].Id, + TxnType: "Invoice", + }, + ], + }, + ], + }; + try { + const result = await oauthClient.makeApiCall({ + url: urlBuilder(req.cookies.qbo_realmId, "payment"), + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(paymentQbo), + }); + setNewRefreshToken(req.user.email, result); + return result && result.Bill; + } catch (error) { + logger.log("qbo-payables-error", "DEBUG", req.user.email, payment.id, { + error: JSON.stringify(error), + method: "InsertPayment", + }); + throw error; + } +} +async function QueryMetaData(oauthClient, req, ro_number) { + const invoice = await oauthClient.makeApiCall({ + url: urlBuilder( + req.cookies.qbo_realmId, + "query", + `select * From Invoice where DocNumber = '${ro_number}'` + ), + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }); + + const paymentMethods = await oauthClient.makeApiCall({ + url: urlBuilder( + req.cookies.qbo_realmId, + "query", + `select * From PaymentMethod` + ), + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }); + setNewRefreshToken(req.user.email, paymentMethods); + + // const classes = await oauthClient.makeApiCall({ + // url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From Class`), + // method: "POST", + // headers: { + // "Content-Type": "application/json", + // }, + // }); + + const paymentMethodMapping = {}; + + paymentMethods.json && + paymentMethods.json.QueryResponse && + paymentMethods.json.QueryResponse.PaymentMethod.forEach((t) => { + paymentMethodMapping[t.Name] = t.Id; + }); + + // const accountMapping = {}; + + // accounts.json && + // accounts.json.QueryResponse && + // accounts.json.QueryResponse.Account.forEach((t) => { + // accountMapping[t.Name] = t.Id; + // }); + + // const classMapping = {}; + // classes.json && + // classes.json.QueryResponse && + // classes.json.QueryResponse.Class.forEach((t) => { + // accountMapping[t.Name] = t.Id; + // }); + + return { + paymentMethods: paymentMethodMapping, + invoices: + invoice.json && + invoice.json.QueryResponse && + invoice.json.QueryResponse.Invoice, + }; +} diff --git a/server/accounting/qbo/qbo-receivables.js b/server/accounting/qbo/qbo-receivables.js index f23873544..3a9eafb31 100644 --- a/server/accounting/qbo/qbo-receivables.js +++ b/server/accounting/qbo/qbo-receivables.js @@ -80,7 +80,7 @@ exports.default = async (req, res) => { ); } } - console.log(insCoCustomerTier); + if (isThreeTier || twoTierPref === "name") { //Insert the name/owner and account for whether the source should be the ins co in 3 tier.. ownerCustomerTier = await QueryOwner(oauthClient, req, job); @@ -95,7 +95,7 @@ exports.default = async (req, res) => { ); } } - console.log(ownerCustomerTier); + //Query for the Job or Create it. jobTier = await QueryJob(oauthClient, req, job); @@ -110,14 +110,15 @@ exports.default = async (req, res) => { ownerCustomerTier ); } - console.log(jobTier); + await InsertInvoice(oauthClient, req, job, bodyshop, jobTier); ret.push({ jobid: job.id, success: true }); } catch (error) { ret.push({ jobid: job.id, success: false, - errorMessage: error.message, + errorMessage: + (error && error.authResponse.body) || JSON.stringify(error), }); } } @@ -160,6 +161,7 @@ async function QueryInsuranceCo(oauthClient, req, job) { throw error; } } +exports.QueryInsuranceCo = QueryInsuranceCo; async function InsertInsuranceCo(oauthClient, req, job, bodyshop) { const insCo = bodyshop.md_ins_cos.find((i) => i.name === job.ins_co_nm); @@ -192,7 +194,7 @@ async function InsertInsuranceCo(oauthClient, req, job, bodyshop) { throw error; } } - +exports.InsertInsuranceCo = InsertInsuranceCo; async function QueryOwner(oauthClient, req, job) { const ownerName = generateOwnerTier(job, true, null); const result = await oauthClient.makeApiCall({ @@ -214,7 +216,7 @@ async function QueryOwner(oauthClient, req, job) { result.json.QueryResponse.Customer[0] ); } - +exports.QueryOwner = QueryOwner; async function InsertOwner(oauthClient, req, job, isThreeTier, parentTierRef) { const ownerName = generateOwnerTier(job, true, null); const Customer = { @@ -254,7 +256,7 @@ async function InsertOwner(oauthClient, req, job, isThreeTier, parentTierRef) { throw error; } } - +exports.InsertOwner = InsertOwner; async function QueryJob(oauthClient, req, job) { const result = await oauthClient.makeApiCall({ url: urlBuilder( @@ -275,7 +277,7 @@ async function QueryJob(oauthClient, req, job) { result.json.QueryResponse.Customer[0] ); } - +exports.QueryJob = QueryJob; async function InsertJob(oauthClient, req, job, isThreeTier, parentTierRef) { const Customer = { DisplayName: job.ro_number, @@ -314,7 +316,7 @@ async function InsertJob(oauthClient, req, job, isThreeTier, parentTierRef) { throw error; } } - +exports.InsertJob = InsertJob; async function QueryMetaData(oauthClient, req) { const items = await oauthClient.makeApiCall({ url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From Item`), diff --git a/server/accounting/qbo/qbo.js b/server/accounting/qbo/qbo.js index 190e5aa07..ab46a86c0 100644 --- a/server/accounting/qbo/qbo.js +++ b/server/accounting/qbo/qbo.js @@ -22,3 +22,4 @@ exports.authorize = require("./qbo-authorize").default; exports.refresh = require("./qbo-callback").refresh; exports.receivables = require("./qbo-receivables").default; exports.payables = require("./qbo-payables").default; +exports.payments = require("./qbo-payments").default; diff --git a/server/accounting/qbxml/qbxml-payments.js b/server/accounting/qbxml/qbxml-payments.js index ef53099cf..ec429aa86 100644 --- a/server/accounting/qbxml/qbxml-payments.js +++ b/server/accounting/qbxml/qbxml-payments.js @@ -142,9 +142,6 @@ const generatePayment = (payment, isThreeTier, twoTierPref) => { payment.stripeid || "" } ${payment.payer ? ` PAID BY ${payment.payer}` : ""}`, IsAutoApply: true, - // AppliedToTxnAdd:{ - // T - // } }, }, }, From 9ee8e9007a11cb23182e84337a357a8b75ad2a52 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Tue, 12 Oct 2021 20:18:06 -0700 Subject: [PATCH 03/23] IO-256 Add Payables Posting. --- .../components/dms-post-form/dms-post-form.component.jsx | 1 - .../jobs-export-all-button.component.jsx | 7 +------ .../payable-export-all-button.component.jsx | 7 +------ .../payable-export-button.component.jsx | 7 +------ .../payment-export-button.component.jsx | 7 +------ .../payments-export-all-button.component.jsx | 7 +------ 6 files changed, 5 insertions(+), 31 deletions(-) diff --git a/client/src/components/dms-post-form/dms-post-form.component.jsx b/client/src/components/dms-post-form/dms-post-form.component.jsx index 637a858af..c3bc99333 100644 --- a/client/src/components/dms-post-form/dms-post-form.component.jsx +++ b/client/src/components/dms-post-form/dms-post-form.component.jsx @@ -259,7 +259,6 @@ export function DmsPostForm({ bodyshop, socket, job }) { ))} ); diff --git a/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx b/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx index 5845fbb73..32726842a 100644 --- a/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx +++ b/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx @@ -169,12 +169,7 @@ export function PayableExportAll({ }; return ( - ); diff --git a/client/src/components/payable-export-button/payable-export-button.component.jsx b/client/src/components/payable-export-button/payable-export-button.component.jsx index 6a9fd2494..6910440ae 100644 --- a/client/src/components/payable-export-button/payable-export-button.component.jsx +++ b/client/src/components/payable-export-button/payable-export-button.component.jsx @@ -171,12 +171,7 @@ export function PayableExportButton({ }; return ( - ); diff --git a/client/src/components/payment-export-button/payment-export-button.component.jsx b/client/src/components/payment-export-button/payment-export-button.component.jsx index 943455a90..4a43484d8 100644 --- a/client/src/components/payment-export-button/payment-export-button.component.jsx +++ b/client/src/components/payment-export-button/payment-export-button.component.jsx @@ -171,12 +171,7 @@ export function PaymentExportButton({ }; return ( - ); diff --git a/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx b/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx index 3e4085c44..3c49c6c7f 100644 --- a/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx +++ b/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx @@ -152,12 +152,7 @@ export function PaymentsExportAllButton({ }; return ( - ); From 3e84fbbaf41886b8c23b484c2c752864e4fb8589 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 13 Oct 2021 11:52:11 -0700 Subject: [PATCH 04/23] IO-1458 IO-1465 Sorting Fixes. --- .../production-list-columns.add.component.jsx | 17 +++++++++++++---- client/src/utils/sorters.js | 4 ++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/client/src/components/production-list-columns/production-list-columns.add.component.jsx b/client/src/components/production-list-columns/production-list-columns.add.component.jsx index 0742235b5..8b671c54c 100644 --- a/client/src/components/production-list-columns/production-list-columns.add.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.add.component.jsx @@ -6,9 +6,11 @@ import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser technician: selectTechnician, + bodyshop: selectBodyshop, }); const mapDispatchToProps = (dispatch) => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) @@ -21,6 +23,7 @@ export default connect( export function ProductionColumnsComponent({ columnState, technician, + bodyshop, tableState, }) { const [columns, setColumns] = columnState; @@ -29,9 +32,11 @@ export function ProductionColumnsComponent({ const handleAdd = (e) => { setColumns([ ...columns, - ...dataSource({ technician, state: tableState }).filter( - (i) => i.key === e.key - ), + ...dataSource({ + technician, + state: tableState, + activeStatuses: bodyshop.md_ro_statuses.active_statuses, + }).filter((i) => i.key === e.key), ]); }; @@ -39,7 +44,11 @@ export function ProductionColumnsComponent({ const menu = ( - {dataSource({ technician, state: tableState }) + {dataSource({ + technician, + state: tableState, + activeStatuses: bodyshop.md_ro_statuses.active_statuses, + }) .filter((i) => !columnKeys.includes(i.key)) .map((item) => ( {item.title} diff --git a/client/src/utils/sorters.js b/client/src/utils/sorters.js index bb111bb81..6e68ab8ee 100644 --- a/client/src/utils/sorters.js +++ b/client/src/utils/sorters.js @@ -9,11 +9,11 @@ export function alphaSort(a, b) { } export function dateSort(a, b) { - return new Date(b) - new Date(a); + return new Date(a) - new Date(b); } export function statusSort(a, b, statusList) { return ( - statusList.findIndex((x) => x === a) > statusList.findIndex((x) => x === b) + statusList.findIndex((x) => x === a) - statusList.findIndex((x) => x === b) ); } From 944229bae3f59fefac9104ed4c267e59e1ab6780 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 13 Oct 2021 13:22:56 -0700 Subject: [PATCH 05/23] Update process env. --- server/accounting/qbo/qbo-callback.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/server/accounting/qbo/qbo-callback.js b/server/accounting/qbo/qbo-callback.js index ed82c0fcd..d29c7c778 100644 --- a/server/accounting/qbo/qbo-callback.js +++ b/server/accounting/qbo/qbo-callback.js @@ -18,6 +18,16 @@ const oauthClient = new OAuthClient({ logging: true, }); +let url; + +if (process.env.NODE_ENV === "production") { + url = `https://imex.online`; +} else if (process.env.NODE_ENV === "test") { + url = `https://test.imex.online`; +} else { + url = `http://localhost:3000`; +} + exports.default = async (req, res) => { const params = queryString.parse(req.url.split("?").reverse()[0]); try { @@ -28,7 +38,7 @@ exports.default = async (req, res) => { error: authResponse.json, }); res.redirect( - `http://localhost:3000/manage/accounting/qbo?error=${encodeURIComponent( + `${url}:3000/manage/accounting/qbo?error=${encodeURIComponent( JSON.stringify(authResponse.json) )}` ); @@ -46,9 +56,7 @@ exports.default = async (req, res) => { ); res.redirect( - `http://localhost:3000/manage/accounting/qbo?${queryString.stringify( - params - )}` + `${url}:3000/manage/accounting/qbo?${queryString.stringify(params)}` ); } } catch (e) { From c19c92ab7ec322a786a5094b02745a9c7ec02e4c Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 13 Oct 2021 13:25:15 -0700 Subject: [PATCH 06/23] QBO port fix. --- server/accounting/qbo/qbo-callback.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/accounting/qbo/qbo-callback.js b/server/accounting/qbo/qbo-callback.js index d29c7c778..761c872d2 100644 --- a/server/accounting/qbo/qbo-callback.js +++ b/server/accounting/qbo/qbo-callback.js @@ -38,7 +38,7 @@ exports.default = async (req, res) => { error: authResponse.json, }); res.redirect( - `${url}:3000/manage/accounting/qbo?error=${encodeURIComponent( + `${url}/manage/accounting/qbo?error=${encodeURIComponent( JSON.stringify(authResponse.json) )}` ); @@ -56,7 +56,7 @@ exports.default = async (req, res) => { ); res.redirect( - `${url}:3000/manage/accounting/qbo?${queryString.stringify(params)}` + `${url}/manage/accounting/qbo?${queryString.stringify(params)}` ); } } catch (e) { From e7ef3b94c115b261ca258a1f44613fe1f3251da7 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 13 Oct 2021 13:34:56 -0700 Subject: [PATCH 07/23] QB Env update. --- server/accounting/qbo/qbo-callback.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/server/accounting/qbo/qbo-callback.js b/server/accounting/qbo/qbo-callback.js index 761c872d2..ae07b996a 100644 --- a/server/accounting/qbo/qbo-callback.js +++ b/server/accounting/qbo/qbo-callback.js @@ -10,10 +10,19 @@ const OAuthClient = require("intuit-oauth"); const client = require("../../graphql-client/graphql-client").client; const queries = require("../../graphql-client/queries"); const queryString = require("query-string"); + +let qbEnv; + +if (process.env.NODE_ENV === "production") { + url = "production"; +} else { + url = `sandbox`; +} + const oauthClient = new OAuthClient({ clientId: process.env.QBO_CLIENT_ID, clientSecret: process.env.QBO_SECRET, - environment: process.env.NODE_ENV === "production" ? "production" : "sandbox", + environment: qbEnv, redirectUri: process.env.QBO_REDIRECT_URI, logging: true, }); From bdad6da6d94084de844bf23ba766d5b3085c6f8a Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 13 Oct 2021 13:35:42 -0700 Subject: [PATCH 08/23] QB URL Fix. --- server/accounting/qbo/qbo-callback.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/accounting/qbo/qbo-callback.js b/server/accounting/qbo/qbo-callback.js index ae07b996a..13483c663 100644 --- a/server/accounting/qbo/qbo-callback.js +++ b/server/accounting/qbo/qbo-callback.js @@ -14,9 +14,9 @@ const queryString = require("query-string"); let qbEnv; if (process.env.NODE_ENV === "production") { - url = "production"; + qbEnv = "production"; } else { - url = `sandbox`; + qbEnv = `sandbox`; } const oauthClient = new OAuthClient({ From 404ade396cf4490d8e1e0e79e63055ab4c7daf41 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 13 Oct 2021 13:39:04 -0700 Subject: [PATCH 09/23] QB URL Updates. --- server/accounting/qbo/qbo-callback.js | 10 +--------- server/accounting/qbo/qbo.js | 4 +--- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/server/accounting/qbo/qbo-callback.js b/server/accounting/qbo/qbo-callback.js index 13483c663..ecf56e7e8 100644 --- a/server/accounting/qbo/qbo-callback.js +++ b/server/accounting/qbo/qbo-callback.js @@ -11,18 +11,10 @@ const client = require("../../graphql-client/graphql-client").client; const queries = require("../../graphql-client/queries"); const queryString = require("query-string"); -let qbEnv; - -if (process.env.NODE_ENV === "production") { - qbEnv = "production"; -} else { - qbEnv = `sandbox`; -} - const oauthClient = new OAuthClient({ clientId: process.env.QBO_CLIENT_ID, clientSecret: process.env.QBO_SECRET, - environment: qbEnv, + environment: process.env.NODE_ENV === "production" ? "production" : "sandbox", redirectUri: process.env.QBO_REDIRECT_URI, logging: true, }); diff --git a/server/accounting/qbo/qbo.js b/server/accounting/qbo/qbo.js index ab46a86c0..2d081c6bf 100644 --- a/server/accounting/qbo/qbo.js +++ b/server/accounting/qbo/qbo.js @@ -8,9 +8,7 @@ require("dotenv").config({ function urlBuilder(realmId, object, query = null) { return `https://${ - process.env.NODE_ENV === "development" || !process.env.NODE_ENV - ? "sandbox-" - : "" + process.env.NODE_ENV === "production" ? "" : "sandbox-" }quickbooks.api.intuit.com/v3/company/${realmId}/${object}${ query ? `?query=${encodeURIComponent(query)}` : "" }`; From 57d9de469a92fc406bf619b846b382b2a5636333 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 13 Oct 2021 14:01:59 -0700 Subject: [PATCH 10/23] Add CORS to Server. --- server.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index 28a7da70a..835486bd0 100644 --- a/server.js +++ b/server.js @@ -45,7 +45,15 @@ app.use(cookieParser()); app.use(bodyParser.json({ limit: "50mb" })); app.use(bodyParser.urlencoded({ limit: "50mb", extended: true })); //app.use(enforce.HTTPS({ trustProtoHeader: true })); -app.use(cors()); +app.use( + cors({ + origin: [ + "https://test.imex.online", + "http://localhost:3000", + "https://imex.online", + ], + }) +); //Email Based Paths. var sendEmail = require("./server/email/sendemail.js"); From 14309b5c96f31d619adaa36d9a4aef4a78f48fd4 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 13 Oct 2021 14:06:25 -0700 Subject: [PATCH 11/23] QBO CORS Updates --- server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server.js b/server.js index 835486bd0..cad3f477c 100644 --- a/server.js +++ b/server.js @@ -47,6 +47,7 @@ app.use(bodyParser.urlencoded({ limit: "50mb", extended: true })); //app.use(enforce.HTTPS({ trustProtoHeader: true })); app.use( cors({ + credentials: true, origin: [ "https://test.imex.online", "http://localhost:3000", From 0ddf009f8fd04ca167b78e049cd7dc64a7e0b4d7 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 13 Oct 2021 14:35:39 -0700 Subject: [PATCH 12/23] Reverse CORS & Fix Cookie Setting. --- .../jobs-close-export-button.component.jsx | 11 +++++++---- .../jobs-export-all-button.component.jsx | 11 +++++++---- .../payable-export-all-button.component.jsx | 11 +++++++---- .../payable-export-button.component.jsx | 11 +++++++---- .../payment-export-button.component.jsx | 11 +++++++---- .../payments-export-all-button.component.jsx | 11 +++++++---- server.js | 17 +++++++++-------- 7 files changed, 51 insertions(+), 32 deletions(-) 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 b104212ca..1344a7411 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 @@ -45,10 +45,13 @@ export function JobsCloseExportButton({ //Check if it's a QBO Setup. let PartnerResponse; if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { - PartnerResponse = await axios.post(`/qbo/receivables`, { - withCredentials: true, - jobIds: [jobId], - }); + PartnerResponse = await axios.post( + `/qbo/receivables`, + { + jobIds: [jobId], + }, + { withCredentials: true } + ); } else { //Default is QBD 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 index a42e48f1d..780817ad5 100644 --- 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 @@ -37,10 +37,13 @@ export function JobsExportAllButton({ let PartnerResponse; setLoading(true); if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { - PartnerResponse = await axios.post(`/qbo/receivables`, { - withCredentials: true, - jobIds: jobIds, - }); + PartnerResponse = await axios.post( + `/qbo/receivables`, + { + jobIds: jobIds, + }, + { withCredentials: true } + ); } else { let QbXmlResponse; try { diff --git a/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx b/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx index 32726842a..44f82faab 100644 --- a/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx +++ b/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx @@ -40,10 +40,13 @@ export function PayableExportAll({ setLoading(true); if (!!loadingCallback) loadingCallback(true); if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { - PartnerResponse = await axios.post(`/qbo/receivables`, { - withCredentials: true, - bills: billids, - }); + PartnerResponse = await axios.post( + `/qbo/receivables`, + { + bills: billids, + }, + { withCredentials: true } + ); } else { let QbXmlResponse; try { diff --git a/client/src/components/payable-export-button/payable-export-button.component.jsx b/client/src/components/payable-export-button/payable-export-button.component.jsx index 6910440ae..f54ff668a 100644 --- a/client/src/components/payable-export-button/payable-export-button.component.jsx +++ b/client/src/components/payable-export-button/payable-export-button.component.jsx @@ -41,10 +41,13 @@ export function PayableExportButton({ //Check if it's a QBO Setup. let PartnerResponse; if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { - PartnerResponse = await axios.post(`/qbo/payables`, { - withCredentials: true, - bills: [billId], - }); + PartnerResponse = await axios.post( + `/qbo/payables`, + { + bills: [billId], + }, + { withCredentials: true } + ); } else { //Default is QBD diff --git a/client/src/components/payment-export-button/payment-export-button.component.jsx b/client/src/components/payment-export-button/payment-export-button.component.jsx index 4a43484d8..2bc6ec19a 100644 --- a/client/src/components/payment-export-button/payment-export-button.component.jsx +++ b/client/src/components/payment-export-button/payment-export-button.component.jsx @@ -37,10 +37,13 @@ export function PaymentExportButton({ //Check if it's a QBO Setup. let PartnerResponse; if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { - PartnerResponse = await axios.post(`/qbo/payments`, { - withCredentials: true, - payments: [paymentId], - }); + PartnerResponse = await axios.post( + `/qbo/payments`, + { + payments: [paymentId], + }, + { withCredentials: true } + ); } else { //Default is QBD diff --git a/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx b/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx index 3c49c6c7f..6bb0f9fc6 100644 --- a/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx +++ b/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx @@ -35,10 +35,13 @@ export function PaymentsExportAllButton({ if (!!loadingCallback) loadingCallback(true); let PartnerResponse; if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { - PartnerResponse = await axios.post(`/qbo/payments`, { - withCredentials: true, - payments: paymentIds, - }); + PartnerResponse = await axios.post( + `/qbo/payments`, + { + payments: paymentIds, + }, + { withCredentials: true } + ); } else { let QbXmlResponse; try { diff --git a/server.js b/server.js index cad3f477c..5cba6fd84 100644 --- a/server.js +++ b/server.js @@ -46,14 +46,15 @@ app.use(bodyParser.json({ limit: "50mb" })); app.use(bodyParser.urlencoded({ limit: "50mb", extended: true })); //app.use(enforce.HTTPS({ trustProtoHeader: true })); app.use( - cors({ - credentials: true, - origin: [ - "https://test.imex.online", - "http://localhost:3000", - "https://imex.online", - ], - }) + cors() + // { + // credentials: true, + // origin: [ + // "https://test.imex.online", + // "http://localhost:3000", + // "https://imex.online", + // ], + // } ); //Email Based Paths. From 804c8ad40acbccdee03bb1eb7c2683c87ac1d77e Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 13 Oct 2021 14:49:47 -0700 Subject: [PATCH 13/23] IO-256 Add cookie location --- client/src/components/qbo-authorize/qbo-authorize.component.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/components/qbo-authorize/qbo-authorize.component.jsx b/client/src/components/qbo-authorize/qbo-authorize.component.jsx index 1f159afb0..0b9d48a11 100644 --- a/client/src/components/qbo-authorize/qbo-authorize.component.jsx +++ b/client/src/components/qbo-authorize/qbo-authorize.component.jsx @@ -33,6 +33,7 @@ export default function QboAuthorizeComponent() { setCookie("qbo_realmId", realmId, { path: "/", expires, + domain: `.${window.location.host}`, }); history.push({ pathname: `/manage/accounting/receivables` }); From 61ad9f0d58efeb5450050462bf33601adef7dc28 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 13 Oct 2021 15:01:59 -0700 Subject: [PATCH 14/23] IO-256 Add CORS back for QBO. --- server.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/server.js b/server.js index 5cba6fd84..cad3f477c 100644 --- a/server.js +++ b/server.js @@ -46,15 +46,14 @@ app.use(bodyParser.json({ limit: "50mb" })); app.use(bodyParser.urlencoded({ limit: "50mb", extended: true })); //app.use(enforce.HTTPS({ trustProtoHeader: true })); app.use( - cors() - // { - // credentials: true, - // origin: [ - // "https://test.imex.online", - // "http://localhost:3000", - // "https://imex.online", - // ], - // } + cors({ + credentials: true, + origin: [ + "https://test.imex.online", + "http://localhost:3000", + "https://imex.online", + ], + }) ); //Email Based Paths. From fda3620ed0b1d84cda7fafaf1c6e2c122d999039 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 13 Oct 2021 15:10:36 -0700 Subject: [PATCH 15/23] IO-256 Genericize email. --- server/accounting/qbo/qbo-payables.js | 9 ++++----- server/accounting/qbo/qbo-payments.js | 5 ++--- server/accounting/qbo/qbo-receivables.js | 3 +-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/server/accounting/qbo/qbo-payables.js b/server/accounting/qbo/qbo-payables.js index 57d357c50..704ed50b4 100644 --- a/server/accounting/qbo/qbo-payables.js +++ b/server/accounting/qbo/qbo-payables.js @@ -78,8 +78,7 @@ exports.default = async (req, res) => { ret.push({ billid: bill.id, success: false, - errorMessage: - (error && error.authResponse.body) || JSON.stringify(error), + errorMessage: JSON.stringify(error), }); } } @@ -114,7 +113,7 @@ async function QueryVendorRecord(oauthClient, req, bill) { ); } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { - error: (error && error.authResponse.body) || JSON.stringify(error), + error: JSON.stringify(error), method: "QueryVendorRecord", }); throw error; @@ -137,7 +136,7 @@ async function InsertVendorRecord(oauthClient, req, bill) { return result && result.Vendor; } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { - error: (error && error.authResponse.body) || JSON.stringify(error), + error: JSON.stringify(error), method: "InsertVendorRecord", }); throw error; @@ -186,7 +185,7 @@ async function InsertBill(oauthClient, req, bill, vendor) { return result && result.Bill; } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { - error: (error && error.authResponse.body) || JSON.stringify(error), + error: JSON.stringify(error), method: "InsertBill", }); throw error; diff --git a/server/accounting/qbo/qbo-payments.js b/server/accounting/qbo/qbo-payments.js index 99ae12fc9..012c3275f 100644 --- a/server/accounting/qbo/qbo-payments.js +++ b/server/accounting/qbo/qbo-payments.js @@ -128,14 +128,13 @@ exports.default = async (req, res) => { ret.push({ paymentid: payment.id, success: true }); } catch (error) { logger.log("qbo-payment-create-error", "ERROR", req.user.email, { - error: (error && error.authResponse.body) || JSON.stringify(error), + error: JSON.stringify(error), }); ret.push({ paymentid: payment.id, success: false, - errorMessage: - (error && error.authResponse.body) || JSON.stringify(error), + errorMessage: JSON.stringify(error), }); } } diff --git a/server/accounting/qbo/qbo-receivables.js b/server/accounting/qbo/qbo-receivables.js index 3a9eafb31..29d0d5dc2 100644 --- a/server/accounting/qbo/qbo-receivables.js +++ b/server/accounting/qbo/qbo-receivables.js @@ -117,8 +117,7 @@ exports.default = async (req, res) => { ret.push({ jobid: job.id, success: false, - errorMessage: - (error && error.authResponse.body) || JSON.stringify(error), + errorMessage: JSON.stringify(error), }); } } From d7b884ff86296d84a217b04ced5f4b1477d1461e Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 13 Oct 2021 15:14:26 -0700 Subject: [PATCH 16/23] Revert "IO-256 Genericize email." This reverts commit fda3620ed0b1d84cda7fafaf1c6e2c122d999039. --- server/accounting/qbo/qbo-payables.js | 9 +++++---- server/accounting/qbo/qbo-payments.js | 5 +++-- server/accounting/qbo/qbo-receivables.js | 3 ++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/server/accounting/qbo/qbo-payables.js b/server/accounting/qbo/qbo-payables.js index 704ed50b4..57d357c50 100644 --- a/server/accounting/qbo/qbo-payables.js +++ b/server/accounting/qbo/qbo-payables.js @@ -78,7 +78,8 @@ exports.default = async (req, res) => { ret.push({ billid: bill.id, success: false, - errorMessage: JSON.stringify(error), + errorMessage: + (error && error.authResponse.body) || JSON.stringify(error), }); } } @@ -113,7 +114,7 @@ async function QueryVendorRecord(oauthClient, req, bill) { ); } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { - error: JSON.stringify(error), + error: (error && error.authResponse.body) || JSON.stringify(error), method: "QueryVendorRecord", }); throw error; @@ -136,7 +137,7 @@ async function InsertVendorRecord(oauthClient, req, bill) { return result && result.Vendor; } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { - error: JSON.stringify(error), + error: (error && error.authResponse.body) || JSON.stringify(error), method: "InsertVendorRecord", }); throw error; @@ -185,7 +186,7 @@ async function InsertBill(oauthClient, req, bill, vendor) { return result && result.Bill; } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { - error: JSON.stringify(error), + error: (error && error.authResponse.body) || JSON.stringify(error), method: "InsertBill", }); throw error; diff --git a/server/accounting/qbo/qbo-payments.js b/server/accounting/qbo/qbo-payments.js index 012c3275f..99ae12fc9 100644 --- a/server/accounting/qbo/qbo-payments.js +++ b/server/accounting/qbo/qbo-payments.js @@ -128,13 +128,14 @@ exports.default = async (req, res) => { ret.push({ paymentid: payment.id, success: true }); } catch (error) { logger.log("qbo-payment-create-error", "ERROR", req.user.email, { - error: JSON.stringify(error), + error: (error && error.authResponse.body) || JSON.stringify(error), }); ret.push({ paymentid: payment.id, success: false, - errorMessage: JSON.stringify(error), + errorMessage: + (error && error.authResponse.body) || JSON.stringify(error), }); } } diff --git a/server/accounting/qbo/qbo-receivables.js b/server/accounting/qbo/qbo-receivables.js index 29d0d5dc2..3a9eafb31 100644 --- a/server/accounting/qbo/qbo-receivables.js +++ b/server/accounting/qbo/qbo-receivables.js @@ -117,7 +117,8 @@ exports.default = async (req, res) => { ret.push({ jobid: job.id, success: false, - errorMessage: JSON.stringify(error), + errorMessage: + (error && error.authResponse.body) || JSON.stringify(error), }); } } From 755ac7f657b74b159470bf6bac56c338042ccef3 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 13 Oct 2021 15:24:14 -0700 Subject: [PATCH 17/23] IO-256 Improved Logging --- server/accounting/qbo/qbo-payables.js | 19 +++++++++++++++---- server/accounting/qbo/qbo-payments.js | 11 +++++++++-- server/accounting/qbo/qbo-receivables.js | 9 ++++++++- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/server/accounting/qbo/qbo-payables.js b/server/accounting/qbo/qbo-payables.js index 704ed50b4..a473ffab2 100644 --- a/server/accounting/qbo/qbo-payables.js +++ b/server/accounting/qbo/qbo-payables.js @@ -78,7 +78,9 @@ exports.default = async (req, res) => { ret.push({ billid: bill.id, success: false, - errorMessage: JSON.stringify(error), + errorMessage: + (error && error.authResponse && error.authResponse.body) || + JSON.stringify(error), }); } } @@ -113,7 +115,9 @@ async function QueryVendorRecord(oauthClient, req, bill) { ); } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { - error: JSON.stringify(error), + error: + (error && error.authResponse && error.authResponse.body) || + JSON.stringify(error), method: "QueryVendorRecord", }); throw error; @@ -136,7 +140,9 @@ async function InsertVendorRecord(oauthClient, req, bill) { return result && result.Vendor; } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { - error: JSON.stringify(error), + error: + (error && error.authResponse && error.authResponse.body) || + JSON.stringify(error), method: "InsertVendorRecord", }); throw error; @@ -169,6 +175,9 @@ async function InsertBill(oauthClient, req, bill, vendor) { ) ), }; + logger.log("qbo-payable-objectlog", "DEBUG", req.user.email, bill.id, { + billQbo, + }); try { const result = await oauthClient.makeApiCall({ url: urlBuilder( @@ -185,7 +194,9 @@ async function InsertBill(oauthClient, req, bill, vendor) { return result && result.Bill; } catch (error) { logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, { - error: JSON.stringify(error), + error: + (error && error.authResponse && error.authResponse.body) || + JSON.stringify(error), method: "InsertBill", }); throw error; diff --git a/server/accounting/qbo/qbo-payments.js b/server/accounting/qbo/qbo-payments.js index 012c3275f..149a4779f 100644 --- a/server/accounting/qbo/qbo-payments.js +++ b/server/accounting/qbo/qbo-payments.js @@ -128,13 +128,17 @@ exports.default = async (req, res) => { ret.push({ paymentid: payment.id, success: true }); } catch (error) { logger.log("qbo-payment-create-error", "ERROR", req.user.email, { - error: JSON.stringify(error), + error: + (error && error.authResponse && error.authResponse.body) || + JSON.stringify(error), }); ret.push({ paymentid: payment.id, success: false, - errorMessage: JSON.stringify(error), + errorMessage: + (error && error.authResponse && error.authResponse.body) || + JSON.stringify(error), }); } } @@ -187,6 +191,9 @@ async function InsertPayment(oauthClient, req, payment, parentRef) { }, ], }; + logger.log("qbo-payments-objectlog", "DEBUG", req.user.email, payment.id, { + paymentQbo, + }); try { const result = await oauthClient.makeApiCall({ url: urlBuilder(req.cookies.qbo_realmId, "payment"), diff --git a/server/accounting/qbo/qbo-receivables.js b/server/accounting/qbo/qbo-receivables.js index 29d0d5dc2..b0e3ea758 100644 --- a/server/accounting/qbo/qbo-receivables.js +++ b/server/accounting/qbo/qbo-receivables.js @@ -117,7 +117,9 @@ exports.default = async (req, res) => { ret.push({ jobid: job.id, success: false, - errorMessage: JSON.stringify(error), + errorMessage: + (error && error.authResponse && error.authResponse.body) || + JSON.stringify(error), }); } } @@ -397,10 +399,15 @@ async function InsertInvoice(oauthClient, req, job, bodyshop, parentTierRef) { : {}), }; + logger.log("qbo-receivable-objectlog", "DEBUG", req.user.email, job.id, { + invoiceObj, + }); + try { const result = await oauthClient.makeApiCall({ url: urlBuilder(req.cookies.qbo_realmId, "invoice"), method: "POST", + headers: { "Content-Type": "application/json", }, From 1cdc34249a588fb38ed7b735c88b1be363886992 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 13 Oct 2021 15:48:43 -0700 Subject: [PATCH 18/23] IO-256 Fix null handling for missing metadata. --- server/accounting/qbo/qbo-payables.js | 3 +++ server/accounting/qbo/qbo-payments.js | 1 + server/accounting/qbo/qbo-receivables.js | 5 ++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/server/accounting/qbo/qbo-payables.js b/server/accounting/qbo/qbo-payables.js index a473ffab2..32ee0e2d9 100644 --- a/server/accounting/qbo/qbo-payables.js +++ b/server/accounting/qbo/qbo-payables.js @@ -280,6 +280,7 @@ async function QueryMetaData(oauthClient, req) { taxCodes.json && taxCodes.json.QueryResponse && + taxCodes.json.QueryResponse.TaxCode && taxCodes.json.QueryResponse.TaxCode.forEach((t) => { taxCodeMapping[t.Name] = t.Id; }); @@ -288,6 +289,7 @@ async function QueryMetaData(oauthClient, req) { accounts.json && accounts.json.QueryResponse && + accounts.json.QueryResponse.Account && accounts.json.QueryResponse.Account.forEach((t) => { accountMapping[t.Name] = t.Id; }); @@ -295,6 +297,7 @@ async function QueryMetaData(oauthClient, req) { const classMapping = {}; classes.json && classes.json.QueryResponse && + classes.json.QueryResponse.Class && classes.json.QueryResponse.Class.forEach((t) => { accountMapping[t.Name] = t.Id; }); diff --git a/server/accounting/qbo/qbo-payments.js b/server/accounting/qbo/qbo-payments.js index 149a4779f..e1042b739 100644 --- a/server/accounting/qbo/qbo-payments.js +++ b/server/accounting/qbo/qbo-payments.js @@ -251,6 +251,7 @@ async function QueryMetaData(oauthClient, req, ro_number) { paymentMethods.json && paymentMethods.json.QueryResponse && + paymentMethods.json.QueryResponse.PaymentMethod && paymentMethods.json.QueryResponse.PaymentMethod.forEach((t) => { paymentMethodMapping[t.Name] = t.Id; }); diff --git a/server/accounting/qbo/qbo-receivables.js b/server/accounting/qbo/qbo-receivables.js index b0e3ea758..e28af80f2 100644 --- a/server/accounting/qbo/qbo-receivables.js +++ b/server/accounting/qbo/qbo-receivables.js @@ -347,6 +347,7 @@ async function QueryMetaData(oauthClient, req) { taxCodes.json && taxCodes.json.QueryResponse && + taxCodes.json.QueryResponse.TaxCode && taxCodes.json.QueryResponse.TaxCode.forEach((t) => { taxCodeMapping[t.Name] = t.Id; }); @@ -355,6 +356,7 @@ async function QueryMetaData(oauthClient, req) { items.json && items.json.QueryResponse && + items.json.QueryResponse.Item && items.json.QueryResponse.Item.forEach((t) => { itemMapping[t.Name] = t.Id; }); @@ -362,6 +364,7 @@ async function QueryMetaData(oauthClient, req) { const classMapping = {}; classes.json && classes.json.QueryResponse && + classes.json.QueryResponse.Class && classes.json.QueryResponse.Class.forEach((t) => { itemMapping[t.Name] = t.Id; }); @@ -394,7 +397,7 @@ async function InsertInvoice(oauthClient, req, job, bodyshop, parentTierRef) { ...(bodyshop.accountingconfig.printlater ? { PrintStatus: "NeedToPrint" } : {}), - ...(bodyshop.accountingconfig.emaillater + ...(bodyshop.accountingconfig.emaillater && job.ownr_ea ? { EmailStatus: "NeedToSend" } : {}), }; From b54d5beb765cb42e03585e2b19c7b9a442db6e0d Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 13 Oct 2021 17:02:39 -0700 Subject: [PATCH 19/23] IO-256 Resolve Bill Center reference --- server/accounting/qbo/qbo-payables.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/server/accounting/qbo/qbo-payables.js b/server/accounting/qbo/qbo-payables.js index 32ee0e2d9..00d3b081d 100644 --- a/server/accounting/qbo/qbo-payables.js +++ b/server/accounting/qbo/qbo-payables.js @@ -171,7 +171,8 @@ async function InsertBill(oauthClient, req, bill, vendor) { bill.job.class, bill.job.bodyshop.md_responsibility_centers.sales_tax_codes, classes, - taxCodes + taxCodes, + bill.job.bodyshop.md_responsibility_centers.costs ) ), }; @@ -219,10 +220,12 @@ const generateBillLine = ( billLine, accounts, jobClass, - responsibilityCenters, + ioSalesTaxCodes, classes, - taxCodes + taxCodes, + costCenters ) => { + const account = costCenters.find((c) => c.name === billLine.cost_center); return { DetailType: "AccountBasedExpenseLineDetail", @@ -230,12 +233,10 @@ const generateBillLine = ( ...(jobClass ? { ClassRef: { Id: classes[jobClass] } } : {}), TaxCodeRef: { value: - taxCodes[ - findTaxCode(billLine.applicable_taxes, responsibilityCenters) - ], + taxCodes[findTaxCode(billLine.applicable_taxes, ioSalesTaxCodes)], }, AccountRef: { - value: accounts[billLine.cost_center], + value: accounts[account.accountname], }, }, @@ -291,7 +292,7 @@ async function QueryMetaData(oauthClient, req) { accounts.json.QueryResponse && accounts.json.QueryResponse.Account && accounts.json.QueryResponse.Account.forEach((t) => { - accountMapping[t.Name] = t.Id; + accountMapping[t.FullyQualifiedName] = t.Id; }); const classMapping = {}; From b56742bcb2e3b4c875229a74c07edafca40e4bde Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 13 Oct 2021 21:12:02 -0700 Subject: [PATCH 20/23] IO-256 QBO Improvements. --- .../jobs-close-export-button.component.jsx | 13 ++++++++++- .../jobs-export-all-button.component.jsx | 13 ++++++++++- .../payable-export-all-button.component.jsx | 13 ++++++++++- .../payable-export-button.component.jsx | 13 ++++++++++- .../payment-export-button.component.jsx | 14 ++++++++++- .../payments-export-all-button.component.jsx | 14 ++++++++++- .../qbo-authorize/qbo-authorize.component.jsx | 5 +++- server/accounting/qbo/qbo-receivables.js | 23 ++++++++----------- 8 files changed, 88 insertions(+), 20 deletions(-) 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 1344a7411..426e9709f 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 @@ -14,6 +14,7 @@ import { import { logImEXEvent } from "../../firebase/firebase.utils"; import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; import { useHistory } from "react-router-dom"; +import { useCookies } from "react-cookie"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -32,6 +33,7 @@ export function JobsCloseExportButton({ const [updateJob] = useMutation(UPDATE_JOB); const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); const [loading, setLoading] = useState(false); + const [cookies] = useCookies(); const handleQbxml = async () => { //Check if it's a CDK setup. @@ -180,7 +182,16 @@ export function JobsCloseExportButton({ }; return ( - ); 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 index 780817ad5..8862bceeb 100644 --- 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 @@ -13,6 +13,7 @@ import { selectBodyshop, selectCurrentUser, } from "../../redux/user/user.selectors"; +import { useCookies } from "react-cookie"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -30,6 +31,7 @@ export function JobsExportAllButton({ const { t } = useTranslation(); const [updateJob] = useMutation(UPDATE_JOBS); const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); + const [cookies] = useCookies(); const [loading, setLoading] = useState(false); const handleQbxml = async () => { @@ -173,7 +175,16 @@ export function JobsExportAllButton({ }; return ( - ); diff --git a/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx b/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx index 44f82faab..95fd184b2 100644 --- a/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx +++ b/client/src/components/payable-export-all-button/payable-export-all-button.component.jsx @@ -14,6 +14,7 @@ import { import { logImEXEvent } from "../../firebase/firebase.utils"; import _ from "lodash"; import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; +import { useCookies } from "react-cookie"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -32,6 +33,7 @@ export function PayableExportAll({ const [updateBill] = useMutation(UPDATE_BILLS); const [loading, setLoading] = useState(false); const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); + const [cookies] = useCookies(); const handleQbxml = async () => { logImEXEvent("accounting_payables_export_all"); @@ -172,7 +174,16 @@ export function PayableExportAll({ }; return ( - ); diff --git a/client/src/components/payable-export-button/payable-export-button.component.jsx b/client/src/components/payable-export-button/payable-export-button.component.jsx index f54ff668a..70b115175 100644 --- a/client/src/components/payable-export-button/payable-export-button.component.jsx +++ b/client/src/components/payable-export-button/payable-export-button.component.jsx @@ -13,6 +13,7 @@ import { } from "../../redux/user/user.selectors"; import { logImEXEvent } from "../../firebase/firebase.utils"; import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; +import { useCookies } from "react-cookie"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -31,6 +32,7 @@ export function PayableExportButton({ const [updateBill] = useMutation(UPDATE_BILLS); const [loading, setLoading] = useState(false); const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); + const [cookies] = useCookies(); const handleQbxml = async () => { logImEXEvent("accounting_export_payable"); @@ -174,7 +176,16 @@ export function PayableExportButton({ }; return ( - ); diff --git a/client/src/components/payment-export-button/payment-export-button.component.jsx b/client/src/components/payment-export-button/payment-export-button.component.jsx index 2bc6ec19a..0d3a124ac 100644 --- a/client/src/components/payment-export-button/payment-export-button.component.jsx +++ b/client/src/components/payment-export-button/payment-export-button.component.jsx @@ -8,6 +8,8 @@ import { createStructuredSelector } from "reselect"; import { auth, logImEXEvent } from "../../firebase/firebase.utils"; import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; import { UPDATE_PAYMENTS } from "../../graphql/payments.queries"; +import { useCookies } from "react-cookie"; + import { selectBodyshop, selectCurrentUser, @@ -30,6 +32,7 @@ export function PaymentExportButton({ const [updatePayment] = useMutation(UPDATE_PAYMENTS); const [loading, setLoading] = useState(false); const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); + const [cookies] = useCookies(); const handleQbxml = async () => { logImEXEvent("accounting_payment_export"); @@ -174,7 +177,16 @@ export function PaymentExportButton({ }; return ( - ); diff --git a/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx b/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx index 6bb0f9fc6..35c58552a 100644 --- a/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx +++ b/client/src/components/payments-export-all-button/payments-export-all-button.component.jsx @@ -8,6 +8,8 @@ import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; import { UPDATE_PAYMENTS } from "../../graphql/payments.queries"; +import { useCookies } from "react-cookie"; + import { selectBodyshop, selectCurrentUser, @@ -29,6 +31,7 @@ export function PaymentsExportAllButton({ const [updatePayments] = useMutation(UPDATE_PAYMENTS); const [loading, setLoading] = useState(false); const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); + const [cookies] = useCookies(); const handleQbxml = async () => { setLoading(true); @@ -155,7 +158,16 @@ export function PaymentsExportAllButton({ }; return ( - ); diff --git a/client/src/components/qbo-authorize/qbo-authorize.component.jsx b/client/src/components/qbo-authorize/qbo-authorize.component.jsx index 0b9d48a11..2ebb75c6f 100644 --- a/client/src/components/qbo-authorize/qbo-authorize.component.jsx +++ b/client/src/components/qbo-authorize/qbo-authorize.component.jsx @@ -33,7 +33,10 @@ export default function QboAuthorizeComponent() { setCookie("qbo_realmId", realmId, { path: "/", expires, - domain: `.${window.location.host}`, + + ...(process.env.NODE_ENV !== "development" + ? { domain: `.${window.location.host}` } + : {}), }); history.push({ pathname: `/manage/accounting/receivables` }); diff --git a/server/accounting/qbo/qbo-receivables.js b/server/accounting/qbo/qbo-receivables.js index e28af80f2..fad3a686c 100644 --- a/server/accounting/qbo/qbo-receivables.js +++ b/server/accounting/qbo/qbo-receivables.js @@ -66,7 +66,7 @@ exports.default = async (req, res) => { //Replace this with a for-each loop to check every single Job that's included in the list. let insCoCustomerTier, ownerCustomerTier, jobTier; - if (isThreeTier || twoTierPref === "source") { + if (isThreeTier || (!isThreeTier && twoTierPref === "source")) { //Insert the insurance company tier. //Query for top level customer, the insurance company name. insCoCustomerTier = await QueryInsuranceCo(oauthClient, req, job); @@ -81,7 +81,7 @@ exports.default = async (req, res) => { } } - if (isThreeTier || twoTierPref === "name") { + if (isThreeTier || (!isThreeTier && twoTierPref === "name")) { //Insert the name/owner and account for whether the source should be the ins co in 3 tier.. ownerCustomerTier = await QueryOwner(oauthClient, req, job); //Query for the owner itself. @@ -106,8 +106,8 @@ exports.default = async (req, res) => { oauthClient, req, job, - isThreeTier, - ownerCustomerTier + + ownerCustomerTier || insCoCustomerTier ); } @@ -279,7 +279,7 @@ async function QueryJob(oauthClient, req, job) { ); } exports.QueryJob = QueryJob; -async function InsertJob(oauthClient, req, job, isThreeTier, parentTierRef) { +async function InsertJob(oauthClient, req, job, parentTierRef) { const Customer = { DisplayName: job.ro_number, BillAddr: { @@ -289,14 +289,11 @@ async function InsertJob(oauthClient, req, job, isThreeTier, parentTierRef) { PostalCode: job.ownr_zip, CountrySubDivisionCode: job.ownr_st, }, - ...(isThreeTier - ? { - Job: true, - ParentRef: { - value: parentTierRef.Id, - }, - } - : {}), + + Job: true, + ParentRef: { + value: parentTierRef.Id, + }, }; try { const result = await oauthClient.makeApiCall({ From 1b885e411488dae2cbe7fb072329b5d2b7b29200 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Wed, 13 Oct 2021 21:22:38 -0700 Subject: [PATCH 21/23] IO-256 Add description to receivables export. --- server/accounting/qb-receivables-lines.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/accounting/qb-receivables-lines.js b/server/accounting/qb-receivables-lines.js index 909473f13..5699c4cf2 100644 --- a/server/accounting/qb-receivables-lines.js +++ b/server/accounting/qb-receivables-lines.js @@ -315,8 +315,12 @@ exports.default = function ({ if (qbo) { Object.keys(invoiceLineHash).forEach((key) => { Object.keys(invoiceLineHash[key]).forEach((key2) => { + const account = responsibilityCenters.profits.find( + (p) => p.name === key + ); InvoiceLineAdd.push({ ...invoiceLineHash[key][key2], + ...(account ? { Description: account.accountdesc } : {}), Amount: invoiceLineHash[key][key2].Amount.toFormat(DineroQbFormat), }); }); From 660f463aeadd9d083bc3cf888e0486ff99cb7f79 Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Thu, 14 Oct 2021 10:52:08 -0700 Subject: [PATCH 22/23] IO-1468 Resolve line markup for MCE. --- .../job-detail-lines/job-lines.component.jsx | 6 +- .../jobs-available-table.container.jsx | 6 ++ server/job/job-totals.js | 99 +++++++++++-------- 3 files changed, 70 insertions(+), 41 deletions(-) diff --git a/client/src/components/job-detail-lines/job-lines.component.jsx b/client/src/components/job-detail-lines/job-lines.component.jsx index 1b1964664..08834e7bb 100644 --- a/client/src/components/job-detail-lines/job-lines.component.jsx +++ b/client/src/components/job-detail-lines/job-lines.component.jsx @@ -162,7 +162,11 @@ export function JobLinesComponent({ ellipsis: true, render: (text, record) => ( <> - {record.act_price} + + {record.db_ref === "900510" || record.db_ref === "900511" + ? record.prt_dsmk_m + : record.act_price} + {record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? ( 0 ? 1 : -1) + ? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(value.act_price * 100), + }) + .multiply(value.part_qty || 0) + .percentage(Math.abs(value.prt_dsmk_p || 0)) + .multiply(value.prt_dsmk_p > 0 ? 1 : -1) ), - list: { - ...acc.parts.list, - [value.part_type]: - acc.parts.list[value.part_type] && - acc.parts.list[value.part_type].total - ? { - total: acc.parts.list[value.part_type].total.add( - Dinero({ - amount: Math.round((value.act_price || 0) * 100), - }).multiply(value.part_qty || 0) - ), - } - : { - total: Dinero({ - amount: Math.round((value.act_price || 0) * 100), - }).multiply(value.part_qty || 0), - }, - }, + ...(value.part_type + ? { + list: { + ...acc.parts.list, + [value.part_type]: + acc.parts.list[value.part_type] && + acc.parts.list[value.part_type].total + ? { + total: acc.parts.list[ + value.part_type + ].total.add( + Dinero({ + amount: Math.round( + (value.act_price || 0) * 100 + ), + }).multiply(value.part_qty || 0) + ), + } + : { + total: Dinero({ + amount: Math.round( + (value.act_price || 0) * 100 + ), + }).multiply(value.part_qty || 0), + }, + }, + } + : {}), subtotal: acc.parts.subtotal .add( Dinero({ @@ -325,13 +340,13 @@ function CalculatePartsTotals(jobLines) { ) .add( value.prt_dsmk_m && value.prt_dsmk_m !== 0 - ? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) }) - : Dinero({ - amount: Math.round(value.act_price * 100), - }) - .multiply(value.part_qty || 0) - .percentage(Math.abs(value.prt_dsmk_p || 0)) - .multiply(value.prt_dsmk_p > 0 ? 1 : -1) + ? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) }) + : Dinero({ + amount: Math.round(value.act_price * 100), + }) + .multiply(value.part_qty || 0) + .percentage(Math.abs(value.prt_dsmk_p || 0)) + .multiply(value.prt_dsmk_p > 0 ? 1 : -1) ), }, }; @@ -465,13 +480,13 @@ function CalculateTaxesTotals(job, otherTotals) { .multiply(val.part_qty || 0) .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) ) .percentage( ((job.parts_tax_rates && @@ -481,6 +496,10 @@ function CalculateTaxesTotals(job, otherTotals) { val.part_type.startsWith("PAG") && BackupGlassTax && BackupGlassTax.prt_tax_rt) || + (!val.part_type && + val.db_ref === "900510" && + job.parts_tax_rates["PAN"] && + job.parts_tax_rates["PAN"].prt_tax_rt) || 0) * 100 ) ); From 1d210a9e5261cbcf7b81499562d7fc618edb5009 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 14 Oct 2021 11:21:06 -0700 Subject: [PATCH 23/23] IO-1468 QB and CDK Updates for MCE Markup --- bodyshop_translations.babel | 23 ++++++++- .../jobs-available-table.container.jsx | 2 +- .../jobs-close-lines.component.jsx | 15 +++++- client/src/graphql/jobs.queries.js | 2 +- client/src/translations/en_us/common.json | 3 +- client/src/translations/es/common.json | 1 + client/src/translations/fr/common.json | 1 + logs/oAuthClient-log.log | 47 +++++++++++++++++++ server/accounting/qb-receivables-lines.js | 4 +- 9 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 logs/oAuthClient-log.log diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 81517d230..15e3f0071 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -1,4 +1,4 @@ - +