Files
bodyshop/server/accounting/qbo/qbo-payables.js
2021-09-29 08:43:15 -07:00

292 lines
7.7 KiB
JavaScript

const urlBuilder = require("./qbo").urlBuilder;
const path = require("path");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
const logger = require("../../utils/logger");
const Dinero = require("dinero.js");
const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
const apiGqlClient = require("../../graphql-client/graphql-client").client;
const queries = require("../../graphql-client/queries");
const {
refresh: refreshOauthToken,
setNewRefreshToken,
} = require("./qbo-callback");
const OAuthClient = require("intuit-oauth");
const moment = require("moment");
const GraphQLClient = require("graphql-request").GraphQLClient;
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,
});
oauthClient.setToken(response.associations[0].qbo_auth);
await refreshOauthToken(oauthClient, req);
const BearerToken = req.headers.authorization;
const { bills: billsToQuery } = req.body;
//Query Job Info
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
headers: {
Authorization: BearerToken,
},
});
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 } = result;
const ret = [];
for (const bill of bills) {
try {
let vendorRecord;
vendorRecord = await QueryVendorRecord(oauthClient, req, bill);
if (!vendorRecord) {
vendorRecord = await InsertVendorRecord(oauthClient, req, bill);
}
const insertResults = await InsertBill(
oauthClient,
req,
bill,
vendorRecord
);
ret.push({ billid: bill.id, success: true });
} catch (error) {
ret.push({
billid: bill.id,
success: false,
errorMessage: error.message,
});
}
}
res.status(200).json(ret);
} catch (error) {
console.log(error);
logger.log("qbo-payable-create-error", "ERROR", req.user.email, { error });
res.status(400).json(error);
}
};
async function QueryVendorRecord(oauthClient, req, bill) {
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(
req.cookies.qbo_realmId,
"query",
`select * From vendor where DisplayName = '${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,
method: "QueryVendorRecord",
});
throw error;
}
}
async function InsertVendorRecord(oauthClient, req, bill) {
const Vendor = {
DisplayName: bill.vendor.name,
};
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "vendor"),
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(Vendor),
});
setNewRefreshToken(req.user.email, result);
return result && result.Vendor;
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
error,
method: "InsertVendorRecord",
});
throw error;
}
}
async function InsertBill(oauthClient, req, bill, vendor) {
const { accounts, taxCodes, classes } = await QueryMetaData(oauthClient, req);
const billQbo = {
VendorRef: {
value: vendor.Id,
},
TxnDate: moment(bill.date).format("YYYY-MM-DD"),
DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"),
DocNumber: bill.invoice_number,
...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}),
Memo: `RO ${bill.job.ro_number || ""} OWNER ${bill.job.ownr_fn || ""} ${
bill.job.ownr_ln || ""
} ${bill.job.ownr_co_nm || ""}`,
Line: bill.billlines.map((il) =>
generateBillLine(
il,
accounts,
bill.job.class,
bill.job.bodyshop.md_responsibility_centers.sales_tax_codes,
classes,
taxCodes
)
),
};
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "bill"),
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(billQbo),
});
setNewRefreshToken(req.user.email, result);
return result && result.Bill;
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
error,
method: "InsertBill",
});
throw error;
}
}
// [
// {
// DetailType: "AccountBasedExpenseLineDetail",
// Amount: 200.0,
// Id: "1",
// AccountBasedExpenseLineDetail: {
// AccountRef: {
// value: "7",
// },
// },
// },
// ],
const generateBillLine = (
billLine,
accounts,
jobClass,
responsibilityCenters,
classes,
taxCodes
) => {
return {
DetailType: "AccountBasedExpenseLineDetail",
AccountBasedExpenseLineDetail: {
...(jobClass ? { ClassRef: { Id: classes[jobClass] } } : {}),
TaxCodeRef: {
value:
taxCodes[
findTaxCode(billLine.applicable_taxes, responsibilityCenters)
],
},
AccountRef: {
value: accounts[billLine.cost_center],
},
},
Amount: Dinero({
amount: Math.round(billLine.actual_cost * 100),
})
.multiply(billLine.quantity || 1)
.toFormat(DineroQbFormat),
};
};
async function QueryMetaData(oauthClient, req) {
const accounts = await oauthClient.makeApiCall({
url: urlBuilder(
req.cookies.qbo_realmId,
"query",
`select * From Account where AccountType = 'Cost of Goods Sold'`
),
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
setNewRefreshToken(req.user.email, accounts);
const taxCodes = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From TaxCode`),
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
const classes = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From Class`),
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
const taxCodeMapping = {};
taxCodes.json &&
taxCodes.json.QueryResponse &&
taxCodes.json.QueryResponse.TaxCode.forEach((t) => {
taxCodeMapping[t.Name] = t.Id;
});
const accountMapping = {};
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;
});
return {
accounts: accountMapping,
taxCodes: taxCodeMapping,
classes: classMapping,
};
}