const urlBuilder = require("./qbo").urlBuilder; const StandardizeName = require("./qbo").StandardizeName; const path = require("path"); require("dotenv").config({ path: path.resolve( process.cwd(), `.env.${process.env.NODE_ENV || "development"}` ), }); const logger = require("../../utils/logger"); const apiGqlClient = require("../../graphql-client/graphql-client").client; const queries = require("../../graphql-client/queries"); const { refresh: refreshOauthToken, setNewRefreshToken, } = require("./qbo-callback"); const OAuthClient = require("intuit-oauth"); const CreateInvoiceLines = require("../qb-receivables-lines").default; const moment = require("moment"); const GraphQLClient = require("graphql-request").GraphQLClient; const { generateOwnerTier } = require("../qbxml/qbxml-utils"); exports.default = async (req, res) => { const oauthClient = new OAuthClient({ clientId: process.env.QBO_CLIENT_ID, clientSecret: process.env.QBO_SECRET, environment: process.env.NODE_ENV === "production" ? "production" : "sandbox", redirectUri: process.env.QBO_REDIRECT_URI, logging: true, }); try { //Fetch the API Access Tokens & Set them for the session. const response = await apiGqlClient.request(queries.GET_QBO_AUTH, { email: req.user.email, }); const { qbo_realmId } = response.associations[0]; if (!qbo_realmId) { res.status(401).json({ error: "No company associated." }); return; } oauthClient.setToken(response.associations[0].qbo_auth); await refreshOauthToken(oauthClient, req); const BearerToken = req.headers.authorization; const { jobIds } = req.body; //Query Job Info const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, { headers: { Authorization: BearerToken, }, }); logger.log("qbo-receivable-create", "DEBUG", req.user.email, jobIds); const result = await client .setHeaders({ Authorization: BearerToken }) .request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, { ids: jobIds, }); const { jobs, bodyshops } = result; const bodyshop = bodyshops[0]; const ret = []; for (const job of jobs) { //const job = jobs[0]; try { const isThreeTier = bodyshop.accountingconfig.tiers === 3; const twoTierPref = bodyshop.accountingconfig.twotierpref; //Replace this with a for-each loop to check every single Job that's included in the list. let insCoCustomerTier, ownerCustomerTier, jobTier; if (isThreeTier || (!isThreeTier && twoTierPref === "source")) { //Insert the insurance company tier. //Query for top level customer, the insurance company name. insCoCustomerTier = await QueryInsuranceCo( oauthClient, qbo_realmId, req, job ); if (!insCoCustomerTier) { //Creating the Insurance Customer. insCoCustomerTier = await InsertInsuranceCo( oauthClient, qbo_realmId, req, job, bodyshop ); } } if (isThreeTier || (!isThreeTier && twoTierPref === "name")) { //Insert the name/owner and account for whether the source should be the ins co in 3 tier.. ownerCustomerTier = await QueryOwner( oauthClient, qbo_realmId, req, job ); //Query for the owner itself. if (!ownerCustomerTier) { ownerCustomerTier = await InsertOwner( oauthClient, qbo_realmId, req, job, isThreeTier, insCoCustomerTier ); } } //Query for the Job or Create it. jobTier = await QueryJob(oauthClient, qbo_realmId, req, job); // Need to validate that the job tier is associated to the right individual? if (!jobTier) { jobTier = await InsertJob( oauthClient, qbo_realmId, req, job, ownerCustomerTier || insCoCustomerTier ); } if (!req.body.custDataOnly) { await InsertInvoice( oauthClient, qbo_realmId, req, job, bodyshop, jobTier ); } ret.push({ jobid: job.id, success: true }); } catch (error) { ret.push({ jobid: job.id, success: false, errorMessage: (error && error.authResponse && error.authResponse.body) || (error && error.message), }); } } res.status(200).json(ret); } catch (error) { console.log(error); logger.log("qbo-receivable-create-error", "ERROR", req.user.email, { error, }); res.status(400).json(error); } }; async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) { try { const result = await oauthClient.makeApiCall({ url: urlBuilder( qbo_realmId, "query", `select * From Customer where DisplayName = '${StandardizeName( job.ins_co_nm )}'` ), method: "POST", headers: { "Content-Type": "application/json", }, }); setNewRefreshToken(req.user.email, result); return ( result.json && result.json.QueryResponse && result.json.QueryResponse.Customer && result.json.QueryResponse.Customer[0] ); } catch (error) { logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { error, method: "QueryInsuranceCo", }); throw error; } } exports.QueryInsuranceCo = QueryInsuranceCo; async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) { const insCo = bodyshop.md_ins_cos.find((i) => i.name === job.ins_co_nm); const Customer = { DisplayName: job.ins_co_nm, BillAddr: { City: job.ownr_city, Line1: insCo.street1, Line2: insCo.street2, PostalCode: insCo.zip, CountrySubDivisionCode: insCo.state, }, }; try { const result = await oauthClient.makeApiCall({ url: urlBuilder(qbo_realmId, "customer"), method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(Customer), }); setNewRefreshToken(req.user.email, result); return result && result.json.Customer; } catch (error) { logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { error, method: "InsertInsuranceCo", }); throw error; } } exports.InsertInsuranceCo = InsertInsuranceCo; async function QueryOwner(oauthClient, qbo_realmId, req, job) { const ownerName = generateOwnerTier(job, true, null); const result = await oauthClient.makeApiCall({ url: urlBuilder( qbo_realmId, "query", `select * From Customer where DisplayName = '${StandardizeName( ownerName )}'` ), method: "POST", headers: { "Content-Type": "application/json", }, }); setNewRefreshToken(req.user.email, result); return ( result.json && result.json.QueryResponse && result.json.QueryResponse.Customer && result.json.QueryResponse.Customer[0] ); } exports.QueryOwner = QueryOwner; async function InsertOwner( oauthClient, qbo_realmId, req, job, isThreeTier, parentTierRef ) { const ownerName = generateOwnerTier(job, true, null); const Customer = { DisplayName: ownerName, BillAddr: { City: job.ownr_city, Line1: job.ownr_addr1, Line2: job.ownr_addr2, PostalCode: job.ownr_zip, CountrySubDivisionCode: job.ownr_st, }, ...(isThreeTier ? { Job: true, ParentRef: { value: parentTierRef.Id, }, } : {}), }; try { const result = await oauthClient.makeApiCall({ url: urlBuilder(qbo_realmId, "customer"), method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(Customer), }); setNewRefreshToken(req.user.email, result); return result && result.json.Customer; } catch (error) { logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { error, method: "InsertOwner", }); throw error; } } exports.InsertOwner = InsertOwner; async function QueryJob(oauthClient, qbo_realmId, req, job) { const result = await oauthClient.makeApiCall({ url: urlBuilder( qbo_realmId, "query", `select * From Customer where DisplayName = '${job.ro_number}'` ), method: "POST", headers: { "Content-Type": "application/json", }, }); setNewRefreshToken(req.user.email, result); return ( result.json && result.json.QueryResponse && result.json.QueryResponse.Customer && result.json.QueryResponse.Customer[0] ); } exports.QueryJob = QueryJob; async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) { const Customer = { DisplayName: job.ro_number, BillAddr: { City: job.ownr_city, Line1: job.ownr_addr1, Line2: job.ownr_addr2, PostalCode: job.ownr_zip, CountrySubDivisionCode: job.ownr_st, }, Job: true, ParentRef: { value: parentTierRef.Id, }, }; try { const result = await oauthClient.makeApiCall({ url: urlBuilder(qbo_realmId, "customer"), method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(Customer), }); setNewRefreshToken(req.user.email, result); return result && result.json.Customer; } catch (error) { logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { error, method: "InsertOwner", }); throw error; } } exports.InsertJob = InsertJob; async function QueryMetaData(oauthClient, qbo_realmId, req) { const items = await oauthClient.makeApiCall({ url: urlBuilder(qbo_realmId, "query", `select * From Item`), method: "POST", headers: { "Content-Type": "application/json", }, }); setNewRefreshToken(req.user.email, items); const taxCodes = await oauthClient.makeApiCall({ url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`), method: "POST", headers: { "Content-Type": "application/json", }, }); const classes = await oauthClient.makeApiCall({ url: urlBuilder(qbo_realmId, "query", `select * From Class`), method: "POST", headers: { "Content-Type": "application/json", }, }); const taxCodeMapping = {}; taxCodes.json && taxCodes.json.QueryResponse && taxCodes.json.QueryResponse.TaxCode && taxCodes.json.QueryResponse.TaxCode.forEach((t) => { taxCodeMapping[t.Name] = t.Id; }); const itemMapping = {}; items.json && items.json.QueryResponse && items.json.QueryResponse.Item && items.json.QueryResponse.Item.forEach((t) => { itemMapping[t.Name] = t.Id; }); const classMapping = {}; classes.json && classes.json.QueryResponse && classes.json.QueryResponse.Class && classes.json.QueryResponse.Class.forEach((t) => { itemMapping[t.Name] = t.Id; }); return { items: itemMapping, taxCodes: taxCodeMapping, classes: classMapping, }; } async function InsertInvoice( oauthClient, qbo_realmId, req, job, bodyshop, parentTierRef ) { const { items, taxCodes, classes } = await QueryMetaData( oauthClient, qbo_realmId, req ); const InvoiceLineAdd = CreateInvoiceLines({ bodyshop, jobs_by_pk: job, qbo: true, items, taxCodes, }); const invoiceObj = { Line: InvoiceLineAdd, TxnDate: moment(job.date_invoiced).format("YYYY-MM-DD"), DocNumber: job.ro_number, ...(job.class ? { ClassRef: { Id: classes[job.class] } } : {}), CustomerMemo: { value: `${job.clm_no ? `Claim No: ${job.clm_no}` : ``}${ job.po_number ? `PO No: ${job.po_number}` : `` }`.trim(), }, CustomerRef: { value: parentTierRef.Id, }, ...(bodyshop.accountingconfig.printlater ? { PrintStatus: "NeedToPrint" } : {}), ...(bodyshop.accountingconfig.emaillater && job.ownr_ea ? { EmailStatus: "NeedToSend" } : {}), BillAddr: { Line3: `${job.ownr_city || ""}, ${job.ownr_st || ""} ${ job.ownr_zip || "" }`.trim(), Line2: job.ownr_addr1 || "", Line1: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${ job.ownr_co_nm || "" }`, }, }; logger.log("qbo-receivable-objectlog", "DEBUG", req.user.email, job.id, { invoiceObj, }); try { const result = await oauthClient.makeApiCall({ url: urlBuilder(qbo_realmId, "invoice"), method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(invoiceObj), }); setNewRefreshToken(req.user.email, result); return result && result.json && result.json.Invoice; } catch (error) { logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, { error, method: "InsertOwner", }); throw error; } }