diff --git a/client/src/components/schedule-job-modal/schedule-job-modal.component.jsx b/client/src/components/schedule-job-modal/schedule-job-modal.component.jsx index bf0bb45ce..0e41b5222 100644 --- a/client/src/components/schedule-job-modal/schedule-job-modal.component.jsx +++ b/client/src/components/schedule-job-modal/schedule-job-modal.component.jsx @@ -6,6 +6,7 @@ import { Row, Select, Space, + Statistic, Switch, Typography, } from "antd"; @@ -75,6 +76,12 @@ export function ScheduleJobModalComponent({ return ( + + {lbrHrsData?.jobs_by_pk?.ro_number} + {`B/R Hrs:${lbrHrsData?.jobs_by_pk.labhrs?.aggregate.sum.mod_lb_hrs}/${lbrHrsData?.jobs_by_pk.larhrs?.aggregate.sum.mod_lb_hrs}`} + { const oauthClient = new OAuthClient({ @@ -135,7 +136,17 @@ exports.default = async (req, res) => { ); } - await InsertPayment(oauthClient, qbo_realmId, req, payment, jobTier); + if (payment.amount > 0) { + await InsertPayment(oauthClient, qbo_realmId, req, payment, jobTier); + } else { + await InsertCreditMemo( + oauthClient, + qbo_realmId, + req, + payment, + jobTier + ); + } ret.push({ paymentid: payment.id, success: true }); } catch (error) { logger.log("qbo-payment-create-error", "ERROR", req.user.email, { @@ -173,7 +184,8 @@ async function InsertPayment( oauthClient, qbo_realmId, req, - payment.job.ro_number + payment.job.ro_number, + false ); if (invoices && invoices.length !== 1) { @@ -235,7 +247,13 @@ async function InsertPayment( throw error; } } -async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number) { +async function QueryMetaData( + oauthClient, + qbo_realmId, + req, + ro_number, + isCreditMemo +) { const invoice = await oauthClient.makeApiCall({ url: urlBuilder( qbo_realmId, @@ -288,8 +306,50 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number) { // classes.json.QueryResponse.Class.forEach((t) => { // accountMapping[t.Name] = t.Id; // }); + let ret = {}; + + if (isCreditMemo) { + const taxCodes = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`), + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }); + const items = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "query", `select * From Item`), + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }); + setNewRefreshToken(req.user.email, items); + + const itemMapping = {}; + + items.json && + items.json.QueryResponse && + items.json.QueryResponse.Item && + items.json.QueryResponse.Item.forEach((t) => { + itemMapping[t.Name] = t.Id; + }); + const taxCodeMapping = {}; + + taxCodes.json && + taxCodes.json.QueryResponse && + taxCodes.json.QueryResponse.TaxCode && + taxCodes.json.QueryResponse.TaxCode.forEach((t) => { + taxCodeMapping[t.Name] = t.Id; + }); + ret = { + ...ret, + items: itemMapping, + taxCodes: taxCodeMapping, + }; + } return { + ...ret, paymentMethods: paymentMethodMapping, invoices: invoice.json && @@ -297,3 +357,85 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number) { invoice.json.QueryResponse.Invoice, }; } +async function InsertCreditMemo( + oauthClient, + qbo_realmId, + req, + payment, + parentRef +) { + const { paymentMethods, invoices, items, taxCodes } = await QueryMetaData( + oauthClient, + qbo_realmId, + req, + payment.job.ro_number, + true + ); + + if (invoices && 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"), + DocNumber: payment.paymentnum, + ...(invoices[0] ? { InvoiceRef: { value: invoices[0].Id } } : {}), + Line: [ + { + DetailType: "SalesItemLineDetail", + Amount: Dinero({ amount: Math.round(payment.amount * -100) }).toFormat( + DineroQbFormat + ), + SalesItemLineDetail: { + ItemRef: { + value: + items[ + payment.job.bodyshop.md_responsibility_centers.refund + .accountitem + ], + }, + Qty: 1, + TaxCodeRef: { + value: + taxCodes[ + findTaxCode( + { + local: false, + federal: false, + state: false, + }, + payment.job.bodyshop.md_responsibility_centers.sales_tax_codes + ) + ], + }, + }, + }, + ], + }; + logger.log("qbo-payments-objectlog", "DEBUG", req.user.email, payment.id, { + paymentQbo, + }); + try { + const result = await oauthClient.makeApiCall({ + url: urlBuilder(qbo_realmId, "creditmemo"), + 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: error && error.message, + method: "InsertCreditMemo", + }); + throw error; + } +} diff --git a/server/accounting/qbxml/qbxml-payments.js b/server/accounting/qbxml/qbxml-payments.js index 7820f9a7e..529171cc1 100644 --- a/server/accounting/qbxml/qbxml-payments.js +++ b/server/accounting/qbxml/qbxml-payments.js @@ -152,7 +152,7 @@ const generatePayment = (payment, isThreeTier, twoTierPref) => { QBXML: { QBXMLMsgsRq: { "@onError": "continueOnError", - CreditMemoAddRq: { + wMemoAddRq: { CreditMemoAdd: { CustomerRef: { FullName: (payment.job.bodyshop.accountingconfig.tiers === 3