Merge branch 'test' into feature/pbs

This commit is contained in:
Patrick Fic
2021-10-14 17:20:15 -07:00
29 changed files with 762 additions and 203 deletions

View File

@@ -31,9 +31,9 @@ exports.default = function ({
hasMashLine = true;
}
//Parts Lines Mappings.
if (jobline.profitcenter_part && jobline.act_price) {
if (jobline.profitcenter_part) {
let DineroAmount = Dinero({
amount: Math.round(jobline.act_price * 100),
amount: Math.round((jobline.act_price || 0) * 100),
}).multiply(jobline.part_qty || 1);
if (
@@ -315,8 +315,12 @@ exports.default = function ({
if (qbo) {
Object.keys(invoiceLineHash).forEach((key) => {
Object.keys(invoiceLineHash[key]).forEach((key2) => {
const account = responsibilityCenters.profits.find(
(p) => p.name === key
);
InvoiceLineAdd.push({
...invoiceLineHash[key][key2],
...(account ? { Description: account.accountdesc } : {}),
Amount: invoiceLineHash[key][key2].Amount.toFormat(DineroQbFormat),
});
});

View File

@@ -10,6 +10,7 @@ const OAuthClient = require("intuit-oauth");
const client = require("../../graphql-client/graphql-client").client;
const queries = require("../../graphql-client/queries");
const queryString = require("query-string");
const oauthClient = new OAuthClient({
clientId: process.env.QBO_CLIENT_ID,
clientSecret: process.env.QBO_SECRET,
@@ -18,6 +19,16 @@ const oauthClient = new OAuthClient({
logging: true,
});
let url;
if (process.env.NODE_ENV === "production") {
url = `https://imex.online`;
} else if (process.env.NODE_ENV === "test") {
url = `https://test.imex.online`;
} else {
url = `http://localhost:3000`;
}
exports.default = async (req, res) => {
const params = queryString.parse(req.url.split("?").reverse()[0]);
try {
@@ -28,7 +39,7 @@ exports.default = async (req, res) => {
error: authResponse.json,
});
res.redirect(
`http://localhost:3000/manage/accounting/qbo?error=${encodeURIComponent(
`${url}/manage/accounting/qbo?error=${encodeURIComponent(
JSON.stringify(authResponse.json)
)}`
);
@@ -46,9 +57,7 @@ exports.default = async (req, res) => {
);
res.redirect(
`http://localhost:3000/manage/accounting/qbo?${queryString.stringify(
params
)}`
`${url}/manage/accounting/qbo?${queryString.stringify(params)}`
);
}
} catch (e) {

View File

@@ -72,12 +72,15 @@ exports.default = async (req, res) => {
bill,
vendorRecord
);
ret.push({ billid: bill.id, success: true });
} catch (error) {
ret.push({
billid: bill.id,
success: false,
errorMessage: error.message,
errorMessage:
(error && error.authResponse && error.authResponse.body) ||
JSON.stringify(error),
});
}
}
@@ -112,7 +115,9 @@ async function QueryVendorRecord(oauthClient, req, bill) {
);
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
error,
error:
(error && error.authResponse && error.authResponse.body) ||
JSON.stringify(error),
method: "QueryVendorRecord",
});
throw error;
@@ -135,7 +140,9 @@ async function InsertVendorRecord(oauthClient, req, bill) {
return result && result.Vendor;
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
error,
error:
(error && error.authResponse && error.authResponse.body) ||
JSON.stringify(error),
method: "InsertVendorRecord",
});
throw error;
@@ -150,7 +157,7 @@ async function InsertBill(oauthClient, req, bill, vendor) {
value: vendor.Id,
},
TxnDate: moment(bill.date).format("YYYY-MM-DD"),
DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"),
//DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"),
DocNumber: bill.invoice_number,
...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}),
@@ -164,13 +171,20 @@ async function InsertBill(oauthClient, req, bill, vendor) {
bill.job.class,
bill.job.bodyshop.md_responsibility_centers.sales_tax_codes,
classes,
taxCodes
taxCodes,
bill.job.bodyshop.md_responsibility_centers.costs
)
),
};
logger.log("qbo-payable-objectlog", "DEBUG", req.user.email, bill.id, {
billQbo,
});
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "bill"),
url: urlBuilder(
req.cookies.qbo_realmId,
bill.is_credit_memo ? "vendorcredit" : "bill"
),
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -181,7 +195,9 @@ async function InsertBill(oauthClient, req, bill, vendor) {
return result && result.Bill;
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
error,
error:
(error && error.authResponse && error.authResponse.body) ||
JSON.stringify(error),
method: "InsertBill",
});
throw error;
@@ -204,10 +220,12 @@ const generateBillLine = (
billLine,
accounts,
jobClass,
responsibilityCenters,
ioSalesTaxCodes,
classes,
taxCodes
taxCodes,
costCenters
) => {
const account = costCenters.find((c) => c.name === billLine.cost_center);
return {
DetailType: "AccountBasedExpenseLineDetail",
@@ -215,12 +233,10 @@ const generateBillLine = (
...(jobClass ? { ClassRef: { Id: classes[jobClass] } } : {}),
TaxCodeRef: {
value:
taxCodes[
findTaxCode(billLine.applicable_taxes, responsibilityCenters)
],
taxCodes[findTaxCode(billLine.applicable_taxes, ioSalesTaxCodes)],
},
AccountRef: {
value: accounts[billLine.cost_center],
value: accounts[account.accountname],
},
},
@@ -231,6 +247,7 @@ const generateBillLine = (
.toFormat(DineroQbFormat),
};
};
async function QueryMetaData(oauthClient, req) {
const accounts = await oauthClient.makeApiCall({
url: urlBuilder(
@@ -264,6 +281,7 @@ async function QueryMetaData(oauthClient, req) {
taxCodes.json &&
taxCodes.json.QueryResponse &&
taxCodes.json.QueryResponse.TaxCode &&
taxCodes.json.QueryResponse.TaxCode.forEach((t) => {
taxCodeMapping[t.Name] = t.Id;
});
@@ -272,13 +290,15 @@ async function QueryMetaData(oauthClient, req) {
accounts.json &&
accounts.json.QueryResponse &&
accounts.json.QueryResponse.Account &&
accounts.json.QueryResponse.Account.forEach((t) => {
accountMapping[t.Name] = t.Id;
accountMapping[t.FullyQualifiedName] = t.Id;
});
const classMapping = {};
classes.json &&
classes.json.QueryResponse &&
classes.json.QueryResponse.Class &&
classes.json.QueryResponse.Class.forEach((t) => {
accountMapping[t.Name] = t.Id;
});

View File

@@ -0,0 +1,281 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
const logger = require("../../utils/logger");
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 OAuthClient = require("intuit-oauth");
const moment = require("moment");
const GraphQLClient = require("graphql-request").GraphQLClient;
const {
QueryInsuranceCo,
InsertInsuranceCo,
InsertJob,
InsertOwner,
QueryJob,
QueryOwner,
} = require("../qbo/qbo-receivables");
const { urlBuilder } = require("./qbo");
const { DineroQbFormat } = require("../accounting-constants");
exports.default = async (req, res) => {
const oauthClient = new OAuthClient({
clientId: process.env.QBO_CLIENT_ID,
clientSecret: process.env.QBO_SECRET,
environment:
process.env.NODE_ENV === "production" ? "production" : "sandbox",
redirectUri: process.env.QBO_REDIRECT_URI,
logging: true,
});
try {
//Fetch the API Access Tokens & Set them for the session.
const response = await apiGqlClient.request(queries.GET_QBO_AUTH, {
email: req.user.email,
});
oauthClient.setToken(response.associations[0].qbo_auth);
await refreshOauthToken(oauthClient, req);
const BearerToken = req.headers.authorization;
const { payments: paymentsToQuery } = req.body;
//Query Job Info
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
headers: {
Authorization: BearerToken,
},
});
logger.log("qbo-payment-create", "DEBUG", req.user.email, paymentsToQuery);
const result = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.QUERY_PAYMENTS_FOR_EXPORT, {
payments: paymentsToQuery,
});
const { payments, bodyshops } = result;
const bodyshop = bodyshops[0];
const ret = [];
for (const payment of payments) {
try {
const isThreeTier = bodyshop.accountingconfig.tiers === 3;
const twoTierPref = bodyshop.accountingconfig.twotierpref;
//Replace this with a for-each loop to check every single Job that's included in the list.
let insCoCustomerTier, ownerCustomerTier, jobTier;
if (isThreeTier || twoTierPref === "source") {
//Insert the insurance company tier.
//Query for top level customer, the insurance company name.
insCoCustomerTier = await QueryInsuranceCo(
oauthClient,
req,
payment.job
);
if (!insCoCustomerTier) {
//Creating the Insurance Customer.
insCoCustomerTier = await InsertInsuranceCo(
oauthClient,
req,
payment.job,
bodyshop
);
}
}
if (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, req, payment.job);
//Query for the owner itself.
if (!ownerCustomerTier) {
ownerCustomerTier = await InsertOwner(
oauthClient,
req,
payment.job,
isThreeTier,
insCoCustomerTier
);
}
}
//Query for the Job or Create it.
jobTier = await QueryJob(oauthClient, req, payment.job);
// Need to validate that the job tier is associated to the right individual?
if (!jobTier) {
jobTier = await InsertJob(
oauthClient,
req,
payment.job,
isThreeTier,
ownerCustomerTier
);
}
await InsertPayment(oauthClient, req, payment, jobTier);
ret.push({ paymentid: payment.id, success: true });
} catch (error) {
logger.log("qbo-payment-create-error", "ERROR", req.user.email, {
error:
(error && error.authResponse && error.authResponse.body) ||
JSON.stringify(error),
});
ret.push({
paymentid: payment.id,
success: false,
errorMessage:
(error && error.authResponse && error.authResponse.body) ||
JSON.stringify(error),
});
}
}
res.status(200).json(ret);
} catch (error) {
console.log(error);
logger.log("qbo-payment-create-error", "ERROR", req.user.email, { error });
res.status(400).json(error);
}
};
async function InsertPayment(oauthClient, req, payment, parentRef) {
const { paymentMethods, invoices } = await QueryMetaData(
oauthClient,
req,
payment.job.ro_number
);
if (invoices.length !== 1) {
throw new Error(
`More than 1 invoice with DocNumber ${payment.ro_number} found.`
);
}
const paymentQbo = {
CustomerRef: {
value: parentRef.Id,
},
TxnDate: moment(payment.date).format("YYYY-MM-DD"),
//DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"),
DocNumber: payment.paymentnum,
TotalAmt: Dinero({
amount: Math.round(payment.amount * 100),
}).toFormat(DineroQbFormat),
PaymentMethodRef: {
value: paymentMethods[payment.type],
},
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, {
paymentQbo,
});
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "payment"),
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(paymentQbo),
});
setNewRefreshToken(req.user.email, result);
return result && result.Bill;
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, payment.id, {
error: JSON.stringify(error),
method: "InsertPayment",
});
throw error;
}
}
async function QueryMetaData(oauthClient, req, ro_number) {
const invoice = await oauthClient.makeApiCall({
url: urlBuilder(
req.cookies.qbo_realmId,
"query",
`select * From Invoice where DocNumber = '${ro_number}'`
),
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
const paymentMethods = await oauthClient.makeApiCall({
url: urlBuilder(
req.cookies.qbo_realmId,
"query",
`select * From PaymentMethod`
),
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
setNewRefreshToken(req.user.email, paymentMethods);
// const classes = await oauthClient.makeApiCall({
// url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From Class`),
// method: "POST",
// headers: {
// "Content-Type": "application/json",
// },
// });
const paymentMethodMapping = {};
paymentMethods.json &&
paymentMethods.json.QueryResponse &&
paymentMethods.json.QueryResponse.PaymentMethod &&
paymentMethods.json.QueryResponse.PaymentMethod.forEach((t) => {
paymentMethodMapping[t.Name] = t.Id;
});
// const accountMapping = {};
// accounts.json &&
// accounts.json.QueryResponse &&
// accounts.json.QueryResponse.Account.forEach((t) => {
// accountMapping[t.Name] = t.Id;
// });
// const classMapping = {};
// classes.json &&
// classes.json.QueryResponse &&
// classes.json.QueryResponse.Class.forEach((t) => {
// accountMapping[t.Name] = t.Id;
// });
return {
paymentMethods: paymentMethodMapping,
invoices:
invoice.json &&
invoice.json.QueryResponse &&
invoice.json.QueryResponse.Invoice,
};
}

View File

@@ -66,7 +66,7 @@ exports.default = async (req, res) => {
//Replace this with a for-each loop to check every single Job that's included in the list.
let insCoCustomerTier, ownerCustomerTier, jobTier;
if (isThreeTier || twoTierPref === "source") {
if (isThreeTier || (!isThreeTier && twoTierPref === "source")) {
//Insert the insurance company tier.
//Query for top level customer, the insurance company name.
insCoCustomerTier = await QueryInsuranceCo(oauthClient, req, job);
@@ -80,8 +80,8 @@ exports.default = async (req, res) => {
);
}
}
console.log(insCoCustomerTier);
if (isThreeTier || twoTierPref === "name") {
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, req, job);
//Query for the owner itself.
@@ -95,7 +95,7 @@ exports.default = async (req, res) => {
);
}
}
console.log(ownerCustomerTier);
//Query for the Job or Create it.
jobTier = await QueryJob(oauthClient, req, job);
@@ -106,18 +106,20 @@ exports.default = async (req, res) => {
oauthClient,
req,
job,
isThreeTier,
ownerCustomerTier
ownerCustomerTier || insCoCustomerTier
);
}
console.log(jobTier);
await InsertInvoice(oauthClient, req, job, bodyshop, jobTier);
ret.push({ jobid: job.id, success: true });
} catch (error) {
ret.push({
jobid: job.id,
success: false,
errorMessage: error.message,
errorMessage:
(error && error.authResponse && error.authResponse.body) ||
JSON.stringify(error),
});
}
}
@@ -160,6 +162,7 @@ async function QueryInsuranceCo(oauthClient, req, job) {
throw error;
}
}
exports.QueryInsuranceCo = QueryInsuranceCo;
async function InsertInsuranceCo(oauthClient, req, job, bodyshop) {
const insCo = bodyshop.md_ins_cos.find((i) => i.name === job.ins_co_nm);
@@ -192,7 +195,7 @@ async function InsertInsuranceCo(oauthClient, req, job, bodyshop) {
throw error;
}
}
exports.InsertInsuranceCo = InsertInsuranceCo;
async function QueryOwner(oauthClient, req, job) {
const ownerName = generateOwnerTier(job, true, null);
const result = await oauthClient.makeApiCall({
@@ -214,7 +217,7 @@ async function QueryOwner(oauthClient, req, job) {
result.json.QueryResponse.Customer[0]
);
}
exports.QueryOwner = QueryOwner;
async function InsertOwner(oauthClient, req, job, isThreeTier, parentTierRef) {
const ownerName = generateOwnerTier(job, true, null);
const Customer = {
@@ -254,7 +257,7 @@ async function InsertOwner(oauthClient, req, job, isThreeTier, parentTierRef) {
throw error;
}
}
exports.InsertOwner = InsertOwner;
async function QueryJob(oauthClient, req, job) {
const result = await oauthClient.makeApiCall({
url: urlBuilder(
@@ -275,8 +278,8 @@ async function QueryJob(oauthClient, req, job) {
result.json.QueryResponse.Customer[0]
);
}
async function InsertJob(oauthClient, req, job, isThreeTier, parentTierRef) {
exports.QueryJob = QueryJob;
async function InsertJob(oauthClient, req, job, parentTierRef) {
const Customer = {
DisplayName: job.ro_number,
BillAddr: {
@@ -286,14 +289,11 @@ async function InsertJob(oauthClient, req, job, isThreeTier, parentTierRef) {
PostalCode: job.ownr_zip,
CountrySubDivisionCode: job.ownr_st,
},
...(isThreeTier
? {
Job: true,
ParentRef: {
value: parentTierRef.Id,
},
}
: {}),
Job: true,
ParentRef: {
value: parentTierRef.Id,
},
};
try {
const result = await oauthClient.makeApiCall({
@@ -314,7 +314,7 @@ async function InsertJob(oauthClient, req, job, isThreeTier, parentTierRef) {
throw error;
}
}
exports.InsertJob = InsertJob;
async function QueryMetaData(oauthClient, req) {
const items = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From Item`),
@@ -344,6 +344,7 @@ async function QueryMetaData(oauthClient, req) {
taxCodes.json &&
taxCodes.json.QueryResponse &&
taxCodes.json.QueryResponse.TaxCode &&
taxCodes.json.QueryResponse.TaxCode.forEach((t) => {
taxCodeMapping[t.Name] = t.Id;
});
@@ -352,6 +353,7 @@ async function QueryMetaData(oauthClient, req) {
items.json &&
items.json.QueryResponse &&
items.json.QueryResponse.Item &&
items.json.QueryResponse.Item.forEach((t) => {
itemMapping[t.Name] = t.Id;
});
@@ -359,6 +361,7 @@ async function QueryMetaData(oauthClient, req) {
const classMapping = {};
classes.json &&
classes.json.QueryResponse &&
classes.json.QueryResponse.Class &&
classes.json.QueryResponse.Class.forEach((t) => {
itemMapping[t.Name] = t.Id;
});
@@ -391,15 +394,20 @@ async function InsertInvoice(oauthClient, req, job, bodyshop, parentTierRef) {
...(bodyshop.accountingconfig.printlater
? { PrintStatus: "NeedToPrint" }
: {}),
...(bodyshop.accountingconfig.emaillater
...(bodyshop.accountingconfig.emaillater && job.ownr_ea
? { EmailStatus: "NeedToSend" }
: {}),
};
logger.log("qbo-receivable-objectlog", "DEBUG", req.user.email, job.id, {
invoiceObj,
});
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "invoice"),
method: "POST",
headers: {
"Content-Type": "application/json",
},

View File

@@ -8,9 +8,7 @@ require("dotenv").config({
function urlBuilder(realmId, object, query = null) {
return `https://${
process.env.NODE_ENV === "development" || !process.env.NODE_ENV
? "sandbox-"
: ""
process.env.NODE_ENV === "production" ? "" : "sandbox-"
}quickbooks.api.intuit.com/v3/company/${realmId}/${object}${
query ? `?query=${encodeURIComponent(query)}` : ""
}`;
@@ -22,3 +20,4 @@ exports.authorize = require("./qbo-authorize").default;
exports.refresh = require("./qbo-callback").refresh;
exports.receivables = require("./qbo-receivables").default;
exports.payables = require("./qbo-payables").default;
exports.payments = require("./qbo-payments").default;

View File

@@ -142,9 +142,6 @@ const generatePayment = (payment, isThreeTier, twoTierPref) => {
payment.stripeid || ""
} ${payment.payer ? ` PAID BY ${payment.payer}` : ""}`,
IsAutoApply: true,
// AppliedToTxnAdd:{
// T
// }
},
},
},

View File

@@ -284,39 +284,54 @@ function CalculatePartsTotals(jobLines) {
};
default:
if (!value.part_type) return acc;
if (
!value.part_type &&
value.db_ref !== "900510" &&
value.db_ref !== "900511"
)
return acc;
return {
...acc,
parts: {
...acc.parts,
prt_dsmk_total: acc.parts.prt_dsmk_total.add(
value.prt_dsmk_m && value.prt_dsmk_m !== 0
? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) })
: Dinero({
amount: Math.round(value.act_price * 100),
})
.multiply(value.part_qty || 0)
.percentage(Math.abs(value.prt_dsmk_p || 0))
.multiply(value.prt_dsmk_p > 0 ? 1 : -1)
? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) })
: Dinero({
amount: Math.round(value.act_price * 100),
})
.multiply(value.part_qty || 0)
.percentage(Math.abs(value.prt_dsmk_p || 0))
.multiply(value.prt_dsmk_p > 0 ? 1 : -1)
),
list: {
...acc.parts.list,
[value.part_type]:
acc.parts.list[value.part_type] &&
acc.parts.list[value.part_type].total
? {
total: acc.parts.list[value.part_type].total.add(
Dinero({
amount: Math.round((value.act_price || 0) * 100),
}).multiply(value.part_qty || 0)
),
}
: {
total: Dinero({
amount: Math.round((value.act_price || 0) * 100),
}).multiply(value.part_qty || 0),
},
},
...(value.part_type
? {
list: {
...acc.parts.list,
[value.part_type]:
acc.parts.list[value.part_type] &&
acc.parts.list[value.part_type].total
? {
total: acc.parts.list[
value.part_type
].total.add(
Dinero({
amount: Math.round(
(value.act_price || 0) * 100
),
}).multiply(value.part_qty || 0)
),
}
: {
total: Dinero({
amount: Math.round(
(value.act_price || 0) * 100
),
}).multiply(value.part_qty || 0),
},
},
}
: {}),
subtotal: acc.parts.subtotal
.add(
Dinero({
@@ -325,13 +340,13 @@ function CalculatePartsTotals(jobLines) {
)
.add(
value.prt_dsmk_m && value.prt_dsmk_m !== 0
? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) })
: Dinero({
amount: Math.round(value.act_price * 100),
})
.multiply(value.part_qty || 0)
.percentage(Math.abs(value.prt_dsmk_p || 0))
.multiply(value.prt_dsmk_p > 0 ? 1 : -1)
? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) })
: Dinero({
amount: Math.round(value.act_price * 100),
})
.multiply(value.part_qty || 0)
.percentage(Math.abs(value.prt_dsmk_p || 0))
.multiply(value.prt_dsmk_p > 0 ? 1 : -1)
),
},
};
@@ -465,13 +480,13 @@ function CalculateTaxesTotals(job, otherTotals) {
.multiply(val.part_qty || 0)
.add(
val.prt_dsmk_m && val.prt_dsmk_m !== 0
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
: Dinero({
amount: Math.round(val.act_price * 100),
})
.multiply(val.part_qty || 0)
.percentage(Math.abs(val.prt_dsmk_p || 0))
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
: Dinero({
amount: Math.round(val.act_price * 100),
})
.multiply(val.part_qty || 0)
.percentage(Math.abs(val.prt_dsmk_p || 0))
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
)
.percentage(
((job.parts_tax_rates &&
@@ -481,6 +496,10 @@ function CalculateTaxesTotals(job, otherTotals) {
val.part_type.startsWith("PAG") &&
BackupGlassTax &&
BackupGlassTax.prt_tax_rt) ||
(!val.part_type &&
val.db_ref === "900510" &&
job.parts_tax_rates["PAN"] &&
job.parts_tax_rates["PAN"].prt_tax_rt) ||
0) * 100
)
);