@@ -1,9 +1,9 @@
|
||||
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 +13,279 @@ 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",
|
||||
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(),
|
||||
};
|
||||
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.
|
||||
|
||||
//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)),
|
||||
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",
|
||||
Lines: [], //socket.apAllocations,
|
||||
},
|
||||
};
|
||||
}
|
||||
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.
|
||||
}
|
||||
});
|
||||
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(),
|
||||
},
|
||||
};
|
||||
|
||||
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(),
|
||||
});
|
||||
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);
|
||||
|
||||
transactionlist.push(transactionObject);
|
||||
});
|
||||
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(),
|
||||
};
|
||||
}
|
||||
|
||||
return transactionlist;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`Error encountered in PbsCalculateAllocationsAp. ${error}`
|
||||
);
|
||||
}
|
||||
//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;
|
||||
}
|
||||
|
||||
@@ -1,16 +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"}`
|
||||
),
|
||||
});
|
||||
|
||||
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 +21,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
@@ -1,32 +1,32 @@
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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");
|
||||
@@ -12,84 +12,84 @@ const queries = require("../../graphql-client/queries");
|
||||
const {parse, stringify} = require("querystring");
|
||||
|
||||
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") {
|
||||
url = `https://romeonline.io`;
|
||||
url = `https://romeonline.io`;
|
||||
} else if (process.env.NODE_ENV === "test") {
|
||||
url = `https://test.romeonline.io`;
|
||||
url = `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)}`
|
||||
);
|
||||
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);
|
||||
}
|
||||
} 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()},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,10 +2,10 @@ 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");
|
||||
@@ -13,8 +13,8 @@ const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
|
||||
const apiGqlClient = require("../../graphql-client/graphql-client").client;
|
||||
const queries = require("../../graphql-client/queries");
|
||||
const {
|
||||
refresh: refreshOauthToken,
|
||||
setNewRefreshToken,
|
||||
refresh: refreshOauthToken,
|
||||
setNewRefreshToken,
|
||||
} = require("./qbo-callback");
|
||||
const OAuthClient = require("intuit-oauth");
|
||||
const moment = require("moment-timezone");
|
||||
@@ -22,316 +22,318 @@ 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,
|
||||
});
|
||||
|
||||
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 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 { 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),
|
||||
try {
|
||||
//Fetch the API Access Tokens & Set them for the session.
|
||||
const response = await apiGqlClient.request(queries.GET_QBO_AUTH, {
|
||||
email: req.user.email,
|
||||
});
|
||||
|
||||
//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,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const {qbo_realmId} = response.associations[0];
|
||||
|
||||
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);
|
||||
}
|
||||
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),
|
||||
});
|
||||
|
||||
//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);
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
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)
|
||||
|
||||
.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,
|
||||
});
|
||||
try {
|
||||
const result = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(
|
||||
req,
|
||||
bill,
|
||||
vendor,
|
||||
bodyshop
|
||||
) {
|
||||
const {accounts, taxCodes, classes} = await QueryMetaData(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
bill.is_credit_memo ? "vendorcredit" : "bill"
|
||||
),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(billQbo),
|
||||
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
|
||||
)
|
||||
);
|
||||
|
||||
//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,
|
||||
});
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// [
|
||||
// {
|
||||
// DetailType: "AccountBasedExpenseLineDetail",
|
||||
@@ -346,96 +348,96 @@ async function InsertBill(
|
||||
// ],
|
||||
|
||||
const generateBillLine = (
|
||||
billLine,
|
||||
accounts,
|
||||
jobClass,
|
||||
ioSalesTaxCodes,
|
||||
classes,
|
||||
taxCodes,
|
||||
costCenters
|
||||
billLine,
|
||||
accounts,
|
||||
jobClass,
|
||||
ioSalesTaxCodes,
|
||||
classes,
|
||||
taxCodes,
|
||||
costCenters
|
||||
) => {
|
||||
const account = costCenters.find((c) => c.name === billLine.cost_center);
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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");
|
||||
@@ -11,519 +11,521 @@ const Dinero = require("dinero.js");
|
||||
const apiGqlClient = require("../../graphql-client/graphql-client").client;
|
||||
const queries = require("../../graphql-client/queries");
|
||||
const {
|
||||
refresh: refreshOauthToken,
|
||||
setNewRefreshToken,
|
||||
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,
|
||||
});
|
||||
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 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 { 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;
|
||||
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);
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
const {payments: paymentsToQuery, elgen} = req.body;
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
const BearerToken = req.BearerToken;
|
||||
const client = req.userGraphQLClient;
|
||||
|
||||
//Query for the Job or Create it.
|
||||
jobTier = await QueryJob(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
payment.job,
|
||||
isThreeTier
|
||||
? ownerCustomerTier
|
||||
: twoTierPref === "source"
|
||||
? insCoCustomerTier
|
||||
: ownerCustomerTier
|
||||
);
|
||||
logger.log("qbo-payment-create", "DEBUG", req.user.email, paymentsToQuery);
|
||||
|
||||
// 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,
|
||||
},
|
||||
],
|
||||
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),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ret.push({ paymentid: payment.id, success: true });
|
||||
} catch (error) {
|
||||
res.status(200).json(ret);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
logger.log("qbo-payment-create-error", "ERROR", req.user.email, {
|
||||
error:
|
||||
(error && error.authResponse && error.authResponse.body) ||
|
||||
(error && error.message),
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
//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(400).json(error);
|
||||
}
|
||||
|
||||
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(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
payment,
|
||||
parentRef,
|
||||
bodyshop
|
||||
) {
|
||||
const { paymentMethods, invoices } = await QueryMetaData(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
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.`
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
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;
|
||||
}
|
||||
}
|
||||
async function QueryMetaData(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
ro_number,
|
||||
isCreditMemo,
|
||||
parentTierRef
|
||||
payment,
|
||||
parentRef,
|
||||
bodyshop
|
||||
) {
|
||||
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, invoices} = await QueryMetaData(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
payment.job.ro_number,
|
||||
false,
|
||||
parentRef
|
||||
);
|
||||
|
||||
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);
|
||||
if (invoices && invoices.length !== 1) {
|
||||
throw new Error(
|
||||
`More than 1 invoice with DocNumber ${payment.job.ro_number} found.`
|
||||
);
|
||||
}
|
||||
|
||||
// const classes = await oauthClient.makeApiCall({
|
||||
// url: urlBuilder(qbo_realmId, "query", `select * From Class`),
|
||||
// method: "POST",
|
||||
// headers: {
|
||||
// "Content-Type": "application/json",
|
||||
// },
|
||||
// });
|
||||
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,
|
||||
});
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
const paymentMethodMapping = {};
|
||||
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",
|
||||
},
|
||||
});
|
||||
|
||||
paymentMethods.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 classes = await oauthClient.makeApiCall({
|
||||
// url: urlBuilder(qbo_realmId, "query", `select * From Class`),
|
||||
// method: "POST",
|
||||
// headers: {
|
||||
// "Content-Type": "application/json",
|
||||
// },
|
||||
// });
|
||||
|
||||
const paymentMethodMapping = {};
|
||||
|
||||
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,
|
||||
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]]),
|
||||
};
|
||||
}
|
||||
|
||||
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(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
payment,
|
||||
parentRef,
|
||||
bodyshop
|
||||
) {
|
||||
const { paymentMethods, invoices, items, taxCodes } = await QueryMetaData(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
payment.job.ro_number,
|
||||
true,
|
||||
parentRef
|
||||
);
|
||||
|
||||
if (invoices && invoices.length !== 1) {
|
||||
throw new Error(
|
||||
`More than 1 invoice with DocNumber ${payment.ro_number} found.`
|
||||
payment,
|
||||
parentRef,
|
||||
bodyshop
|
||||
) {
|
||||
const {paymentMethods, invoices, items, taxCodes} = await QueryMetaData(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
payment.job.ro_number,
|
||||
true,
|
||||
parentRef
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
],
|
||||
},
|
||||
if (invoices && invoices.length !== 1) {
|
||||
throw new Error(
|
||||
`More than 1 invoice with DocNumber ${payment.ro_number} found.`
|
||||
);
|
||||
}
|
||||
|
||||
const paymentQbo = {
|
||||
CustomerRef: {
|
||||
value: parentRef.Id,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
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),
|
||||
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,
|
||||
});
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,21 +1,21 @@
|
||||
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;
|
||||
|
||||
@@ -9,152 +9,152 @@ const moment = require("moment-timezone");
|
||||
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"}`
|
||||
),
|
||||
});
|
||||
|
||||
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,
|
||||
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
|
||||
)
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
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 } } : {}),
|
||||
...(process.env.COUNTRY !== "USA"
|
||||
? {
|
||||
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}} : {}),
|
||||
...(process.env.COUNTRY !== "USA"
|
||||
? {
|
||||
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";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,202 +9,202 @@ const QbxmlReceivables = require("./qbxml-receivables");
|
||||
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.
|
||||
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),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
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,
|
||||
|
||||
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" },
|
||||
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,
|
||||
|
||||
var paymentQbxmlPartial = builder
|
||||
.create(paymentQbxmlObj, {
|
||||
version: "1.30",
|
||||
encoding: "UTF-8",
|
||||
headless: true,
|
||||
})
|
||||
.end({ pretty: true });
|
||||
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"},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const paymentQbxmlFull = QbXmlUtils.addQbxmlHeader(paymentQbxmlPartial);
|
||||
var paymentQbxmlPartial = builder
|
||||
.create(paymentQbxmlObj, {
|
||||
version: "1.30",
|
||||
encoding: "UTF-8",
|
||||
headless: true,
|
||||
})
|
||||
.end({pretty: true});
|
||||
|
||||
return paymentQbxmlFull;
|
||||
const paymentQbxmlFull = QbXmlUtils.addQbxmlHeader(paymentQbxmlPartial);
|
||||
|
||||
return paymentQbxmlFull;
|
||||
};
|
||||
|
||||
@@ -9,304 +9,304 @@ const CreateInvoiceLines = require("../qb-receivables-lines").default;
|
||||
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"}`
|
||||
),
|
||||
});
|
||||
|
||||
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.
|
||||
//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
|
||||
),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//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));
|
||||
}
|
||||
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
|
||||
jobs_by_pk,
|
||||
bodyshop,
|
||||
isThreeTier,
|
||||
tierLevel,
|
||||
twoTierPref
|
||||
) => {
|
||||
let Name;
|
||||
let ParentRefName;
|
||||
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",
|
||||
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,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
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,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var jobQbxml_partial = builder
|
||||
.create(jobQbxmlObj, {
|
||||
version: "1.30",
|
||||
encoding: "UTF-8",
|
||||
headless: true,
|
||||
})
|
||||
.end({ pretty: true });
|
||||
var jobQbxml_partial = builder
|
||||
.create(jobQbxmlObj, {
|
||||
version: "1.30",
|
||||
encoding: "UTF-8",
|
||||
headless: true,
|
||||
})
|
||||
.end({pretty: true});
|
||||
|
||||
const jobQbxml_Full = QbXmlUtils.addQbxmlHeader(jobQbxml_partial);
|
||||
const jobQbxml_Full = QbXmlUtils.addQbxmlHeader(jobQbxml_partial);
|
||||
|
||||
return jobQbxml_Full;
|
||||
return jobQbxml_Full;
|
||||
};
|
||||
|
||||
exports.generateJobQbxml = generateJobQbxml;
|
||||
const generateInvoiceQbxml = (
|
||||
jobs_by_pk,
|
||||
bodyshop,
|
||||
isThreeTier,
|
||||
twoTierPref
|
||||
jobs_by_pk,
|
||||
bodyshop,
|
||||
isThreeTier,
|
||||
twoTierPref
|
||||
) => {
|
||||
//Build the Invoice XML file.
|
||||
//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 } }
|
||||
: {}),
|
||||
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,
|
||||
ItemSalesTaxRef: {
|
||||
FullName:
|
||||
bodyshop.md_responsibility_centers.taxes.invoiceexemptcode,
|
||||
},
|
||||
IsToBePrinted: bodyshop.accountingconfig.printlater,
|
||||
...(jobs_by_pk.ownr_ea
|
||||
? { IsToBeEmailed: bodyshop.accountingconfig.emaillater }
|
||||
: {}),
|
||||
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,
|
||||
ItemSalesTaxRef: {
|
||||
FullName:
|
||||
bodyshop.md_responsibility_centers.taxes.invoiceexemptcode,
|
||||
},
|
||||
IsToBePrinted: bodyshop.accountingconfig.printlater,
|
||||
...(jobs_by_pk.ownr_ea
|
||||
? {IsToBeEmailed: bodyshop.accountingconfig.emaillater}
|
||||
: {}),
|
||||
|
||||
InvoiceLineAdd: InvoiceLineAdd,
|
||||
},
|
||||
InvoiceLineAdd: InvoiceLineAdd,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var invoiceQbxml_partial = builder
|
||||
.create(invoiceQbxmlObj, {
|
||||
version: "1.30",
|
||||
encoding: "UTF-8",
|
||||
headless: true,
|
||||
})
|
||||
.end({ pretty: true });
|
||||
var invoiceQbxml_partial = builder
|
||||
.create(invoiceQbxmlObj, {
|
||||
version: "1.30",
|
||||
encoding: "UTF-8",
|
||||
headless: true,
|
||||
})
|
||||
.end({pretty: true});
|
||||
|
||||
const invoiceQbxml_Full = QbXmlUtils.addQbxmlHeader(invoiceQbxml_partial);
|
||||
const invoiceQbxml_Full = QbXmlUtils.addQbxmlHeader(invoiceQbxml_partial);
|
||||
|
||||
return invoiceQbxml_Full;
|
||||
return invoiceQbxml_Full;
|
||||
};
|
||||
|
||||
// const generateInvoiceLine = (job, allocation, responsibilityCenters) => {
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
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(":", " ");
|
||||
} else {
|
||||
//What's the 2 tier pref?
|
||||
if (twotierpref === "source") {
|
||||
return this.generateSourceTier(jobs_by_pk);
|
||||
//It should be the insurance co.
|
||||
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 {
|
||||
//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(":", " ");
|
||||
//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(":", " ");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"id": "12345",
|
||||
"okStatusCodes": ["0", "31400"],
|
||||
"okStatusCodes": [
|
||||
"0",
|
||||
"31400"
|
||||
],
|
||||
"qbxml": "the qbxml string"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user