UNPKG

@synotech/utils

Version:

a collection of utilities for internal use

368 lines (338 loc) 9.63 kB
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, }; }); }