From b0147449402b9ccc245fcbb76163b9918822dce4 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Mon, 19 May 2025 11:10:02 -0700 Subject: [PATCH] IO-3239 Add integration log statements on QBO. --- server/accounting/qbo/qbo-callback.js | 1 - server/accounting/qbo/qbo-payables.js | 72 ++++++++++--- server/accounting/qbo/qbo-payments.js | 96 ++++++++++++----- server/accounting/qbo/qbo-receivables.js | 126 +++++++++++------------ server/graphql-client/queries.js | 8 ++ server/utils/logger.js | 33 ++++-- 6 files changed, 229 insertions(+), 107 deletions(-) diff --git a/server/accounting/qbo/qbo-callback.js b/server/accounting/qbo/qbo-callback.js index f1de7d551..3045dbcc8 100644 --- a/server/accounting/qbo/qbo-callback.js +++ b/server/accounting/qbo/qbo-callback.js @@ -14,7 +14,6 @@ const oauthClient = new OAuthClient({ clientSecret: process.env.QBO_SECRET, environment: process.env.NODE_ENV === "production" ? "production" : "sandbox", redirectUri: process.env.QBO_REDIRECT_URI, - logging: true }); //TODO:AIO Add in QBO callbacks. diff --git a/server/accounting/qbo/qbo-payables.js b/server/accounting/qbo/qbo-payables.js index 04b0c7e08..0e234c0e8 100644 --- a/server/accounting/qbo/qbo-payables.js +++ b/server/accounting/qbo/qbo-payables.js @@ -20,7 +20,6 @@ exports.default = async (req, res) => { clientSecret: process.env.QBO_SECRET, environment: process.env.NODE_ENV === "production" ? "production" : "sandbox", redirectUri: process.env.QBO_REDIRECT_URI, - logging: true }); try { @@ -149,6 +148,15 @@ async function QueryVendorRecord(oauthClient, qbo_realmId, req, bill) { "Content-Type": "application/json" } }); + logger.LogIntegrationCall({ + platform: "QBO", + methodType: "POST", + methodName: "QueryVendorRecord", + billid: bill.id, + statusCode: result.status, + bodyshopid: req.user.bodyshopid, + email: req.user.email + }) setNewRefreshToken(req.user.email, result); return ( result.json && @@ -178,6 +186,15 @@ async function InsertVendorRecord(oauthClient, qbo_realmId, req, bill) { }, body: JSON.stringify(Vendor) }); + logger.LogIntegrationCall({ + platform: "QBO", + methodType: "POST", + methodName: "InsertVendorRecord", + billid: bill.id, + statusCode: result.status, + bodyshopid: req.user.bodyshopid, + email: req.user.email + }) setNewRefreshToken(req.user.email, result); return result && result.json && result.json.Vendor; } catch (error) { @@ -246,11 +263,11 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop) .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") - }), + 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] } } : {}), ...(!( @@ -263,8 +280,8 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop) : {}), ...(bodyshop.accountingconfig.qbo_departmentid && bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && { - DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid } - }), + DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid } + }), PrivateNote: `RO ${bill.job.ro_number || ""}`, Line: lines }; @@ -280,6 +297,15 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop) }, body: JSON.stringify(billQbo) }); + logger.LogIntegrationCall({ + platform: "QBO", + methodType: "POST", + methodName: "InsertBill", + billid: bill.id, + statusCode: result.status, + bodyshopid: req.user.bodyshopid, + email: req.user.email + }) setNewRefreshToken(req.user.email, result); return result && result.json && result.json.Bill; } catch (error) { @@ -327,8 +353,8 @@ const generateBillLine = ( accountingconfig.qbo && accountingconfig.qbo_usa && region_config.includes("CA_") ? {} : { - value: taxCodes[findTaxCode(billLine.applicable_taxes, ioSalesTaxCodes)] - }, + value: taxCodes[findTaxCode(billLine.applicable_taxes, ioSalesTaxCodes)] + }, AccountRef: { value: accounts[account.accountname] } @@ -354,6 +380,14 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) { "Content-Type": "application/json" } }); + logger.LogIntegrationCall({ + platform: "QBO", + methodType: "POST", + methodName: "QueryAccountType", + statusCode: accounts.status, + bodyshopid: req.user.bodyshopid, + email: req.user.email + }) setNewRefreshToken(req.user.email, accounts); const taxCodes = await oauthClient.makeApiCall({ url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`), @@ -362,7 +396,14 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) { "Content-Type": "application/json" } }); - + logger.LogIntegrationCall({ + platform: "QBO", + methodType: "POST", + methodName: "QueryTaxCode", + statusCode: taxCodes.status, + bodyshopid: req.user.bodyshopid, + email: req.user.email + }) const classes = await oauthClient.makeApiCall({ url: urlBuilder(qbo_realmId, "query", `select * From Class`), method: "POST", @@ -370,7 +411,14 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) { "Content-Type": "application/json" } }); - + logger.LogIntegrationCall({ + platform: "QBO", + methodType: "POST", + methodName: "QueryClasses", + statusCode: classes.status, + bodyshopid: req.user.bodyshopid, + email: req.user.email + }) const taxCodeMapping = {}; taxCodes.json && diff --git a/server/accounting/qbo/qbo-payments.js b/server/accounting/qbo/qbo-payments.js index 19ee23bc0..1b54ac8c0 100644 --- a/server/accounting/qbo/qbo-payments.js +++ b/server/accounting/qbo/qbo-payments.js @@ -29,7 +29,6 @@ exports.default = async (req, res) => { 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. @@ -227,20 +226,20 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef, 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" - } - ] - } - ] - } + 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, { @@ -255,6 +254,15 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef, }, body: JSON.stringify(paymentQbo) }); + logger.LogIntegrationCall({ + platform: "QBO", + methodType: "POST", + methodName: "InsertPayment", + paymentid: payment.id, + statusCode: result.status, + bodyshopid: req.user.bodyshopid, + email: req.user.email + }) setNewRefreshToken(req.user.email, result); return result && result.Bill; } catch (error) { @@ -274,7 +282,15 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM "Content-Type": "application/json" } }); + logger.LogIntegrationCall({ + platform: "QBO", + methodType: "POST", + methodName: "QueryInvoice", + statusCode: invoice.status, + bodyshopid: req.user.bodyshopid, + email: req.user.email + }) const paymentMethods = await oauthClient.makeApiCall({ url: urlBuilder(qbo_realmId, "query", `select * From PaymentMethod`), method: "POST", @@ -282,6 +298,14 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM "Content-Type": "application/json" } }); + logger.LogIntegrationCall({ + platform: "QBO", + methodType: "POST", + methodName: "QueryPaymentMethod", + statusCode: paymentMethods.status, + bodyshopid: req.user.bodyshopid, + email: req.user.email + }) setNewRefreshToken(req.user.email, paymentMethods); // const classes = await oauthClient.makeApiCall({ @@ -325,6 +349,15 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM "Content-Type": "application/json" } }); + logger.LogIntegrationCall({ + platform: "QBO", + methodType: "POST", + methodName: "QueryTaxCode", + + statusCode: taxCodes.status, + bodyshopid: req.user.bodyshopid, + email: req.user.email + }) const items = await oauthClient.makeApiCall({ url: urlBuilder(qbo_realmId, "query", `select * From Item`), method: "POST", @@ -332,6 +365,14 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM "Content-Type": "application/json" } }); + logger.LogIntegrationCall({ + platform: "QBO", + methodType: "POST", + methodName: "QueryItems", + statusCode: items.status, + bodyshopid: req.user.bodyshopid, + email: req.user.email + }) setNewRefreshToken(req.user.email, items); const itemMapping = {}; @@ -406,14 +447,14 @@ async function InsertCreditMemo(oauthClient, qbo_realmId, req, payment, parentRe TaxCodeRef: { value: taxCodes[ - findTaxCode( - { - local: false, - federal: false, - state: false - }, - payment.job.bodyshop.md_responsibility_centers.sales_tax_codes - ) + findTaxCode( + { + local: false, + federal: false, + state: false + }, + payment.job.bodyshop.md_responsibility_centers.sales_tax_codes + ) ] } } @@ -432,6 +473,15 @@ async function InsertCreditMemo(oauthClient, qbo_realmId, req, payment, parentRe }, body: JSON.stringify(paymentQbo) }); + logger.LogIntegrationCall({ + platform: "QBO", + methodType: "POST", + methodName: "InsertCreditMemo", + paymentid: payment.id, + statusCode: result.status, + bodyshopid: req.user.bodyshopid, + email: req.user.email + }) setNewRefreshToken(req.user.email, result); return result && result.Bill; } catch (error) { diff --git a/server/accounting/qbo/qbo-receivables.js b/server/accounting/qbo/qbo-receivables.js index 36f3e5113..881a6fae5 100644 --- a/server/accounting/qbo/qbo-receivables.js +++ b/server/accounting/qbo/qbo-receivables.js @@ -22,8 +22,8 @@ exports.default = async (req, res) => { 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, { @@ -331,11 +331,11 @@ async function InsertOwner(oauthClient, qbo_realmId, req, job, isThreeTier, pare ...(job.ownr_ea ? { PrimaryEmailAddr: { Address: job.ownr_ea.trim() } } : {}), ...(isThreeTier ? { - Job: true, - ParentRef: { - value: parentTierRef.Id - } + Job: true, + ParentRef: { + value: parentTierRef.Id } + } : {}) }; try { @@ -499,57 +499,55 @@ async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, paren DocNumber: job.ro_number, ...(job.class ? { ClassRef: { value: classes[job.class] } } : {}), CustomerMemo: { - value: `${job.clm_no ? `Claim No: ${job.clm_no}` : ``}${ - job.po_number ? `PO No: ${job.po_number}` : `` - } Vehicle:${job.v_model_yr || ""} ${job.v_make_desc || ""} ${ - job.v_model_desc || "" - } ${job.v_vin || ""} ${job.plate_no || ""} `.trim() + value: `${job.clm_no ? `Claim No: ${job.clm_no}` : ``}${job.po_number ? `PO No: ${job.po_number}` : `` + } Vehicle:${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || "" + } ${job.v_vin || ""} ${job.plate_no || ""} `.trim() }, CustomerRef: { value: parentTierRef.Id }, ...(bodyshop.accountingconfig.qbo_departmentid && bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && { - DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid } - }), + DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid } + }), CustomField: [ ...(bodyshop.accountingconfig.ReceivableCustomField1 ? [ - { - DefinitionId: "1", - StringValue: job[bodyshop.accountingconfig.ReceivableCustomField1], - Type: "StringType" - } - ] + { + DefinitionId: "1", + StringValue: job[bodyshop.accountingconfig.ReceivableCustomField1], + Type: "StringType" + } + ] : []), ...(bodyshop.accountingconfig.ReceivableCustomField2 ? [ - { - DefinitionId: "2", - StringValue: job[bodyshop.accountingconfig.ReceivableCustomField2], - Type: "StringType" - } - ] + { + DefinitionId: "2", + StringValue: job[bodyshop.accountingconfig.ReceivableCustomField2], + Type: "StringType" + } + ] : []), ...(bodyshop.accountingconfig.ReceivableCustomField3 ? [ - { - DefinitionId: "3", - StringValue: job[bodyshop.accountingconfig.ReceivableCustomField3], - Type: "StringType" - } - ] + { + DefinitionId: "3", + StringValue: job[bodyshop.accountingconfig.ReceivableCustomField3], + Type: "StringType" + } + ] : []) ], ...(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && bodyshop.accountingconfig.qbo_usa && { - TxnTaxDetail: { - TxnTaxCodeRef: { - value: taxCodes[bodyshop.md_responsibility_centers.taxes.state.accountitem] - } + TxnTaxDetail: { + TxnTaxCodeRef: { + value: taxCodes[bodyshop.md_responsibility_centers.taxes.state.accountitem] } - }), + } + }), ...(bodyshop.accountingconfig.printlater ? { PrintStatus: "NeedToPrint" } : {}), ...(bodyshop.accountingconfig.emaillater && job.ownr_ea ? { EmailStatus: "NeedToSend" } : {}), @@ -616,58 +614,56 @@ async function InsertInvoiceMultiPayerInvoice( DocNumber: job.ro_number + suffix, ...(job.class ? { ClassRef: { value: classes[job.class] } } : {}), CustomerMemo: { - value: `${job.clm_no ? `Claim No: ${job.clm_no}` : ``}${ - job.po_number ? `PO No: ${job.po_number}` : `` - } Vehicle:${job.v_model_yr || ""} ${job.v_make_desc || ""} ${ - job.v_model_desc || "" - } ${job.v_vin || ""} ${job.plate_no || ""} `.trim() + value: `${job.clm_no ? `Claim No: ${job.clm_no}` : ``}${job.po_number ? `PO No: ${job.po_number}` : `` + } Vehicle:${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || "" + } ${job.v_vin || ""} ${job.plate_no || ""} `.trim() }, CustomerRef: { value: parentTierRef.Id }, ...(bodyshop.accountingconfig.qbo_departmentid && bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && { - DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid } - }), + DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid } + }), CustomField: [ ...(bodyshop.accountingconfig.ReceivableCustomField1 ? [ - { - DefinitionId: "1", - StringValue: job[bodyshop.accountingconfig.ReceivableCustomField1], - Type: "StringType" - } - ] + { + DefinitionId: "1", + StringValue: job[bodyshop.accountingconfig.ReceivableCustomField1], + Type: "StringType" + } + ] : []), ...(bodyshop.accountingconfig.ReceivableCustomField2 ? [ - { - DefinitionId: "2", - StringValue: job[bodyshop.accountingconfig.ReceivableCustomField2], - Type: "StringType" - } - ] + { + DefinitionId: "2", + StringValue: job[bodyshop.accountingconfig.ReceivableCustomField2], + Type: "StringType" + } + ] : []), ...(bodyshop.accountingconfig.ReceivableCustomField3 ? [ - { - DefinitionId: "3", - StringValue: job[bodyshop.accountingconfig.ReceivableCustomField3], - Type: "StringType" - } - ] + { + DefinitionId: "3", + StringValue: job[bodyshop.accountingconfig.ReceivableCustomField3], + Type: "StringType" + } + ] : []) ], ...(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && bodyshop.accountingconfig.qbo_usa && bodyshop.region_config.includes("CA_") && { - TxnTaxDetail: { - TxnTaxCodeRef: { - value: taxCodes[bodyshop.md_responsibility_centers.taxes.state.accountitem] - } + TxnTaxDetail: { + TxnTaxCodeRef: { + value: taxCodes[bodyshop.md_responsibility_centers.taxes.state.accountitem] } - }), + } + }), ...(bodyshop.accountingconfig.printlater ? { PrintStatus: "NeedToPrint" } : {}), ...(bodyshop.accountingconfig.emaillater && job.ownr_ea ? { EmailStatus: "NeedToSend" } : {}), diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index d61d814b9..aec90bac5 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -2968,3 +2968,11 @@ exports.GET_JOB_WATCHERS_MINIMAL = ` } } `; + +exports.INSERT_INTEGRATION_LOG = ` + mutation INSERT_INTEGRATION_LOG($log: integration_log_insert_input!) { + insert_integration_log_one(object: $log) { + id + } + } +`; \ No newline at end of file diff --git a/server/utils/logger.js b/server/utils/logger.js index 9a27105fd..560a77b8b 100644 --- a/server/utils/logger.js +++ b/server/utils/logger.js @@ -12,6 +12,9 @@ const { uploadFileToS3 } = require("./s3"); const { v4 } = require("uuid"); const { InstanceRegion } = require("./instanceMgr"); const getHostNameOrIP = require("./getHostNameOrIP"); +const client = require("../graphql-client/graphql-client").client; +const queries = require("../graphql-client/queries"); + const LOG_LEVELS = { error: { level: 0, name: "error" }, @@ -99,13 +102,11 @@ const createLogger = () => { const labelColor = "\x1b[33m"; // Yellow const separatorColor = "\x1b[35m|\x1b[0m"; // Magenta for separators - return `${timestampColor} [${hostnameColor}] [${level}]: ${message} ${ - user ? `${separatorColor} ${labelColor}user:\x1b[0m ${JSON.stringify(user)}` : "" - } ${record ? `${separatorColor} ${labelColor}record:\x1b[0m ${JSON.stringify(record)}` : ""}${ - meta + return `${timestampColor} [${hostnameColor}] [${level}]: ${message} ${user ? `${separatorColor} ${labelColor}user:\x1b[0m ${JSON.stringify(user)}` : "" + } ${record ? `${separatorColor} ${labelColor}record:\x1b[0m ${JSON.stringify(record)}` : ""}${meta ? `\n${separatorColor} ${labelColor}meta:\x1b[0m ${JSON.stringify(meta, null, 2)} ${separatorColor}` : "" - }`; + }`; }) ) }) @@ -194,9 +195,29 @@ const createLogger = () => { winstonLogger.log(logEntry); }; + const LogIntegrationCall = async ({ platform, methodType, methodName, jobid, paymentid, billid, statusCode, bodyshopid, email }) => { + try { + //Insert the record. + await client.request(queries.INSERT_INTEGRATION_LOG, { + platform, + methodType, + methodName, jobid, paymentid, billid, + statusCode, + bodyshopid, + email + }); + + } catch (error) { + log("integration-log-error", "ERROR", email, null, { + error + }); + } + }; + return { log, - logger: winstonLogger + logger: winstonLogger, + LogIntegrationCall }; } catch (e) { console.error("Error setting up enhanced Logger, defaulting to console.: " + e?.message || "");