IO-2054 QBO Split AR

This commit is contained in:
Patrick Fic
2022-09-22 11:21:08 -07:00
parent cccd025a24
commit ceafab55fa
14 changed files with 43570 additions and 17 deletions

View File

@@ -648,6 +648,56 @@ exports.default = function ({
});
}
//Check if there are multiple payers. If there are, add a deduction line and make sure we create new invoices.
if (
jobs_by_pk.qb_multiple_payers &&
jobs_by_pk.qb_multiple_payers.length > 0
) {
jobs_by_pk.qb_multiple_payers.forEach((payer) => {
if (qbo) {
InvoiceLineAdd.push({
DetailType: "SalesItemLineDetail",
Amount: Dinero({ amount: (payer.amount || 0) * 100 * -1 }).toFormat(
DineroQbFormat
),
SalesItemLineDetail: {
...(jobs_by_pk.class
? { ClassRef: { value: classes[jobs_by_pk.class] } }
: {}),
ItemRef: {
value:
items[responsibilityCenters.qb_multiple_payers?.accountitem],
},
Qty: 1,
TaxCodeRef: {
value:
taxCodes[
findTaxCode(
{
local: false,
federal: false,
state: false,
},
bodyshop.md_responsibility_centers.sales_tax_codes
)
],
},
},
});
} else {
InvoiceLineAdd.push({
ItemRef: {
FullName: responsibilityCenters.qb_multiple_payers?.accountitem,
},
Desc: `${payer.name} Liability`,
Amount: Dinero({ amount: (payer.amount || 0) * 100 * -1 }).toFormat(
DineroQbFormat
),
});
}
});
}
return InvoiceLineAdd;
};
@@ -667,3 +717,65 @@ const findTaxCode = ({ local, state, federal }, taxcode) => {
}
};
exports.findTaxCode = findTaxCode;
exports.createMultiQbPayerLines = function ({
bodyshop,
jobs_by_pk,
qbo = false,
items,
taxCodes,
classes,
payer,
}) {
const InvoiceLineAdd = [];
const responsibilityCenters = bodyshop.md_responsibility_centers;
const invoiceLineHash = {}; //The hash of cost and profit centers based on the center name.
if (qbo) {
//Going to always assume that we need to apply GST and PST for labor.
const taxAccountCode = findTaxCode(
{
local: false,
federal: false,
state: false,
},
bodyshop.md_responsibility_centers.sales_tax_codes
);
const QboTaxId = taxCodes[taxAccountCode];
InvoiceLineAdd.push({
DetailType: "SalesItemLineDetail",
Amount: Dinero({
amount: Math.round((payer.amount || 0) * 100),
}).toFormat(DineroQbFormat),
SalesItemLineDetail: {
...(jobs_by_pk.class
? { ClassRef: { value: classes[jobs_by_pk.class] } }
: {}),
ItemRef: {
value: items[responsibilityCenters.qb_multiple_payers?.accountitem],
},
TaxCodeRef: {
value: QboTaxId,
},
Qty: 1,
},
});
} else {
InvoiceLineAdd.push({
ItemRef: {
FullName: responsibilityCenters.qb_multiple_payers?.accountitem,
},
Desc: `${payer.name} Liability`,
Quantity: 1,
Amount: Dinero({
amount: Math.round((payer.amount || 0) * 100),
}).toFormat(DineroQbFormat),
SalesTaxCodeRef: {
FullName: "E",
},
});
}
return InvoiceLineAdd;
};

View File

@@ -21,6 +21,7 @@ const moment = require("moment-timezone");
const GraphQLClient = require("graphql-request").GraphQLClient;
const { generateOwnerTier } = require("../qbxml/qbxml-utils");
const { createMultiQbPayerLines } = require("../qb-receivables-lines");
exports.default = async (req, res) => {
const oauthClient = new OAuthClient({
@@ -115,7 +116,13 @@ exports.default = async (req, res) => {
}
//Query for the Job or Create it.
jobTier = await QueryJob(oauthClient, qbo_realmId, req, job);
jobTier = await QueryJob(
oauthClient,
qbo_realmId,
req,
job,
isThreeTier ? ownerCustomerTier : null // ownerCustomerTier || insCoCustomerTier
);
// Need to validate that the job tier is associated to the right individual?
@@ -140,6 +147,65 @@ exports.default = async (req, res) => {
jobTier
);
if (job.qb_multiple_payers && job.qb_multiple_payers.length > 0) {
for (const [index, payer] of job.qb_multiple_payers.entries()) {
//do the thing.
//Create the source level.
let insCoCustomerTier, ownerCustomerTier, jobTier;
//Insert the insurance company tier.
//Query for top level customer, the insurance company name.
insCoCustomerTier = await QueryInsuranceCo(
oauthClient,
qbo_realmId,
req,
{ ...job, ins_co_nm: payer.name }
);
if (!insCoCustomerTier) {
//Creating the Insurance Customer.
insCoCustomerTier = await InsertInsuranceCo(
oauthClient,
qbo_realmId,
req,
{ ...job, ins_co_nm: payer.name },
bodyshop
);
}
//Query for the Job or Create it.
jobTier = await QueryJob(
oauthClient,
qbo_realmId,
req,
job,
insCoCustomerTier
);
// Need to validate that the job tier is associated to the right individual?
if (!jobTier) {
jobTier = await InsertJob(
oauthClient,
qbo_realmId,
req,
job,
insCoCustomerTier
);
}
//Create the RO level
await InsertInvoiceMultiPayerInvoice(
oauthClient,
qbo_realmId,
req,
job,
bodyshop,
jobTier,
payer,
`-${index + 1}`
);
}
}
// //No error. Mark the job exported & insert export log.
if (elgen) {
const result = await client
@@ -212,7 +278,7 @@ async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) {
"query",
`select * From Customer where DisplayName = '${StandardizeName(
job.ins_co_nm.trim()
)}'`
)}' and Active = true`
),
method: "POST",
headers: {
@@ -284,7 +350,7 @@ async function QueryOwner(oauthClient, qbo_realmId, req, job) {
"query",
`select * From Customer where DisplayName = '${StandardizeName(
ownerName
)}'`
)}' and Active = true`
),
method: "POST",
headers: {
@@ -348,12 +414,12 @@ async function InsertOwner(
}
}
exports.InsertOwner = InsertOwner;
async function QueryJob(oauthClient, qbo_realmId, req, job) {
async function QueryJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
const result = await oauthClient.makeApiCall({
url: urlBuilder(
qbo_realmId,
"query",
`select * From Customer where DisplayName = '${job.ro_number}'`
`select * From Customer where DisplayName = '${job.ro_number}' and Active = true`
),
method: "POST",
headers: {
@@ -365,9 +431,14 @@ async function QueryJob(oauthClient, qbo_realmId, req, job) {
result.json &&
result.json.QueryResponse &&
result.json.QueryResponse.Customer &&
result.json.QueryResponse.Customer[0]
(parentTierRef
? result.json.QueryResponse.Customer.find(
(x) => x.ParentRef.value === parentTierRef.Id
)
: result.json.QueryResponse.Customer[0])
);
}
exports.QueryJob = QueryJob;
async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
const Customer = {
@@ -602,3 +673,137 @@ async function InsertInvoice(
throw error;
}
}
async function InsertInvoiceMultiPayerInvoice(
oauthClient,
qbo_realmId,
req,
job,
bodyshop,
parentTierRef,
payer,
suffix
) {
const { items, taxCodes, classes } = await QueryMetaData(
oauthClient,
qbo_realmId,
req
);
const InvoiceLineAdd = createMultiQbPayerLines({
bodyshop,
jobs_by_pk: job,
qbo: true,
items,
taxCodes,
classes,
payer,
suffix,
});
const invoiceObj = {
Line: InvoiceLineAdd,
TxnDate: moment(job.date_invoiced)
.tz(bodyshop.timezone)
.format("YYYY-MM-DD"),
DocNumber: job.ro_number + suffix,
...(job.class ? { ClassRef: { value: classes[job.class] } } : {}),
CustomerMemo: {
value: `${job.clm_no ? `Claim No: ${job.clm_no}` : ``}${
job.po_number ? `PO No: ${job.po_number}` : ``
} Vehicle:${job.v_model_yr || ""} ${job.v_make_desc || ""} ${
job.v_model_desc || ""
} ${job.v_vin || ""} ${job.plate_no || ""} `.trim(),
},
CustomerRef: {
value: parentTierRef.Id,
},
...(bodyshop.accountingconfig.qbo_departmentid &&
bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && {
DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid },
}),
CustomField: [
...(bodyshop.accountingconfig.ReceivableCustomField1
? [
{
DefinitionId: "1",
StringValue:
job[bodyshop.accountingconfig.ReceivableCustomField1],
Type: "StringType",
},
]
: []),
...(bodyshop.accountingconfig.ReceivableCustomField2
? [
{
DefinitionId: "2",
StringValue:
job[bodyshop.accountingconfig.ReceivableCustomField2],
Type: "StringType",
},
]
: []),
...(bodyshop.accountingconfig.ReceivableCustomField3
? [
{
DefinitionId: "3",
StringValue:
job[bodyshop.accountingconfig.ReceivableCustomField3],
Type: "StringType",
},
]
: []),
],
...(bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
bodyshop.accountingconfig.qbo_usa &&
bodyshop.region_config.includes("CA_") && {
TxnTaxDetail: {
TxnTaxCodeRef: {
value:
taxCodes[
bodyshop.md_responsibility_centers.taxes.state.accountitem
],
},
},
}),
...(bodyshop.accountingconfig.printlater
? { PrintStatus: "NeedToPrint" }
: {}),
...(bodyshop.accountingconfig.emaillater && job.ownr_ea
? { EmailStatus: "NeedToSend" }
: {}),
BillAddr: {
Line3: `${job.ownr_city || ""}, ${job.ownr_st || ""} ${
job.ownr_zip || ""
}`.trim(),
Line2: job.ownr_addr1 || "",
Line1: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
job.ownr_co_nm || ""
}`,
},
};
logger.log("qbo-receivable-objectlog", "DEBUG", req.user.email, job.id, {
invoiceObj,
});
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "invoice"),
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(invoiceObj),
});
setNewRefreshToken(req.user.email, result);
return result && result.json && result.json.Invoice;
} catch (error) {
logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
error,
method: "InsertOwner",
});
throw error;
}
}