IO-3239 QBO Logging and integration log schema changes.

This commit is contained in:
Patrick Fic
2025-05-22 11:54:17 -07:00
parent 16a91c772a
commit 2c508cf1a1
9 changed files with 202 additions and 66 deletions

View File

@@ -2681,6 +2681,9 @@
- active: - active:
_eq: true _eq: true
allow_aggregations: true allow_aggregations: true
- table:
name: integration_log
schema: public
- table: - table:
name: inventory name: inventory
schema: public schema: public

View File

@@ -0,0 +1 @@
DROP TABLE "public"."integration_log";

View File

@@ -0,0 +1,18 @@
CREATE TABLE "public"."integration_log" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "bodyshopid" uuid NOT NULL, "email" text NOT NULL, "jobid" uuid, "billid" uuid, "paymentid" uuid, "method" text NOT NULL, "name" text NOT NULL, "status" text NOT NULL, "platform" text NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("bodyshopid") REFERENCES "public"."bodyshops"("id") ON UPDATE restrict ON DELETE restrict, FOREIGN KEY ("email") REFERENCES "public"."users"("email") ON UPDATE restrict ON DELETE restrict, FOREIGN KEY ("billid") REFERENCES "public"."bills"("id") ON UPDATE restrict ON DELETE set null, FOREIGN KEY ("jobid") REFERENCES "public"."jobs"("id") ON UPDATE restrict ON DELETE restrict, FOREIGN KEY ("paymentid") REFERENCES "public"."payments"("id") ON UPDATE restrict ON DELETE set null);
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
RETURNS TRIGGER AS $$
DECLARE
_new record;
BEGIN
_new := NEW;
_new."updated_at" = NOW();
RETURN _new;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER "set_public_integration_log_updated_at"
BEFORE UPDATE ON "public"."integration_log"
FOR EACH ROW
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
COMMENT ON TRIGGER "set_public_integration_log_updated_at" ON "public"."integration_log"
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
CREATE EXTENSION IF NOT EXISTS pgcrypto;

View File

@@ -16,7 +16,6 @@ const oauthClient = new OAuthClient({
redirectUri: process.env.QBO_REDIRECT_URI, redirectUri: process.env.QBO_REDIRECT_URI,
}); });
//TODO:AIO Add in QBO callbacks.
const url = InstanceEndpoints(); const url = InstanceEndpoints();
exports.default = async (req, res) => { exports.default = async (req, res) => {

View File

@@ -150,11 +150,11 @@ async function QueryVendorRecord(oauthClient, qbo_realmId, req, bill) {
}); });
logger.LogIntegrationCall({ logger.LogIntegrationCall({
platform: "QBO", platform: "QBO",
methodType: "POST", method: "POST",
methodName: "QueryVendorRecord", name: "QueryVendorRecord",
billid: bill.id, billid: bill.id,
statusCode: result.status, status: result.response?.status,
bodyshopid: req.user.bodyshopid, bodyshopid: bill.job.shopid,
email: req.user.email email: req.user.email
}) })
setNewRefreshToken(req.user.email, result); setNewRefreshToken(req.user.email, result);
@@ -188,11 +188,11 @@ async function InsertVendorRecord(oauthClient, qbo_realmId, req, bill) {
}); });
logger.LogIntegrationCall({ logger.LogIntegrationCall({
platform: "QBO", platform: "QBO",
methodType: "POST", method: "POST",
methodName: "InsertVendorRecord", name: "InsertVendorRecord",
billid: bill.id, billid: bill.id,
statusCode: result.status, status: result.response?.status,
bodyshopid: req.user.bodyshopid, bodyshopid: bill.job.shopid,
email: req.user.email email: req.user.email
}) })
setNewRefreshToken(req.user.email, result); setNewRefreshToken(req.user.email, result);
@@ -207,7 +207,7 @@ async function InsertVendorRecord(oauthClient, qbo_realmId, req, bill) {
} }
async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop) { async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop) {
const { accounts, taxCodes, classes } = await QueryMetaData(oauthClient, qbo_realmId, req); const { accounts, taxCodes, classes } = await QueryMetaData(oauthClient, qbo_realmId, req, bill.job.shopid);
const lines = bill.billlines.map((il) => const lines = bill.billlines.map((il) =>
generateBillLine( generateBillLine(
@@ -299,11 +299,11 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop)
}); });
logger.LogIntegrationCall({ logger.LogIntegrationCall({
platform: "QBO", platform: "QBO",
methodType: "POST", method: "POST",
methodName: "InsertBill", name: "InsertBill",
billid: bill.id, billid: bill.id,
statusCode: result.status, status: result.response?.status,
bodyshopid: req.user.bodyshopid, bodyshopid: bill.job.shopid,
email: req.user.email email: req.user.email
}) })
setNewRefreshToken(req.user.email, result); setNewRefreshToken(req.user.email, result);
@@ -368,7 +368,7 @@ const generateBillLine = (
}; };
}; };
async function QueryMetaData(oauthClient, qbo_realmId, req) { async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) {
const accounts = await oauthClient.makeApiCall({ const accounts = await oauthClient.makeApiCall({
url: urlBuilder( url: urlBuilder(
qbo_realmId, qbo_realmId,
@@ -382,10 +382,10 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) {
}); });
logger.LogIntegrationCall({ logger.LogIntegrationCall({
platform: "QBO", platform: "QBO",
methodType: "POST", method: "POST",
methodName: "QueryAccountType", name: "QueryAccountType",
statusCode: accounts.status, status: accounts.response?.status,
bodyshopid: req.user.bodyshopid, bodyshopid,
email: req.user.email email: req.user.email
}) })
setNewRefreshToken(req.user.email, accounts); setNewRefreshToken(req.user.email, accounts);
@@ -398,10 +398,10 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) {
}); });
logger.LogIntegrationCall({ logger.LogIntegrationCall({
platform: "QBO", platform: "QBO",
methodType: "POST", method: "POST",
methodName: "QueryTaxCode", name: "QueryTaxCode",
statusCode: taxCodes.status, status: taxCodes.status,
bodyshopid: req.user.bodyshopid, bodyshopid,
email: req.user.email email: req.user.email
}) })
const classes = await oauthClient.makeApiCall({ const classes = await oauthClient.makeApiCall({
@@ -413,10 +413,10 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) {
}); });
logger.LogIntegrationCall({ logger.LogIntegrationCall({
platform: "QBO", platform: "QBO",
methodType: "POST", method: "POST",
methodName: "QueryClasses", name: "QueryClasses",
statusCode: classes.status, status: classes.status,
bodyshopid: req.user.bodyshopid, bodyshopid,
email: req.user.email email: req.user.email
}) })
const taxCodeMapping = {}; const taxCodeMapping = {};

View File

@@ -197,7 +197,8 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef,
req, req,
payment.job.ro_number, payment.job.ro_number,
false, false,
parentRef parentRef,
payment.job.shopid
); );
if (invoices && invoices.length !== 1) { if (invoices && invoices.length !== 1) {
@@ -256,11 +257,11 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef,
}); });
logger.LogIntegrationCall({ logger.LogIntegrationCall({
platform: "QBO", platform: "QBO",
methodType: "POST", method: "POST",
methodName: "InsertPayment", name: "InsertPayment",
paymentid: payment.id, paymentid: payment.id,
statusCode: result.status, status: result.response?.status,
bodyshopid: req.user.bodyshopid, bodyshopid: payment.job.shopid,
email: req.user.email email: req.user.email
}) })
setNewRefreshToken(req.user.email, result); setNewRefreshToken(req.user.email, result);
@@ -274,7 +275,7 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef,
} }
} }
async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditMemo, parentTierRef) { async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditMemo, parentTierRef, bodyshopid) {
const invoice = await oauthClient.makeApiCall({ const invoice = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "query", `select * From Invoice where DocNumber like '${ro_number}%'`), url: urlBuilder(qbo_realmId, "query", `select * From Invoice where DocNumber like '${ro_number}%'`),
method: "POST", method: "POST",
@@ -284,11 +285,11 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM
}); });
logger.LogIntegrationCall({ logger.LogIntegrationCall({
platform: "QBO", platform: "QBO",
methodType: "POST", method: "POST",
methodName: "QueryInvoice", name: "QueryInvoice",
statusCode: invoice.status, status: invoice.response?.status,
bodyshopid: req.user.bodyshopid, bodyshopid,
email: req.user.email email: req.user.email
}) })
const paymentMethods = await oauthClient.makeApiCall({ const paymentMethods = await oauthClient.makeApiCall({
@@ -300,10 +301,10 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM
}); });
logger.LogIntegrationCall({ logger.LogIntegrationCall({
platform: "QBO", platform: "QBO",
methodType: "POST", method: "POST",
methodName: "QueryPaymentMethod", name: "QueryPaymentMethod",
statusCode: paymentMethods.status, status: paymentMethods.response?.status,
bodyshopid: req.user.bodyshopid, bodyshopid,
email: req.user.email email: req.user.email
}) })
setNewRefreshToken(req.user.email, paymentMethods); setNewRefreshToken(req.user.email, paymentMethods);
@@ -351,11 +352,11 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM
}); });
logger.LogIntegrationCall({ logger.LogIntegrationCall({
platform: "QBO", platform: "QBO",
methodType: "POST", method: "POST",
methodName: "QueryTaxCode", name: "QueryTaxCode",
statusCode: taxCodes.status, status: taxCodes.response?.status,
bodyshopid: req.user.bodyshopid, bodyshopid,
email: req.user.email email: req.user.email
}) })
const items = await oauthClient.makeApiCall({ const items = await oauthClient.makeApiCall({
@@ -367,10 +368,10 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM
}); });
logger.LogIntegrationCall({ logger.LogIntegrationCall({
platform: "QBO", platform: "QBO",
methodType: "POST", method: "POST",
methodName: "QueryItems", name: "QueryItems",
statusCode: items.status, status: items.response?.status,
bodyshopid: req.user.bodyshopid, bodyshopid,
email: req.user.email email: req.user.email
}) })
setNewRefreshToken(req.user.email, items); setNewRefreshToken(req.user.email, items);
@@ -418,7 +419,8 @@ async function InsertCreditMemo(oauthClient, qbo_realmId, req, payment, parentRe
req, req,
payment.job.ro_number, payment.job.ro_number,
true, true,
parentRef parentRef,
payment.job.shopid
); );
if (invoices && invoices.length !== 1) { if (invoices && invoices.length !== 1) {
@@ -475,10 +477,10 @@ async function InsertCreditMemo(oauthClient, qbo_realmId, req, payment, parentRe
}); });
logger.LogIntegrationCall({ logger.LogIntegrationCall({
platform: "QBO", platform: "QBO",
methodType: "POST", method: "POST",
methodName: "InsertCreditMemo", name: "InsertCreditMemo",
paymentid: payment.id, paymentid: payment.id,
statusCode: result.status, status: result.response?.status,
bodyshopid: req.user.bodyshopid, bodyshopid: req.user.bodyshopid,
email: req.user.email email: req.user.email
}) })

View File

@@ -233,6 +233,15 @@ async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) {
"Content-Type": "application/json" "Content-Type": "application/json"
} }
}); });
logger.LogIntegrationCall({
platform: "QBO",
method: "POST",
name: "QueryCustomer",
status: result.response?.status,
bodyshopid: job.shopid,
jobid: job.id,
email: req.user.email
})
setNewRefreshToken(req.user.email, result); setNewRefreshToken(req.user.email, result);
return ( return (
result.json && result.json &&
@@ -279,6 +288,15 @@ async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) {
}, },
body: JSON.stringify(Customer) body: JSON.stringify(Customer)
}); });
logger.LogIntegrationCall({
platform: "QBO",
method: "POST",
name: "InsertCustomer",
status: result.response.status,
bodyshopid: job.shopid,
jobid: job.id,
email: req.user.email
})
setNewRefreshToken(req.user.email, result); setNewRefreshToken(req.user.email, result);
return result && result.json.Customer; return result && result.json.Customer;
} catch (error) { } catch (error) {
@@ -305,6 +323,15 @@ async function QueryOwner(oauthClient, qbo_realmId, req, job, isThreeTier, paren
"Content-Type": "application/json" "Content-Type": "application/json"
} }
}); });
logger.LogIntegrationCall({
platform: "QBO",
method: "POST",
name: "QueryCustomer",
status: result.response?.status,
bodyshopid: job.shopid,
jobid: job.id,
email: req.user.email
})
setNewRefreshToken(req.user.email, result); setNewRefreshToken(req.user.email, result);
return ( return (
result.json && result.json &&
@@ -347,6 +374,15 @@ async function InsertOwner(oauthClient, qbo_realmId, req, job, isThreeTier, pare
}, },
body: JSON.stringify(Customer) body: JSON.stringify(Customer)
}); });
logger.LogIntegrationCall({
platform: "QBO",
method: "POST",
name: "InsertCustomer",
status: result.response?.status,
bodyshopid: job.shopid,
jobid: job.id,
email: req.user.email
})
setNewRefreshToken(req.user.email, result); setNewRefreshToken(req.user.email, result);
return result && result.json.Customer; return result && result.json.Customer;
} catch (error) { } catch (error) {
@@ -372,6 +408,15 @@ async function QueryJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
"Content-Type": "application/json" "Content-Type": "application/json"
} }
}); });
logger.LogIntegrationCall({
platform: "QBO",
method: "POST",
name: "QueryCustomer",
status: result.response?.status,
bodyshopid: job.shopid,
jobid: job.id,
email: req.user.email
})
setNewRefreshToken(req.user.email, result); setNewRefreshToken(req.user.email, result);
return ( return (
result.json && result.json &&
@@ -411,6 +456,15 @@ async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
}, },
body: JSON.stringify(Customer) body: JSON.stringify(Customer)
}); });
logger.LogIntegrationCall({
platform: "QBO",
method: "POST",
name: "InsertCustomer",
status: result.response?.status,
bodyshopid: job.shopid,
jobid: job.id,
email: req.user.email
})
setNewRefreshToken(req.user.email, result); setNewRefreshToken(req.user.email, result);
return result && result.json.Customer; return result && result.json.Customer;
} catch (error) { } catch (error) {
@@ -424,7 +478,7 @@ async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
exports.InsertJob = InsertJob; exports.InsertJob = InsertJob;
async function QueryMetaData(oauthClient, qbo_realmId, req) { async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) {
const items = await oauthClient.makeApiCall({ const items = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "query", `select * From Item where active=true maxresults 1000`), url: urlBuilder(qbo_realmId, "query", `select * From Item where active=true maxresults 1000`),
method: "POST", method: "POST",
@@ -432,6 +486,14 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) {
"Content-Type": "application/json" "Content-Type": "application/json"
} }
}); });
logger.LogIntegrationCall({
platform: "QBO",
method: "POST",
name: "QueryItems",
status: items.response?.status,
bodyshopid,
email: req.user.email
})
setNewRefreshToken(req.user.email, items); setNewRefreshToken(req.user.email, items);
const taxCodes = await oauthClient.makeApiCall({ const taxCodes = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "query", `select * From TaxCode where active=true`), url: urlBuilder(qbo_realmId, "query", `select * From TaxCode where active=true`),
@@ -440,7 +502,14 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) {
"Content-Type": "application/json" "Content-Type": "application/json"
} }
}); });
logger.LogIntegrationCall({
platform: "QBO",
method: "POST",
name: "QueryTaxCodes",
status: taxCodes.response?.status,
bodyshopid,
email: req.user.email
})
const classes = await oauthClient.makeApiCall({ const classes = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "query", `select * From Class`), url: urlBuilder(qbo_realmId, "query", `select * From Class`),
method: "POST", method: "POST",
@@ -448,7 +517,14 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) {
"Content-Type": "application/json" "Content-Type": "application/json"
} }
}); });
logger.LogIntegrationCall({
platform: "QBO",
method: "POST",
name: "QueryClasses",
status: classes.response?.status,
bodyshopid,
email: req.user.email
})
const taxCodeMapping = {}; const taxCodeMapping = {};
taxCodes.json && taxCodes.json &&
@@ -483,7 +559,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) {
} }
async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, parentTierRef) { async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, parentTierRef) {
const { items, taxCodes, classes } = await QueryMetaData(oauthClient, qbo_realmId, req); const { items, taxCodes, classes } = await QueryMetaData(oauthClient, qbo_realmId, req, job.shopid);
const InvoiceLineAdd = CreateInvoiceLines({ const InvoiceLineAdd = CreateInvoiceLines({
bodyshop, bodyshop,
jobs_by_pk: job, jobs_by_pk: job,
@@ -573,6 +649,15 @@ async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, paren
}, },
body: JSON.stringify(invoiceObj) body: JSON.stringify(invoiceObj)
}); });
logger.LogIntegrationCall({
platform: "QBO",
method: "POST",
name: "InsertInvoice",
status: result.status,
bodyshopid: job.shopid,
jobid: job.id,
email: req.user.email
})
setNewRefreshToken(req.user.email, result); setNewRefreshToken(req.user.email, result);
return result && result.json && result.json.Invoice; return result && result.json && result.json.Invoice;
} catch (error) { } catch (error) {
@@ -596,7 +681,7 @@ async function InsertInvoiceMultiPayerInvoice(
payer, payer,
suffix suffix
) { ) {
const { items, taxCodes, classes } = await QueryMetaData(oauthClient, qbo_realmId, req); const { items, taxCodes, classes } = await QueryMetaData(oauthClient, qbo_realmId, req, job.shopid);
const InvoiceLineAdd = createMultiQbPayerLines({ const InvoiceLineAdd = createMultiQbPayerLines({
bodyshop, bodyshop,
jobs_by_pk: job, jobs_by_pk: job,
@@ -689,6 +774,15 @@ async function InsertInvoiceMultiPayerInvoice(
}, },
body: JSON.stringify(invoiceObj) body: JSON.stringify(invoiceObj)
}); });
logger.LogIntegrationCall({
platform: "QBO",
method: "POST",
name: "InsertInvoice",
status: result.response.status,
bodyshopid: job.shopid,
jobid: job.id,
email: req.user.email
})
setNewRefreshToken(req.user.email, result); setNewRefreshToken(req.user.email, result);
return result && result.json && result.json.Invoice; return result && result.json && result.json.Invoice;
} catch (error) { } catch (error) {

View File

@@ -222,6 +222,7 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
rate_mash rate_mash
rate_matd rate_matd
class class
shopid
ca_bc_pvrt ca_bc_pvrt
ca_customer_gst ca_customer_gst
towing_payable towing_payable
@@ -480,6 +481,7 @@ query QUERY_BILLS_FOR_PAYABLES_EXPORT($bills: [uuid!]!) {
ownr_ln ownr_ln
ownr_co_nm ownr_co_nm
class class
shopid
} }
billlines{ billlines{
id id
@@ -530,6 +532,7 @@ exports.QUERY_PAYMENTS_FOR_EXPORT = `
ownr_fn ownr_fn
ownr_ln ownr_ln
ownr_co_nm ownr_co_nm
shopid
bodyshop { bodyshop {
accountingconfig accountingconfig
md_responsibility_centers md_responsibility_centers

View File

@@ -195,21 +195,37 @@ const createLogger = () => {
winstonLogger.log(logEntry); winstonLogger.log(logEntry);
}; };
const LogIntegrationCall = async ({ platform, methodType, methodName, jobid, paymentid, billid, statusCode, bodyshopid, email }) => { const LogIntegrationCall = async ({ platform, method, name, jobid, paymentid, billid, status, bodyshopid, email }) => {
try { try {
//Insert the record. //Insert the record.
await client.request(queries.INSERT_INTEGRATION_LOG, { await client.request(queries.INSERT_INTEGRATION_LOG, {
platform, log: {
methodType, platform,
methodName, jobid, paymentid, billid, method,
statusCode, name,
bodyshopid, jobid,
email paymentid,
billid,
status: status.toString() ?? "0",
bodyshopid,
email
}
}); });
} catch (error) { } catch (error) {
console.trace("Stack", error?.stack);
log("integration-log-error", "ERROR", email, null, { log("integration-log-error", "ERROR", email, null, {
error message: error?.message,
stack: error?.stack,
platform,
method,
name,
jobid,
paymentid,
billid,
status,
bodyshopid,
email
}); });
} }
}; };