@synotech/utils
Version:
a collection of utilities for internal use
368 lines (338 loc) • 9.63 kB
text/typescript
import _ from 'lodash';
import { sleep } from './sleep';
export type InvoiceTotalsDoc = {
taxableSubTotal: number;
adjustment: number;
shipping: number;
discountType?: 'exact' | 'percent';
paid?: number;
discountPercentage?: number;
discountAmount?: number;
number?: number;
objectId?: string;
};
export type InvoiceTotalsTax = {
name: string;
value: number;
category?: string;
};
/**
* Calculates the invoice totals based on the provided parameters.
* @module invoiceTotals
* @param doc - The invoice totals document.
* @param docProducts - An array of products in the invoice.
* @param taxes - An array of taxes applied to the invoice.
* @returns A Promise that resolves to an object containing the calculated invoice totals.
*
* @example
* const doc = {
* number: 10001,
* objectId: 'testDocObject',
* discountAmount: 0,
* discountPercentage: 0,
* taxableSubTotal: 438.5,
* paid: 0,
* adjustment: 0,
* shipping: 0,
* }
*
* const docProducts = [{ qty: 2, price: 100, tax: 15 }]
* const taxes = [
* {
* name: 'VAT',
* category: 'sales',
* value: 15,
* },
* {
* name: 'None',
* category: 'sales',
* value: 0,
* },
* ]
*
*
*/
export async function invoiceTotals(
doc: InvoiceTotalsDoc,
docProducts: any[],
taxes: InvoiceTotalsTax[]
): Promise<any> {
docProducts =
docProducts && docProducts?.length > 0
? docProducts?.map((item: any) => ({
qty: item?.qty || 1,
price: item?.price || 0,
discount: item?.discount || 0,
tax: item?.tax || 0,
}))
: [];
taxes = taxes
? taxes.map((t: any) => ({ name: t?.name || '', value: t?.value || 0 }))
: [];
if (taxes.length === 0) {
taxes = [{ name: 'None', value: 0 }];
}
let discountType = doc?.discountType ?? 'exact';
let discountPercentage = doc?.discountPercentage ?? 0;
let discountAmount = doc?.discountAmount ?? 0;
let shipping = doc?.shipping || 0;
let adjustment = doc?.adjustment || 0;
let paid = doc?.paid ?? 0;
for (let i in docProducts) {
if (docProducts.hasOwnProperty(i)) {
// check each product for ~ qty
if (!docProducts[i].hasOwnProperty('qty')) {
docProducts[i].qty = 1;
}
// check each product for ~ price
if (!docProducts[i].hasOwnProperty('price')) {
docProducts[i].price = 0;
}
// check each product for ~ discount
if (!docProducts[i].hasOwnProperty('discount')) {
docProducts[i].discount = 0;
}
// check each product for ~ tax
if (!docProducts[i].hasOwnProperty('tax')) {
docProducts[i].tax = '0';
} else {
docProducts[i].tax = docProducts[i]?.tax?.toString();
}
}
}
let groupedProductsByTax: any;
let taxArray: string[] = [];
let rowSubtotal: { rate: string; total: number }[] = [];
let taxGroupSubtotal: any[] = [];
let taxableSubTotal = 0;
let subTotal = 0;
let taxTotalsObj: { rate: number; total: number }[] = [];
let unDiscountedTaxTotals: number[] = [];
let getProductsByTax = new Promise(async (resolve) => {
groupedProductsByTax = _.groupBy(docProducts, function (product) {
return product.tax;
});
await sleep(1);
resolve({ groupedProductsByTax: groupedProductsByTax });
});
let getProductsTaxToArray = new Promise(async (resolve) => {
for (let tax in groupedProductsByTax) {
if (groupedProductsByTax.hasOwnProperty(tax)) {
taxArray.push(tax);
}
}
await sleep(1);
resolve({ taxArray: taxArray });
});
let getProductsTotalByTaxGroup = new Promise(async (resolve) => {
taxArray.forEach(function callback(tax) {
groupedProductsByTax[tax].forEach(function (groupSubtotal: {
price: number;
discount: number;
qty: number;
}) {
rowSubtotal.push({
rate: tax,
total:
(groupSubtotal.price - groupSubtotal.discount) *
groupSubtotal.qty,
});
});
});
await sleep(1);
resolve({ rowSubtotal: rowSubtotal });
});
let getTaxGroupSubtotal = new Promise(async (resolve) => {
rowSubtotal.reduce(function (res: any, value) {
if (!res[value.rate]) {
res[value.rate] = {
total: 0,
rate: value.rate,
};
taxGroupSubtotal.push(res[value.rate]);
}
res[value.rate].total += value.total;
return res;
}, {});
await sleep(1);
resolve({ taxGroupSubtotal: taxGroupSubtotal });
});
let getSubtotal = new Promise(function (resolve) {
for (let key in taxGroupSubtotal) {
if (taxGroupSubtotal.hasOwnProperty(key)) {
subTotal +=
taxGroupSubtotal[key][
Object.keys(taxGroupSubtotal[key])[0]
];
}
}
setTimeout(resolve, 5, { subTotal: subTotal });
});
let getDiscountAmount = new Promise(function (resolve) {
if (discountType === 'exact') {
// @ts-ignore
discountAmount = parseFloat(discountAmount).toFixed(2);
} else if (discountType === 'percent') {
// @ts-ignore
let percentage = (parseFloat(discountPercentage) / 100) * subTotal;
// @ts-ignore
discountAmount = parseFloat(percentage.toString()).toFixed(2);
} else {
discountAmount = 0;
}
setTimeout(resolve, 6, { discountAmount: discountAmount });
});
let getTaxTotalsObj = new Promise(async (resolve) => {
for (let key in taxGroupSubtotal) {
if (taxGroupSubtotal.hasOwnProperty(key)) {
let tax =
(parseFloat(taxArray[key]) / 100) *
taxGroupSubtotal[key][
Object.keys(taxGroupSubtotal[key])[0]
];
taxTotalsObj.push({
rate: parseFloat(taxArray[key]),
total: tax,
});
}
}
await sleep(1);
resolve({ taxTotalsObj: taxTotalsObj });
});
let addNameToTaxGroupSubtotal = new Promise((resolve) => {
for (let i in taxGroupSubtotal) {
if (taxGroupSubtotal.hasOwnProperty(i)) {
let getRate = _.filter(taxes, {
value: parseFloat(taxGroupSubtotal[i].rate),
});
taxGroupSubtotal[i].name = getRate[0].name;
}
}
resolve(taxGroupSubtotal);
});
let getUnDiscountedTaxTotals = new Promise(async (resolve) => {
taxTotalsObj.forEach(function (tax) {
unDiscountedTaxTotals.push(tax.total);
});
await sleep(1);
resolve({ unDiscountedTaxTotals: unDiscountedTaxTotals });
});
return Promise.all([
getProductsByTax,
getProductsTaxToArray,
getProductsTotalByTaxGroup,
getTaxGroupSubtotal,
getSubtotal,
getDiscountAmount,
getTaxTotalsObj,
addNameToTaxGroupSubtotal,
getUnDiscountedTaxTotals,
]).then(() => {
function applyDiscountOnSalesTax(
arr: string | any[],
unDiscounted: number,
discounted: number
) {
let percentage = 100 - (discounted / unDiscounted) * 100; // get 25%
let discountTaxGroup: { rate: string; total: number; name: any }[] =
[];
let _loop = (i: string | number) => {
// @ts-ignore
let valueInString = arr[i];
let num = parseFloat(valueInString);
let taxRate = num - num * (percentage / 100);
let taxName = _.find(taxes, function (o) {
// @ts-ignore
return o.value === parseFloat(taxArray[i]);
});
discountTaxGroup.push({
// @ts-ignore
rate: taxArray[i],
total: taxRate,
name: (taxName && taxName.name) || '',
});
};
for (let i = 0; i < arr.length; i++) {
_loop(i);
}
return discountTaxGroup;
}
taxableSubTotal =
parseFloat(String(subTotal)) - parseFloat(String(discountAmount));
let discountedSaleTax = applyDiscountOnSalesTax(
unDiscountedTaxTotals,
subTotal,
taxableSubTotal
);
let discountedSaleTaxTotal = _.sumBy(discountedSaleTax, (o) => {
return o.total;
});
function percentageDifference() {
return (
100 -
((parseFloat(String(subTotal)) -
parseFloat(String(discountAmount))) /
parseFloat(String(subTotal))) *
100
);
}
function minusPercent(n: number, p: number) {
return n - n * (p / 100);
}
for (let i in taxGroupSubtotal) {
if (taxGroupSubtotal.hasOwnProperty(i)) {
// @ts-ignore
taxGroupSubtotal[i].total = minusPercent(
taxGroupSubtotal[i].total,
percentageDifference()
);
}
}
// @ts-ignore
let total = _.sum([
parseFloat(String(taxableSubTotal)),
parseFloat(String(discountedSaleTaxTotal)),
// @ts-ignore
parseFloat(shipping),
// @ts-ignore
parseFloat(adjustment),
]);
console.log(`
--------------------------------------
--------------------------------------
TOTALS - ${doc?.number} - ~ : ${doc?.objectId} ---------
--------------------------------------
--------------------------------------
subtotal : -------------------------> ${subTotal}
discount type : --------------------> ${discountType}
discount discountPercentage : ------> ${
percentageDifference() ? percentageDifference() : 0
}
discount amount : ------------------> ${discountAmount}
taxable subtotal : -----------------> ${taxableSubTotal}
amount to effect discount : --------> ${JSON.stringify(taxGroupSubtotal)}
sales tax (no discount) : ----------> ${_.sumBy(unDiscountedTaxTotals)}
sales tax (after discount) : -------> ${discountedSaleTaxTotal || 0}
sales tax (array grouped) :---------> ${JSON.stringify(discountedSaleTax)}
shipping : -------------------------> ${shipping}
adjustment : -----------------------> ${adjustment}
-------------------------------------
-------------------------------------
TOTAL : ------------------>>>>>>>>>>> ${total || 0}
`);
return {
discountType,
discountAmount: parseFloat(discountAmount.toString()),
discountPercentage,
taxGroupSubtotal,
taxGroupTotal: discountedSaleTax,
shipping: shipping,
adjustment: adjustment,
taxableSubTotal,
taxesTotal: discountedSaleTaxTotal,
subTotal: subTotal,
total: total || 0,
totalDue: total ? Number(total) - Number(paid) : 0,
};
});
}