Reformat all project files to use the prettier config file.

This commit is contained in:
Patrick Fic
2024-03-27 15:35:07 -07:00
parent b161530381
commit e1df64d592
873 changed files with 111387 additions and 125473 deletions

View File

@@ -1,9 +1,6 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const GraphQLClient = require("graphql-request").GraphQLClient;
@@ -13,279 +10,240 @@ const moment = require("moment");
const Dinero = require("dinero.js");
const AxiosLib = require("axios").default;
const axios = AxiosLib.create();
const {PBS_ENDPOINTS, PBS_CREDENTIALS} = require("./pbs-constants");
const {CheckForErrors} = require("./pbs-job-export");
const { PBS_ENDPOINTS, PBS_CREDENTIALS } = require("./pbs-constants");
const { CheckForErrors } = require("./pbs-job-export");
const uuid = require("uuid").v4;
axios.interceptors.request.use((x) => {
const socket = x.socket;
const socket = x.socket;
const headers = {
...x.headers.common,
...x.headers[x.method],
...x.headers,
};
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
x.url
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
console.log(printable);
const headers = {
...x.headers.common,
...x.headers[x.method],
...x.headers
};
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
x.url
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
console.log(printable);
CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data);
CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data);
return x;
return x;
});
axios.interceptors.response.use((x) => {
const socket = x.config.socket;
const socket = x.config.socket;
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(
x.data
)}`;
console.log(printable);
CdkBase.createJsonEvent(
socket,
"TRACE",
`Raw Response: ${printable}`,
x.data
);
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`;
console.log(printable);
CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data);
return x;
return x;
});
async function PbsCalculateAllocationsAp(socket, billids) {
try {
CdkBase.createLogEvent(
socket,
"DEBUG",
`Received request to calculate allocations for ${billids}`
);
const {bills, bodyshops} = await QueryBillData(socket, billids);
const bodyshop = bodyshops[0];
socket.bodyshop = bodyshop;
socket.bills = bills;
try {
CdkBase.createLogEvent(socket, "DEBUG", `Received request to calculate allocations for ${billids}`);
const { bills, bodyshops } = await QueryBillData(socket, billids);
const bodyshop = bodyshops[0];
socket.bodyshop = bodyshop;
socket.bills = bills;
//Each bill will enter it's own top level transaction.
//Each bill will enter it's own top level transaction.
const transactionlist = [];
if (bills.length === 0) {
CdkBase.createLogEvent(
socket,
"ERROR",
`No bills found for export. Ensure they have not already been exported and try again.`
);
}
bills.forEach((bill) => {
//Keep the allocations at the bill level.
const transactionObject = {
SerialNumber: socket.bodyshop.pbs_serialnumber,
billid: bill.id,
Posting: {
Reference: bill.invoice_number,
JournalCode: socket.txEnvelope ? socket.txEnvelope.journal : null,
TransactionDate: moment().tz(socket.bodyshop.timezone).toISOString(), //"0001-01-01T00:00:00.0000000Z",
//Description: "Bulk AP posting.",
//AdditionalInfo: "String",
Source: "ImEX Online", //TODO:AIO Resolve this for rome online.
Lines: [], //socket.apAllocations,
},
};
const billHash = {
[bodyshop.md_responsibility_centers.taxes.federal_itc.name]: {
Account:
bodyshop.md_responsibility_centers.taxes.federal_itc.dms_acctnumber,
ControlNumber: bill.vendor.dmsid,
Amount: Dinero(),
// Comment: "String",
AdditionalInfo: bill.vendor.name,
InvoiceNumber: bill.invoice_number,
InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString(),
},
[bodyshop.md_responsibility_centers.taxes.state.name]: {
Account:
bodyshop.md_responsibility_centers.taxes.state.dms_acctnumber,
ControlNumber: bill.vendor.dmsid,
Amount: Dinero(),
// Comment: "String",
AdditionalInfo: bill.vendor.name,
InvoiceNumber: bill.invoice_number,
InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString(),
},
};
bill.billlines.forEach((bl) => {
let lineDinero = Dinero({
amount: Math.round((bl.actual_cost || 0) * 100),
})
.multiply(bl.quantity)
.multiply(bill.is_credit_memo ? -1 : 1);
const cc = getCostAccount(bl, bodyshop.md_responsibility_centers);
if (!billHash[cc.name]) {
billHash[cc.name] = {
Account:
bodyshop.pbs_configuration.appostingaccount === "wip"
? cc.dms_wip_acctnumber
: cc.dms_acctnumber,
ControlNumber:
bodyshop.pbs_configuration.apcontrol === "ro"
? bill.job.ro_number
: bill.vendor.dmsid,
Amount: Dinero(),
// Comment: "String",
AdditionalInfo: bill.vendor.name,
InvoiceNumber: bill.invoice_number,
InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString(),
};
}
//Add the line amount.
billHash[cc.name] = {
...billHash[cc.name],
Amount: billHash[cc.name].Amount.add(lineDinero),
};
//Does the line have taxes?
if (bl.applicable_taxes.federal) {
billHash[bodyshop.md_responsibility_centers.taxes.federal_itc.name] =
{
...billHash[
bodyshop.md_responsibility_centers.taxes.federal_itc.name
],
Amount: billHash[
bodyshop.md_responsibility_centers.taxes.federal_itc.name
].Amount.add(lineDinero.percentage(bill.federal_tax_rate || 0)),
};
}
if (bl.applicable_taxes.state) {
billHash[bodyshop.md_responsibility_centers.taxes.state.name] = {
...billHash[bodyshop.md_responsibility_centers.taxes.state.name],
Amount: billHash[
bodyshop.md_responsibility_centers.taxes.state.name
].Amount.add(lineDinero.percentage(bill.state_tax_rate || 0)),
};
}
//End tax check
});
let APAmount = Dinero();
Object.keys(billHash).map((key) => {
if (billHash[key].Amount.getAmount() > 0 || billHash[key].Amount.getAmount() < 0) {
transactionObject.Posting.Lines.push({
...billHash[key],
Amount: billHash[key].Amount.toFormat("0.00"),
});
APAmount = APAmount.add(billHash[key].Amount); //Calculate the total expense for the bill iteratively to create the corresponding credit to AP.
}
});
transactionObject.Posting.Lines.push({
Account: bodyshop.md_responsibility_centers.ap.dms_acctnumber,
ControlNumber: bill.vendor.dmsid,
Amount: APAmount.multiply(-1).toFormat("0.00"),
// Comment: "String",
AdditionalInfo: bill.vendor.name,
InvoiceNumber: bill.invoice_number,
InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString(),
});
transactionlist.push(transactionObject);
});
return transactionlist;
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error encountered in PbsCalculateAllocationsAp. ${error}`
);
const transactionlist = [];
if (bills.length === 0) {
CdkBase.createLogEvent(
socket,
"ERROR",
`No bills found for export. Ensure they have not already been exported and try again.`
);
}
bills.forEach((bill) => {
//Keep the allocations at the bill level.
const transactionObject = {
SerialNumber: socket.bodyshop.pbs_serialnumber,
billid: bill.id,
Posting: {
Reference: bill.invoice_number,
JournalCode: socket.txEnvelope ? socket.txEnvelope.journal : null,
TransactionDate: moment().tz(socket.bodyshop.timezone).toISOString(), //"0001-01-01T00:00:00.0000000Z",
//Description: "Bulk AP posting.",
//AdditionalInfo: "String",
Source: "ImEX Online", //TODO:AIO Resolve this for rome online.
Lines: [] //socket.apAllocations,
}
};
const billHash = {
[bodyshop.md_responsibility_centers.taxes.federal_itc.name]: {
Account: bodyshop.md_responsibility_centers.taxes.federal_itc.dms_acctnumber,
ControlNumber: bill.vendor.dmsid,
Amount: Dinero(),
// Comment: "String",
AdditionalInfo: bill.vendor.name,
InvoiceNumber: bill.invoice_number,
InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString()
},
[bodyshop.md_responsibility_centers.taxes.state.name]: {
Account: bodyshop.md_responsibility_centers.taxes.state.dms_acctnumber,
ControlNumber: bill.vendor.dmsid,
Amount: Dinero(),
// Comment: "String",
AdditionalInfo: bill.vendor.name,
InvoiceNumber: bill.invoice_number,
InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString()
}
};
bill.billlines.forEach((bl) => {
let lineDinero = Dinero({
amount: Math.round((bl.actual_cost || 0) * 100)
})
.multiply(bl.quantity)
.multiply(bill.is_credit_memo ? -1 : 1);
const cc = getCostAccount(bl, bodyshop.md_responsibility_centers);
if (!billHash[cc.name]) {
billHash[cc.name] = {
Account: bodyshop.pbs_configuration.appostingaccount === "wip" ? cc.dms_wip_acctnumber : cc.dms_acctnumber,
ControlNumber: bodyshop.pbs_configuration.apcontrol === "ro" ? bill.job.ro_number : bill.vendor.dmsid,
Amount: Dinero(),
// Comment: "String",
AdditionalInfo: bill.vendor.name,
InvoiceNumber: bill.invoice_number,
InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString()
};
}
//Add the line amount.
billHash[cc.name] = {
...billHash[cc.name],
Amount: billHash[cc.name].Amount.add(lineDinero)
};
//Does the line have taxes?
if (bl.applicable_taxes.federal) {
billHash[bodyshop.md_responsibility_centers.taxes.federal_itc.name] = {
...billHash[bodyshop.md_responsibility_centers.taxes.federal_itc.name],
Amount: billHash[bodyshop.md_responsibility_centers.taxes.federal_itc.name].Amount.add(
lineDinero.percentage(bill.federal_tax_rate || 0)
)
};
}
if (bl.applicable_taxes.state) {
billHash[bodyshop.md_responsibility_centers.taxes.state.name] = {
...billHash[bodyshop.md_responsibility_centers.taxes.state.name],
Amount: billHash[bodyshop.md_responsibility_centers.taxes.state.name].Amount.add(
lineDinero.percentage(bill.state_tax_rate || 0)
)
};
}
//End tax check
});
let APAmount = Dinero();
Object.keys(billHash).map((key) => {
if (billHash[key].Amount.getAmount() > 0 || billHash[key].Amount.getAmount() < 0) {
transactionObject.Posting.Lines.push({
...billHash[key],
Amount: billHash[key].Amount.toFormat("0.00")
});
APAmount = APAmount.add(billHash[key].Amount); //Calculate the total expense for the bill iteratively to create the corresponding credit to AP.
}
});
transactionObject.Posting.Lines.push({
Account: bodyshop.md_responsibility_centers.ap.dms_acctnumber,
ControlNumber: bill.vendor.dmsid,
Amount: APAmount.multiply(-1).toFormat("0.00"),
// Comment: "String",
AdditionalInfo: bill.vendor.name,
InvoiceNumber: bill.invoice_number,
InvoiceDate: moment(bill.date).tz(bodyshop.timezone).toISOString()
});
transactionlist.push(transactionObject);
});
return transactionlist;
} catch (error) {
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsCalculateAllocationsAp. ${error}`);
}
}
exports.PbsCalculateAllocationsAp = PbsCalculateAllocationsAp;
async function QueryBillData(socket, billids) {
CdkBase.createLogEvent(
socket,
"DEBUG",
`Querying bill data for id(s) ${billids}`
);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client
.setHeaders({Authorization: `Bearer ${socket.handshake.auth.token}`})
.request(queries.GET_PBS_AP_ALLOCATIONS, {billids: billids});
CdkBase.createLogEvent(
socket,
"TRACE",
`Bill data query result ${JSON.stringify(result, null, 2)}`
);
CdkBase.createLogEvent(socket, "DEBUG", `Querying bill data for id(s) ${billids}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.GET_PBS_AP_ALLOCATIONS, { billids: billids });
CdkBase.createLogEvent(socket, "TRACE", `Bill data query result ${JSON.stringify(result, null, 2)}`);
return result;
return result;
}
//@returns the account object.
function getCostAccount(billline, respcenters) {
if (!billline.cost_center) return null;
if (!billline.cost_center) return null;
const acctName = respcenters.defaults.costs[billline.cost_center];
const acctName = respcenters.defaults.costs[billline.cost_center];
return respcenters.costs.find((c) => c.name === acctName);
return respcenters.costs.find((c) => c.name === acctName);
}
exports.PbsExportAp = async function (socket, {billids, txEnvelope}) {
CdkBase.createLogEvent(socket, "DEBUG", `Exporting selected AP.`);
exports.PbsExportAp = async function (socket, { billids, txEnvelope }) {
CdkBase.createLogEvent(socket, "DEBUG", `Exporting selected AP.`);
//apAllocations has the same shap as the lines key for the accounting posting to PBS.
socket.apAllocations = await PbsCalculateAllocationsAp(socket, billids);
socket.txEnvelope = txEnvelope;
for (const allocation of socket.apAllocations) {
const {billid, ...restAllocation} = allocation;
const {data: AccountPostingChange} = await axios.post(
PBS_ENDPOINTS.AccountingPostingChange,
restAllocation,
{auth: PBS_CREDENTIALS, socket}
);
//apAllocations has the same shap as the lines key for the accounting posting to PBS.
socket.apAllocations = await PbsCalculateAllocationsAp(socket, billids);
socket.txEnvelope = txEnvelope;
for (const allocation of socket.apAllocations) {
const { billid, ...restAllocation } = allocation;
const { data: AccountPostingChange } = await axios.post(PBS_ENDPOINTS.AccountingPostingChange, restAllocation, {
auth: PBS_CREDENTIALS,
socket
});
CheckForErrors(socket, AccountPostingChange);
CheckForErrors(socket, AccountPostingChange);
if (AccountPostingChange.WasSuccessful) {
CdkBase.createLogEvent(socket, "DEBUG", `Marking bill as exported.`);
await MarkApExported(socket, [billid]);
if (AccountPostingChange.WasSuccessful) {
CdkBase.createLogEvent(socket, "DEBUG", `Marking bill as exported.`);
await MarkApExported(socket, [billid]);
socket.emit("ap-export-success", billid);
} else {
CdkBase.createLogEvent(socket, "ERROR", `Export was not succesful.`);
socket.emit("ap-export-failure", {
billid,
error: AccountPostingChange.Message,
});
}
socket.emit("ap-export-success", billid);
} else {
CdkBase.createLogEvent(socket, "ERROR", `Export was not succesful.`);
socket.emit("ap-export-failure", {
billid,
error: AccountPostingChange.Message
});
}
socket.emit("ap-export-complete");
}
socket.emit("ap-export-complete");
};
async function MarkApExported(socket, billids) {
CdkBase.createLogEvent(
socket,
"DEBUG",
`Marking bills as exported for id ${billids}`
);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client
.setHeaders({Authorization: `Bearer ${socket.handshake.auth.token}`})
.request(queries.MARK_BILLS_EXPORTED, {
billids,
bill: {
exported: true,
exported_at: new Date(),
},
logs: socket.bills.map((bill) => ({
bodyshopid: socket.bodyshop.id,
billid: bill.id,
successful: true,
useremail: socket.user.email,
})),
});
CdkBase.createLogEvent(socket, "DEBUG", `Marking bills as exported for id ${billids}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.MARK_BILLS_EXPORTED, {
billids,
bill: {
exported: true,
exported_at: new Date()
},
logs: socket.bills.map((bill) => ({
bodyshopid: socket.bodyshop.id,
billid: bill.id,
successful: true,
useremail: socket.user.email
}))
});
return result;
return result;
}

View File

@@ -1,16 +1,13 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const IMEX_PBS_USER = process.env.IMEX_PBS_USER,
IMEX_PBS_PASSWORD = process.env.IMEX_PBS_PASSWORD;
IMEX_PBS_PASSWORD = process.env.IMEX_PBS_PASSWORD;
const PBS_CREDENTIALS = {
password: IMEX_PBS_PASSWORD,
username: IMEX_PBS_USER,
password: IMEX_PBS_PASSWORD,
username: IMEX_PBS_USER
};
exports.PBS_CREDENTIALS = PBS_CREDENTIALS;
@@ -21,10 +18,10 @@ exports.PBS_CREDENTIALS = PBS_CREDENTIALS;
const pbsDomain = `https://partnerhub.pbsdealers.com/json/reply`;
exports.PBS_ENDPOINTS = {
AccountGet: `${pbsDomain}/AccountGet`,
ContactGet: `${pbsDomain}/ContactGet`,
VehicleGet: `${pbsDomain}/VehicleGet`,
AccountingPostingChange: `${pbsDomain}/AccountingPostingChange`,
ContactChange: `${pbsDomain}/ContactChange`,
VehicleChange: `${pbsDomain}/VehicleChange`,
AccountGet: `${pbsDomain}/AccountGet`,
ContactGet: `${pbsDomain}/ContactGet`,
VehicleGet: `${pbsDomain}/VehicleGet`,
AccountingPostingChange: `${pbsDomain}/AccountingPostingChange`,
ContactChange: `${pbsDomain}/ContactChange`,
VehicleChange: `${pbsDomain}/VehicleChange`
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,32 +1,29 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const OAuthClient = require("intuit-oauth");
const logger = require("../../utils/logger");
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,
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
});
exports.default = async (req, res) => {
try {
logger.log("qbo-auth-uri", "DEBUG", req.user.email, null, null);
const authUri = oauthClient.authorizeUri({
scope: [OAuthClient.scopes.Accounting, OAuthClient.scopes.OpenId],
state: req.user.email,
}); // can be an array of multiple scopes ex : {scope:[OAuthClient.scopes.Accounting,OAuthClient.scopes.OpenId]}
try {
logger.log("qbo-auth-uri", "DEBUG", req.user.email, null, null);
const authUri = oauthClient.authorizeUri({
scope: [OAuthClient.scopes.Accounting, OAuthClient.scopes.OpenId],
state: req.user.email
}); // can be an array of multiple scopes ex : {scope:[OAuthClient.scopes.Accounting,OAuthClient.scopes.OpenId]}
res.send(authUri);
} catch (error) {
logger.log("qbo-auth-uri-error", "ERROR", req.user.email, null, {error});
res.send(authUri);
} catch (error) {
logger.log("qbo-auth-uri-error", "ERROR", req.user.email, null, { error });
res.status(500).json(error);
}
res.status(500).json(error);
}
};

View File

@@ -1,96 +1,82 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const logger = require("../../utils/logger");
const OAuthClient = require("intuit-oauth");
const client = require("../../graphql-client/graphql-client").client;
const queries = require("../../graphql-client/queries");
const {parse, stringify} = require("querystring");
const { parse, stringify } = require("querystring");
const InstanceManager = require("../../utils/instanceMgr").default;
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,
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
});
let url;
if (process.env.NODE_ENV === "production") { //TODO:AIO Add in QBO callbacks.
url = InstanceManager({imex: `https://imex.online`, rome: `https://romeonline.io`,});
if (process.env.NODE_ENV === "production") {
//TODO:AIO Add in QBO callbacks.
url = InstanceManager({ imex: `https://imex.online`, rome: `https://romeonline.io` });
} else if (process.env.NODE_ENV === "test") {
url = InstanceManager({imex: `https://test.imex.online`,rome: `https://test.romeonline.io`});
url = InstanceManager({ imex: `https://test.imex.online`, rome: `https://test.romeonline.io` });
} else {
url = `http://localhost:3000`;
url = `http://localhost:3000`;
}
exports.default = async (req, res) => {
const queryString = req.url.split("?").reverse()[0];
const params = parse(queryString);
try {
logger.log("qbo-callback-create-token", "DEBUG", params.state, null, null);
const authResponse = await oauthClient.createToken(req.url);
if (authResponse.json.error) {
logger.log("qbo-callback-error", "ERROR", params.state, null, {
error: authResponse.json,
});
res.redirect(
`${url}/manage/accounting/qbo?error=${encodeURIComponent(
JSON.stringify(authResponse.json)
)}`
);
} else {
await client.request(queries.SET_QBO_AUTH_WITH_REALM, {
email: params.state,
qbo_auth: {...authResponse.json, createdAt: Date.now()},
qbo_realmId: params.realmId,
});
logger.log(
"qbo-callback-create-token-success",
"DEBUG",
params.state,
null,
null
);
const queryString = req.url.split("?").reverse()[0];
const params = parse(queryString);
try {
logger.log("qbo-callback-create-token", "DEBUG", params.state, null, null);
const authResponse = await oauthClient.createToken(req.url);
if (authResponse.json.error) {
logger.log("qbo-callback-error", "ERROR", params.state, null, {
error: authResponse.json
});
res.redirect(`${url}/manage/accounting/qbo?error=${encodeURIComponent(JSON.stringify(authResponse.json))}`);
} else {
await client.request(queries.SET_QBO_AUTH_WITH_REALM, {
email: params.state,
qbo_auth: { ...authResponse.json, createdAt: Date.now() },
qbo_realmId: params.realmId
});
logger.log("qbo-callback-create-token-success", "DEBUG", params.state, null, null);
res.redirect(
`${url}/manage/accounting/qbo?${stringify(params)}`
);
}
} catch (e) {
logger.log("qbo-callback-error", "ERROR", params.state, null, {
error: e,
});
res.status(400).json(e);
res.redirect(`${url}/manage/accounting/qbo?${stringify(params)}`);
}
} catch (e) {
logger.log("qbo-callback-error", "ERROR", params.state, null, {
error: e
});
res.status(400).json(e);
}
};
exports.refresh = async (oauthClient, req) => {
try {
// logger.log("qbo-token-refresh", "DEBUG", req.user.email, null, null);
const authResponse = await oauthClient.refresh();
await client.request(queries.SET_QBO_AUTH, {
email: req.user.email,
qbo_auth: {...authResponse.json, createdAt: Date.now()},
});
} catch (error) {
logger.log("qbo-token-refresh-error", "ERROR", req.user.email, null, {
error,
});
}
try {
// logger.log("qbo-token-refresh", "DEBUG", req.user.email, null, null);
const authResponse = await oauthClient.refresh();
await client.request(queries.SET_QBO_AUTH, {
email: req.user.email,
qbo_auth: { ...authResponse.json, createdAt: Date.now() }
});
} catch (error) {
logger.log("qbo-token-refresh-error", "ERROR", req.user.email, null, {
error
});
}
};
exports.setNewRefreshToken = async (email, apiResponse) => {
//logger.log("qbo-token-updated", "DEBUG", email, null, null);
//logger.log("qbo-token-updated", "DEBUG", email, null, null);
await client.request(queries.SET_QBO_AUTH, {
email,
qbo_auth: {...apiResponse.token, createdAt: Date.now()},
});
await client.request(queries.SET_QBO_AUTH, {
email,
qbo_auth: { ...apiResponse.token, createdAt: Date.now() }
});
};

View File

@@ -2,336 +2,282 @@ 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"}`
),
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const logger = require("../../utils/logger");
const Dinero = require("dinero.js");
const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
const apiGqlClient = require("../../graphql-client/graphql-client").client;
const queries = require("../../graphql-client/queries");
const {
refresh: refreshOauthToken,
setNewRefreshToken,
} = require("./qbo-callback");
const { refresh: refreshOauthToken, setNewRefreshToken } = require("./qbo-callback");
const OAuthClient = require("intuit-oauth");
const moment = require("moment-timezone");
const findTaxCode = require("../qb-receivables-lines").findTaxCode;
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
});
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
});
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];
oauthClient.setToken(response.associations[0].qbo_auth);
if (!qbo_realmId) {
res.status(401).json({ error: "No company associated." });
return;
}
await refreshOauthToken(oauthClient, req);
const { bills: billsToQuery, elgen } = req.body;
const BearerToken = req.BearerToken;
const client = req.userGraphQLClient;
logger.log("qbo-payable-create", "DEBUG", req.user.email, billsToQuery);
const result = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.QUERY_BILLS_FOR_PAYABLES_EXPORT, {
bills: billsToQuery
});
const { bills, bodyshops } = result;
const ret = [];
const bodyshop = bodyshops[0];
for (const bill of bills) {
try {
let vendorRecord;
vendorRecord = await QueryVendorRecord(oauthClient, qbo_realmId, req, bill);
if (!vendorRecord) {
vendorRecord = await InsertVendorRecord(oauthClient, qbo_realmId, req, bill);
}
const insertResults = await InsertBill(oauthClient, qbo_realmId, req, bill, vendorRecord, bodyshop);
// //No error. Mark the job exported & insert export log.
if (elgen) {
const result = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.QBO_MARK_BILL_EXPORTED, {
billId: bill.id,
bill: {
exported: true,
exported_at: moment().tz(bodyshop.timezone)
},
logs: [
{
bodyshopid: bodyshop.id,
billid: bill.id,
successful: true,
useremail: req.user.email
}
]
});
}
ret.push({ billid: bill.id, success: true });
} catch (error) {
ret.push({
billid: bill.id,
success: false,
errorMessage: (error && error.authResponse && error.authResponse.body) || (error && error.message)
});
const {qbo_realmId} = response.associations[0];
oauthClient.setToken(response.associations[0].qbo_auth);
if (!qbo_realmId) {
res.status(401).json({error: "No company associated."});
return;
//Add the export log error.
if (elgen) {
const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.INSERT_EXPORT_LOG, {
logs: [
{
bodyshopid: bodyshop.id,
billid: bill.id,
successful: false,
message: JSON.stringify([
(error && error.authResponse && error.authResponse.body) || (error && error.message)
]),
useremail: req.user.email
}
]
});
}
await refreshOauthToken(oauthClient, req);
const {bills: billsToQuery, elgen} = req.body;
const BearerToken = req.BearerToken;
const client = req.userGraphQLClient;
logger.log("qbo-payable-create", "DEBUG", req.user.email, billsToQuery);
const result = await client
.setHeaders({Authorization: BearerToken})
.request(queries.QUERY_BILLS_FOR_PAYABLES_EXPORT, {
bills: billsToQuery,
});
const {bills, bodyshops} = result;
const ret = [];
const bodyshop = bodyshops[0];
for (const bill of bills) {
try {
let vendorRecord;
vendorRecord = await QueryVendorRecord(
oauthClient,
qbo_realmId,
req,
bill
);
if (!vendorRecord) {
vendorRecord = await InsertVendorRecord(
oauthClient,
qbo_realmId,
req,
bill
);
}
const insertResults = await InsertBill(
oauthClient,
qbo_realmId,
req,
bill,
vendorRecord,
bodyshop
);
// //No error. Mark the job exported & insert export log.
if (elgen) {
const result = await client
.setHeaders({Authorization: BearerToken})
.request(queries.QBO_MARK_BILL_EXPORTED, {
billId: bill.id,
bill: {
exported: true,
exported_at: moment().tz(bodyshop.timezone),
},
logs: [
{
bodyshopid: bodyshop.id,
billid: bill.id,
successful: true,
useremail: req.user.email,
},
],
});
}
ret.push({billid: bill.id, success: true});
} catch (error) {
ret.push({
billid: bill.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,
billid: bill.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-payable-create-error", "ERROR", req.user.email, {error: error.message, stack: error.stack});
res.status(400).json(error);
}
}
res.status(200).json(ret);
} catch (error) {
console.log(error);
logger.log("qbo-payable-create-error", "ERROR", req.user.email, {
error: error.message,
stack: error.stack
});
res.status(400).json(error);
}
};
async function QueryVendorRecord(oauthClient, qbo_realmId, req, bill) {
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(
qbo_realmId,
"query",
`select * From vendor where DisplayName = '${StandardizeName(
bill.vendor.name
)}'`
),
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
setNewRefreshToken(req.user.email, result);
return (
result.json &&
result.json.QueryResponse &&
result.json.QueryResponse.Vendor &&
result.json.QueryResponse.Vendor[0]
);
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
error:
(error && error.authResponse && error.authResponse.body) ||
(error && error.message),
method: "QueryVendorRecord",
});
throw error;
}
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(
qbo_realmId,
"query",
`select * From vendor where DisplayName = '${StandardizeName(bill.vendor.name)}'`
),
method: "POST",
headers: {
"Content-Type": "application/json"
}
});
setNewRefreshToken(req.user.email, result);
return (
result.json &&
result.json.QueryResponse &&
result.json.QueryResponse.Vendor &&
result.json.QueryResponse.Vendor[0]
);
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
error: (error && error.authResponse && error.authResponse.body) || (error && error.message),
method: "QueryVendorRecord"
});
throw error;
}
}
async function InsertVendorRecord(oauthClient, qbo_realmId, req, bill) {
const Vendor = {
DisplayName: bill.vendor.name,
};
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "vendor"),
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(Vendor),
});
setNewRefreshToken(req.user.email, result);
return result && result.json && result.json.Vendor;
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
error:
(error && error.authResponse && error.authResponse.body) ||
(error && error.message),
method: "InsertVendorRecord",
});
throw error;
}
const Vendor = {
DisplayName: bill.vendor.name
};
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "vendor"),
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(Vendor)
});
setNewRefreshToken(req.user.email, result);
return result && result.json && result.json.Vendor;
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
error: (error && error.authResponse && error.authResponse.body) || (error && error.message),
method: "InsertVendorRecord"
});
throw error;
}
}
async function InsertBill(
oauthClient,
qbo_realmId,
req,
bill,
vendor,
bodyshop
) {
const {accounts, taxCodes, classes} = await QueryMetaData(
oauthClient,
qbo_realmId,
req
);
async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop) {
const { accounts, taxCodes, classes } = await QueryMetaData(oauthClient, qbo_realmId, req);
const lines = bill.billlines.map((il) =>
generateBillLine(
il,
accounts,
bill.job.class,
bodyshop.md_responsibility_centers.sales_tax_codes,
classes,
taxCodes,
bodyshop.md_responsibility_centers.costs
const lines = bill.billlines.map((il) =>
generateBillLine(
il,
accounts,
bill.job.class,
bodyshop.md_responsibility_centers.sales_tax_codes,
classes,
taxCodes,
bodyshop.md_responsibility_centers.costs
)
);
//QB USA with GST
//This was required for the No. 1 Collision Group.
if (
bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
bodyshop.accountingconfig.qbo_usa &&
bodyshop.region_config.includes("CA_")
) {
lines.push({
DetailType: "AccountBasedExpenseLineDetail",
AccountBasedExpenseLineDetail: {
...(bill.job.class ? { ClassRef: { value: classes[bill.job.class] } } : {}),
AccountRef: {
value: accounts[bodyshop.md_responsibility_centers.taxes.federal.accountdesc]
}
},
Amount: Dinero({
amount: Math.round(
bill.billlines.reduce((acc, val) => {
return acc + val.actual_cost * val.quantity;
}, 0) * 100
)
);
})
.percentage(bill.federal_tax_rate)
//QB USA with GST
//This was required for the No. 1 Collision Group.
if (
bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
bodyshop.accountingconfig.qbo_usa &&
bodyshop.region_config.includes("CA_")
) {
lines.push({
DetailType: "AccountBasedExpenseLineDetail",
AccountBasedExpenseLineDetail: {
...(bill.job.class
? {ClassRef: {value: classes[bill.job.class]}}
: {}),
AccountRef: {
value:
accounts[
bodyshop.md_responsibility_centers.taxes.federal.accountdesc
],
},
},
Amount: Dinero({
amount: Math.round(
bill.billlines.reduce((acc, val) => {
return acc + val.actual_cost * val.quantity;
}, 0) * 100
),
})
.percentage(bill.federal_tax_rate)
.toFormat(DineroQbFormat),
});
}
const billQbo = {
VendorRef: {
value: vendor.Id,
},
TxnDate: moment(bill.date)
//.tz(bill.job.bodyshop.timezone)
.format("YYYY-MM-DD"),
...(!bill.is_credit_memo &&
bill.vendor.due_date && {
DueDate: moment(bill.date)
//.tz(bill.job.bodyshop.timezone)
.add(bill.vendor.due_date, "days")
.format("YYYY-MM-DD"),
}),
DocNumber: bill.invoice_number,
//...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}),
...(!(
bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
bodyshop.accountingconfig.qbo_usa &&
bodyshop.region_config.includes("CA_")
)
? {GlobalTaxCalculation: "TaxExcluded"}
: {}),
...(bodyshop.accountingconfig.qbo_departmentid &&
bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && {
DepartmentRef: {value: bodyshop.accountingconfig.qbo_departmentid},
}),
PrivateNote: `RO ${bill.job.ro_number || ""}`,
Line: lines,
};
logger.log("qbo-payable-objectlog", "DEBUG", req.user.email, bill.id, {
billQbo,
.toFormat(DineroQbFormat)
});
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(
qbo_realmId,
bill.is_credit_memo ? "vendorcredit" : "bill"
),
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(billQbo),
});
setNewRefreshToken(req.user.email, result);
return result && result.json && result.json.Bill;
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
error:
(error && error.authResponse && error.authResponse.body) ||
(error && error.message),
method: "InsertBill",
});
throw error;
}
}
const billQbo = {
VendorRef: {
value: vendor.Id
},
TxnDate: moment(bill.date)
//.tz(bill.job.bodyshop.timezone)
.format("YYYY-MM-DD"),
...(!bill.is_credit_memo &&
bill.vendor.due_date && {
DueDate: moment(bill.date)
//.tz(bill.job.bodyshop.timezone)
.add(bill.vendor.due_date, "days")
.format("YYYY-MM-DD")
}),
DocNumber: bill.invoice_number,
//...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}),
...(!(
bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
bodyshop.accountingconfig.qbo_usa &&
bodyshop.region_config.includes("CA_")
)
? { GlobalTaxCalculation: "TaxExcluded" }
: {}),
...(bodyshop.accountingconfig.qbo_departmentid &&
bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && {
DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid }
}),
PrivateNote: `RO ${bill.job.ro_number || ""}`,
Line: lines
};
logger.log("qbo-payable-objectlog", "DEBUG", req.user.email, bill.id, {
billQbo
});
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, bill.is_credit_memo ? "vendorcredit" : "bill"),
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(billQbo)
});
setNewRefreshToken(req.user.email, result);
return result && result.json && result.json.Bill;
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
error: (error && error.authResponse && error.authResponse.body) || (error && error.message),
method: "InsertBill"
});
throw error;
}
}
// [
@@ -347,97 +293,88 @@ async function InsertBill(
// },
// ],
const generateBillLine = (
billLine,
accounts,
jobClass,
ioSalesTaxCodes,
classes,
taxCodes,
costCenters
) => {
const account = costCenters.find((c) => c.name === billLine.cost_center);
const generateBillLine = (billLine, accounts, jobClass, ioSalesTaxCodes, classes, taxCodes, costCenters) => {
const account = costCenters.find((c) => c.name === billLine.cost_center);
return {
DetailType: "AccountBasedExpenseLineDetail",
return {
DetailType: "AccountBasedExpenseLineDetail",
AccountBasedExpenseLineDetail: {
...(jobClass ? {ClassRef: {value: classes[jobClass]}} : {}),
TaxCodeRef: {
value:
taxCodes[findTaxCode(billLine.applicable_taxes, ioSalesTaxCodes)],
},
AccountRef: {
value: accounts[account.accountname],
},
},
AccountBasedExpenseLineDetail: {
...(jobClass ? { ClassRef: { value: classes[jobClass] } } : {}),
TaxCodeRef: {
value: taxCodes[findTaxCode(billLine.applicable_taxes, ioSalesTaxCodes)]
},
AccountRef: {
value: accounts[account.accountname]
}
},
Amount: Dinero({
amount: Math.round(billLine.actual_cost * 100),
})
.multiply(billLine.quantity || 1)
.toFormat(DineroQbFormat),
};
Amount: Dinero({
amount: Math.round(billLine.actual_cost * 100)
})
.multiply(billLine.quantity || 1)
.toFormat(DineroQbFormat)
};
};
async function QueryMetaData(oauthClient, qbo_realmId, req) {
const accounts = await oauthClient.makeApiCall({
url: urlBuilder(
qbo_realmId,
"query",
`select * From Account where AccountType in ('Cost of Goods Sold', 'Other Current Liability')`
),
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
setNewRefreshToken(req.user.email, accounts);
const taxCodes = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`),
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
const accounts = await oauthClient.makeApiCall({
url: urlBuilder(
qbo_realmId,
"query",
`select * From Account where AccountType in ('Cost of Goods Sold', 'Other Current Liability')`
),
method: "POST",
headers: {
"Content-Type": "application/json"
}
});
setNewRefreshToken(req.user.email, accounts);
const taxCodes = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`),
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 classes = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "query", `select * From Class`),
method: "POST",
headers: {
"Content-Type": "application/json"
}
});
const taxCodeMapping = {};
const taxCodeMapping = {};
taxCodes.json &&
taxCodes.json &&
taxCodes.json.QueryResponse &&
taxCodes.json.QueryResponse.TaxCode &&
taxCodes.json.QueryResponse.TaxCode.forEach((t) => {
taxCodeMapping[t.Name] = t.Id;
taxCodeMapping[t.Name] = t.Id;
});
const accountMapping = {};
const accountMapping = {};
accounts.json &&
accounts.json &&
accounts.json.QueryResponse &&
accounts.json.QueryResponse.Account &&
accounts.json.QueryResponse.Account.forEach((t) => {
accountMapping[t.FullyQualifiedName] = t.Id;
accountMapping[t.FullyQualifiedName] = t.Id;
});
const classMapping = {};
classes.json &&
const classMapping = {};
classes.json &&
classes.json.QueryResponse &&
classes.json.QueryResponse.Class &&
classes.json.QueryResponse.Class.forEach((t) => {
classMapping[t.Name] = t.Id;
classMapping[t.Name] = t.Id;
});
return {
accounts: accountMapping,
taxCodes: taxCodeMapping,
classes: classMapping,
};
return {
accounts: accountMapping,
taxCodes: taxCodeMapping,
classes: classMapping
};
}

View File

@@ -1,531 +1,439 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
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 { refresh: refreshOauthToken, setNewRefreshToken } = require("./qbo-callback");
const OAuthClient = require("intuit-oauth");
const moment = require("moment-timezone");
const GraphQLClient = require("graphql-request").GraphQLClient;
const {
QueryInsuranceCo,
InsertInsuranceCo,
InsertJob,
InsertOwner,
QueryJob,
QueryOwner,
QueryInsuranceCo,
InsertInsuranceCo,
InsertJob,
InsertOwner,
QueryJob,
QueryOwner
} = require("../qbo/qbo-receivables");
const {urlBuilder} = require("./qbo");
const {DineroQbFormat} = require("../accounting-constants");
const {findTaxCode} = require("../qb-receivables-lines");
const { urlBuilder } = require("./qbo");
const { DineroQbFormat } = require("../accounting-constants");
const { findTaxCode } = 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,
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
});
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];
oauthClient.setToken(response.associations[0].qbo_auth);
if (!qbo_realmId) {
res.status(401).json({error: "No company associated."});
return;
}
await refreshOauthToken(oauthClient, req);
const {payments: paymentsToQuery, elgen} = req.body;
const BearerToken = req.BearerToken;
const client = req.userGraphQLClient;
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 {
let isThreeTier = bodyshop.accountingconfig.tiers === 3;
let twoTierPref = bodyshop.accountingconfig.twotierpref;
//Replace this with a for-each loop to check every single Job that's included in the list.
//QB Multi AR - If it is in this scenario, overwrite whatever defaults are set since multi AR
//will always go Source => RO
if (payment.payer !== "Customer" && payment.payer !== "Insurance") {
payment.job.ins_co_nm = payment.payer;
twoTierPref = "source";
isThreeTier = false;
}
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,
payment.job
);
if (!insCoCustomerTier) {
//Creating the Insurance Customer.
insCoCustomerTier = await InsertInsuranceCo(
oauthClient,
qbo_realmId,
req,
payment.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,
payment.job,
isThreeTier,
insCoCustomerTier
);
//Query for the owner itself.
if (!ownerCustomerTier) {
ownerCustomerTier = await InsertOwner(
oauthClient,
qbo_realmId,
req,
payment.job,
isThreeTier,
insCoCustomerTier
);
}
}
//Query for the Job or Create it.
jobTier = await QueryJob(
oauthClient,
qbo_realmId,
req,
payment.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,
payment.job,
ownerCustomerTier || insCoCustomerTier
);
}
if (payment.amount > 0) {
await InsertPayment(
oauthClient,
qbo_realmId,
req,
payment,
jobTier,
bodyshop
);
} else {
await InsertCreditMemo(
oauthClient,
qbo_realmId,
req,
payment,
jobTier,
bodyshop
);
}
// //No error. Mark the payment exported & insert export log.
if (elgen) {
const result = await client
.setHeaders({Authorization: BearerToken})
.request(queries.QBO_MARK_PAYMENT_EXPORTED, {
paymentId: payment.id,
payment: {
exportedat: moment().tz(bodyshop.timezone),
},
logs: [
{
bodyshopid: bodyshop.id,
paymentid: payment.id,
successful: true,
useremail: req.user.email,
},
],
});
}
ret.push({paymentid: payment.id, success: true});
} catch (error) {
logger.log("qbo-payment-create-error", "ERROR", req.user.email, {
error:
(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,
paymentid: payment.id,
successful: false,
message: JSON.stringify([
(error && error.authResponse && error.authResponse.body) ||
(error && error.message),
]),
useremail: req.user.email,
},
],
});
}
ret.push({
paymentid: payment.id,
success: false,
errorMessage:
(error && error.authResponse && error.authResponse.body) ||
(error && error.message),
});
}
}
res.status(200).json(ret);
} catch (error) {
console.log(error);
logger.log("qbo-payment-create-error", "ERROR", req.user.email, {
error: error.message,
stack: error.stack,
});
res.status(400).json(error);
const { qbo_realmId } = response.associations[0];
oauthClient.setToken(response.associations[0].qbo_auth);
if (!qbo_realmId) {
res.status(401).json({ error: "No company associated." });
return;
}
await refreshOauthToken(oauthClient, req);
const { payments: paymentsToQuery, elgen } = req.body;
const BearerToken = req.BearerToken;
const client = req.userGraphQLClient;
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 {
let isThreeTier = bodyshop.accountingconfig.tiers === 3;
let twoTierPref = bodyshop.accountingconfig.twotierpref;
//Replace this with a for-each loop to check every single Job that's included in the list.
//QB Multi AR - If it is in this scenario, overwrite whatever defaults are set since multi AR
//will always go Source => RO
if (payment.payer !== "Customer" && payment.payer !== "Insurance") {
payment.job.ins_co_nm = payment.payer;
twoTierPref = "source";
isThreeTier = false;
}
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, payment.job);
if (!insCoCustomerTier) {
//Creating the Insurance Customer.
insCoCustomerTier = await InsertInsuranceCo(oauthClient, qbo_realmId, req, payment.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,
payment.job,
isThreeTier,
insCoCustomerTier
);
//Query for the owner itself.
if (!ownerCustomerTier) {
ownerCustomerTier = await InsertOwner(
oauthClient,
qbo_realmId,
req,
payment.job,
isThreeTier,
insCoCustomerTier
);
}
}
//Query for the Job or Create it.
jobTier = await QueryJob(
oauthClient,
qbo_realmId,
req,
payment.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, payment.job, ownerCustomerTier || insCoCustomerTier);
}
if (payment.amount > 0) {
await InsertPayment(oauthClient, qbo_realmId, req, payment, jobTier, bodyshop);
} else {
await InsertCreditMemo(oauthClient, qbo_realmId, req, payment, jobTier, bodyshop);
}
// //No error. Mark the payment exported & insert export log.
if (elgen) {
const result = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.QBO_MARK_PAYMENT_EXPORTED, {
paymentId: payment.id,
payment: {
exportedat: moment().tz(bodyshop.timezone)
},
logs: [
{
bodyshopid: bodyshop.id,
paymentid: payment.id,
successful: true,
useremail: req.user.email
}
]
});
}
ret.push({ paymentid: payment.id, success: true });
} catch (error) {
logger.log("qbo-payment-create-error", "ERROR", req.user.email, {
error: (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,
paymentid: payment.id,
successful: false,
message: JSON.stringify([
(error && error.authResponse && error.authResponse.body) || (error && error.message)
]),
useremail: req.user.email
}
]
});
}
ret.push({
paymentid: payment.id,
success: false,
errorMessage: (error && error.authResponse && error.authResponse.body) || (error && error.message)
});
}
}
res.status(200).json(ret);
} catch (error) {
console.log(error);
logger.log("qbo-payment-create-error", "ERROR", req.user.email, {
error: error.message,
stack: error.stack
});
res.status(400).json(error);
}
};
async function InsertPayment(
async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef, bodyshop) {
const { paymentMethods, invoices } = await QueryMetaData(
oauthClient,
qbo_realmId,
req,
payment,
parentRef,
bodyshop
) {
const {paymentMethods, invoices} = await QueryMetaData(
oauthClient,
qbo_realmId,
req,
payment.job.ro_number,
false,
parentRef
);
payment.job.ro_number,
false,
parentRef
);
if (invoices && invoices.length !== 1) {
throw new Error(
`More than 1 invoice with DocNumber ${payment.job.ro_number} found.`
);
}
if (invoices && invoices.length !== 1) {
throw new Error(`More than 1 invoice with DocNumber ${payment.job.ro_number} found.`);
}
const paymentQbo = {
CustomerRef: {
value: parentRef.Id,
},
TxnDate: moment(payment.date) //.tz(bodyshop.timezone)
.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],
},
PaymentRefNum: payment.transactionid,
...(invoices && invoices.length === 1 && invoices[0]
? {
Line: [
{
Amount: Dinero({
amount: Math.round(payment.amount * 100),
}).toFormat(DineroQbFormat),
LinkedTxn: [
{
TxnId: invoices[0].Id,
TxnType: "Invoice",
},
],
},
],
const paymentQbo = {
CustomerRef: {
value: parentRef.Id
},
TxnDate: moment(payment.date) //.tz(bodyshop.timezone)
.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]
},
PaymentRefNum: payment.transactionid,
...(invoices && invoices.length === 1 && invoices[0]
? {
Line: [
{
Amount: Dinero({
amount: Math.round(payment.amount * 100)
}).toFormat(DineroQbFormat),
LinkedTxn: [
{
TxnId: invoices[0].Id,
TxnType: "Invoice"
}
]
}
: {}),
};
logger.log("qbo-payments-objectlog", "DEBUG", req.user.email, payment.id, {
paymentQbo,
]
}
: {})
};
logger.log("qbo-payments-objectlog", "DEBUG", req.user.email, payment.id, {
paymentQbo
});
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "payment"),
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(paymentQbo)
});
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(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: error && error.message,
method: "InsertPayment",
});
throw error;
}
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: "InsertPayment"
});
throw error;
}
}
async function QueryMetaData(
oauthClient,
qbo_realmId,
req,
ro_number,
isCreditMemo,
parentTierRef
) {
const invoice = await oauthClient.makeApiCall({
url: urlBuilder(
qbo_realmId,
"query",
`select * From Invoice where DocNumber like '${ro_number}%'`
),
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditMemo, parentTierRef) {
const invoice = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "query", `select * From Invoice where DocNumber like '${ro_number}%'`),
method: "POST",
headers: {
"Content-Type": "application/json"
}
});
const paymentMethods = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "query", `select * From PaymentMethod`),
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
setNewRefreshToken(req.user.email, paymentMethods);
const paymentMethods = await oauthClient.makeApiCall({
url: urlBuilder(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(qbo_realmId, "query", `select * From Class`),
// 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 paymentMethodMapping = {};
const paymentMethodMapping = {};
paymentMethods.json &&
paymentMethods.json &&
paymentMethods.json.QueryResponse &&
paymentMethods.json.QueryResponse.PaymentMethod &&
paymentMethods.json.QueryResponse.PaymentMethod.forEach((t) => {
paymentMethodMapping[t.Name] = t.Id;
paymentMethodMapping[t.Name] = t.Id;
});
// const accountMapping = {};
// const accountMapping = {};
// accounts.json &&
// accounts.json.QueryResponse &&
// accounts.json.QueryResponse.Account.forEach((t) => {
// accountMapping[t.Name] = t.Id;
// });
// 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;
// });
let ret = {};
// const classMapping = {};
// classes.json &&
// classes.json.QueryResponse &&
// 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);
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 = {};
const itemMapping = {};
items.json &&
items.json.QueryResponse &&
items.json.QueryResponse.Item &&
items.json.QueryResponse.Item.forEach((t) => {
itemMapping[t.Name] = t.Id;
});
const taxCodeMapping = {};
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 &&
invoice.json.QueryResponse &&
invoice.json.QueryResponse.Invoice &&
(parentTierRef
? [
invoice.json.QueryResponse.Invoice.find(
(x) => x.CustomerRef.value === parentTierRef.Id
),
]
: [invoice.json.QueryResponse.Invoice[0]]),
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 &&
invoice.json.QueryResponse &&
invoice.json.QueryResponse.Invoice &&
(parentTierRef
? [invoice.json.QueryResponse.Invoice.find((x) => x.CustomerRef.value === parentTierRef.Id)]
: [invoice.json.QueryResponse.Invoice[0]])
};
}
async function InsertCreditMemo(
async function InsertCreditMemo(oauthClient, qbo_realmId, req, payment, parentRef, bodyshop) {
const { paymentMethods, invoices, items, taxCodes } = await QueryMetaData(
oauthClient,
qbo_realmId,
req,
payment,
parentRef,
bodyshop
) {
const {paymentMethods, invoices, items, taxCodes} = await QueryMetaData(
oauthClient,
qbo_realmId,
req,
payment.job.ro_number,
true,
parentRef
);
payment.job.ro_number,
true,
parentRef
);
if (invoices && invoices.length !== 1) {
throw new Error(
`More than 1 invoice with DocNumber ${payment.ro_number} found.`
);
}
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)
//.tz(bodyshop.timezone)
.format("YYYY-MM-DD"),
DocNumber: payment.paymentnum,
...(invoices && invoices[0]
? {InvoiceRef: {value: invoices[0].Id}}
: {}),
PaymentRefNum: payment.transactionid,
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,
const paymentQbo = {
CustomerRef: {
value: parentRef.Id
},
TxnDate: moment(payment.date)
//.tz(bodyshop.timezone)
.format("YYYY-MM-DD"),
DocNumber: payment.paymentnum,
...(invoices && invoices[0] ? { InvoiceRef: { value: invoices[0].Id } } : {}),
PaymentRefNum: payment.transactionid,
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)
});
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;
}
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;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,16 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
function urlBuilder(realmId, object, query = null) {
return `https://${
process.env.NODE_ENV === "production" ? "" : "sandbox-"
}quickbooks.api.intuit.com/v3/company/${realmId}/${object}${
query ? `?query=${encodeURIComponent(query)}` : ""
}`;
return `https://${
process.env.NODE_ENV === "production" ? "" : "sandbox-"
}quickbooks.api.intuit.com/v3/company/${realmId}/${object}${query ? `?query=${encodeURIComponent(query)}` : ""}`;
}
function StandardizeName(str) {
return str.replace(new RegExp(/'/g), "\\'");
return str.replace(new RegExp(/'/g), "\\'");
}
exports.urlBuilder = urlBuilder;

View File

@@ -6,154 +6,131 @@ const Dinero = require("dinero.js");
const builder = require("xmlbuilder2");
const QbXmlUtils = require("./qbxml-utils");
const moment = require("moment-timezone");
const logger = require('../../utils/logger');
const logger = require("../../utils/logger");
const InstanceManager = require("../../utils/instanceMgr").default;
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
exports.default = async (req, res) => {
const {bills: billsToQuery} = req.body;
const { bills: billsToQuery } = req.body;
const BearerToken = req.BearerToken;
const client = req.userGraphQLClient;
const BearerToken = req.BearerToken;
const client = req.userGraphQLClient;
try {
logger.log(
"qbxml-payable-create",
"DEBUG",
req.user.email,
req.body.billsToQuery
);
try {
logger.log("qbxml-payable-create", "DEBUG", req.user.email, req.body.billsToQuery);
const result = await client
.setHeaders({Authorization: BearerToken})
.request(queries.QUERY_BILLS_FOR_PAYABLES_EXPORT, {
bills: billsToQuery,
});
const {bills, bodyshops} = result;
const bodyshop = bodyshops[0];
const result = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.QUERY_BILLS_FOR_PAYABLES_EXPORT, {
bills: billsToQuery
});
const { bills, bodyshops } = result;
const bodyshop = bodyshops[0];
const QbXmlToExecute = [];
bills.map((i) => {
QbXmlToExecute.push({
id: i.id,
okStatusCodes: ["0"],
qbxml: generateBill(i, bodyshop),
});
});
const QbXmlToExecute = [];
bills.map((i) => {
QbXmlToExecute.push({
id: i.id,
okStatusCodes: ["0"],
qbxml: generateBill(i, bodyshop)
});
});
//For each invoice.
res.status(200).json(QbXmlToExecute);
} catch (error) {
logger.log(
"qbxml-payable-error",
"ERROR",
req.user.email,
req.body.billsToQuery,
{error: error.message, stack: error.stack}
);
res.status(400).send(JSON.stringify(error));
}
//For each invoice.
res.status(200).json(QbXmlToExecute);
} catch (error) {
logger.log("qbxml-payable-error", "ERROR", req.user.email, req.body.billsToQuery, {
error: error.message,
stack: error.stack
});
res.status(400).send(JSON.stringify(error));
}
};
const generateBill = (bill, bodyshop) => {
const billQbxmlObj = {
QBXML: {
QBXMLMsgsRq: {
"@onError": "continueOnError",
[`${bill.is_credit_memo ? "VendorCreditAddRq" : "BillAddRq"}`]: {
[`${bill.is_credit_memo ? "VendorCreditAdd" : "BillAdd"}`]: {
VendorRef: {
FullName: bill.vendor.name,
},
TxnDate: moment(bill.date)
//.tz(bill.job.bodyshop.timezone)
.format("YYYY-MM-DD"),
...(!bill.is_credit_memo &&
bill.vendor.due_date && {
DueDate: moment(bill.date)
// .tz(bill.job.bodyshop.timezone)
.add(bill.vendor.due_date, "days")
.format("YYYY-MM-DD"),
}),
RefNumber: bill.invoice_number,
Memo: `RO ${bill.job.ro_number || ""}`,
ExpenseLineAdd: bill.billlines.map((il) =>
generateBillLine(
il,
bodyshop.md_responsibility_centers,
bill.job.class
)
),
},
},
const billQbxmlObj = {
QBXML: {
QBXMLMsgsRq: {
"@onError": "continueOnError",
[`${bill.is_credit_memo ? "VendorCreditAddRq" : "BillAddRq"}`]: {
[`${bill.is_credit_memo ? "VendorCreditAdd" : "BillAdd"}`]: {
VendorRef: {
FullName: bill.vendor.name
},
},
};
TxnDate: moment(bill.date)
//.tz(bill.job.bodyshop.timezone)
.format("YYYY-MM-DD"),
...(!bill.is_credit_memo &&
bill.vendor.due_date && {
DueDate: moment(bill.date)
// .tz(bill.job.bodyshop.timezone)
.add(bill.vendor.due_date, "days")
.format("YYYY-MM-DD")
}),
RefNumber: bill.invoice_number,
Memo: `RO ${bill.job.ro_number || ""}`,
ExpenseLineAdd: bill.billlines.map((il) =>
generateBillLine(il, bodyshop.md_responsibility_centers, bill.job.class)
)
}
}
}
}
};
var billQbxml_partial = builder
.create(billQbxmlObj, {
version: "1.30",
encoding: "UTF-8",
headless: true,
})
.end({pretty: true});
var billQbxml_partial = builder
.create(billQbxmlObj, {
version: "1.30",
encoding: "UTF-8",
headless: true
})
.end({ pretty: true });
const billQbxml_Full = QbXmlUtils.addQbxmlHeader(billQbxml_partial);
const billQbxml_Full = QbXmlUtils.addQbxmlHeader(billQbxml_partial);
return billQbxml_Full;
return billQbxml_Full;
};
const generateBillLine = (billLine, responsibilityCenters, jobClass) => {
return {
AccountRef: {
FullName: responsibilityCenters.costs.find(
(c) => c.name === billLine.cost_center
).accountname,
},
Amount: Dinero({
amount: Math.round(billLine.actual_cost * 100),
})
.multiply(billLine.quantity || 1)
.toFormat(DineroQbFormat),
...(jobClass ? {ClassRef: {FullName: jobClass}} : {}),
...InstanceManager({imex:{
SalesTaxCodeRef: {
FullName: findTaxCode(
billLine,
responsibilityCenters.sales_tax_codes
),
},
} })
};
return {
AccountRef: {
FullName: responsibilityCenters.costs.find((c) => c.name === billLine.cost_center).accountname
},
Amount: Dinero({
amount: Math.round(billLine.actual_cost * 100)
})
.multiply(billLine.quantity || 1)
.toFormat(DineroQbFormat),
...(jobClass ? { ClassRef: { FullName: jobClass } } : {}),
...InstanceManager({
imex: {
SalesTaxCodeRef: {
FullName: findTaxCode(billLine, responsibilityCenters.sales_tax_codes)
}
}
})
};
};
const findTaxCode = (billLine, taxcode) => {
const {
applicable_taxes: {local, state, federal},
} =
billLine.applicable_taxes === null
? {
...billLine,
applicable_taxes: {local: false, state: false, federal: false},
}
: billLine;
const t = taxcode.filter(
(t) =>
!!t.local === !!local &&
!!t.state === !!state &&
!!t.federal === !!federal
);
if (t.length === 1) {
return t[0].code;
} else if (t.length > 1) {
return "Multiple Tax Codes Match";
} else {
return "No Tax Code Matches";
}
const {
applicable_taxes: { local, state, federal }
} =
billLine.applicable_taxes === null
? {
...billLine,
applicable_taxes: { local: false, state: false, federal: false }
}
: billLine;
const t = taxcode.filter((t) => !!t.local === !!local && !!t.state === !!state && !!t.federal === !!federal);
if (t.length === 1) {
return t[0].code;
} else if (t.length > 1) {
return "Multiple Tax Codes Match";
} else {
return "No Tax Code Matches";
}
};

View File

@@ -6,205 +6,166 @@ const builder = require("xmlbuilder2");
const moment = require("moment-timezone");
const QbXmlUtils = require("./qbxml-utils");
const QbxmlReceivables = require("./qbxml-receivables");
const logger = require('../../utils/logger');
const logger = require("../../utils/logger");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const {generateJobTier, generateOwnerTier, generateSourceTier} = QbXmlUtils;
const { generateJobTier, generateOwnerTier, generateSourceTier } = QbXmlUtils;
exports.default = async (req, res) => {
const {payments: paymentsToQuery} = req.body;
const { payments: paymentsToQuery } = req.body;
const BearerToken = req.BearerToken;
const client = req.userGraphQLClient;
const BearerToken = req.BearerToken;
const client = req.userGraphQLClient;
try {
logger.log(
"qbxml-payments-create",
"DEBUG",
req.user.email,
req.body.paymentsToQuery,
null
);
try {
logger.log("qbxml-payments-create", "DEBUG", req.user.email, req.body.paymentsToQuery, null);
const result = await client
.setHeaders({Authorization: BearerToken})
.request(queries.QUERY_PAYMENTS_FOR_EXPORT, {
payments: paymentsToQuery,
});
const {payments, bodyshops} = result;
const bodyshop = bodyshops[0];
const isThreeTier = bodyshop.accountingconfig.tiers === 3;
const twoTierPref = bodyshop.accountingconfig.twotierpref;
const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.QUERY_PAYMENTS_FOR_EXPORT, {
payments: paymentsToQuery
});
const { payments, bodyshops } = result;
const bodyshop = bodyshops[0];
const isThreeTier = bodyshop.accountingconfig.tiers === 3;
const twoTierPref = bodyshop.accountingconfig.twotierpref;
const QbXmlToExecute = [];
payments.map((i) => {
if (isThreeTier) {
QbXmlToExecute.push({
id: i.id,
okStatusCodes: ["0", "3100"],
qbxml: QbxmlReceivables.generateSourceCustomerQbxml(i.job, bodyshop), // Create the source customer.
});
}
QbXmlToExecute.push({
id: i.id,
okStatusCodes: ["0", "3100"],
qbxml: QbxmlReceivables.generateJobQbxml(
i.job,
bodyshop,
isThreeTier,
2,
twoTierPref
),
});
QbXmlToExecute.push({
id: i.id,
okStatusCodes: ["0", "3100"],
qbxml: QbxmlReceivables.generateJobQbxml(
i.job,
bodyshop,
isThreeTier,
3,
twoTierPref
),
});
QbXmlToExecute.push({
id: i.id,
okStatusCodes: ["0"],
qbxml: generatePayment(i, isThreeTier, twoTierPref, bodyshop),
});
const QbXmlToExecute = [];
payments.map((i) => {
if (isThreeTier) {
QbXmlToExecute.push({
id: i.id,
okStatusCodes: ["0", "3100"],
qbxml: QbxmlReceivables.generateSourceCustomerQbxml(i.job, bodyshop) // Create the source customer.
});
}
res.status(200).json(QbXmlToExecute);
} catch (error) {
logger.log(
"qbxml-payments-error",
"error",
req.user.email,
req.body.paymentsToQuery,
{error: error.message, stack: error.stack}
);
res.status(400).send(JSON.stringify(error));
}
QbXmlToExecute.push({
id: i.id,
okStatusCodes: ["0", "3100"],
qbxml: QbxmlReceivables.generateJobQbxml(i.job, bodyshop, isThreeTier, 2, twoTierPref)
});
QbXmlToExecute.push({
id: i.id,
okStatusCodes: ["0", "3100"],
qbxml: QbxmlReceivables.generateJobQbxml(i.job, bodyshop, isThreeTier, 3, twoTierPref)
});
QbXmlToExecute.push({
id: i.id,
okStatusCodes: ["0"],
qbxml: generatePayment(i, isThreeTier, twoTierPref, bodyshop)
});
});
res.status(200).json(QbXmlToExecute);
} catch (error) {
logger.log("qbxml-payments-error", "error", req.user.email, req.body.paymentsToQuery, {
error: error.message,
stack: error.stack
});
res.status(400).send(JSON.stringify(error));
}
};
const generatePayment = (payment, isThreeTier, twoTierPref, bodyshop) => {
let paymentQbxmlObj;
if (payment.amount > 0) {
paymentQbxmlObj = {
QBXML: {
QBXMLMsgsRq: {
"@onError": "continueOnError",
ReceivePaymentAddRq: {
ReceivePaymentAdd: {
CustomerRef: {
FullName: (payment.job.bodyshop.accountingconfig.tiers === 3
? `${generateSourceTier(payment.job)}:${generateOwnerTier(
payment.job,
isThreeTier,
twoTierPref
)}:${generateJobTier(payment.job)}`
: `${generateOwnerTier(
payment.job,
isThreeTier,
twoTierPref
)}:${generateJobTier(payment.job)}`
).trim(),
},
ARAccountRef: {
FullName:
payment.job.bodyshop.md_responsibility_centers.ar.accountname,
},
TxnDate: moment(payment.date)
// .tz(bodyshop.timezone)
.format("YYYY-MM-DD"), //Trim String
RefNumber: payment.paymentnum || payment.transactionid,
TotalAmount: Dinero({
amount: Math.round(payment.amount * 100),
}).toFormat(DineroQbFormat),
PaymentMethodRef: {
FullName: payment.type,
},
Memo: `RO ${payment.job.ro_number || ""} OWNER ${
payment.job.ownr_fn || ""
} ${payment.job.ownr_ln || ""} ${payment.job.ownr_co_nm || ""} ${
payment.stripeid || ""
} ${payment.payer ? ` PAID BY ${payment.payer}` : ""}`,
IsAutoApply: true,
},
},
},
},
};
} else {
paymentQbxmlObj = {
QBXML: {
QBXMLMsgsRq: {
"@onError": "continueOnError",
CreditMemoAddRq: {
CreditMemoAdd: {
CustomerRef: {
FullName: (payment.job.bodyshop.accountingconfig.tiers === 3
? `${generateSourceTier(payment.job)}:${generateOwnerTier(
payment.job,
isThreeTier,
twoTierPref
)}:${generateJobTier(payment.job)}`
: `${generateOwnerTier(
payment.job,
isThreeTier,
twoTierPref
)}:${generateJobTier(payment.job)}`
).trim(),
},
ARAccountRef: {
FullName:
payment.job.bodyshop.md_responsibility_centers.ar.accountname,
},
TxnDate: moment(payment.date)
//.tz(bodyshop.timezone)
.format("YYYY-MM-DD"), //Trim String
RefNumber:
payment.paymentnum || payment.stripeid || payment.transactionid,
let paymentQbxmlObj;
if (payment.amount > 0) {
paymentQbxmlObj = {
QBXML: {
QBXMLMsgsRq: {
"@onError": "continueOnError",
ReceivePaymentAddRq: {
ReceivePaymentAdd: {
CustomerRef: {
FullName: (payment.job.bodyshop.accountingconfig.tiers === 3
? `${generateSourceTier(payment.job)}:${generateOwnerTier(
payment.job,
isThreeTier,
twoTierPref
)}:${generateJobTier(payment.job)}`
: `${generateOwnerTier(payment.job, isThreeTier, twoTierPref)}:${generateJobTier(payment.job)}`
).trim()
},
ARAccountRef: {
FullName: payment.job.bodyshop.md_responsibility_centers.ar.accountname
},
TxnDate: moment(payment.date)
// .tz(bodyshop.timezone)
.format("YYYY-MM-DD"), //Trim String
RefNumber: payment.paymentnum || payment.transactionid,
TotalAmount: Dinero({
amount: Math.round(payment.amount * 100)
}).toFormat(DineroQbFormat),
PaymentMethodRef: {
FullName: payment.type
},
Memo: `RO ${payment.job.ro_number || ""} OWNER ${
payment.job.ownr_fn || ""
} ${payment.job.ownr_ln || ""} ${payment.job.ownr_co_nm || ""} ${
payment.stripeid || ""
} ${payment.payer ? ` PAID BY ${payment.payer}` : ""}`,
IsAutoApply: true
}
}
}
}
};
} else {
paymentQbxmlObj = {
QBXML: {
QBXMLMsgsRq: {
"@onError": "continueOnError",
CreditMemoAddRq: {
CreditMemoAdd: {
CustomerRef: {
FullName: (payment.job.bodyshop.accountingconfig.tiers === 3
? `${generateSourceTier(payment.job)}:${generateOwnerTier(
payment.job,
isThreeTier,
twoTierPref
)}:${generateJobTier(payment.job)}`
: `${generateOwnerTier(payment.job, isThreeTier, twoTierPref)}:${generateJobTier(payment.job)}`
).trim()
},
ARAccountRef: {
FullName: payment.job.bodyshop.md_responsibility_centers.ar.accountname
},
TxnDate: moment(payment.date)
//.tz(bodyshop.timezone)
.format("YYYY-MM-DD"), //Trim String
RefNumber: payment.paymentnum || payment.stripeid || payment.transactionid,
CreditMemoLineAdd: [
{
ItemRef: {
FullName:
payment.job.bodyshop.md_responsibility_centers.refund
.accountitem,
},
Desc: payment.memo,
Amount: Dinero({
amount: Math.round(payment.amount * 100 * -1),
}).toFormat(DineroQbFormat),
SalesTaxCodeRef: {FullName: "E"},
},
],
},
},
},
},
};
}
CreditMemoLineAdd: [
{
ItemRef: {
FullName: payment.job.bodyshop.md_responsibility_centers.refund.accountitem
},
Desc: payment.memo,
Amount: Dinero({
amount: Math.round(payment.amount * 100 * -1)
}).toFormat(DineroQbFormat),
SalesTaxCodeRef: { FullName: "E" }
}
]
}
}
}
}
};
}
var paymentQbxmlPartial = builder
.create(paymentQbxmlObj, {
version: "1.30",
encoding: "UTF-8",
headless: true,
})
.end({pretty: true});
var paymentQbxmlPartial = builder
.create(paymentQbxmlObj, {
version: "1.30",
encoding: "UTF-8",
headless: true
})
.end({ pretty: true });
const paymentQbxmlFull = QbXmlUtils.addQbxmlHeader(paymentQbxmlPartial);
const paymentQbxmlFull = QbXmlUtils.addQbxmlHeader(paymentQbxmlPartial);
return paymentQbxmlFull;
return paymentQbxmlFull;
};

View File

@@ -6,310 +6,255 @@ const moment = require("moment-timezone");
const builder = require("xmlbuilder2");
const QbXmlUtils = require("./qbxml-utils");
const CreateInvoiceLines = require("../qb-receivables-lines").default;
const logger = require('../../utils/logger');
const InstanceManager = require('../../utils/instanceMgr').default;
const logger = require("../../utils/logger");
const InstanceManager = require("../../utils/instanceMgr").default;
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
Dinero.globalRoundingMode = "HALF_EVEN";
const {generateJobTier, generateOwnerTier, generateSourceTier} = QbXmlUtils;
const { generateJobTier, generateOwnerTier, generateSourceTier } = QbXmlUtils;
exports.default = async (req, res) => {
const {jobIds} = req.body;
const { jobIds } = req.body;
const BearerToken = req.BearerToken;
const client = req.userGraphQLClient;
const BearerToken = req.BearerToken;
const client = req.userGraphQLClient;
try {
logger.log(
"qbxml-receivables-create",
"DEBUG",
req.user.email,
req.body.jobIds,
null
);
try {
logger.log("qbxml-receivables-create", "DEBUG", req.user.email, req.body.jobIds, null);
const result = await client
.setHeaders({Authorization: BearerToken})
.request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, {ids: jobIds});
const {jobs, bodyshops} = result;
const QbXmlToExecute = [];
const result = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, { ids: jobIds });
const { jobs, bodyshops } = result;
const QbXmlToExecute = [];
const bodyshop = bodyshops[0];
const bodyshop = bodyshops[0];
jobs.map((jobs_by_pk) => {
//Is this a two tier, or 3 tier setup?
const isThreeTier = bodyshop.accountingconfig.tiers === 3;
const twoTierPref = bodyshop.accountingconfig.twotierpref;
jobs.map((jobs_by_pk) => {
//Is this a two tier, or 3 tier setup?
const isThreeTier = bodyshop.accountingconfig.tiers === 3;
const twoTierPref = bodyshop.accountingconfig.twotierpref;
//This is the Insurance Company tier IF 3 tier is selected.
if (isThreeTier) {
QbXmlToExecute.push({
id: jobs_by_pk.id,
okStatusCodes: ["0", "3100"],
qbxml: generateSourceCustomerQbxml(jobs_by_pk, bodyshop), // Create the source customer.
});
}
//If 3 tier, this should be the customer.
//If 2 tier, this should be based on the pref.
QbXmlToExecute.push({
id: jobs_by_pk.id,
okStatusCodes: ["0", "3100"],
qbxml: generateJobQbxml(
jobs_by_pk,
bodyshop,
isThreeTier,
2,
twoTierPref
),
});
//This is always going to be the job.
QbXmlToExecute.push({
id: jobs_by_pk.id,
okStatusCodes: ["0", "3100"],
qbxml: generateJobQbxml(
jobs_by_pk,
bodyshop,
isThreeTier,
3,
twoTierPref
),
});
if (!req.body.custDataOnly) {
//Generate the actual invoice.
QbXmlToExecute.push({
id: jobs_by_pk.id,
okStatusCodes: ["0"],
qbxml: generateInvoiceQbxml(
jobs_by_pk,
bodyshop,
isThreeTier,
twoTierPref
),
});
}
//This is the Insurance Company tier IF 3 tier is selected.
if (isThreeTier) {
QbXmlToExecute.push({
id: jobs_by_pk.id,
okStatusCodes: ["0", "3100"],
qbxml: generateSourceCustomerQbxml(jobs_by_pk, bodyshop) // Create the source customer.
});
}
res.status(200).json(QbXmlToExecute);
} catch (error) {
logger.log(
"qbxml-receivables-error",
"error",
req.user.email,
req.body.jobIds,
{error: error.message, stack: error.stack}
);
res.status(400).send(JSON.stringify(error));
}
//If 3 tier, this should be the customer.
//If 2 tier, this should be based on the pref.
QbXmlToExecute.push({
id: jobs_by_pk.id,
okStatusCodes: ["0", "3100"],
qbxml: generateJobQbxml(jobs_by_pk, bodyshop, isThreeTier, 2, twoTierPref)
});
//This is always going to be the job.
QbXmlToExecute.push({
id: jobs_by_pk.id,
okStatusCodes: ["0", "3100"],
qbxml: generateJobQbxml(jobs_by_pk, bodyshop, isThreeTier, 3, twoTierPref)
});
if (!req.body.custDataOnly) {
//Generate the actual invoice.
QbXmlToExecute.push({
id: jobs_by_pk.id,
okStatusCodes: ["0"],
qbxml: generateInvoiceQbxml(jobs_by_pk, bodyshop, isThreeTier, twoTierPref)
});
}
});
res.status(200).json(QbXmlToExecute);
} catch (error) {
logger.log("qbxml-receivables-error", "error", req.user.email, req.body.jobIds, {
error: error.message,
stack: error.stack
});
res.status(400).send(JSON.stringify(error));
}
};
const generateSourceCustomerQbxml = (jobs_by_pk, bodyshop) => {
const customerQbxmlObj = {
QBXML: {
QBXMLMsgsRq: {
"@onError": "continueOnError",
CustomerAddRq: {
CustomerAdd: {
Name: jobs_by_pk.ins_co_nm.trim(),
// BillAddress: {
// Addr1: jobs_by_pk.ownr_addr1,
// Addr2: jobs_by_pk.ownr_addr2,
// City: jobs_by_pk.ownr_city,
// State: jobs_by_pk.ownr_st,
// PostalCode: jobs_by_pk.ownr_zip,
// },
},
},
},
},
};
const customerQbxmlObj = {
QBXML: {
QBXMLMsgsRq: {
"@onError": "continueOnError",
CustomerAddRq: {
CustomerAdd: {
Name: jobs_by_pk.ins_co_nm.trim()
// BillAddress: {
// Addr1: jobs_by_pk.ownr_addr1,
// Addr2: jobs_by_pk.ownr_addr2,
// City: jobs_by_pk.ownr_city,
// State: jobs_by_pk.ownr_st,
// PostalCode: jobs_by_pk.ownr_zip,
// },
}
}
}
}
};
var customerQbxml_partial = builder
.create(customerQbxmlObj, {
version: "1.30",
encoding: "UTF-8",
headless: true,
})
.end({pretty: true});
var customerQbxml_partial = builder
.create(customerQbxmlObj, {
version: "1.30",
encoding: "UTF-8",
headless: true
})
.end({ pretty: true });
const customerQbxml_Full = QbXmlUtils.addQbxmlHeader(customerQbxml_partial);
const customerQbxml_Full = QbXmlUtils.addQbxmlHeader(customerQbxml_partial);
return customerQbxml_Full;
return customerQbxml_Full;
};
exports.generateSourceCustomerQbxml = generateSourceCustomerQbxml;
const generateJobQbxml = (
jobs_by_pk,
bodyshop,
isThreeTier,
tierLevel,
twoTierPref
) => {
let Name;
let ParentRefName;
const generateJobQbxml = (jobs_by_pk, bodyshop, isThreeTier, tierLevel, twoTierPref) => {
let Name;
let ParentRefName;
if (tierLevel === 2) {
Name = generateOwnerTier(jobs_by_pk, isThreeTier, twoTierPref);
ParentRefName = isThreeTier ? generateSourceTier(jobs_by_pk) : null;
} else if (tierLevel === 3) {
Name = generateJobTier(jobs_by_pk);
ParentRefName = isThreeTier
? `${generateSourceTier(jobs_by_pk)}:${generateOwnerTier(jobs_by_pk)}`
: generateOwnerTier(jobs_by_pk, isThreeTier, twoTierPref);
if (tierLevel === 2) {
Name = generateOwnerTier(jobs_by_pk, isThreeTier, twoTierPref);
ParentRefName = isThreeTier ? generateSourceTier(jobs_by_pk) : null;
} else if (tierLevel === 3) {
Name = generateJobTier(jobs_by_pk);
ParentRefName = isThreeTier
? `${generateSourceTier(jobs_by_pk)}:${generateOwnerTier(jobs_by_pk)}`
: generateOwnerTier(jobs_by_pk, isThreeTier, twoTierPref);
}
const jobQbxmlObj = {
QBXML: {
QBXMLMsgsRq: {
"@onError": "continueOnError",
CustomerAddRq: {
CustomerAdd: {
Name: Name,
ParentRef: ParentRefName
? {
FullName: ParentRefName
}
: null,
...(tierLevel === 3
? {
BillAddress: {
Addr1: jobs_by_pk.ownr_addr1,
Addr2: jobs_by_pk.ownr_addr2,
City: jobs_by_pk.ownr_city,
State: jobs_by_pk.ownr_st,
PostalCode: jobs_by_pk.ownr_zip
},
ShipAddress: {
Addr1: jobs_by_pk.ownr_addr1,
Addr2: jobs_by_pk.ownr_addr2,
City: jobs_by_pk.ownr_city,
State: jobs_by_pk.ownr_st,
PostalCode: jobs_by_pk.ownr_zip
},
Email: jobs_by_pk.ownr_ea
}
: {})
}
}
}
}
};
const jobQbxmlObj = {
QBXML: {
QBXMLMsgsRq: {
"@onError": "continueOnError",
var jobQbxml_partial = builder
.create(jobQbxmlObj, {
version: "1.30",
encoding: "UTF-8",
headless: true
})
.end({ pretty: true });
CustomerAddRq: {
CustomerAdd: {
Name: Name,
ParentRef: ParentRefName
? {
FullName: ParentRefName,
}
: null,
...(tierLevel === 3
? {
BillAddress: {
Addr1: jobs_by_pk.ownr_addr1,
Addr2: jobs_by_pk.ownr_addr2,
City: jobs_by_pk.ownr_city,
State: jobs_by_pk.ownr_st,
PostalCode: jobs_by_pk.ownr_zip,
},
ShipAddress: {
Addr1: jobs_by_pk.ownr_addr1,
Addr2: jobs_by_pk.ownr_addr2,
City: jobs_by_pk.ownr_city,
State: jobs_by_pk.ownr_st,
PostalCode: jobs_by_pk.ownr_zip,
},
Email: jobs_by_pk.ownr_ea,
}
: {}),
},
},
},
},
};
const jobQbxml_Full = QbXmlUtils.addQbxmlHeader(jobQbxml_partial);
var jobQbxml_partial = builder
.create(jobQbxmlObj, {
version: "1.30",
encoding: "UTF-8",
headless: true,
})
.end({pretty: true});
const jobQbxml_Full = QbXmlUtils.addQbxmlHeader(jobQbxml_partial);
return jobQbxml_Full;
return jobQbxml_Full;
};
exports.generateJobQbxml = generateJobQbxml;
const generateInvoiceQbxml = (
jobs_by_pk,
bodyshop,
isThreeTier,
twoTierPref
) => {
//Build the Invoice XML file.
const generateInvoiceQbxml = (jobs_by_pk, bodyshop, isThreeTier, twoTierPref) => {
//Build the Invoice XML file.
const InvoiceLineAdd = CreateInvoiceLines({bodyshop, jobs_by_pk});
const InvoiceLineAdd = CreateInvoiceLines({ bodyshop, jobs_by_pk });
const invoiceQbxmlObj = {
QBXML: {
QBXMLMsgsRq: {
"@onError": "stopOnError",
InvoiceAddRq: {
InvoiceAdd: {
CustomerRef: {
FullName: (bodyshop.accountingconfig.tiers === 3
? `${generateSourceTier(jobs_by_pk)}:${generateOwnerTier(
jobs_by_pk
)}:${generateJobTier(jobs_by_pk)}`
: `${generateOwnerTier(
jobs_by_pk,
isThreeTier,
twoTierPref
)}:${generateJobTier(jobs_by_pk)}`
).trim(),
},
...(jobs_by_pk.class
? {ClassRef: {FullName: jobs_by_pk.class}}
: {}),
ARAccountRef: {
FullName: bodyshop.md_responsibility_centers.ar.accountname,
},
TxnDate: moment(jobs_by_pk.date_invoiced)
.tz(bodyshop.timezone)
.format("YYYY-MM-DD"),
RefNumber: jobs_by_pk.ro_number,
BillAddress: {
Addr1: jobs_by_pk.ownr_co_nm
? jobs_by_pk.ownr_co_nm.substring(0, 30).trim()
: `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}`
.substring(0, 30)
.trim()}`,
Addr2: jobs_by_pk.ownr_addr1,
Addr3: jobs_by_pk.ownr_addr2,
City: jobs_by_pk.ownr_city,
State: jobs_by_pk.ownr_st,
PostalCode: jobs_by_pk.ownr_zip,
},
ShipAddress: {
Addr1: jobs_by_pk.ownr_co_nm
? jobs_by_pk.ownr_co_nm.substring(0, 30).trim()
: `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}`
.substring(0, 30)
.trim()}`,
Addr2: jobs_by_pk.ownr_addr1,
Addr3: jobs_by_pk.ownr_addr2,
City: jobs_by_pk.ownr_city,
State: jobs_by_pk.ownr_st,
PostalCode: jobs_by_pk.ownr_zip,
},
PONumber: jobs_by_pk.clm_no,
...InstanceManager({rome: {
ItemSalesTaxRef: {
FullName:
bodyshop.md_responsibility_centers.taxes.invoiceexemptcode,
},
}}),
IsToBePrinted: bodyshop.accountingconfig.printlater,
...(jobs_by_pk.ownr_ea
? {IsToBeEmailed: bodyshop.accountingconfig.emaillater}
: {}),
InvoiceLineAdd: InvoiceLineAdd,
},
},
const invoiceQbxmlObj = {
QBXML: {
QBXMLMsgsRq: {
"@onError": "stopOnError",
InvoiceAddRq: {
InvoiceAdd: {
CustomerRef: {
FullName: (bodyshop.accountingconfig.tiers === 3
? `${generateSourceTier(jobs_by_pk)}:${generateOwnerTier(jobs_by_pk)}:${generateJobTier(jobs_by_pk)}`
: `${generateOwnerTier(jobs_by_pk, isThreeTier, twoTierPref)}:${generateJobTier(jobs_by_pk)}`
).trim()
},
},
};
...(jobs_by_pk.class ? { ClassRef: { FullName: jobs_by_pk.class } } : {}),
var invoiceQbxml_partial = builder
.create(invoiceQbxmlObj, {
version: "1.30",
encoding: "UTF-8",
headless: true,
})
.end({pretty: true});
ARAccountRef: {
FullName: bodyshop.md_responsibility_centers.ar.accountname
},
TxnDate: moment(jobs_by_pk.date_invoiced).tz(bodyshop.timezone).format("YYYY-MM-DD"),
RefNumber: jobs_by_pk.ro_number,
BillAddress: {
Addr1: jobs_by_pk.ownr_co_nm
? jobs_by_pk.ownr_co_nm.substring(0, 30).trim()
: `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}`.substring(0, 30).trim()}`,
Addr2: jobs_by_pk.ownr_addr1,
Addr3: jobs_by_pk.ownr_addr2,
City: jobs_by_pk.ownr_city,
State: jobs_by_pk.ownr_st,
PostalCode: jobs_by_pk.ownr_zip
},
ShipAddress: {
Addr1: jobs_by_pk.ownr_co_nm
? jobs_by_pk.ownr_co_nm.substring(0, 30).trim()
: `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}`.substring(0, 30).trim()}`,
Addr2: jobs_by_pk.ownr_addr1,
Addr3: jobs_by_pk.ownr_addr2,
City: jobs_by_pk.ownr_city,
State: jobs_by_pk.ownr_st,
PostalCode: jobs_by_pk.ownr_zip
},
PONumber: jobs_by_pk.clm_no,
...InstanceManager({
rome: {
ItemSalesTaxRef: {
FullName: bodyshop.md_responsibility_centers.taxes.invoiceexemptcode
}
}
}),
IsToBePrinted: bodyshop.accountingconfig.printlater,
...(jobs_by_pk.ownr_ea ? { IsToBeEmailed: bodyshop.accountingconfig.emaillater } : {}),
const invoiceQbxml_Full = QbXmlUtils.addQbxmlHeader(invoiceQbxml_partial);
InvoiceLineAdd: InvoiceLineAdd
}
}
}
}
};
return invoiceQbxml_Full;
var invoiceQbxml_partial = builder
.create(invoiceQbxmlObj, {
version: "1.30",
encoding: "UTF-8",
headless: true
})
.end({ pretty: true });
const invoiceQbxml_Full = QbXmlUtils.addQbxmlHeader(invoiceQbxml_partial);
return invoiceQbxml_Full;
};
// const generateInvoiceLine = (job, allocation, responsibilityCenters) => {

View File

@@ -1,50 +1,46 @@
exports.addQbxmlHeader = addQbxmlHeader = (xml) => {
return `<?xml version="1.0" encoding="utf-8"?>
return `<?xml version="1.0" encoding="utf-8"?>
<?qbxml version="13.0"?>
${xml}
`;
};
exports.generateSourceTier = (jobs_by_pk) => {
return jobs_by_pk.ins_co_nm && jobs_by_pk.ins_co_nm.trim().replace(":", " ");
return jobs_by_pk.ins_co_nm && jobs_by_pk.ins_co_nm.trim().replace(":", " ");
};
exports.generateJobTier = (jobs_by_pk) => {
return jobs_by_pk.ro_number && jobs_by_pk.ro_number.trim().replace(":", " ");
return jobs_by_pk.ro_number && jobs_by_pk.ro_number.trim().replace(":", " ");
};
exports.generateOwnerTier = (jobs_by_pk, isThreeTier, twotierpref) => {
if (isThreeTier) {
//It's always gonna be the owner now. Same as 2 tier by name
return (
jobs_by_pk.ownr_co_nm
? `${jobs_by_pk.ownr_co_nm.substring(0, 30)} #${
jobs_by_pk.owner.accountingid || ""
}`
: `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}`
.substring(0, 30)
.trim()} #${jobs_by_pk.owner.accountingid || ""}`
)
.trim()
.replace(":", " ");
if (isThreeTier) {
//It's always gonna be the owner now. Same as 2 tier by name
return (
jobs_by_pk.ownr_co_nm
? `${jobs_by_pk.ownr_co_nm.substring(0, 30)} #${jobs_by_pk.owner.accountingid || ""}`
: `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}`
.substring(0, 30)
.trim()} #${jobs_by_pk.owner.accountingid || ""}`
)
.trim()
.replace(":", " ");
} else {
//What's the 2 tier pref?
if (twotierpref === "source") {
return this.generateSourceTier(jobs_by_pk);
//It should be the insurance co.
} else {
//What's the 2 tier pref?
if (twotierpref === "source") {
return this.generateSourceTier(jobs_by_pk);
//It should be the insurance co.
} else {
//Same as 3 tier
return (
jobs_by_pk.ownr_co_nm
? `${jobs_by_pk.ownr_co_nm.substring(0, 30)} #${
jobs_by_pk.owner.accountingid || ""
}`
: `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}`
.substring(0, 30)
.trim()} #${jobs_by_pk.owner.accountingid || ""}`
)
.trim()
.replace(":", " ");
}
//Same as 3 tier
return (
jobs_by_pk.ownr_co_nm
? `${jobs_by_pk.ownr_co_nm.substring(0, 30)} #${jobs_by_pk.owner.accountingid || ""}`
: `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}`
.substring(0, 30)
.trim()} #${jobs_by_pk.owner.accountingid || ""}`
)
.trim()
.replace(":", " ");
}
}
};

View File

@@ -1,8 +1,5 @@
{
"id": "12345",
"okStatusCodes": [
"0",
"31400"
],
"okStatusCodes": ["0", "31400"],
"qbxml": "the qbxml string"
}