1134 lines
38 KiB
JavaScript
1134 lines
38 KiB
JavaScript
const DineroQbFormat = require("./accounting-constants").DineroQbFormat;
|
|
|
|
const Dinero = require("dinero.js");
|
|
const InstanceManager = require("../utils/instanceMgr").default;
|
|
const { DiscountNotAlreadyCounted } = InstanceManager({
|
|
imex: require("../job/job-totals"),
|
|
rome: require("../job/job-totals-USA"),
|
|
promanager: "USE_ROME"
|
|
});
|
|
const logger = require("../utils/logger");
|
|
|
|
exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes, classes }) {
|
|
const InvoiceLineAdd = [];
|
|
const responsibilityCenters = bodyshop.md_responsibility_centers;
|
|
|
|
const invoiceLineHash = {}; //The hash of cost and profit centers based on the center name.
|
|
|
|
//Determine if there are MAPA and MASH lines already on the estimate.
|
|
//If there are, don't do anything extra (mitchell estimate)
|
|
//Otherwise, calculate them and add them to the default MAPA and MASH centers.
|
|
let hasMapaLine = false;
|
|
let hasMashLine = false;
|
|
|
|
//Create the invoice lines mapping.
|
|
jobs_by_pk.joblines.map((jobline) => {
|
|
if (jobline.db_ref === "936008") {
|
|
//If either of these DB REFs change, they also need to change in job-totals/job-costing calculations.
|
|
hasMapaLine = true;
|
|
}
|
|
if (jobline.db_ref === "936007") {
|
|
hasMashLine = true;
|
|
}
|
|
|
|
//Check if the line is a Towing Line and flag as such.
|
|
let isTowingLine = false;
|
|
if (jobline.db_ref === "936001" && jobline.line_desc.includes("Towing")) {
|
|
isTowingLine = true;
|
|
}
|
|
//Parts Lines Mappings.
|
|
if (!isTowingLine && jobline.profitcenter_part) {
|
|
//TODO:AIO This appears to be a net 0 change exept for default quantity as 0 instead of 1 for imex. Need to verify.
|
|
const discountAmount =
|
|
((jobline.prt_dsmk_m && jobline.prt_dsmk_m !== 0) || (jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0)) &&
|
|
DiscountNotAlreadyCounted(jobline, jobs_by_pk.joblines)
|
|
? jobline.prt_dsmk_m
|
|
? Dinero({ amount: Math.round(jobline.prt_dsmk_m * 100) })
|
|
: Dinero({
|
|
amount: Math.round(jobline.act_price * 100)
|
|
})
|
|
.multiply(jobline.part_qty || 0)
|
|
.percentage(Math.abs(jobline.prt_dsmk_p || 0))
|
|
.multiply(jobline.prt_dsmk_p > 0 ? 1 : -1)
|
|
: Dinero();
|
|
|
|
let DineroAmount = Dinero({
|
|
amount: Math.round((jobline.act_price || 0) * 100)
|
|
})
|
|
.multiply(jobline.part_qty || 0)
|
|
.add(discountAmount);
|
|
|
|
const account = responsibilityCenters.profits.find(
|
|
(i) => jobline.profitcenter_part.toLowerCase() === i.name.toLowerCase()
|
|
);
|
|
|
|
if (!account) {
|
|
logger.log("qbxml-receivables-no-account", "warn", null, jobline.id);
|
|
throw new Error(
|
|
`A matching account does not exist for the part allocation. Center: ${jobline.profitcenter_part}`
|
|
);
|
|
}
|
|
if (qbo) {
|
|
//Do the mapping as per QBO.
|
|
//Determine the Tax code grouping.
|
|
|
|
//Going to always assume that we need to apply GST.
|
|
const taxAccountCode = findTaxCode(
|
|
{
|
|
local: false,
|
|
federal: InstanceManager({ imex: true, rome: false }),
|
|
state: checkStateTax(jobline, jobs_by_pk)
|
|
},
|
|
bodyshop.md_responsibility_centers.sales_tax_codes
|
|
);
|
|
|
|
const QboTaxId = InstanceManager({
|
|
imex: taxCodes[taxAccountCode],
|
|
rome: CheckQBOUSATaxID({
|
|
jobline: jobline,
|
|
type: "part",
|
|
job: jobs_by_pk
|
|
})
|
|
});
|
|
if (!invoiceLineHash[account.name]) invoiceLineHash[account.name] = {};
|
|
if (!invoiceLineHash[account.name][QboTaxId]) {
|
|
invoiceLineHash[account.name][QboTaxId] = {
|
|
DetailType: "SalesItemLineDetail",
|
|
Amount: DineroAmount,
|
|
SalesItemLineDetail: {
|
|
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
|
|
ItemRef: {
|
|
value: items[account.accountitem]
|
|
},
|
|
TaxCodeRef: {
|
|
value: QboTaxId
|
|
},
|
|
Qty: 1
|
|
}
|
|
};
|
|
} else {
|
|
invoiceLineHash[account.name][QboTaxId].Amount =
|
|
invoiceLineHash[account.name][QboTaxId].Amount.add(DineroAmount);
|
|
}
|
|
} else {
|
|
if (!invoiceLineHash[account.name]) {
|
|
invoiceLineHash[account.name] = {
|
|
ItemRef: { FullName: account.accountitem },
|
|
Desc: account.accountdesc,
|
|
Quantity: 1, //jobline.part_qty,
|
|
Amount: DineroAmount, //.toFormat(DineroQbFormat),
|
|
SalesTaxCodeRef: InstanceManager({
|
|
imex: {
|
|
FullName: "E"
|
|
},
|
|
rome: {
|
|
FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON"
|
|
}
|
|
})
|
|
};
|
|
} else {
|
|
invoiceLineHash[account.name].Amount = invoiceLineHash[account.name].Amount.add(DineroAmount);
|
|
}
|
|
}
|
|
}
|
|
//End Parts line mappings.
|
|
|
|
// Labor Lines
|
|
if (jobline.profitcenter_labor && jobline.mod_lb_hrs) {
|
|
const DineroAmount = Dinero({
|
|
amount: Math.round(jobs_by_pk[`rate_${jobline.mod_lbr_ty.toLowerCase()}`] * 100)
|
|
}).multiply(jobline.mod_lb_hrs);
|
|
const account = responsibilityCenters.profits.find(
|
|
(i) => jobline.profitcenter_labor.toLowerCase() === i.name.toLowerCase()
|
|
);
|
|
|
|
if (!account) {
|
|
throw new Error(
|
|
`A matching account does not exist for the labor allocation. Center: ${jobline.profitcenter_labor}`
|
|
);
|
|
}
|
|
if (qbo) {
|
|
//Going to always assume that we need to apply GST and PST for labor.
|
|
const taxAccountCode = findTaxCode(
|
|
{
|
|
local: false,
|
|
federal: InstanceManager({ imex: true, rome: false }),
|
|
state: jobs_by_pk.tax_lbr_rt === 0 ? false : true
|
|
},
|
|
bodyshop.md_responsibility_centers.sales_tax_codes
|
|
);
|
|
|
|
const QboTaxId = InstanceManager({
|
|
imex: taxCodes[taxAccountCode],
|
|
rome: CheckQBOUSATaxID({ jobline: jobline, type: "labor" })
|
|
});
|
|
if (!invoiceLineHash[account.name]) invoiceLineHash[account.name] = {};
|
|
if (!invoiceLineHash[account.name][QboTaxId]) {
|
|
invoiceLineHash[account.name][QboTaxId] = {
|
|
DetailType: "SalesItemLineDetail",
|
|
Amount: DineroAmount,
|
|
SalesItemLineDetail: {
|
|
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
|
|
ItemRef: {
|
|
value: items[account.accountitem]
|
|
},
|
|
TaxCodeRef: {
|
|
value: QboTaxId
|
|
},
|
|
Qty: 1
|
|
}
|
|
};
|
|
} else {
|
|
invoiceLineHash[account.name][QboTaxId].Amount =
|
|
invoiceLineHash[account.name][QboTaxId].Amount.add(DineroAmount);
|
|
}
|
|
} else {
|
|
if (!invoiceLineHash[account.name]) {
|
|
invoiceLineHash[account.name] = {
|
|
ItemRef: { FullName: account.accountitem },
|
|
Desc: account.accountdesc,
|
|
Quantity: 1, // jobline.mod_lb_hrs,
|
|
Amount: DineroAmount,
|
|
//Amount: DineroAmount.toFormat(DineroQbFormat),
|
|
SalesTaxCodeRef: InstanceManager({
|
|
imex: {
|
|
FullName: "E"
|
|
},
|
|
rome: {
|
|
FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON"
|
|
}
|
|
})
|
|
};
|
|
} else {
|
|
invoiceLineHash[account.name].Amount = invoiceLineHash[account.name].Amount.add(DineroAmount);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
if (!hasMapaLine && jobs_by_pk.job_totals.rates.mapa.total.amount > 0) {
|
|
// //console.log("Adding MAPA Line Manually.");
|
|
const mapaAccountName = responsibilityCenters.defaults.profits.MAPA;
|
|
|
|
const mapaAccount = responsibilityCenters.profits.find((c) => c.name === mapaAccountName);
|
|
|
|
if (mapaAccount) {
|
|
if (qbo) {
|
|
//Add QBO MAPA
|
|
|
|
//Going to always assume that we need to apply GST and PST for labor.
|
|
const taxAccountCode = findTaxCode(
|
|
{
|
|
local: false,
|
|
federal: InstanceManager({ imex: true, rome: false }),
|
|
state: jobs_by_pk.tax_paint_mat_rt === 0 ? false : true
|
|
},
|
|
bodyshop.md_responsibility_centers.sales_tax_codes
|
|
);
|
|
|
|
const QboTaxId = InstanceManager({
|
|
imex: taxCodes[taxAccountCode],
|
|
rome: CheckQBOUSATaxID({
|
|
// jobline: jobline,
|
|
job: jobs_by_pk,
|
|
type: "materials"
|
|
})
|
|
});
|
|
if (!invoiceLineHash[mapaAccount.name]) invoiceLineHash[mapaAccount.name] = {};
|
|
if (!invoiceLineHash[mapaAccount.name][QboTaxId]) {
|
|
invoiceLineHash[mapaAccount.name][QboTaxId] = {
|
|
DetailType: "SalesItemLineDetail",
|
|
Amount: Dinero(jobs_by_pk.job_totals.rates.mapa.total),
|
|
SalesItemLineDetail: {
|
|
ItemRef: {
|
|
value: items[mapaAccount.accountitem]
|
|
},
|
|
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
|
|
TaxCodeRef: {
|
|
value: QboTaxId
|
|
},
|
|
Qty: 1
|
|
}
|
|
};
|
|
} else {
|
|
invoiceLineHash[mapaAccount.name][QboTaxId].Amount = invoiceLineHash[mapaAccount.name][QboTaxId].Amount.add(
|
|
Dinero(jobs_by_pk.job_totals.rates.mapa.total)
|
|
);
|
|
}
|
|
} else {
|
|
InvoiceLineAdd.push({
|
|
ItemRef: { FullName: mapaAccount.accountitem },
|
|
Desc: mapaAccount.accountdesc,
|
|
Quantity: 1,
|
|
Amount: Dinero(jobs_by_pk.job_totals.rates.mapa.total).toFormat(DineroQbFormat),
|
|
SalesTaxCodeRef: InstanceManager({
|
|
imex: {
|
|
FullName: "E"
|
|
},
|
|
rome: {
|
|
FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON"
|
|
}
|
|
})
|
|
});
|
|
}
|
|
} else {
|
|
////console.log("NO MAPA ACCOUNT FOUND!!");
|
|
}
|
|
}
|
|
|
|
if (!hasMashLine && jobs_by_pk.job_totals.rates.mash.total.amount > 0) {
|
|
// //console.log("Adding MASH Line Manually.");
|
|
|
|
const mashAccountName = responsibilityCenters.defaults.profits.MASH;
|
|
|
|
const mashAccount = responsibilityCenters.profits.find((c) => c.name === mashAccountName);
|
|
|
|
if (mashAccount) {
|
|
if (qbo) {
|
|
//Going to always assume that we need to apply GST and PST for labor.
|
|
const taxAccountCode = findTaxCode(
|
|
{
|
|
local: false,
|
|
federal: InstanceManager({ imex: true, rome: false }),
|
|
state: jobs_by_pk.tax_shop_mat_rt === 0 ? false : true
|
|
},
|
|
bodyshop.md_responsibility_centers.sales_tax_codes
|
|
);
|
|
|
|
const QboTaxId = InstanceManager({
|
|
imex: taxCodes[taxAccountCode],
|
|
rome: CheckQBOUSATaxID({
|
|
// jobline: jobline,
|
|
job: jobs_by_pk,
|
|
type: "materials"
|
|
})
|
|
});
|
|
if (!invoiceLineHash[mashAccount.name]) invoiceLineHash[mashAccount.name] = {};
|
|
if (!invoiceLineHash[mashAccount.name][QboTaxId]) {
|
|
invoiceLineHash[mashAccount.name][QboTaxId] = {
|
|
DetailType: "SalesItemLineDetail",
|
|
Amount: Dinero(jobs_by_pk.job_totals.rates.mash.total),
|
|
SalesItemLineDetail: {
|
|
ItemRef: {
|
|
value: items[mashAccount.accountitem]
|
|
},
|
|
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
|
|
TaxCodeRef: {
|
|
value: QboTaxId
|
|
},
|
|
Qty: 1
|
|
}
|
|
};
|
|
} else {
|
|
invoiceLineHash[mashAccount.name][QboTaxId].Amount = invoiceLineHash[mashAccount.name][QboTaxId].Amount.add(
|
|
Dinero(jobs_by_pk.job_totals.rates.mash.total)
|
|
);
|
|
}
|
|
} else {
|
|
InvoiceLineAdd.push({
|
|
ItemRef: { FullName: mashAccount.accountitem },
|
|
Desc: mashAccount.accountdesc,
|
|
Quantity: 1,
|
|
Amount: Dinero(jobs_by_pk.job_totals.rates.mash.total).toFormat(DineroQbFormat),
|
|
SalesTaxCodeRef: InstanceManager({
|
|
imex: {
|
|
FullName: "E"
|
|
},
|
|
rome: {
|
|
FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON"
|
|
}
|
|
})
|
|
});
|
|
}
|
|
} else {
|
|
// //console.log("NO MASH ACCOUNT FOUND!!");
|
|
}
|
|
}
|
|
|
|
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)
|
|
});
|
|
});
|
|
});
|
|
} else {
|
|
Object.keys(invoiceLineHash).forEach((key) => {
|
|
InvoiceLineAdd.push({
|
|
...invoiceLineHash[key],
|
|
Amount: invoiceLineHash[key].Amount.toFormat(DineroQbFormat)
|
|
});
|
|
});
|
|
}
|
|
//Convert the hash to an array.
|
|
|
|
//Add Towing, storage and adjustment lines.
|
|
|
|
///TODO:AIO Check if this towing check works for imex and not just Rome.
|
|
if (jobs_by_pk.job_totals.additional.towing.amount > 0) {
|
|
if (qbo) {
|
|
//Going to always assume that we need to apply GST and PST for labor.
|
|
const taxAccountCode = findTaxCode(
|
|
{
|
|
local: false,
|
|
federal: InstanceManager({ imex: true, rome: false }),
|
|
state: jobs_by_pk.tax_tow_rt === 0 ? false : true
|
|
},
|
|
bodyshop.md_responsibility_centers.sales_tax_codes
|
|
);
|
|
const account = responsibilityCenters.profits.find(
|
|
(c) => c.name === responsibilityCenters.defaults.profits["TOW"]
|
|
);
|
|
const QboTaxId = InstanceManager({
|
|
imex: taxCodes[taxAccountCode],
|
|
rome: CheckQBOUSATaxID({
|
|
// jobline: jobline,
|
|
job: jobs_by_pk,
|
|
type: "towing"
|
|
})
|
|
});
|
|
|
|
InvoiceLineAdd.push({
|
|
DetailType: "SalesItemLineDetail",
|
|
///TODO:AIO Check if this towing check works for imex and not just Rome.
|
|
Amount: Dinero(jobs_by_pk.job_totals.additional.towing).toFormat(DineroQbFormat),
|
|
SalesItemLineDetail: {
|
|
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
|
|
ItemRef: {
|
|
value: items[account.accountitem]
|
|
},
|
|
TaxCodeRef: {
|
|
value: QboTaxId
|
|
},
|
|
Qty: 1
|
|
}
|
|
});
|
|
} else {
|
|
InvoiceLineAdd.push({
|
|
ItemRef: {
|
|
FullName: responsibilityCenters.profits.find((c) => c.name === responsibilityCenters.defaults.profits["TOW"])
|
|
.accountitem
|
|
},
|
|
Desc: "Towing",
|
|
Quantity: 1,
|
|
///TODO:AIO Check if this towing check works for imex and not just Rome.
|
|
Amount: Dinero(jobs_by_pk.job_totals.additional.towing).toFormat(DineroQbFormat),
|
|
SalesTaxCodeRef: InstanceManager({
|
|
imex: {
|
|
FullName: "E"
|
|
},
|
|
rome: {
|
|
FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON"
|
|
}
|
|
})
|
|
});
|
|
}
|
|
}
|
|
///TODO:AIO Check if this storage check works for imex and not just Rome.
|
|
if (jobs_by_pk.job_totals.additional.storage.amount > 0) {
|
|
if (qbo) {
|
|
//Going to always assume that we need to apply GST and PST for labor.
|
|
const taxAccountCode = findTaxCode(
|
|
{
|
|
local: false,
|
|
federal: InstanceManager({ imex: true, rome: false }),
|
|
state: jobs_by_pk.tax_str_rt === 0 ? false : true
|
|
},
|
|
bodyshop.md_responsibility_centers.sales_tax_codes
|
|
);
|
|
const account = responsibilityCenters.profits.find(
|
|
(c) => c.name === responsibilityCenters.defaults.profits["TOW"]
|
|
);
|
|
const QboTaxId = InstanceManager({
|
|
imex: taxCodes[taxAccountCode],
|
|
rome: CheckQBOUSATaxID({
|
|
// jobline: jobline,
|
|
job: jobs_by_pk,
|
|
type: "storage"
|
|
})
|
|
});
|
|
InvoiceLineAdd.push({
|
|
DetailType: "SalesItemLineDetail",
|
|
///TODO:AIO Check if this storage check works for imex and not just Rome.
|
|
|
|
Amount: Dinero(jobs_by_pk.job_totals.additional.storage.amount).toFormat(DineroQbFormat),
|
|
SalesItemLineDetail: {
|
|
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
|
|
ItemRef: {
|
|
value: items[account.accountitem]
|
|
},
|
|
TaxCodeRef: {
|
|
value: QboTaxId
|
|
},
|
|
Qty: 1
|
|
}
|
|
});
|
|
} else {
|
|
InvoiceLineAdd.push({
|
|
ItemRef: {
|
|
FullName: responsibilityCenters.profits.find((c) => c.name === responsibilityCenters.defaults.profits["TOW"])
|
|
.accountitem
|
|
},
|
|
Desc: "Storage",
|
|
Quantity: 1,
|
|
///TODO:AIO Check if this storage check works for imex and not just Rome.
|
|
|
|
Amount: Dinero(jobs_by_pk.job_totals.additional.storage.amount).toFormat(DineroQbFormat),
|
|
SalesTaxCodeRef: InstanceManager({
|
|
imex: {
|
|
FullName: "E"
|
|
},
|
|
rome: {
|
|
FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON"
|
|
}
|
|
})
|
|
});
|
|
}
|
|
}
|
|
if (jobs_by_pk.adjustment_bottom_line && jobs_by_pk.adjustment_bottom_line !== 0) {
|
|
if (qbo) {
|
|
//Going to always assume that we need to apply GST and PST for labor.
|
|
const taxAccountCode = findTaxCode(
|
|
{
|
|
local: false,
|
|
federal: InstanceManager({ imex: true, rome: false }),
|
|
state: jobs_by_pk.tax_lbr_rt === 0 ? false : true
|
|
},
|
|
bodyshop.md_responsibility_centers.sales_tax_codes
|
|
);
|
|
|
|
const QboTaxId = InstanceManager({
|
|
imex: taxCodes[taxAccountCode],
|
|
rome: CheckQBOUSATaxID({
|
|
// jobline: jobline,
|
|
type: "adjustment",
|
|
job: jobs_by_pk
|
|
})
|
|
});
|
|
|
|
InvoiceLineAdd.push({
|
|
DetailType: "SalesItemLineDetail",
|
|
Amount: Dinero({
|
|
amount: Math.round((jobs_by_pk.adjustment_bottom_line || 0) * 100)
|
|
}).toFormat(DineroQbFormat),
|
|
SalesItemLineDetail: {
|
|
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
|
|
ItemRef: {
|
|
value: items[responsibilityCenters.refund.accountitem]
|
|
},
|
|
TaxCodeRef: {
|
|
value: QboTaxId
|
|
},
|
|
Qty: 1
|
|
}
|
|
});
|
|
} else {
|
|
InvoiceLineAdd.push({
|
|
ItemRef: {
|
|
FullName: responsibilityCenters.refund.accountitem
|
|
},
|
|
Desc: "Adjustment",
|
|
Quantity: 1,
|
|
Amount: Dinero({
|
|
amount: Math.round((jobs_by_pk.adjustment_bottom_line || 0) * 100)
|
|
}).toFormat(DineroQbFormat),
|
|
SalesTaxCodeRef: InstanceManager({
|
|
imex: {
|
|
FullName: "E"
|
|
},
|
|
rome: {
|
|
FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON"
|
|
}
|
|
})
|
|
});
|
|
}
|
|
}
|
|
|
|
if (jobs_by_pk.job_totals.totals.ttl_adjustment) {
|
|
// Do not need to check for ImEX or Rome because ImEX uses a different totals calculation that will never set this field.
|
|
if (qbo) {
|
|
const taxAccountCode = findTaxCode(
|
|
{
|
|
local: false,
|
|
federal: InstanceManager({ imex: true, rome: false }),
|
|
state: jobs_by_pk.tax_lbr_rt === 0 ? false : true
|
|
},
|
|
bodyshop.md_responsibility_centers.sales_tax_codes
|
|
);
|
|
|
|
const QboTaxId = InstanceManager({
|
|
imex: taxCodes[taxAccountCode],
|
|
rome: CheckQBOUSATaxID({
|
|
// jobline: jobline,
|
|
type: "adjustment",
|
|
job: jobs_by_pk
|
|
})
|
|
});
|
|
|
|
InvoiceLineAdd.push({
|
|
DetailType: "SalesItemLineDetail",
|
|
Amount: Dinero(jobs_by_pk.job_totals.totals.ttl_adjustment).toFormat(DineroQbFormat),
|
|
SalesItemLineDetail: {
|
|
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
|
|
ItemRef: {
|
|
value: items[responsibilityCenters.ttl_adjustment.accountitem]
|
|
},
|
|
TaxCodeRef: {
|
|
value: QboTaxId
|
|
},
|
|
Qty: 1
|
|
}
|
|
});
|
|
} else {
|
|
InvoiceLineAdd.push({
|
|
ItemRef: {
|
|
FullName: responsibilityCenters.ttl_adjustment.accountitem
|
|
},
|
|
Desc: "Adjustment",
|
|
Quantity: 1,
|
|
Amount: Dinero(jobs_by_pk.job_totals.totals.ttl_adjustment).toFormat(DineroQbFormat),
|
|
SalesTaxCodeRef: InstanceManager({
|
|
imex: {
|
|
FullName: "E"
|
|
},
|
|
rome: {
|
|
FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON"
|
|
}
|
|
})
|
|
});
|
|
}
|
|
}
|
|
|
|
//Add tax lines
|
|
const job_totals = jobs_by_pk.job_totals;
|
|
const federal_tax = Dinero(job_totals.totals.federal_tax);
|
|
const state_tax = Dinero(job_totals.totals.state_tax);
|
|
const local_tax = Dinero(job_totals.totals.local_tax);
|
|
|
|
const RulesetToUse = InstanceManager({ imex: "CANADA", rome: "US", promanager: "US" });
|
|
|
|
if (RulesetToUse === "CANADA") {
|
|
if (federal_tax.getAmount() > 0) {
|
|
if (qbo) {
|
|
// do qbo
|
|
} else {
|
|
InvoiceLineAdd.push({
|
|
ItemRef: {
|
|
FullName: bodyshop.md_responsibility_centers.taxes.federal.accountitem
|
|
},
|
|
Desc: bodyshop.md_responsibility_centers.taxes.federal.accountdesc,
|
|
Amount: federal_tax.toFormat(DineroQbFormat)
|
|
});
|
|
}
|
|
}
|
|
|
|
if (state_tax.getAmount() > 0) {
|
|
if (qbo) {
|
|
// do qbo
|
|
} else {
|
|
InvoiceLineAdd.push({
|
|
ItemRef: {
|
|
FullName: bodyshop.md_responsibility_centers.taxes.state.accountitem
|
|
},
|
|
Desc: bodyshop.md_responsibility_centers.taxes.state.accountdesc,
|
|
Amount: state_tax.toFormat(DineroQbFormat)
|
|
});
|
|
}
|
|
}
|
|
|
|
if (local_tax.getAmount() > 0) {
|
|
if (qbo) {
|
|
// do qbo
|
|
} else {
|
|
InvoiceLineAdd.push({
|
|
ItemRef: {
|
|
FullName: bodyshop.md_responsibility_centers.taxes.local.accountitem
|
|
},
|
|
Desc: bodyshop.md_responsibility_centers.taxes.local.accountdesc,
|
|
Amount: local_tax.toFormat(DineroQbFormat)
|
|
});
|
|
}
|
|
}
|
|
|
|
//Region Specific
|
|
const { ca_bc_pvrt } = jobs_by_pk;
|
|
if (ca_bc_pvrt) {
|
|
if (qbo) {
|
|
InvoiceLineAdd.push({
|
|
DetailType: "SalesItemLineDetail",
|
|
Amount: Dinero({ amount: (ca_bc_pvrt || 0) * 100 }).toFormat(DineroQbFormat),
|
|
SalesItemLineDetail: {
|
|
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
|
|
ItemRef: {
|
|
value: items["PVRT"]
|
|
},
|
|
Qty: 1,
|
|
TaxCodeRef: {
|
|
value:
|
|
taxCodes[
|
|
findTaxCode(
|
|
{
|
|
local: false,
|
|
federal: true,
|
|
state: false
|
|
},
|
|
bodyshop.md_responsibility_centers.sales_tax_codes
|
|
)
|
|
]
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
InvoiceLineAdd.push({
|
|
ItemRef: {
|
|
FullName: bodyshop.md_responsibility_centers.taxes.state.accountitem
|
|
},
|
|
Desc: "PVRT",
|
|
Amount: Dinero({ amount: (ca_bc_pvrt || 0) * 100 }).toFormat(DineroQbFormat)
|
|
});
|
|
}
|
|
}
|
|
|
|
//QB USA with GST and PST
|
|
//This was required for the No. 1 Collision Group.
|
|
if (
|
|
bodyshop.accountingconfig &&
|
|
bodyshop.accountingconfig.qbo &&
|
|
bodyshop.accountingconfig.qbo_usa &&
|
|
bodyshop.region_config.includes("CA_")
|
|
) {
|
|
InvoiceLineAdd.push({
|
|
DetailType: "SalesItemLineDetail",
|
|
Amount: Dinero(jobs_by_pk.job_totals.totals.federal_tax).toFormat(DineroQbFormat),
|
|
SalesItemLineDetail: {
|
|
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
|
|
ItemRef: {
|
|
value: items[bodyshop.md_responsibility_centers.taxes.federal.accountitem]
|
|
},
|
|
Qty: 1
|
|
}
|
|
});
|
|
InvoiceLineAdd.push({
|
|
DetailType: "SalesItemLineDetail",
|
|
Amount: Dinero(jobs_by_pk.job_totals.totals.state_tax).toFormat(DineroQbFormat),
|
|
SalesItemLineDetail: {
|
|
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
|
|
ItemRef: {
|
|
value: items[bodyshop.md_responsibility_centers.taxes.state.accountitem]
|
|
},
|
|
Qty: 1
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
//Handle insurance profile adjustments for Parts
|
|
Object.keys(job_totals.parts.adjustments).forEach((key) => {
|
|
if (qbo) {
|
|
//Going to always assume that we need to apply GST and PST for labor.
|
|
const taxAccountCode = findTaxCode(
|
|
{
|
|
local: false,
|
|
federal: process.env.COUNTRY === "USA" ? false : true,
|
|
state: jobs_by_pk.state_tax_rate === 0 ? false : true
|
|
},
|
|
bodyshop.md_responsibility_centers.sales_tax_codes
|
|
);
|
|
const account = responsibilityCenters.profits.find(
|
|
(c) => c.name === responsibilityCenters.defaults.profits[key]
|
|
);
|
|
const QboTaxId =
|
|
process.env.COUNTRY === "USA"
|
|
? CheckQBOUSATaxID({
|
|
// jobline: jobline,
|
|
job: jobs_by_pk,
|
|
type: "storage"
|
|
})
|
|
: taxCodes[taxAccountCode];
|
|
InvoiceLineAdd.push({
|
|
DetailType: "SalesItemLineDetail",
|
|
Amount: Dinero(job_totals.parts.adjustments[key]).toFormat(DineroQbFormat),
|
|
Description: `${account.accountdesc} - Adjustment`,
|
|
SalesItemLineDetail: {
|
|
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
|
|
ItemRef: {
|
|
value: items[account.accountitem]
|
|
},
|
|
TaxCodeRef: {
|
|
value: QboTaxId
|
|
},
|
|
Qty: 1
|
|
}
|
|
});
|
|
} else {
|
|
InvoiceLineAdd.push({
|
|
ItemRef: {
|
|
FullName: responsibilityCenters.profits.find((c) => c.name === responsibilityCenters.defaults.profits[key])
|
|
.accountitem
|
|
},
|
|
Desc: "Storage",
|
|
Quantity: 1,
|
|
Amount: Dinero(job_totals.parts.adjustments[key]).toFormat(DineroQbFormat),
|
|
SalesTaxCodeRef: {
|
|
FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON"
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
//Handle insurance profile adjustments for Labor and Materials
|
|
Object.keys(job_totals.rates).forEach((key) => {
|
|
if (
|
|
job_totals.rates[key] &&
|
|
job_totals.rates[key].adjustment &&
|
|
Dinero(job_totals.rates[key].adjustment).isZero() === false
|
|
) {
|
|
if (qbo) {
|
|
//Going to always assume that we need to apply GST and PST for labor.
|
|
const taxAccountCode = findTaxCode(
|
|
{
|
|
local: false,
|
|
federal: process.env.COUNTRY === "USA" ? false : true,
|
|
state: jobs_by_pk.state_tax_rate === 0 ? false : true
|
|
},
|
|
bodyshop.md_responsibility_centers.sales_tax_codes
|
|
);
|
|
const account = responsibilityCenters.profits.find(
|
|
(c) => c.name === responsibilityCenters.defaults.profits[key.toUpperCase()]
|
|
);
|
|
const QboTaxId =
|
|
process.env.COUNTRY === "USA"
|
|
? CheckQBOUSATaxID({
|
|
// jobline: jobline,
|
|
job: jobs_by_pk,
|
|
type: "storage"
|
|
})
|
|
: taxCodes[taxAccountCode];
|
|
InvoiceLineAdd.push({
|
|
DetailType: "SalesItemLineDetail",
|
|
Amount: Dinero(job_totals.rates[key].adjustment).toFormat(DineroQbFormat),
|
|
Description: `${account.accountdesc} - Adjustment`,
|
|
SalesItemLineDetail: {
|
|
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
|
|
ItemRef: {
|
|
value: items[account.accountitem]
|
|
},
|
|
TaxCodeRef: {
|
|
value: QboTaxId
|
|
},
|
|
Qty: 1
|
|
}
|
|
});
|
|
} else {
|
|
InvoiceLineAdd.push({
|
|
ItemRef: {
|
|
FullName: responsibilityCenters.profits.find(
|
|
(c) => c.name === responsibilityCenters.defaults.profits[key.toUpperCase()]
|
|
).accountitem
|
|
},
|
|
Desc: "Storage",
|
|
Quantity: 1,
|
|
Amount: Dinero(job_totals.rates[key].adjustment).toFormat(DineroQbFormat),
|
|
SalesTaxCodeRef: {
|
|
FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON"
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
const QboTaxId =
|
|
process.env.COUNTRY === "USA"
|
|
? CheckQBOUSATaxID({
|
|
// jobline: jobline,
|
|
type: "adjustment",
|
|
job: jobs_by_pk
|
|
})
|
|
: taxCodes[taxAccountCode];
|
|
for (let tyCounter = 1; tyCounter <= 5; tyCounter++) {
|
|
const taxAmount = Dinero(job_totals.totals.us_sales_tax_breakdown[`ty${tyCounter}Tax`]);
|
|
//console.log(`Tax ${tyCounter}`, taxAmount.toFormat());
|
|
if (taxAmount.getAmount() > 0) {
|
|
if (qbo) {
|
|
InvoiceLineAdd.push({
|
|
DetailType: "SalesItemLineDetail",
|
|
Amount: taxAmount.toFormat(DineroQbFormat),
|
|
SalesItemLineDetail: {
|
|
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
|
|
ItemRef: {
|
|
value: items[responsibilityCenters.taxes[`tax_ty${tyCounter}`].accountitem]
|
|
},
|
|
TaxCodeRef: {
|
|
value: QboTaxId
|
|
},
|
|
Qty: 1
|
|
}
|
|
});
|
|
} else {
|
|
InvoiceLineAdd.push({
|
|
ItemRef: {
|
|
FullName: bodyshop.md_responsibility_centers.taxes[`tax_ty${tyCounter}`].accountitem
|
|
},
|
|
Desc: bodyshop.md_responsibility_centers.taxes[`tax_ty${tyCounter}`].accountdesc,
|
|
Amount: taxAmount.toFormat(DineroQbFormat)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (jobs_by_pk.job_totals.totals.ttl_tax_adjustment) {
|
|
// Do not need to check for ImEX or Rome because ImEX uses a different totals calculation that will never set this field.
|
|
if (qbo) {
|
|
const taxAccountCode = findTaxCode(
|
|
{
|
|
local: false,
|
|
federal: InstanceManager({ imex: true, rome: false }),
|
|
state: jobs_by_pk.tax_lbr_rt === 0 ? false : true
|
|
},
|
|
bodyshop.md_responsibility_centers.sales_tax_codes
|
|
);
|
|
|
|
const QboTaxId = InstanceManager({
|
|
imex: taxCodes[taxAccountCode],
|
|
rome: CheckQBOUSATaxID({
|
|
// jobline: jobline,
|
|
type: "adjustment",
|
|
job: jobs_by_pk
|
|
})
|
|
});
|
|
|
|
InvoiceLineAdd.push({
|
|
DetailType: "SalesItemLineDetail",
|
|
Amount: Dinero(jobs_by_pk.job_totals.totals.ttl_tax_adjustment).toFormat(DineroQbFormat),
|
|
SalesItemLineDetail: {
|
|
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
|
|
ItemRef: {
|
|
value: items[responsibilityCenters.ttl_tax_adjustment.accountitem]
|
|
},
|
|
TaxCodeRef: {
|
|
value: QboTaxId
|
|
},
|
|
Qty: 1
|
|
}
|
|
});
|
|
} else {
|
|
InvoiceLineAdd.push({
|
|
ItemRef: {
|
|
FullName: responsibilityCenters.ttl_tax_adjustment.accountitem
|
|
},
|
|
Desc: "Tax Adjustment",
|
|
Quantity: 1,
|
|
Amount: Dinero(jobs_by_pk.job_totals.totals.ttl_tax_adjustment).toFormat(DineroQbFormat),
|
|
SalesTaxCodeRef: InstanceManager({
|
|
imex: {
|
|
FullName: "E"
|
|
},
|
|
rome: {
|
|
FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON"
|
|
}
|
|
})
|
|
});
|
|
}
|
|
}
|
|
if (!qbo && InvoiceLineAdd.length === 0) {
|
|
//Handle the scenario where there is a $0 sale invoice.
|
|
InvoiceLineAdd.push({
|
|
Desc: "No estimate lines."
|
|
});
|
|
}
|
|
|
|
//Check if there are multiple payers. If there are, add a deduction line and make sure we create new invoices.
|
|
|
|
if (jobs_by_pk.qb_multiple_payers && jobs_by_pk.qb_multiple_payers.length > 0) {
|
|
jobs_by_pk.qb_multiple_payers.forEach((payer) => {
|
|
if (qbo) {
|
|
InvoiceLineAdd.push({
|
|
DetailType: "SalesItemLineDetail",
|
|
Amount: Dinero({
|
|
amount: Math.round((payer.amount || 0) * 100) * -1
|
|
}).toFormat(DineroQbFormat),
|
|
SalesItemLineDetail: {
|
|
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
|
|
ItemRef: {
|
|
value: items[responsibilityCenters.qb_multiple_payers?.accountitem]
|
|
},
|
|
Qty: 1,
|
|
TaxCodeRef: {
|
|
value:
|
|
taxCodes[
|
|
findTaxCode(
|
|
{
|
|
local: false,
|
|
federal: false,
|
|
state: false
|
|
},
|
|
bodyshop.md_responsibility_centers.sales_tax_codes
|
|
)
|
|
]
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
InvoiceLineAdd.push({
|
|
ItemRef: {
|
|
FullName: responsibilityCenters.qb_multiple_payers?.accountitem
|
|
},
|
|
Desc: `${payer.name} Liability`,
|
|
Amount: Dinero({
|
|
amount: Math.round((payer.amount || 0) * 100) * -1
|
|
}).toFormat(DineroQbFormat)
|
|
});
|
|
}
|
|
});
|
|
}
|
|
return InvoiceLineAdd;
|
|
};
|
|
|
|
const findTaxCode = ({ local, state, federal }, taxcode) => {
|
|
const t = taxcode.filter((t) => !!t.local === !!local && !!t.state === !!state && !!t.federal === !!federal);
|
|
if (t.length === 1) {
|
|
return t[0].code;
|
|
} else if (t.length > 1) {
|
|
return "Multiple Tax Codes Match";
|
|
} else {
|
|
return "";
|
|
}
|
|
};
|
|
exports.findTaxCode = findTaxCode;
|
|
|
|
exports.createMultiQbPayerLines = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes, classes, payer }) {
|
|
const InvoiceLineAdd = [];
|
|
const responsibilityCenters = bodyshop.md_responsibility_centers;
|
|
|
|
const invoiceLineHash = {}; //The hash of cost and profit centers based on the center name.
|
|
|
|
if (qbo) {
|
|
//Going to always assume that we need to apply GST and PST for labor.
|
|
const taxAccountCode = findTaxCode(
|
|
{
|
|
local: false,
|
|
federal: false,
|
|
state: false
|
|
},
|
|
bodyshop.md_responsibility_centers.sales_tax_codes
|
|
);
|
|
const QboTaxId = taxCodes[taxAccountCode];
|
|
InvoiceLineAdd.push({
|
|
DetailType: "SalesItemLineDetail",
|
|
Amount: Dinero({
|
|
amount: Math.round((payer.amount || 0) * 100)
|
|
}).toFormat(DineroQbFormat),
|
|
SalesItemLineDetail: {
|
|
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
|
|
ItemRef: {
|
|
value: items[responsibilityCenters.qb_multiple_payers?.accountitem]
|
|
},
|
|
TaxCodeRef: {
|
|
value: QboTaxId
|
|
},
|
|
Qty: 1
|
|
}
|
|
});
|
|
} else {
|
|
InvoiceLineAdd.push({
|
|
ItemRef: {
|
|
FullName: responsibilityCenters.qb_multiple_payers?.accountitem
|
|
},
|
|
Desc: `${payer.name} Liability`,
|
|
Quantity: 1,
|
|
Amount: Dinero({
|
|
amount: Math.round((payer.amount || 0) * 100)
|
|
}).toFormat(DineroQbFormat),
|
|
SalesTaxCodeRef: InstanceManager({
|
|
imex: {
|
|
FullName: "E"
|
|
},
|
|
rome: {
|
|
FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON"
|
|
}
|
|
})
|
|
});
|
|
}
|
|
|
|
return InvoiceLineAdd;
|
|
};
|
|
|
|
function checkStateTax(jobline, jobs_by_pk) {
|
|
const isPaintOrShopMat = jobline.db_ref === "936008" || jobline.db_ref === "936007";
|
|
|
|
if (isPaintOrShopMat) {
|
|
if (jobline.db_ref === "936008") {
|
|
if (jobs_by_pk.tax_paint_mat_rt === 0) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
if (jobline.db_ref === "936007") {
|
|
if (jobs_by_pk.tax_shop_mat_rt === 0) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
const isAdditionalCost =
|
|
(jobline.lbr_op === "OP13" ||
|
|
(jobline.lbr_op === "OP14" && jobline.act_price > 0 && jobline.mod_lb_hrs === 0) ||
|
|
(jobline.db_ref && jobline.db_ref.startsWith("9360")) ||
|
|
(jobline.db_ref && jobline.db_ref.startsWith("90051"))) &&
|
|
!isPaintOrShopMat;
|
|
|
|
if (!jobline.part_type && isAdditionalCost) {
|
|
if (jobs_by_pk.tax_lbr_rt === 0) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (jobline.tax_part === false) {
|
|
return false;
|
|
} else {
|
|
if (jobline.part_type) {
|
|
if (
|
|
!jobs_by_pk.parts_tax_rates[`${jobline.part_type.toUpperCase()}`] ||
|
|
jobs_by_pk.parts_tax_rates[`${jobline.part_type.toUpperCase()}`].prt_tax_in === false ||
|
|
jobs_by_pk.parts_tax_rates[`${jobline.part_type.toUpperCase()}`].prt_tax_rt === 0
|
|
) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
} else {
|
|
if (jobs_by_pk.tax_lbr_rt === 0) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function CheckQBOUSATaxID({ jobline, job, type }) {
|
|
//Replacing this to be all non-taxable items with the refactor of parts tax rates.
|
|
return "NON";
|
|
// if (type === "labor") {
|
|
// return jobline.lbr_tax ? "TAX" : "NON";
|
|
// } else if (type === "part") {
|
|
// return jobline.tax_part ? "TAX" : "NON";
|
|
// } else if (type === "materials") {
|
|
// return job.tax_paint_mat_rt > 0 ? "TAX" : "NON";
|
|
// } else if (type === " towing") {
|
|
// return true ? "TAX" : "NON";
|
|
// } else if (type === "adjustment") {
|
|
// return false ? "TAX" : "NON";
|
|
// } else {
|
|
// throw new Error(`Unknown type to calculate tax id: ${type} `);
|
|
// }
|
|
}
|