diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index c43b4e1dd..c10ac7604 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -2681,6 +2681,9 @@ - active: _eq: true allow_aggregations: true +- table: + name: integration_log + schema: public - table: name: inventory schema: public diff --git a/hasura/migrations/1747932876194_create_table_public_integration_log/down.sql b/hasura/migrations/1747932876194_create_table_public_integration_log/down.sql new file mode 100644 index 000000000..76dd504df --- /dev/null +++ b/hasura/migrations/1747932876194_create_table_public_integration_log/down.sql @@ -0,0 +1 @@ +DROP TABLE "public"."integration_log"; diff --git a/hasura/migrations/1747932876194_create_table_public_integration_log/up.sql b/hasura/migrations/1747932876194_create_table_public_integration_log/up.sql new file mode 100644 index 000000000..73baf287b --- /dev/null +++ b/hasura/migrations/1747932876194_create_table_public_integration_log/up.sql @@ -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; diff --git a/server/accounting/qbo/qbo-callback.js b/server/accounting/qbo/qbo-callback.js index f1de7d551..7dfa6adfa 100644 --- a/server/accounting/qbo/qbo-callback.js +++ b/server/accounting/qbo/qbo-callback.js @@ -14,10 +14,8 @@ 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. const url = InstanceEndpoints(); exports.default = async (req, res) => { diff --git a/server/accounting/qbo/qbo-payables.js b/server/accounting/qbo/qbo-payables.js index 04b0c7e08..9ed0670de 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", + method: "POST", + name: "QueryVendorRecord", + billid: bill.id, + status: result.response?.status, + bodyshopid: bill.job.shopid, + 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", + method: "POST", + name: "InsertVendorRecord", + billid: bill.id, + status: result.response?.status, + bodyshopid: bill.job.shopid, + email: req.user.email + }) setNewRefreshToken(req.user.email, result); return result && result.json && result.json.Vendor; } catch (error) { @@ -190,7 +207,7 @@ async function InsertVendorRecord(oauthClient, qbo_realmId, req, bill) { } 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) => generateBillLine( @@ -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", + method: "POST", + name: "InsertBill", + billid: bill.id, + status: result.response?.status, + bodyshopid: bill.job.shopid, + 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] } @@ -342,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({ url: urlBuilder( qbo_realmId, @@ -354,6 +380,14 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) { "Content-Type": "application/json" } }); + logger.LogIntegrationCall({ + platform: "QBO", + method: "POST", + name: "QueryAccountType", + status: accounts.response?.status, + 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", + method: "POST", + name: "QueryTaxCode", + status: taxCodes.status, + 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", + method: "POST", + name: "QueryClasses", + status: classes.status, + 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..f0d4ccdde 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. @@ -198,7 +197,8 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef, req, payment.job.ro_number, false, - parentRef + parentRef, + payment.job.shopid ); if (invoices && invoices.length !== 1) { @@ -227,20 +227,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 +255,15 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef, }, body: JSON.stringify(paymentQbo) }); + logger.LogIntegrationCall({ + platform: "QBO", + method: "POST", + name: "InsertPayment", + paymentid: payment.id, + status: result.response?.status, + bodyshopid: payment.job.shopid, + email: req.user.email + }) setNewRefreshToken(req.user.email, result); return result && result.Bill; } catch (error) { @@ -266,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({ url: urlBuilder(qbo_realmId, "query", `select * From Invoice where DocNumber like '${ro_number}%'`), method: "POST", @@ -274,7 +283,15 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM "Content-Type": "application/json" } }); + logger.LogIntegrationCall({ + platform: "QBO", + method: "POST", + name: "QueryInvoice", + status: invoice.response?.status, + bodyshopid, + email: req.user.email + }) const paymentMethods = await oauthClient.makeApiCall({ url: urlBuilder(qbo_realmId, "query", `select * From PaymentMethod`), method: "POST", @@ -282,6 +299,14 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM "Content-Type": "application/json" } }); + logger.LogIntegrationCall({ + platform: "QBO", + method: "POST", + name: "QueryPaymentMethod", + status: paymentMethods.response?.status, + bodyshopid, + email: req.user.email + }) setNewRefreshToken(req.user.email, paymentMethods); // const classes = await oauthClient.makeApiCall({ @@ -325,6 +350,15 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM "Content-Type": "application/json" } }); + logger.LogIntegrationCall({ + platform: "QBO", + method: "POST", + name: "QueryTaxCode", + + status: taxCodes.response?.status, + bodyshopid, + email: req.user.email + }) const items = await oauthClient.makeApiCall({ url: urlBuilder(qbo_realmId, "query", `select * From Item`), method: "POST", @@ -332,6 +366,14 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM "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); const itemMapping = {}; @@ -377,7 +419,8 @@ async function InsertCreditMemo(oauthClient, qbo_realmId, req, payment, parentRe req, payment.job.ro_number, true, - parentRef + parentRef, + payment.job.shopid ); if (invoices && invoices.length !== 1) { @@ -406,14 +449,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 +475,15 @@ async function InsertCreditMemo(oauthClient, qbo_realmId, req, payment, parentRe }, body: JSON.stringify(paymentQbo) }); + logger.LogIntegrationCall({ + platform: "QBO", + method: "POST", + name: "InsertCreditMemo", + paymentid: payment.id, + status: result.response?.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..e2a0fa307 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, { @@ -233,6 +233,15 @@ async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) { "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); return ( result.json && @@ -279,6 +288,15 @@ async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) { }, 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); return result && result.json.Customer; } catch (error) { @@ -305,6 +323,15 @@ async function QueryOwner(oauthClient, qbo_realmId, req, job, isThreeTier, paren "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); return ( result.json && @@ -331,11 +358,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 { @@ -347,6 +374,15 @@ async function InsertOwner(oauthClient, qbo_realmId, req, job, isThreeTier, pare }, 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); return result && result.json.Customer; } catch (error) { @@ -372,6 +408,15 @@ async function QueryJob(oauthClient, qbo_realmId, req, job, parentTierRef) { "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); return ( result.json && @@ -411,6 +456,15 @@ async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) { }, 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); return result && result.json.Customer; } catch (error) { @@ -424,7 +478,7 @@ async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) { exports.InsertJob = InsertJob; -async function QueryMetaData(oauthClient, qbo_realmId, req) { +async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) { const items = await oauthClient.makeApiCall({ url: urlBuilder(qbo_realmId, "query", `select * From Item where active=true maxresults 1000`), method: "POST", @@ -432,6 +486,14 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) { "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); const taxCodes = await oauthClient.makeApiCall({ 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" } }); - + logger.LogIntegrationCall({ + platform: "QBO", + method: "POST", + name: "QueryTaxCodes", + status: taxCodes.response?.status, + bodyshopid, + email: req.user.email + }) const classes = await oauthClient.makeApiCall({ url: urlBuilder(qbo_realmId, "query", `select * From Class`), method: "POST", @@ -448,7 +517,14 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) { "Content-Type": "application/json" } }); - + logger.LogIntegrationCall({ + platform: "QBO", + method: "POST", + name: "QueryClasses", + status: classes.response?.status, + bodyshopid, + email: req.user.email + }) const taxCodeMapping = {}; taxCodes.json && @@ -483,7 +559,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) { } 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({ bodyshop, jobs_by_pk: job, @@ -499,57 +575,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" } : {}), @@ -575,6 +649,15 @@ async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, paren }, 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); return result && result.json && result.json.Invoice; } catch (error) { @@ -598,7 +681,7 @@ async function InsertInvoiceMultiPayerInvoice( payer, 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({ bodyshop, jobs_by_pk: job, @@ -616,58 +699,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" } : {}), @@ -693,6 +774,15 @@ async function InsertInvoiceMultiPayerInvoice( }, 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); return result && result.json && result.json.Invoice; } catch (error) { diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 386e21a9f..ccaf566c7 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -222,6 +222,7 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) { rate_mash rate_matd class + shopid ca_bc_pvrt ca_customer_gst towing_payable @@ -480,6 +481,7 @@ query QUERY_BILLS_FOR_PAYABLES_EXPORT($bills: [uuid!]!) { ownr_ln ownr_co_nm class + shopid } billlines{ id @@ -530,6 +532,7 @@ exports.QUERY_PAYMENTS_FOR_EXPORT = ` ownr_fn ownr_ln ownr_co_nm + shopid bodyshop { accountingconfig md_responsibility_centers @@ -2970,3 +2973,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..41d71cea6 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,45 @@ const createLogger = () => { winstonLogger.log(logEntry); }; + const LogIntegrationCall = async ({ platform, method, name, jobid, paymentid, billid, status, bodyshopid, email }) => { + try { + //Insert the record. + await client.request(queries.INSERT_INTEGRATION_LOG, { + log: { + platform, + method, + name, + jobid, + paymentid, + billid, + status: status.toString() ?? "0", + bodyshopid, + email + } + }); + + } catch (error) { + console.trace("Stack", error?.stack); + log("integration-log-error", "ERROR", email, null, { + message: error?.message, + stack: error?.stack, + platform, + method, + name, + jobid, + paymentid, + billid, + status, + bodyshopid, + email + }); + } + }; + return { log, - logger: winstonLogger + logger: winstonLogger, + LogIntegrationCall }; } catch (e) { console.error("Error setting up enhanced Logger, defaulting to console.: " + e?.message || "");