825 lines
23 KiB
JavaScript
825 lines
23 KiB
JavaScript
const urlBuilder = require("./qbo").urlBuilder;
|
|
const StandardizeName = require("./qbo").StandardizeName;
|
|
|
|
const path = require("path");
|
|
require("dotenv").config({
|
|
path: path.resolve(
|
|
process.cwd(),
|
|
`.env.${process.env.NODE_ENV || "development"}`
|
|
),
|
|
});
|
|
const logger = require("../../utils/logger");
|
|
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 CreateInvoiceLines = require("../qb-receivables-lines").default;
|
|
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({
|
|
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,
|
|
});
|
|
const { qbo_realmId } = response.associations[0];
|
|
if (!qbo_realmId) {
|
|
res.status(401).json({ error: "No company associated." });
|
|
return;
|
|
}
|
|
oauthClient.setToken(response.associations[0].qbo_auth);
|
|
|
|
await refreshOauthToken(oauthClient, req);
|
|
|
|
const BearerToken = req.headers.authorization;
|
|
const { jobIds, elgen } = req.body;
|
|
//Query Job Info
|
|
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
|
headers: {
|
|
Authorization: BearerToken,
|
|
},
|
|
});
|
|
logger.log("qbo-receivable-create", "DEBUG", req.user.email, jobIds);
|
|
const result = await client
|
|
.setHeaders({ Authorization: BearerToken })
|
|
.request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, {
|
|
ids: jobIds,
|
|
});
|
|
const { jobs, bodyshops } = result;
|
|
const bodyshop = bodyshops[0];
|
|
|
|
const ret = [];
|
|
for (const job of jobs) {
|
|
//const job = jobs[0];
|
|
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 || (!isThreeTier && twoTierPref === "source")) {
|
|
//Insert the insurance company tier.
|
|
//Query for top level customer, the insurance company name.
|
|
insCoCustomerTier = await QueryInsuranceCo(
|
|
oauthClient,
|
|
qbo_realmId,
|
|
req,
|
|
job
|
|
);
|
|
if (!insCoCustomerTier) {
|
|
//Creating the Insurance Customer.
|
|
insCoCustomerTier = await InsertInsuranceCo(
|
|
oauthClient,
|
|
qbo_realmId,
|
|
req,
|
|
job,
|
|
bodyshop
|
|
);
|
|
}
|
|
}
|
|
|
|
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,
|
|
qbo_realmId,
|
|
req,
|
|
job,
|
|
isThreeTier,
|
|
insCoCustomerTier
|
|
);
|
|
//Query for the owner itself.
|
|
if (!ownerCustomerTier) {
|
|
ownerCustomerTier = await InsertOwner(
|
|
oauthClient,
|
|
qbo_realmId,
|
|
req,
|
|
job,
|
|
isThreeTier,
|
|
insCoCustomerTier
|
|
);
|
|
}
|
|
}
|
|
|
|
//Query for the Job or Create it.
|
|
jobTier = await QueryJob(
|
|
oauthClient,
|
|
qbo_realmId,
|
|
req,
|
|
job,
|
|
isThreeTier
|
|
? ownerCustomerTier
|
|
: twoTierPref === "source"
|
|
? insCoCustomerTier
|
|
: ownerCustomerTier
|
|
);
|
|
|
|
// Need to validate that the job tier is associated to the right individual?
|
|
|
|
if (!jobTier) {
|
|
jobTier = await InsertJob(
|
|
oauthClient,
|
|
qbo_realmId,
|
|
req,
|
|
job,
|
|
|
|
ownerCustomerTier || insCoCustomerTier
|
|
);
|
|
}
|
|
|
|
if (!req.body.custDataOnly) {
|
|
await InsertInvoice(
|
|
oauthClient,
|
|
qbo_realmId,
|
|
req,
|
|
job,
|
|
bodyshop,
|
|
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
|
|
.setHeaders({ Authorization: BearerToken })
|
|
.request(queries.QBO_MARK_JOB_EXPORTED, {
|
|
jobId: job.id,
|
|
job: {
|
|
status:
|
|
bodyshop.md_ro_statuses.default_exported || "Exported*",
|
|
date_exported: moment().tz(bodyshop.timezone),
|
|
},
|
|
logs: [
|
|
{
|
|
bodyshopid: bodyshop.id,
|
|
jobid: job.id,
|
|
successful: true,
|
|
useremail: req.user.email,
|
|
},
|
|
],
|
|
});
|
|
}
|
|
}
|
|
ret.push({ jobid: job.id, success: true });
|
|
} catch (error) {
|
|
ret.push({
|
|
jobid: job.id,
|
|
success: false,
|
|
errorMessage:
|
|
(error && error.authResponse && error.authResponse.body) ||
|
|
(error && error.message),
|
|
});
|
|
//Add the export log error.
|
|
if (elgen) {
|
|
const result = await client
|
|
.setHeaders({ Authorization: BearerToken })
|
|
.request(queries.INSERT_EXPORT_LOG, {
|
|
logs: [
|
|
{
|
|
bodyshopid: bodyshop.id,
|
|
jobid: job.id,
|
|
successful: false,
|
|
message: JSON.stringify([
|
|
(error && error.authResponse && error.authResponse.body) ||
|
|
(error && error.message),
|
|
]),
|
|
useremail: req.user.email,
|
|
},
|
|
],
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
res.status(200).json(ret);
|
|
} catch (error) {
|
|
console.log(error);
|
|
logger.log("qbo-receivable-create-error", "ERROR", req.user.email, {
|
|
error: error.message,
|
|
stack: error.stack,
|
|
});
|
|
res.status(400).json(error);
|
|
}
|
|
};
|
|
|
|
async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) {
|
|
try {
|
|
const result = await oauthClient.makeApiCall({
|
|
url: urlBuilder(
|
|
qbo_realmId,
|
|
"query",
|
|
`select * From Customer where DisplayName = '${StandardizeName(
|
|
job.ins_co_nm.trim()
|
|
)}' and Active = true`
|
|
),
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
});
|
|
setNewRefreshToken(req.user.email, result);
|
|
return (
|
|
result.json &&
|
|
result.json.QueryResponse &&
|
|
result.json.QueryResponse.Customer &&
|
|
result.json.QueryResponse.Customer[0]
|
|
);
|
|
} catch (error) {
|
|
logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
|
|
error,
|
|
method: "QueryInsuranceCo",
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
exports.QueryInsuranceCo = QueryInsuranceCo;
|
|
async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) {
|
|
const insCo = bodyshop.md_ins_cos.find((i) => i.name === job.ins_co_nm);
|
|
|
|
if (!insCo) {
|
|
throw new Error(
|
|
`Insurance Company '${job.ins_co_nm}' not found in shop configuration. Please make sure it exists or change the insurance company name on the job to one that exists.`
|
|
);
|
|
return;
|
|
}
|
|
const Customer = {
|
|
DisplayName: job.ins_co_nm.trim(),
|
|
BillWithParent: true,
|
|
BillAddr: {
|
|
City: job.ownr_city,
|
|
Line1: insCo.street1,
|
|
Line2: insCo.street2,
|
|
PostalCode: insCo.zip,
|
|
CountrySubDivisionCode: insCo.state,
|
|
},
|
|
};
|
|
try {
|
|
const result = await oauthClient.makeApiCall({
|
|
url: urlBuilder(qbo_realmId, "customer"),
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(Customer),
|
|
});
|
|
setNewRefreshToken(req.user.email, result);
|
|
return result && result.json.Customer;
|
|
} catch (error) {
|
|
logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
|
|
error,
|
|
method: "InsertInsuranceCo",
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
exports.InsertInsuranceCo = InsertInsuranceCo;
|
|
|
|
async function QueryOwner(
|
|
oauthClient,
|
|
qbo_realmId,
|
|
req,
|
|
job,
|
|
isThreeTier,
|
|
parentTierRef
|
|
) {
|
|
const ownerName = generateOwnerTier(job, true, null);
|
|
const result = await oauthClient.makeApiCall({
|
|
url: urlBuilder(
|
|
qbo_realmId,
|
|
"query",
|
|
`select * From Customer where DisplayName = '${StandardizeName(
|
|
ownerName
|
|
)}' and Active = true`
|
|
),
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
});
|
|
setNewRefreshToken(req.user.email, result);
|
|
return (
|
|
result.json &&
|
|
result.json.QueryResponse &&
|
|
result.json.QueryResponse.Customer &&
|
|
result.json.QueryResponse.Customer.find(
|
|
(x) => x.ParentRef?.value === parentTierRef?.Id
|
|
)
|
|
);
|
|
}
|
|
exports.QueryOwner = QueryOwner;
|
|
async function InsertOwner(
|
|
oauthClient,
|
|
qbo_realmId,
|
|
req,
|
|
job,
|
|
isThreeTier,
|
|
parentTierRef
|
|
) {
|
|
const ownerName = generateOwnerTier(job, true, null);
|
|
const Customer = {
|
|
DisplayName: ownerName,
|
|
BillWithParent: true,
|
|
BillAddr: {
|
|
City: job.ownr_city,
|
|
Line1: job.ownr_addr1,
|
|
Line2: job.ownr_addr2,
|
|
PostalCode: job.ownr_zip,
|
|
CountrySubDivisionCode: job.ownr_st,
|
|
},
|
|
...(isThreeTier
|
|
? {
|
|
Job: true,
|
|
ParentRef: {
|
|
value: parentTierRef.Id,
|
|
},
|
|
}
|
|
: {}),
|
|
};
|
|
try {
|
|
const result = await oauthClient.makeApiCall({
|
|
url: urlBuilder(qbo_realmId, "customer"),
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(Customer),
|
|
});
|
|
setNewRefreshToken(req.user.email, result);
|
|
return result && result.json.Customer;
|
|
} catch (error) {
|
|
logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
|
|
error,
|
|
method: "InsertOwner",
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
exports.InsertOwner = InsertOwner;
|
|
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}' and Active = true`
|
|
),
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
});
|
|
setNewRefreshToken(req.user.email, result);
|
|
return (
|
|
result.json &&
|
|
result.json.QueryResponse &&
|
|
result.json.QueryResponse.Customer &&
|
|
(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 = {
|
|
DisplayName: job.ro_number,
|
|
BillWithParent: true,
|
|
BillAddr: {
|
|
City: job.ownr_city,
|
|
Line1: job.ownr_addr1,
|
|
Line2: job.ownr_addr2,
|
|
PostalCode: job.ownr_zip,
|
|
CountrySubDivisionCode: job.ownr_st,
|
|
},
|
|
|
|
Job: true,
|
|
ParentRef: {
|
|
value: parentTierRef.Id,
|
|
},
|
|
};
|
|
try {
|
|
const result = await oauthClient.makeApiCall({
|
|
url: urlBuilder(qbo_realmId, "customer"),
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(Customer),
|
|
});
|
|
setNewRefreshToken(req.user.email, result);
|
|
return result && result.json.Customer;
|
|
} catch (error) {
|
|
logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
|
|
error,
|
|
method: "InsertOwner",
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
exports.InsertJob = InsertJob;
|
|
async function QueryMetaData(oauthClient, qbo_realmId, req) {
|
|
const items = await oauthClient.makeApiCall({
|
|
url: urlBuilder(
|
|
qbo_realmId,
|
|
"query",
|
|
`select * From Item where active=true maxresults 1000`
|
|
),
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
});
|
|
setNewRefreshToken(req.user.email, items);
|
|
const taxCodes = await oauthClient.makeApiCall({
|
|
url: urlBuilder(
|
|
qbo_realmId,
|
|
"query",
|
|
`select * From TaxCode where active=true`
|
|
),
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
});
|
|
|
|
const classes = await oauthClient.makeApiCall({
|
|
url: urlBuilder(qbo_realmId, "query", `select * From Class`),
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
});
|
|
|
|
const taxCodeMapping = {};
|
|
|
|
taxCodes.json &&
|
|
taxCodes.json.QueryResponse &&
|
|
taxCodes.json.QueryResponse.TaxCode &&
|
|
taxCodes.json.QueryResponse.TaxCode.forEach((t) => {
|
|
taxCodeMapping[t.Name] = t.Id;
|
|
});
|
|
|
|
const itemMapping = {};
|
|
|
|
items.json &&
|
|
items.json.QueryResponse &&
|
|
items.json.QueryResponse.Item &&
|
|
items.json.QueryResponse.Item.forEach((t) => {
|
|
itemMapping[t.Name] = t.Id;
|
|
});
|
|
|
|
const classMapping = {};
|
|
classes.json &&
|
|
classes.json.QueryResponse &&
|
|
classes.json.QueryResponse.Class &&
|
|
classes.json.QueryResponse.Class.forEach((t) => {
|
|
classMapping[t.Name] = t.Id;
|
|
});
|
|
|
|
return {
|
|
items: itemMapping,
|
|
taxCodes: taxCodeMapping,
|
|
classes: classMapping,
|
|
};
|
|
}
|
|
|
|
async function InsertInvoice(
|
|
oauthClient,
|
|
qbo_realmId,
|
|
req,
|
|
job,
|
|
bodyshop,
|
|
parentTierRef
|
|
) {
|
|
const { items, taxCodes, classes } = await QueryMetaData(
|
|
oauthClient,
|
|
qbo_realmId,
|
|
req
|
|
);
|
|
const InvoiceLineAdd = CreateInvoiceLines({
|
|
bodyshop,
|
|
jobs_by_pk: job,
|
|
qbo: true,
|
|
items,
|
|
taxCodes,
|
|
classes,
|
|
});
|
|
|
|
const invoiceObj = {
|
|
Line: InvoiceLineAdd,
|
|
TxnDate: moment(job.date_invoiced)
|
|
.tz(bodyshop.timezone)
|
|
.format("YYYY-MM-DD"),
|
|
DocNumber: job.ro_number,
|
|
...(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;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|