Files
bigcapital/packages/webapp/src/containers/Entries/utils.tsx
2023-09-11 23:17:27 +02:00

269 lines
6.8 KiB
TypeScript

// @ts-nocheck
import React, { useCallback } from 'react';
import * as R from 'ramda';
import { sumBy, isEmpty, last, keyBy } from 'lodash';
import { useItem } from '@/hooks/query';
import {
toSafeNumber,
saveInvoke,
compose,
updateTableCell,
updateAutoAddNewLine,
orderingLinesIndexes,
updateTableRow,
} from '@/utils';
import { useItemEntriesTableContext } from './ItemEntriesTableProvider';
export const ITEM_TYPE = {
SELLABLE: 'SELLABLE',
PURCHASABLE: 'PURCHASABLE',
};
/**
* Retrieve item entry total from the given rate, quantity and discount.
* @param {number} rate
* @param {number} quantity
* @param {number} discount
* @return {number}
*/
export const calcItemEntryTotal = (discount, quantity, rate) => {
const _quantity = toSafeNumber(quantity);
const _rate = toSafeNumber(rate);
const _discount = toSafeNumber(discount);
return _quantity * _rate - (_quantity * _rate * _discount) / 100;
};
/**
* Updates the items entries total.
*/
export function updateItemsEntriesTotal(rows) {
return rows.map((row) => ({
...row,
amount: calcItemEntryTotal(row.discount, row.quantity, row.rate),
}));
}
/**
* Retrieve total of the given items entries.
*/
export function getEntriesTotal(entries) {
return sumBy(entries, 'amount');
}
/**
* Ensure the given entries have enough empty line on the last.
* @param {Object} defaultEntry - Default entry.
* @param {Array} entries - Entries.
* @return {Array}
*/
export const ensureEntriesHaveEmptyLine = R.curry((defaultEntry, entries) => {
const lastEntry = last(entries);
if (isEmpty(lastEntry.account_id) || isEmpty(lastEntry.amount)) {
return [...entries, defaultEntry];
}
return entries;
});
/**
* Disable landed cost checkbox once the item type is not service or non-inventorty.
* @returns {boolean}
*/
export const isLandedCostDisabled = (item) =>
['service', 'non-inventory'].indexOf(item.type) === -1;
/**
* Handle fetch item row details and retrieves the new table row.
*/
export function useFetchItemRow({ landedCost, itemType, notifyNewRow }) {
const [itemRow, setItemRow] = React.useState(null);
const [cellsLoading, setCellsLoading] = React.useState(null);
// Fetches the item details.
const {
data: item,
isFetching: isItemFetching,
isSuccess: isItemSuccess,
} = useItem(itemRow && itemRow.itemId, {
enabled: !!(itemRow && itemRow.itemId),
});
// Once the item start loading give the table cells loading state.
React.useEffect(() => {
if (itemRow && isItemFetching) {
setCellsLoading([
[itemRow.rowIndex, 'rate'],
[itemRow.rowIndex, 'description'],
[itemRow.rowIndex, 'quantity'],
[itemRow.rowIndex, 'discount'],
]);
} else {
setCellsLoading(null);
}
}, [isItemFetching, setCellsLoading, itemRow]);
// Once the item selected and fetched set the initial details to the table.
React.useEffect(() => {
if (isItemSuccess && item && itemRow) {
const { rowIndex } = itemRow;
const price =
itemType === ITEM_TYPE.PURCHASABLE ? item.cost_price : item.sell_price;
const description =
itemType === ITEM_TYPE.PURCHASABLE
? item.purchase_description
: item.sell_description;
// Detarmines whether the landed cost checkbox should be disabled.
const landedCostDisabled = isLandedCostDisabled(item);
// The new row.
const newRow = {
rate: price,
description,
quantity: 1,
...(landedCost
? {
landed_cost: false,
landed_cost_disabled: landedCostDisabled,
}
: {}),
};
setItemRow(null);
saveInvoke(notifyNewRow, newRow, rowIndex);
}
}, [item, itemRow, itemType, isItemSuccess, landedCost, notifyNewRow]);
return {
isItemFetching,
isItemSuccess,
item,
setItemRow,
itemRow,
cellsLoading,
};
}
/**
* Compose table rows when edit specific row index of table rows.
*/
export const composeRowsOnEditCell = R.curry(
(rowIndex, columnId, value, defaultEntry, rows) => {
return compose()(rows);
},
);
/**
* Compose table rows when insert a new row to table rows.
*/
export const composeRowsOnNewRow = R.curry((rowIndex, newRow, rows) => {
return compose(
orderingLinesIndexes,
updateItemsEntriesTotal,
updateTableRow(rowIndex, newRow),
)(rows);
});
/**
* Associate tax rate to entries.
*/
export const assignEntriesTaxRate = R.curry((taxRates, entries) => {
const taxRatesById = keyBy(taxRates, 'id');
return entries.map((entry) => {
const taxRate = taxRatesById[entry.tax_rate_id];
return {
...entry,
tax_rate: taxRate?.rate || 0,
};
});
});
/**
* Assign tax amount to entries.
* @param {boolean} isInclusiveTax
* @param entries
* @returns
*/
export const assignEntriesTaxAmount = R.curry(
(isInclusiveTax: boolean, entries) => {
return entries.map((entry) => {
const taxAmount = isInclusiveTax
? getInclusiveTaxAmount(entry.amount, entry.tax_rate)
: getExlusiveTaxAmount(entry.amount, entry.tax_rate);
return {
...entry,
tax_amount: taxAmount,
};
});
},
);
/**
* Get inclusive tax amount.
* @param {number} amount
* @param {number} taxRate
* @returns {number}
*/
export const getInclusiveTaxAmount = (amount: number, taxRate: number) => {
return (amount * taxRate) / (100 + taxRate);
};
/**
* Get exclusive tax amount.
* @param {number} amount
* @param {number} taxRate
* @returns {number}
*/
export const getExlusiveTaxAmount = (amount: number, taxRate: number) => {
return (amount * taxRate) / 100;
};
/**
* Compose rows when edit a table cell.
* @returns {Function}
*/
export const useComposeRowsOnEditTableCell = () => {
const { taxRates, isInclusiveTax, localValue, defaultEntry } =
useItemEntriesTableContext();
return useCallback(
(rowIndex, columnId, value) => {
return R.compose(
assignEntriesTaxAmount(isInclusiveTax),
assignEntriesTaxRate(taxRates),
orderingLinesIndexes,
updateAutoAddNewLine(defaultEntry, ['item_id']),
updateItemsEntriesTotal,
updateTableCell(rowIndex, columnId, value),
)(localValue);
},
[taxRates, isInclusiveTax, localValue, defaultEntry],
);
};
/**
* Compose rows when remove a table row.
* @returns {Function}
*/
export const useComposeRowsOnRemoveTableRow = () => {
const { minLinesNumber, defaultEntry, localValue } =
useItemEntriesTableContext();
return useCallback(
(rowIndex) => {
return compose(
// Ensure minimum lines count.
updateMinEntriesLines(minLinesNumber, defaultEntry),
// Remove the line by the given index.
updateRemoveLineByIndex(rowIndex),
)(localValue);
},
[minLinesNumber, defaultEntry, localValue],
);
};