Compare commits

...

18 Commits

Author SHA1 Message Date
Allan Carr
78770ed54e IO-3503 Job Costing Corrections
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-01-30 16:45:25 -08:00
Allan Carr
e1666baddd IO-3503 Job Costing Fixes for CAD
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-01-23 14:17:03 -08:00
Allan Carr
c29ac5f711 IO-3503 Job Costing Fixes
Correction to handle CAD

Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-01-22 15:52:20 -08:00
Allan Carr
1716c3e6b2 IO-3503 Job Costing Fixes
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-01-20 11:55:12 -08:00
Allan Carr
f3513a80c5 Merged in hotfix/2026-01-15 (pull request #2848)
IO-3498 No SalesTerms for Credits
2026-01-19 18:57:06 +00:00
Allan Carr
3c38d9daeb Merged in feature/IO-3498-QBO-Auth-Token (pull request #2847)
IO-3498 No SalesTerms for Credits

Approved-by: Dave Richer
2026-01-19 18:54:15 +00:00
Allan Carr
a9a49009ba IO-3498 No SalesTerms for Credits
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-01-19 10:34:32 -08:00
Dave Richer
27b9c3f342 Merged in hotfix/2026-01-15 (pull request #2844)
Hotfix/2026 01 15

Approved-by: Allan Carr
2026-01-17 01:48:21 +00:00
Allan Carr
334077a39d IO-3498 Remove setNewRefreshToken and replace forEach with map
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-01-16 17:07:27 -08:00
Allan Carr
23ce1c42d1 Merged in feature/IO-3498-QBO-Auth-Token (pull request #2843)
IO-3498 QBO Fix for Returned Data from oauthClient

Approved-by: Dave Richer
2026-01-17 01:07:25 +00:00
Allan Carr
6a521c0f46 IO-3498 QBO Fix for Returned Data from oauthClient
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-01-16 16:37:29 -08:00
Dave Richer
55dd0c6e14 Merged in hotfix/2026-01-15 (pull request #2833)
IO-3498 QBO Changes due to oauthClient response change

Approved-by: Allan Carr
2026-01-16 00:55:49 +00:00
Dave
d30e03a184 Merge remote-tracking branch 'origin/feature/IO-3498-QBO-Auth-Token' into hotfix/2026-01-15 2026-01-15 19:35:44 -05:00
Allan Carr
f40af8cba4 IO-3498 QBO Changes due to oauthClient response change
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-01-15 16:10:42 -08:00
Allan Carr
aef04ec29e Merged in hotfix/2026-01-15 (pull request #2828)
Hotfix/2026 01 15
2026-01-15 16:57:04 +00:00
Allan Carr
69e57195d3 Merged in feature/IO-3503-Job-Costing-Bug-Fix (pull request #2824)
IO-3503 Job Costing Bug Fix

Approved-by: Dave Richer
2026-01-15 16:40:47 +00:00
Allan Carr
883c7257db Merged in feature/IO-3500-PROFIT-CENTER-ITEM (pull request #2825)
IO-3500 Profit Center Item

Approved-by: Dave Richer
2026-01-15 16:39:02 +00:00
Allan Carr
54ce0e1802 IO-3500 Profit Center Item
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-01-13 16:40:24 -08:00
8 changed files with 338 additions and 399 deletions

View File

@@ -695,25 +695,25 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
>
<Input onBlur={handleBlur} />
</Form.Item>
{!hasDMSKey && (
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountitem")}
key={`${index}accountitem`}
name={[field.name, "accountitem"]}
rules={[{ required: true }]}
>
<Input onBlur={handleBlur} />
</Form.Item>
)}
{hasDMSKey && !bodyshop.rr_dealerid && (
<>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountitem")}
key={`${index}accountitem`}
name={[field.name, "accountitem"]}
rules={[{ required: true }]}
>
<Input onBlur={handleBlur} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.dms.dms_acctnumber")}
key={`${index}dms_acctnumber`}
name={[field.name, "dms_acctnumber"]}
rules={[{ required: true }]}
>
<Input onBlur={handleBlur} />
</Form.Item>
</>
<Form.Item
label={t("bodyshop.fields.dms.dms_acctnumber")}
key={`${index}dms_acctnumber`}
name={[field.name, "dms_acctnumber"]}
rules={[{ required: true }]}
>
<Input onBlur={handleBlur} />
</Form.Item>
)}
{bodyshop.cdk_dealerid && (
<Form.Item

View File

@@ -57,14 +57,3 @@ exports.refresh = async (oauthClient, req) => {
});
}
};
exports.setNewRefreshToken = async (email, apiResponse) => {
// Deprecated - tokens are now auto-updated in the oauthClient and the token isn't pushed back from QBO API calls anymore
// logger.log("qbo-token-updated", "DEBUG", email, null, {apiResponse: apiResponse});
// await client.request(queries.SET_QBO_AUTH, {
// email,
// qbo_auth: { ...apiResponse.token, createdAt: Date.now() }
// });
};

View File

@@ -6,7 +6,7 @@ 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 { refresh: refreshOauthToken } = require("./qbo-callback");
const OAuthClient = require("intuit-oauth");
const moment = require("moment-timezone");
const findTaxCode = require("../qb-receivables-lines").findTaxCode;
@@ -87,17 +87,17 @@ exports.default = async (req, res) => {
} catch (error) {
logger.log("qbo-paybles-create-error", "ERROR", req.user.email, null, {
error:
(error?.authResponse && error.authResponse.body) ||
error.response?.data?.Fault?.Error.map((e) => e.Detail).join(", ") ||
error?.authResponse?.body ||
error?.response?.data?.Fault?.Error.map((e) => e.Detail).join(", ") ||
error?.message
});
ret.push({
billid: bill.id,
success: false,
errorMessage:
(error && error.authResponse && error.authResponse.body) ||
error.response?.data?.Fault?.Error.map((e) => e.Detail).join(", ") ||
(error && error.message)
error?.authResponse?.body ||
error?.response?.data?.Fault?.Error.map((e) => e.Detail).join(", ") ||
error?.message
});
//Add the export log error.
@@ -108,9 +108,7 @@ exports.default = async (req, res) => {
bodyshopid: bodyshop.id,
billid: bill.id,
successful: false,
message: JSON.stringify([
(error && error.authResponse && error.authResponse.body) || (error && error.message)
]),
message: JSON.stringify([error?.authResponse?.body || error?.message]),
useremail: req.user.email
}
]
@@ -136,9 +134,7 @@ async function QueryVendorRecord(oauthClient, qbo_realmId, req, bill) {
url: urlBuilder(
qbo_realmId,
"query",
`select *
From vendor
where DisplayName = '${StandardizeName(bill.vendor.name)}'`
`select * From vendor where DisplayName = '${StandardizeName(bill.vendor.name)}'`
),
method: "POST",
headers: {
@@ -150,21 +146,17 @@ async function QueryVendorRecord(oauthClient, qbo_realmId, req, bill) {
method: "POST",
name: "QueryVendorRecord",
billid: bill.id,
status: result.response?.status,
status: result.status,
bodyshopid: bill.job.shopid,
email: req.user.email
});
setNewRefreshToken(req.user.email, result);
return (
result.json &&
result.json.QueryResponse &&
result.json.QueryResponse.Vendor &&
result.json.QueryResponse.Vendor[0]
);
return result.json?.QueryResponse?.Vendor?.[0];
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
error: (error && error.authResponse && error.authResponse.body) || (error && error.message),
method: "QueryVendorRecord"
method: "QueryVendorRecord",
error: error.message,
stack: error.stack
});
throw error;
}
@@ -188,16 +180,20 @@ async function InsertVendorRecord(oauthClient, qbo_realmId, req, bill) {
method: "POST",
name: "InsertVendorRecord",
billid: bill.id,
status: result.response?.status,
status: result.status,
bodyshopid: bill.job.shopid,
email: req.user.email
});
setNewRefreshToken(req.user.email, result);
return result && result.json && result.json.Vendor;
if (result.status >= 400) {
throw new Error(JSON.stringify(result.json.Fault));
}
if (result.status === 200) return result?.json.Vendor;
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
error: (error && error.authResponse && error.authResponse.body) || (error && error.message),
method: "InsertVendorRecord"
method: "InsertVendorRecord",
validationError: error.message,
stack: error.stack
});
throw error;
}
@@ -250,12 +246,7 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop)
}
//QB USA with GST
//This was required for the No. 1 Collision Group.
if (
bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
bodyshop.accountingconfig.qbo_usa &&
bodyshop.region_config.includes("CA_")
) {
if (bodyshop.accountingconfig?.qbo && bodyshop.accountingconfig?.qbo_usa && bodyshop.region_config.includes("CA_")) {
lines.push({
DetailType: "AccountBasedExpenseLineDetail",
@@ -274,16 +265,16 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop)
)
})
.percentage(bill.federal_tax_rate)
.toFormat(DineroQbFormat)
});
}
const billQbo = {
let billQbo, VendorCredit;
const billObject = {
VendorRef: {
value: vendor.Id
},
...(vendor.TermRef && {
...(vendor.TermRef && !bill.is_credit_memo && {
SalesTermRef: {
value: vendor.TermRef.value
}
@@ -301,22 +292,30 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop)
DocNumber: bill.invoice_number,
//...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}),
...(!(
bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
bodyshop.accountingconfig.qbo_usa &&
bodyshop.accountingconfig?.qbo &&
bodyshop.accountingconfig?.qbo_usa &&
bodyshop.region_config.includes("CA_")
)
? { GlobalTaxCalculation: "TaxExcluded" }
: {}),
...(bodyshop.accountingconfig.qbo_departmentid &&
bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && {
DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid }
}),
...(bodyshop.accountingconfig.qbo_departmentid?.trim() !== "" && {
DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid }
}),
PrivateNote: `RO ${bill.job.ro_number || ""}`,
Line: lines
};
if (bill.is_credit_memo) {
VendorCredit = billObject;
} else {
billQbo = billObject;
}
const logKey = bill.is_credit_memo ? "VendorCredit" : "billQbo";
const logValue = bill.is_credit_memo ? VendorCredit : billQbo;
logger.log("qbo-payable-objectlog", "DEBUG", req.user.email, bill.id, {
billQbo
[logKey]: logValue
});
try {
const result = await oauthClient.makeApiCall({
@@ -325,25 +324,28 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop)
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(billQbo)
body: JSON.stringify(bill.is_credit_memo ? VendorCredit : billQbo)
});
logger.LogIntegrationCall({
platform: "QBO",
method: "POST",
name: "InsertBill",
billid: bill.id,
status: result.response?.status,
status: result.status,
bodyshopid: bill.job.shopid,
email: req.user.email
});
setNewRefreshToken(req.user.email, result);
return result && result.json && result.json.Bill;
if (result.status >= 400) {
throw new Error(JSON.stringify(result.json.Fault));
}
if (result.status === 200) return result?.json;
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
error: error, //(error && error.authResponse && error.authResponse.body) || (error && error.message),
validationError: JSON.stringify(error?.response?.data),
method: "InsertBill",
validationError: error.message,
accountmeta: JSON.stringify({ accounts, taxCodes, classes }),
method: "InsertBill"
stack: error.stack
});
throw error;
}
@@ -403,9 +405,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) {
url: urlBuilder(
qbo_realmId,
"query",
`select *
From Account
where AccountType in ('Cost of Goods Sold', 'Other Current Liability')`
`select * From Account where AccountType in ('Cost of Goods Sold', 'Other Current Liability')`
),
method: "POST",
headers: {
@@ -416,18 +416,13 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) {
platform: "QBO",
method: "POST",
name: "QueryAccountType",
status: accounts.response?.status,
status: accounts.status,
bodyshopid,
email: req.user.email
});
setNewRefreshToken(req.user.email, accounts);
const taxCodes = await oauthClient.makeApiCall({
url: urlBuilder(
qbo_realmId,
"query",
`select *
From TaxCode`
),
url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -442,12 +437,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) {
email: req.user.email
});
const classes = await oauthClient.makeApiCall({
url: urlBuilder(
qbo_realmId,
"query",
`select *
From Class`
),
url: urlBuilder(qbo_realmId, "query", `select * From Class`),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -461,31 +451,14 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) {
bodyshopid,
email: req.user.email
});
const taxCodeMapping = {};
taxCodes.json &&
taxCodes.json.QueryResponse &&
taxCodes.json.QueryResponse.TaxCode &&
taxCodes.json.QueryResponse.TaxCode.forEach((t) => {
taxCodeMapping[t.Name] = t.Id;
});
const taxCodeMapping = Object.fromEntries((taxCodes.json?.QueryResponse?.TaxCode || []).map((t) => [t.Name, t.Id]));
const accountMapping = {};
const accountMapping = Object.fromEntries(
(accounts.json?.QueryResponse?.Account || []).map((t) => [t.FullyQualifiedName, t.Id])
);
accounts.json &&
accounts.json.QueryResponse &&
accounts.json.QueryResponse.Account &&
accounts.json.QueryResponse.Account.forEach((t) => {
accountMapping[t.FullyQualifiedName] = t.Id;
});
const classMapping = {};
classes.json &&
classes.json.QueryResponse &&
classes.json.QueryResponse.Class &&
classes.json.QueryResponse.Class.forEach((t) => {
classMapping[t.Name] = t.Id;
});
const classMapping = Object.fromEntries((classes.json?.QueryResponse?.Class || []).map((c) => [c.Name, c.Id]));
return {
accounts: accountMapping,

View File

@@ -3,7 +3,7 @@ const Dinero = require("dinero.js");
const apiGqlClient = require("../../graphql-client/graphql-client").client;
const queries = require("../../graphql-client/queries");
const { refresh: refreshOauthToken, setNewRefreshToken } = require("./qbo-callback");
const { refresh: refreshOauthToken } = require("./qbo-callback");
const OAuthClient = require("intuit-oauth");
const moment = require("moment-timezone");
const {
@@ -145,7 +145,7 @@ exports.default = async (req, res) => {
ret.push({ paymentid: payment.id, success: true });
} catch (error) {
logger.log("qbo-payment-create-error", "ERROR", req.user.email, null, {
error: (error && error.authResponse && error.authResponse.body) || (error && error.message)
error: error?.authResponse?.body || error?.message
});
//Add the export log error.
if (elgen) {
@@ -155,9 +155,7 @@ exports.default = async (req, res) => {
bodyshopid: bodyshop.id,
paymentid: payment.id,
successful: false,
message: JSON.stringify([
(error && error.authResponse && error.authResponse.body) || (error && error.message)
]),
message: JSON.stringify([error?.authResponse?.body || error?.message]),
useremail: req.user.email
}
]
@@ -167,14 +165,13 @@ exports.default = async (req, res) => {
ret.push({
paymentid: payment.id,
success: false,
errorMessage: (error && error.authResponse && error.authResponse.body) || (error && error.message)
errorMessage: error?.authResponse?.body || error?.message
});
}
}
res.status(200).json(ret);
} catch (error) {
//console.log(error);
logger.log("qbo-payment-create-error", "ERROR", req.user.email, null, {
error: error.message,
stack: error.stack
@@ -202,9 +199,7 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef)
CustomerRef: {
value: parentRef.Id
},
TxnDate: moment(payment.date) //.tz(bodyshop.timezone)
.format("YYYY-MM-DD"),
//DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"),
TxnDate: moment(payment.date).format("YYYY-MM-DD"),
DocNumber: payment.paymentnum,
TotalAmt: Dinero({
amount: Math.round(payment.amount * 100)
@@ -212,19 +207,13 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef)
PaymentMethodRef: {
value: paymentMethods[payment.type]
},
PrivateNote: payment.memo
? payment.memo.length > 4000
? payment.memo.substring(0, 4000).trim()
: payment.memo.trim()
: "",
PrivateNote: payment.memo?.substring(0, 4000)?.trim() ?? "",
PaymentRefNum: payment.transactionid,
...(invoices && invoices.length === 1 && invoices[0]
...(invoices?.length === 1 && invoices[0]
? {
Line: [
{
Amount: Dinero({
amount: Math.round(payment.amount * 100)
}).toFormat(DineroQbFormat),
Amount: Dinero({ amount: Math.round(payment.amount * 100) }).toFormat(DineroQbFormat),
LinkedTxn: [
{
TxnId: invoices[0].Id,
@@ -253,16 +242,20 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef)
method: "POST",
name: "InsertPayment",
paymentid: payment.id,
status: result.response?.status,
status: result.status,
bodyshopid: payment.job.shopid,
email: req.user.email
});
setNewRefreshToken(req.user.email, result);
return result && result.Bill;
if (result.status >= 400) {
throw new Error(JSON.stringify(result.json.Fault));
}
if (result.status === 200) return result?.json.Customer;
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, payment.id, {
error: error && error.message,
method: "InsertPayment"
method: "InsertPayment",
validationError: error.message,
stack: error.stack
});
throw error;
}
@@ -270,13 +263,7 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef)
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}%'`
),
url: urlBuilder(qbo_realmId, "query", `select * From Invoice where DocNumber like '${ro_number}%'`),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -286,18 +273,12 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM
platform: "QBO",
method: "POST",
name: "QueryInvoice",
status: invoice.response?.status,
status: invoice.status,
bodyshopid,
email: req.user.email
});
const paymentMethods = await oauthClient.makeApiCall({
url: urlBuilder(
qbo_realmId,
"query",
`select *
From PaymentMethod`
),
url: urlBuilder(qbo_realmId, "query", `select * From PaymentMethod`),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -307,11 +288,10 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM
platform: "QBO",
method: "POST",
name: "QueryPaymentMethod",
status: paymentMethods.response?.status,
status: paymentMethods.status,
bodyshopid,
email: req.user.email
});
setNewRefreshToken(req.user.email, paymentMethods);
// const classes = await oauthClient.makeApiCall({
// url: urlBuilder(qbo_realmId, "query", `select * From Class`),
@@ -321,14 +301,9 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM
// },
// });
const paymentMethodMapping = {};
paymentMethods.json &&
paymentMethods.json.QueryResponse &&
paymentMethods.json.QueryResponse.PaymentMethod &&
paymentMethods.json.QueryResponse.PaymentMethod.forEach((t) => {
paymentMethodMapping[t.Name] = t.Id;
});
const paymentMethodMapping = Object.fromEntries(
(paymentMethods.json?.QueryResponse?.PaymentMethod || []).map((t) => [t.Name, t.Id])
);
// const accountMapping = {};
@@ -348,12 +323,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM
if (isCreditMemo) {
const taxCodes = await oauthClient.makeApiCall({
url: urlBuilder(
qbo_realmId,
"query",
`select *
From TaxCode`
),
url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -363,18 +333,12 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM
platform: "QBO",
method: "POST",
name: "QueryTaxCode",
status: taxCodes.response?.status,
status: taxCodes.status,
bodyshopid,
email: req.user.email
});
const items = await oauthClient.makeApiCall({
url: urlBuilder(
qbo_realmId,
"query",
`select *
From Item`
),
url: urlBuilder(qbo_realmId, "query", `select * From Item`),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -384,28 +348,15 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM
platform: "QBO",
method: "POST",
name: "QueryItems",
status: items.response?.status,
status: items.status,
bodyshopid,
email: req.user.email
});
setNewRefreshToken(req.user.email, items);
const itemMapping = {};
const taxCodeMapping = Object.fromEntries((taxCodes.json?.QueryResponse?.TaxCode || []).map((t) => [t.Name, t.Id]));
items.json &&
items.json.QueryResponse &&
items.json.QueryResponse.Item &&
items.json.QueryResponse.Item.forEach((t) => {
itemMapping[t.Name] = t.Id;
});
const taxCodeMapping = {};
const itemMapping = Object.fromEntries((items.json?.QueryResponse?.Item || []).map((item) => [item.Name, item.Id]));
taxCodes.json &&
taxCodes.json.QueryResponse &&
taxCodes.json.QueryResponse.TaxCode &&
taxCodes.json.QueryResponse.TaxCode.forEach((t) => {
taxCodeMapping[t.Name] = t.Id;
});
ret = {
...ret,
items: itemMapping,
@@ -417,12 +368,10 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number, isCreditM
...ret,
paymentMethods: paymentMethodMapping,
invoices:
invoice.json &&
invoice.json.QueryResponse &&
invoice.json.QueryResponse.Invoice &&
invoice.json?.QueryResponse?.Invoice &&
(parentTierRef
? [invoice.json.QueryResponse.Invoice.find((x) => x.CustomerRef.value === parentTierRef.Id)]
: [invoice.json.QueryResponse.Invoice[0]])
? [invoice.json?.QueryResponse?.Invoice.find((x) => x.CustomerRef?.value === parentTierRef?.Id)]
: [invoice.json?.QueryResponse?.Invoice?.[0]])
};
}
@@ -437,7 +386,7 @@ async function InsertCreditMemo(oauthClient, qbo_realmId, req, payment, parentRe
payment.job.shopid
);
if (invoices && invoices.length !== 1) {
if (invoices?.length !== 1) {
throw new Error(`More than 1 invoice with DocNumber ${payment.ro_number} found.`);
}
@@ -445,11 +394,9 @@ async function InsertCreditMemo(oauthClient, qbo_realmId, req, payment, parentRe
CustomerRef: {
value: parentRef.Id
},
TxnDate: moment(payment.date)
//.tz(bodyshop.timezone)
.format("YYYY-MM-DD"),
TxnDate: moment(payment.date).format("YYYY-MM-DD"),
DocNumber: payment.paymentnum,
...(invoices && invoices[0] ? { InvoiceRef: { value: invoices[0].Id } } : {}),
...(invoices?.[0] ? { InvoiceRef: { value: invoices[0].Id } } : {}),
PaymentRefNum: payment.transactionid,
Line: [
{
@@ -494,18 +441,21 @@ async function InsertCreditMemo(oauthClient, qbo_realmId, req, payment, parentRe
method: "POST",
name: "InsertCreditMemo",
paymentid: payment.id,
status: result.response?.status,
status: result.status,
bodyshopid: req.user.bodyshopid,
email: req.user.email
});
setNewRefreshToken(req.user.email, result);
return result && result.Bill;
if (result.status >= 400) {
throw new Error(JSON.stringify(result.json.Fault));
}
if (result.status === 200) return result?.json;
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, payment.id, {
error: error,
validationError: JSON.stringify(error?.response?.data),
method: "InsertCreditMemo",
validationError: error.message,
accountmeta: JSON.stringify({ items, taxCodes }),
method: "InsertCreditMemo"
stack: error.stack
});
throw error;
}

View File

@@ -4,7 +4,7 @@ const StandardizeName = require("./qbo").StandardizeName;
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 { refresh: refreshOauthToken } = require("./qbo-callback");
const OAuthClient = require("intuit-oauth");
const CreateInvoiceLines = require("../qb-receivables-lines").default;
const moment = require("moment-timezone");
@@ -71,7 +71,7 @@ exports.default = async (req, res) => {
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, isThreeTier, insCoCustomerTier);
ownerCustomerTier = await QueryOwner(oauthClient, qbo_realmId, req, job, insCoCustomerTier);
//Query for the owner itself.
if (!ownerCustomerTier) {
ownerCustomerTier = await InsertOwner(oauthClient, qbo_realmId, req, job, isThreeTier, insCoCustomerTier);
@@ -103,12 +103,12 @@ exports.default = async (req, res) => {
if (!req.body.custDataOnly) {
await InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, jobTier);
if (job.qb_multiple_payers && job.qb_multiple_payers.length > 0) {
if (job.qb_multiple_payers?.length > 0) {
for (const [index, payer] of job.qb_multiple_payers.entries()) {
//do the thing.
//Create the source level.
let insCoCustomerTier, ownerCustomerTier, jobTier;
let insCoCustomerTier, jobTier;
//Insert the insurance company tier.
//Query for top level customer, the insurance company name.
@@ -150,23 +150,21 @@ exports.default = async (req, res) => {
// //No error. Mark the job exported & insert export log.
if (elgen) {
const result = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.QBO_MARK_JOB_EXPORTED, {
jobId: job.id,
job: {
status: bodyshop.md_ro_statuses.default_exported || "Exported*",
date_exported: moment().tz(bodyshop.timezone)
},
logs: [
{
bodyshopid: bodyshop.id,
jobid: job.id,
successful: true,
useremail: req.user.email
}
]
});
await client.setHeaders({ Authorization: BearerToken }).request(queries.QBO_MARK_JOB_EXPORTED, {
jobId: job.id,
job: {
status: bodyshop.md_ro_statuses.default_exported || "Exported*",
date_exported: moment().tz(bodyshop.timezone)
},
logs: [
{
bodyshopid: bodyshop.id,
jobid: job.id,
successful: true,
useremail: req.user.email
}
]
});
}
}
ret.push({ jobid: job.id, success: true });
@@ -187,15 +185,13 @@ exports.default = async (req, res) => {
});
//Add the export log error.
if (elgen) {
const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.INSERT_EXPORT_LOG, {
await client.setHeaders({ Authorization: BearerToken }).request(queries.INSERT_EXPORT_LOG, {
logs: [
{
bodyshopid: bodyshop.id,
jobid: job.id,
successful: false,
message: JSON.stringify([
(error && error.authResponse && error.authResponse.body) || (error && error.message)
]),
message: JSON.stringify([error?.authResponse?.body || error?.message]),
useremail: req.user.email
}
]
@@ -221,10 +217,7 @@ async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) {
url: urlBuilder(
qbo_realmId,
"query",
`select *
From Customer
where DisplayName = '${StandardizeName(job.ins_co_nm.trim())}'
and Active = true`
`select * From Customer where DisplayName = '${StandardizeName(job.ins_co_nm.trim())}' and Active = true`
),
method: "POST",
headers: {
@@ -235,18 +228,13 @@ async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) {
platform: "QBO",
method: "POST",
name: "QueryCustomer",
status: result.response?.status,
status: result.status,
bodyshopid: job.shopid,
jobid: job.id,
email: req.user.email
});
setNewRefreshToken(req.user.email, result);
return (
result.json &&
result.json.QueryResponse &&
result.json.QueryResponse.Customer &&
result.json.QueryResponse.Customer[0]
);
return result.json?.QueryResponse?.Customer?.[0];
} catch (error) {
logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
error,
@@ -290,13 +278,13 @@ async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) {
platform: "QBO",
method: "POST",
name: "InsertCustomer",
status: result.response.status,
status: result.status,
bodyshopid: job.shopid,
jobid: job.id,
email: req.user.email
});
setNewRefreshToken(req.user.email, result);
return result && result.json.Customer;
return result.json?.Customer;
} catch (error) {
logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
error,
@@ -308,16 +296,13 @@ async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) {
exports.InsertInsuranceCo = InsertInsuranceCo;
async function QueryOwner(oauthClient, qbo_realmId, req, job, isThreeTier, parentTierRef) {
async function QueryOwner(oauthClient, qbo_realmId, req, job, parentTierRef) {
const ownerName = generateOwnerTier(job, true, null);
const result = await oauthClient.makeApiCall({
url: urlBuilder(
qbo_realmId,
"query",
`select *
From Customer
where DisplayName = '${StandardizeName(ownerName)}'
and Active = true`
`select * From Customer where DisplayName = '${StandardizeName(ownerName)}' and Active = true`
),
method: "POST",
headers: {
@@ -328,18 +313,13 @@ async function QueryOwner(oauthClient, qbo_realmId, req, job, isThreeTier, paren
platform: "QBO",
method: "POST",
name: "QueryCustomer",
status: result.response?.status,
status: result.status,
bodyshopid: job.shopid,
jobid: job.id,
email: req.user.email
});
setNewRefreshToken(req.user.email, result);
return (
result.json &&
result.json.QueryResponse &&
result.json.QueryResponse.Customer &&
result.json.QueryResponse.Customer.find((x) => x.ParentRef?.value === parentTierRef?.Id)
);
return result.json?.QueryResponse?.Customer?.find((x) => x.ParentRef?.value === parentTierRef?.Id);
}
exports.QueryOwner = QueryOwner;
@@ -379,13 +359,13 @@ async function InsertOwner(oauthClient, qbo_realmId, req, job, isThreeTier, pare
platform: "QBO",
method: "POST",
name: "InsertCustomer",
status: result.response?.status,
status: result.status,
bodyshopid: job.shopid,
jobid: job.id,
email: req.user.email
});
setNewRefreshToken(req.user.email, result);
return result && result.json.Customer;
return result.json?.Customer;
} catch (error) {
logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
error,
@@ -402,10 +382,7 @@ async function QueryJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
url: urlBuilder(
qbo_realmId,
"query",
`select *
From Customer
where DisplayName = '${job.ro_number}'
and Active = true`
`select * From Customer where DisplayName = '${job.ro_number}' and Active = true`
),
method: "POST",
headers: {
@@ -416,20 +393,14 @@ async function QueryJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
platform: "QBO",
method: "POST",
name: "QueryCustomer",
status: result.response?.status,
status: result.status,
bodyshopid: job.shopid,
jobid: job.id,
email: req.user.email
});
setNewRefreshToken(req.user.email, result);
return (
result.json &&
result.json.QueryResponse &&
result.json.QueryResponse.Customer &&
(parentTierRef
? result.json.QueryResponse.Customer.find((x) => x.ParentRef.value === parentTierRef.Id)
: result.json.QueryResponse.Customer[0])
);
const customers = result.json?.QueryResponse?.Customer;
return customers && (parentTierRef ? customers.find((x) => x.ParentRef.value === parentTierRef.Id) : customers[0]);
}
exports.QueryJob = QueryJob;
@@ -464,17 +435,21 @@ async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
platform: "QBO",
method: "POST",
name: "InsertCustomer",
status: result.response?.status,
status: result.status,
bodyshopid: job.shopid,
jobid: job.id,
email: req.user.email
});
setNewRefreshToken(req.user.email, result);
return result && result.json.Customer;
if (result.status >= 400) {
throw new Error(JSON.stringify(result.json.Fault));
}
if (result.status === 200) return result?.json.Customer;
} catch (error) {
logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
error,
method: "InsertOwner"
method: "InsertOwner",
validationError: error.message,
stack: error.stack
});
throw error;
}
@@ -484,13 +459,7 @@ exports.InsertJob = InsertJob;
async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid, jobid) {
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",
headers: {
"Content-Type": "application/json"
@@ -500,20 +469,14 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid, jobid) {
platform: "QBO",
method: "POST",
name: "QueryItems",
status: items.response?.status,
status: items.status,
bodyshopid,
jobid: jobid,
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`
),
url: urlBuilder(qbo_realmId, "query", `select * From TaxCode where active = true`),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -523,18 +486,13 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid, jobid) {
platform: "QBO",
method: "POST",
name: "QueryTaxCodes",
status: taxCodes.response?.status,
status: taxCodes.status,
bodyshopid,
jobid: jobid,
email: req.user.email
});
const classes = await oauthClient.makeApiCall({
url: urlBuilder(
qbo_realmId,
"query",
`select *
From Class`
),
url: urlBuilder(qbo_realmId, "query", `select * From Class`),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -544,36 +502,17 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid, jobid) {
platform: "QBO",
method: "POST",
name: "QueryClasses",
status: classes.response?.status,
status: classes.status,
bodyshopid,
jobid: jobid,
email: req.user.email
});
const taxCodeMapping = {};
taxCodes.json &&
taxCodes.json.QueryResponse &&
taxCodes.json.QueryResponse.TaxCode &&
taxCodes.json.QueryResponse.TaxCode.forEach((t) => {
taxCodeMapping[t.Name] = t.Id;
});
const taxCodeMapping = Object.fromEntries((taxCodes.json?.QueryResponse?.TaxCode || []).map((t) => [t.Name, t.Id]));
const itemMapping = {};
const itemMapping = Object.fromEntries((items.json?.QueryResponse?.Item || []).map((item) => [item.Name, item.Id]));
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) => {
classMapping[t.Name] = t.Id;
});
const classMapping = Object.fromEntries((classes.json?.QueryResponse?.Class || []).map((c) => [c.Name, c.Id]));
return {
items: itemMapping,
@@ -606,12 +545,11 @@ async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, paren
} ${job.v_vin || ""} ${job.plate_no || ""} `.trim()
},
CustomerRef: {
value: parentTierRef.Id
value: parentTierRef?.Id
},
...(bodyshop.accountingconfig.qbo_departmentid &&
bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && {
DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid }
}),
...(bodyshop.accountingconfig.qbo_departmentid?.trim() !== "" && {
DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid }
}),
CustomField: [
...(bodyshop.accountingconfig.ReceivableCustomField1
? [
@@ -641,9 +579,8 @@ async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, paren
]
: [])
],
...(bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
bodyshop.accountingconfig.qbo_usa && {
...(bodyshop.accountingconfig?.qbo &&
bodyshop.accountingconfig?.qbo_usa && {
TxnTaxDetail: {
TxnTaxCodeRef: {
value: taxCodes[bodyshop.md_responsibility_centers.taxes.state.accountitem]
@@ -679,19 +616,22 @@ async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, paren
platform: "QBO",
method: "POST",
name: "InsertInvoice",
status: result.response?.status,
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;
if (result.status >= 400) {
throw new Error(JSON.stringify(result.json.Fault));
}
if (result.status === 200) return result?.json;
} catch (error) {
logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
error,
method: "InsertInvoice",
validationError: JSON.stringify(error?.response?.data),
accountmeta: JSON.stringify({ items, taxCodes, classes })
validationError: error.message,
accountmeta: JSON.stringify({ items, taxCodes, classes }),
stack: error.stack
});
throw error;
}
@@ -734,10 +674,9 @@ async function InsertInvoiceMultiPayerInvoice(
CustomerRef: {
value: parentTierRef.Id
},
...(bodyshop.accountingconfig.qbo_departmentid &&
bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && {
DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid }
}),
...(bodyshop.accountingconfig.qbo_departmentid?.trim() !== "" && {
DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid }
}),
CustomField: [
...(bodyshop.accountingconfig.ReceivableCustomField1
? [
@@ -767,9 +706,8 @@ async function InsertInvoiceMultiPayerInvoice(
]
: [])
],
...(bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
bodyshop.accountingconfig.qbo_usa &&
...(bodyshop.accountingconfig?.qbo &&
bodyshop.accountingconfig?.qbo_usa &&
bodyshop.region_config.includes("CA_") && {
TxnTaxDetail: {
TxnTaxCodeRef: {
@@ -805,18 +743,23 @@ async function InsertInvoiceMultiPayerInvoice(
logger.LogIntegrationCall({
platform: "QBO",
method: "POST",
name: "InsertInvoice",
status: result.response?.status,
name: "InsertInvoiceMultiPayerInvoice",
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;
if (result.status >= 400) {
throw new Error(JSON.stringify(result.json.Fault));
}
if (result.status === 200) return result?.json;
} catch (error) {
logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
error,
method: "InsertOwner"
method: "InsertInvoiceMultiPayerInvoice",
validationError: error.message,
accountmeta: JSON.stringify({ items, taxCodes, classes }),
stack: error.stack
});
throw error;
}

View File

@@ -1725,6 +1725,7 @@ query QUERY_JOB_COSTING_DETAILS($id: uuid!) {
profitcenter_part
profitcenter_labor
act_price_before_ppc
manual_line
}
bills {
id
@@ -1842,6 +1843,7 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
op_code_desc
profitcenter_part
profitcenter_labor
manual_line
}
bills {
id

View File

@@ -2,6 +2,7 @@ const _ = require("lodash");
const Dinero = require("dinero.js");
const queries = require("../graphql-client/queries");
const logger = require("../utils/logger");
const { ParseCalopCode } = require("../job/job-totals-USA");
const InstanceManager = require("../utils/instanceMgr").default;
const { DiscountNotAlreadyCounted } = InstanceManager({
imex: require("../job/job-totals"),
@@ -265,6 +266,9 @@ function GenerateCostingData(job) {
);
const materialsHours = { mapaHrs: 0, mashHrs: 0 };
let mashOpCodes = InstanceManager({
rome: ParseCalopCode(job.materials["MASH"]?.cal_opcode)
});
let hasMapaLine = false;
let hasMashLine = false;
@@ -339,7 +343,7 @@ function GenerateCostingData(job) {
if (!acc.labor[laborProfitCenter]) acc.labor[laborProfitCenter] = Dinero();
acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(laborAmount);
if (val.act_price > 0 && val.lbr_op === "OP14") {
if (val.act_price > 0 && val.lbr_op === "OP14" && !val.part_type) {
//Scenario where SGI may pay out hours using a part price.
acc.labor[laborProfitCenter] = acc.labor[laborProfitCenter].add(
Dinero({
@@ -351,8 +355,17 @@ function GenerateCostingData(job) {
if (val.mod_lbr_ty === "LAR") {
materialsHours.mapaHrs += val.mod_lb_hrs || 0;
}
if (val.mod_lbr_ty !== "LAR") {
materialsHours.mashHrs += val.mod_lb_hrs || 0;
if (InstanceManager({ imex: true, rome: false })) {
if (val.mod_lbr_ty !== "LAR") {
materialsHours.mashHrs += val.mod_lb_hrs || 0;
}
} else {
if (val.mod_lbr_ty !== "LAR" && mashOpCodes.includes(val.lbr_op)) {
materialsHours.mashHrs += val.mod_lb_hrs || 0;
}
if (val.manual_line === true && !mashOpCodes.includes(val.lbr_op) && val.mod_lbr_ty !== "LAR" ) {
materialsHours.mashHrs += val.mod_lb_hrs || 0;
}
}
}
@@ -398,32 +411,6 @@ function GenerateCostingData(job) {
: Dinero()
);
// Profile Discount for Parts
if (job.parts_tax_rates && job.parts_tax_rates[val.part_type.toUpperCase()]) {
if (
job.parts_tax_rates[val.part_type.toUpperCase()].prt_discp !== undefined &&
job.parts_tax_rates[val.part_type.toUpperCase()].prt_discp >= 0
) {
const discountRate =
Math.abs(job.parts_tax_rates[val.part_type.toUpperCase()].prt_discp) > 1
? job.parts_tax_rates[val.part_type.toUpperCase()].prt_discp
: job.parts_tax_rates[val.part_type.toUpperCase()].prt_discp * 100;
const disc = partsAmount.percentage(discountRate).multiply(-1);
partsAmount = partsAmount.add(disc);
}
if (
job.parts_tax_rates[val.part_type.toUpperCase()].prt_mkupp !== undefined &&
job.parts_tax_rates[val.part_type.toUpperCase()].prt_mkupp >= 0
) {
const markupRate =
Math.abs(job.parts_tax_rates[val.part_type.toUpperCase()].prt_mkupp) > 1
? job.parts_tax_rates[val.part_type.toUpperCase()].prt_mkupp
: job.parts_tax_rates[val.part_type.toUpperCase()].prt_mkupp * 100;
const markup = partsAmount.percentage(markupRate);
partsAmount = partsAmount.add(markup);
}
}
if (!acc.parts[partsProfitCenter]) acc.parts[partsProfitCenter] = Dinero();
acc.parts[partsProfitCenter] = acc.parts[partsProfitCenter].add(partsAmount);
}
@@ -510,10 +497,67 @@ function GenerateCostingData(job) {
{ parts: {}, labor: {}, additional: {}, sublet: {} }
);
// Profile Discount for Parts
Object.keys(jobLineTotalsByProfitCenter.parts).forEach((key) => {
let disc = Dinero(),
markup = Dinero();
const convertedKey = Object.keys(defaultProfits).find((k) => defaultProfits[k] === key);
if (convertedKey && job.parts_tax_rates && job.parts_tax_rates[convertedKey.toUpperCase()]) {
if (
job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp !== undefined &&
job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp >= 0
) {
const discountRate =
Math.abs(job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp) > 1
? job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp
: job.parts_tax_rates[convertedKey.toUpperCase()].prt_discp * 100;
disc = jobLineTotalsByProfitCenter.parts[key].percentage(discountRate).multiply(-1);
}
if (
job.parts_tax_rates[convertedKey.toUpperCase()].prt_mkupp !== undefined &&
job.parts_tax_rates[convertedKey.toUpperCase()].prt_mkupp >= 0
) {
const markupRate =
Math.abs(job.parts_tax_rates[convertedKey.toUpperCase()].prt_mkupp) > 1
? job.parts_tax_rates[convertedKey.toUpperCase()].prt_mkupp
: job.parts_tax_rates[convertedKey.toUpperCase()].prt_mkupp * 100;
markup = jobLineTotalsByProfitCenter.parts[key].percentage(markupRate);
}
}
if (InstanceManager({ rome: true })) {
if (convertedKey) {
const correspondingCiecaStlTotalLine = job.cieca_stl?.data.find(
(c) => c.ttl_typecd === convertedKey.toUpperCase()
);
if (
correspondingCiecaStlTotalLine &&
Math.abs(jobLineTotalsByProfitCenter.parts[key].getAmount() - correspondingCiecaStlTotalLine.ttl_amt * 100) > 1
) {
jobLineTotalsByProfitCenter.parts[key] = jobLineTotalsByProfitCenter.parts[key].add(disc).add(markup);
}
}
}
});
if (!hasMapaLine) {
let threshold;
if (
job.materials["MAPA"] &&
job.materials["MAPA"].cal_maxdlr !== undefined &&
job.materials["MAPA"].cal_maxdlr >= 0
) {
//It has an upper threshhold.
threshold = Dinero({
amount: Math.round(job.materials["MAPA"].cal_maxdlr * 100)
});
}
if (!jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]])
jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = Dinero();
const origMAPAAmount = jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]];
jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = jobLineTotalsByProfitCenter.additional[
defaultProfits["MAPA"]
].add(
@@ -541,11 +585,29 @@ function GenerateCostingData(job) {
.percentage(adjp < 0 ? adjp * -1 : adjp)
.multiply(adjp < 0 ? -1 : 1)
);
if (threshold && jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]].greaterThanOrEqual(threshold)) {
jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = threshold.add(origMAPAAmount);
}
}
if (!hasMashLine) {
let threshold;
if (
job.materials["MASH"] &&
job.materials["MASH"].cal_maxdlr !== undefined &&
job.materials["MASH"].cal_maxdlr >= 0
) {
//It has an upper threshhold.
threshold = Dinero({
amount: Math.round(job.materials["MASH"].cal_maxdlr * 100)
});
}
if (!jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]])
jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] = Dinero();
const origMASHAmount = jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]];
jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] = jobLineTotalsByProfitCenter.additional[
defaultProfits["MASH"]
].add(
@@ -573,6 +635,10 @@ function GenerateCostingData(job) {
.percentage(adjp < 0 ? adjp * -1 : adjp)
.multiply(adjp < 0 ? -1 : 1)
);
if (threshold && jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]].greaterThanOrEqual(threshold)) {
jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] = threshold.add(origMASHAmount);
}
}
if (InstanceManager({ imex: false, rome: true })) {
@@ -582,20 +648,34 @@ function GenerateCostingData(job) {
if (!jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]])
jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]] = Dinero();
jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]] = stlTowing
? Dinero({ amount: Math.round((stlTowing.ttl_amt || 0) * 100) })
: Dinero({
if (stlTowing)
jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]] = Dinero({
amount: Math.round((stlTowing.ttl_amt || 0) * 100)
});
if (!stlTowing && job.towing_payable && job.towing_payable > 0)
jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]] = jobLineTotalsByProfitCenter.additional[
defaultProfits["TOW"]
].add(
Dinero({
amount: Math.round((job.towing_payable || 0) * 100)
});
})
);
if (!jobLineTotalsByProfitCenter.additional[defaultProfits["STO"]])
jobLineTotalsByProfitCenter.additional[defaultProfits["STO"]] = Dinero();
jobLineTotalsByProfitCenter.additional[defaultProfits["STO"]] = stlStorage
? Dinero({ amount: Math.round((stlStorage.ttl_amt || 0) * 100) })
: Dinero({
if (stlStorage)
jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]] = Dinero({
amount: Math.round((stlStorage.ttl_amt || 0) * 100)
});
if (!stlStorage && job.storage_payable && job.storage_payable > 0)
jobLineTotalsByProfitCenter.additional[defaultProfits["STO"]] = jobLineTotalsByProfitCenter.additional[
defaultProfits["STO"]
].add(
Dinero({
amount: Math.round((job.storage_payable || 0) * 100)
});
})
);
}
//Is it a DMS Setup?

View File

@@ -1227,6 +1227,8 @@ function ParseCalopCode(opcode) {
return opcode.trim().split(" ");
}
exports.ParseCalopCode = ParseCalopCode;
function IsTrueOrYes(value) {
return value === true || value === "Y" || value === "y";
}