Skip to content

Commit

Permalink
feat(webapp): assign tax rates to bill entries
Browse files Browse the repository at this point in the history
  • Loading branch information
abouolia committed Oct 5, 2023
1 parent 372baf5 commit 06ee20c
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 46 deletions.
36 changes: 34 additions & 2 deletions packages/webapp/src/containers/Entries/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// @ts-nocheck
import React, { useCallback } from 'react';
import * as R from 'ramda';
import { sumBy, isEmpty, last, keyBy } from 'lodash';

import { sumBy, isEmpty, last, keyBy, groupBy } from 'lodash';
import { useItem } from '@/hooks/query';
import {
toSafeNumber,
Expand All @@ -12,6 +11,7 @@ import {
updateAutoAddNewLine,
orderingLinesIndexes,
updateTableRow,
formattedAmount,
} from '@/utils';
import { useItemEntriesTableContext } from './ItemEntriesTableProvider';

Expand Down Expand Up @@ -116,6 +116,11 @@ export function useFetchItemRow({ landedCost, itemType, notifyNewRow }) {
? item.purchase_description
: item.sell_description;

const taxRateId =
itemType === ITEM_TYPE.PURCHASABLE
? item.purchase_tax_rate_id
: item.sell_tax_rate_id;

// Detarmines whether the landed cost checkbox should be disabled.
const landedCostDisabled = isLandedCostDisabled(item);

Expand All @@ -130,6 +135,7 @@ export function useFetchItemRow({ landedCost, itemType, notifyNewRow }) {
landed_cost_disabled: landedCostDisabled,
}
: {}),
taxRateId,
};
setItemRow(null);
saveInvoke(notifyNewRow, newRow, rowIndex);
Expand Down Expand Up @@ -266,3 +272,29 @@ export const useComposeRowsOnRemoveTableRow = () => {
[minLinesNumber, defaultEntry, localValue],
);
};

/**
* Retrieves the aggregate tax rates from the given item entries.
*/
export const aggregateItemEntriesTaxRates = R.curry((taxRates, entries) => {
const taxRatesById = keyBy(taxRates, 'id');

// Calculate the total tax amount of invoice entries.
const filteredEntries = entries.filter((e) => e.tax_rate_id);
const groupedTaxRates = groupBy(filteredEntries, 'tax_rate_id');

return Object.keys(groupedTaxRates).map((taxRateId) => {
const taxRate = taxRatesById[taxRateId];
const taxRates = groupedTaxRates[taxRateId];
const totalTaxAmount = sumBy(taxRates, 'tax_amount');
const taxAmountFormatted = formattedAmount(totalTaxAmount, 'USD');

return {
taxRateId,
taxRate: taxRate.rate,
label: `${taxRate.name} [${taxRate.rate}%]`,
taxAmount: totalTaxAmount,
taxAmountFormatted,
};
});
});
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
// @ts-nocheck
import React from 'react';
import styled from 'styled-components';

import {
T,
TotalLines,
TotalLine,
TotalLineBorderStyle,
TotalLineTextStyle,
} from '@/components';
import { useBillTotals } from './utils';
import { useBillAggregatedTaxRates, useBillTotals } from './utils';
import { useFormikContext } from 'formik';
import { TaxType } from '@/interfaces/TaxRates';

export function BillFormFooterRight() {
const {
Expand All @@ -19,26 +18,46 @@ export function BillFormFooterRight() {
formattedPaymentTotal,
} = useBillTotals();

const {
values: { inclusive_exclusive_tax, currency_code },
} = useFormikContext();

const taxEntries = useBillAggregatedTaxRates();

return (
<BillTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
<TotalLine
title={<T id={'bill_form.label.subtotal'} />}
title={
<>
{inclusive_exclusive_tax === TaxType.Inclusive
? 'Subtotal (Tax Inclusive)'
: 'Subtotal'}
</>
}
value={formattedSubtotal}
borderStyle={TotalLineBorderStyle.None}
/>
{taxEntries.map((tax, index) => (
<TotalLine
key={index}
title={tax.label}
value={tax.taxAmountFormatted}
borderStyle={TotalLineBorderStyle.None}
/>
))}
<TotalLine
title={<T id={'bill_form.label.total'} />}
title={`Total (${currency_code})`}
value={formattedTotal}
borderStyle={TotalLineBorderStyle.SingleDark}
textStyle={TotalLineTextStyle.Bold}
/>
<TotalLine
title={<T id={'bill_form.label.total'} />}
title={'Paid Amount'}
value={formattedPaymentTotal}
borderStyle={TotalLineBorderStyle.None}
/>
<TotalLine
title={<T id={'bill_form.label.total'} />}
title={'Due Amount'}
value={formattedDueTotal}
textStyle={TotalLineTextStyle.Bold}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
useCreateBill,
useEditBill,
} from '@/hooks/query';
import { useTaxRates } from '@/hooks/query/taxRates';

const BillFormContext = createContext();

Expand Down Expand Up @@ -83,6 +84,9 @@ function BillFormProvider({ billId, ...props }) {
isSuccess: isBranchesSuccess,
} = useBranches({}, { enabled: isBranchFeatureCan });

// Fetch tax rates.
const { data: taxRates, isLoading: isTaxRatesLoading } = useTaxRates();

// Fetches the projects list.
const {
data: { projects },
Expand All @@ -103,7 +107,10 @@ function BillFormProvider({ billId, ...props }) {

// Determines whether the warehouse and branches are loading.
const isFeatureLoading =
isWarehouesLoading || isBranchesLoading || isProjectsLoading;
isWarehouesLoading ||
isBranchesLoading ||
isProjectsLoading ||
isTaxRatesLoading;

const provider = {
accounts,
Expand All @@ -113,6 +120,7 @@ function BillFormProvider({ billId, ...props }) {
warehouses,
branches,
projects,
taxRates,
submitPayload,
isNewMode,

Expand All @@ -124,6 +132,7 @@ function BillFormProvider({ billId, ...props }) {
isFeatureLoading,
isBranchesSuccess,
isWarehousesSuccess,
isTaxRatesLoading,

createBillMutate,
editBillMutate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ITEM_TYPE } from '@/containers/Entries/utils';
* Bill form body.
*/
export default function BillFormBody({ defaultBill }) {
const { items } = useBillFormContext();
const { items, taxRates } = useBillFormContext();

return (
<FastField
Expand All @@ -32,6 +32,7 @@ export default function BillFormBody({ defaultBill }) {
linesNumber={4}
currencyCode={values.currency_code}
itemType={ITEM_TYPE.PURCHASABLE}
taxRates={taxRates}
landedCost={true}
/>
)}
Expand Down
86 changes: 79 additions & 7 deletions packages/webapp/src/containers/Purchases/Bills/BillForm/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';
import moment from 'moment';
import intl from 'react-intl-universal';
import * as R from 'ramda';
import { first } from 'lodash';
import { first, chain } from 'lodash';
import { Intent } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import { AppToaster } from '@/components';
Expand All @@ -18,6 +18,7 @@ import {
updateItemsEntriesTotal,
ensureEntriesHaveEmptyLine,
assignEntriesTaxAmount,
aggregateItemEntriesTaxRates,
} from '@/containers/Entries/utils';
import { useCurrentOrganization } from '@/hooks/state';
import {
Expand Down Expand Up @@ -85,7 +86,7 @@ export const transformToEditForm = (bill) => {

return {
...transformToForm(bill, defaultBill),
inclusive_exclusive_tax: invoice.is_inclusive_tax
inclusive_exclusive_tax: bill.is_inclusive_tax
? TaxType.Inclusive
: TaxType.Exclusive,
entries,
Expand Down Expand Up @@ -234,11 +235,12 @@ export const useSetPrimaryWarehouseToForm = () => {
*/
export const useBillTotals = () => {
const {
values: { entries, currency_code: currencyCode },
values: { currency_code: currencyCode },
} = useFormikContext();

// Retrieves the bili entries total.
const total = React.useMemo(() => getEntriesTotal(entries), [entries]);
// Retrieves the bill subtotal.
const subtotal = useBillSubtotal();
const total = useBillTotal();

// Retrieves the formatted total money.
const formattedTotal = React.useMemo(
Expand All @@ -247,8 +249,8 @@ export const useBillTotals = () => {
);
// Retrieves the formatted subtotal.
const formattedSubtotal = React.useMemo(
() => formattedAmount(total, currencyCode, { money: false }),
[total, currencyCode],
() => formattedAmount(subtotal, currencyCode, { money: false }),
[subtotal, currencyCode],
);
// Retrieves the payment total.
const paymentTotal = React.useMemo(() => 0, []);
Expand Down Expand Up @@ -307,3 +309,73 @@ export const composeEntriesOnEditInclusiveTax = (
assignEntriesTaxAmount(inclusiveExclusiveTax === 'inclusive'),
)(entries);
};

/**
* Retreives the bill aggregated tax rates.
* @returns {Array}
*/
export const useBillAggregatedTaxRates = () => {
const { values } = useFormikContext();
const { taxRates } = useBillFormContext();

const aggregateTaxRates = React.useMemo(
() => aggregateItemEntriesTaxRates(taxRates),
[taxRates],
);
// Calculate the total tax amount of bill entries.
return React.useMemo(() => {
return aggregateTaxRates(values.entries);
}, [aggregateTaxRates, values.entries]);
};

/**
* Retrieves the bill subtotal.
* @returns {number}
*/
export const useBillSubtotal = () => {
const {
values: { entries },
} = useFormikContext();

// Calculate the total due amount of bill entries.
return React.useMemo(() => getEntriesTotal(entries), [entries]);
};

/**
* Retreives the bill total tax amount.
* @returns {number}
*/
export const useBillTotalTaxAmount = () => {
const { values } = useFormikContext();

return React.useMemo(() => {
return chain(values.entries)
.filter((entry) => entry.tax_amount)
.sumBy('tax_amount')
.value();
}, [values.entries]);
};

/**
* Detarmines whether the tax is exclusive.
* @returns {boolean}
*/
export const useIsBillTaxExclusive = () => {
const { values } = useFormikContext();

return values.inclusive_exclusive_tax === TaxType.Exclusive;
};

/**
* Retreives the bill total.
* @returns {number}
*/
export const useBillTotal = () => {
const subtotal = useBillSubtotal();
const totalTaxAmount = useBillTotalTaxAmount();
const isExclusiveTax = useIsBillTaxExclusive();

return R.compose(R.when(R.always(isExclusiveTax), R.add(totalTaxAmount)))(
subtotal,
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
TotalLineTextStyle,
} from '@/components';
import { useInvoiceAggregatedTaxRates, useInvoiceTotals } from './utils';
import { TaxType } from '@/interfaces/TaxRates';

export function InvoiceFormFooterRight() {
// Calculate the total due amount of invoice entries.
Expand All @@ -32,7 +33,7 @@ export function InvoiceFormFooterRight() {
<TotalLine
title={
<>
{inclusive_exclusive_tax === 'inclusive'
{inclusive_exclusive_tax === TaxType.Inclusive
? 'Subtotal (Tax Inclusive)'
: 'Subtotal'}
</>
Expand Down
Loading

0 comments on commit 06ee20c

Please sign in to comment.