Skip to content

Commit 42c9352

Browse files
committed
fix(invoices): B2B-4553 Disable pay invoice button when selecting different currencies invoice
1 parent cd1952a commit 42c9352

4 files changed

Lines changed: 55 additions & 47 deletions

File tree

apps/storefront/src/components/table/B3PaginationTable.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,6 @@ function PaginationTable<GetRequestListParams, Row extends object>(
295295
getList,
296296
getCacheList,
297297
refresh,
298-
setSelectCheckbox,
299298
}),
300299
[getList, getCacheList, getSelectedValue, refresh],
301300
);

apps/storefront/src/pages/Invoice/components/InvoiceFooter.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import { BcCartData, BcCartDataLineItem, InvoiceListNode } from '@/types/invoice
88
import { snackbar } from '@/utils/b3Tip';
99
import { handleGetCorrespondingCurrencyToken } from '@/utils/currencyUtils';
1010

11-
import { formattingNumericValues, gotoInvoiceCheckoutUrl } from '../utils/payment';
11+
import {
12+
filterInvoicesBySameCurrency,
13+
formattingNumericValues,
14+
gotoInvoiceCheckoutUrl,
15+
} from '../utils/payment';
1216

1317
interface InvoiceFooterProps {
1418
selectedPay: CustomFieldItems;
@@ -35,6 +39,8 @@ function InvoiceFooter(props: InvoiceFooterProps) {
3539

3640
const { selectedPay, decimalPlaces } = props;
3741

42+
const { hasMixedCurrency } = filterInvoicesBySameCurrency(selectedPay as InvoiceListNode[]);
43+
3844
const handlePay = async () => {
3945
const lineItems: BcCartDataLineItem[] = [];
4046
let currency = 'SGD';
@@ -173,12 +179,14 @@ function InvoiceFooter(props: InvoiceFooterProps) {
173179
sx={{
174180
fontSize: '16px',
175181
fontWeight: '700',
176-
color: '#000000',
182+
color: hasMixedCurrency ? 'error.main' : '#000000',
177183
}}
178184
>
179-
{b3Lang('invoice.footer.totalPayment', {
180-
total: `${currentToken}${selectedAccount}`,
181-
})}
185+
{hasMixedCurrency
186+
? b3Lang('invoice.footer.differentCurrencyError')
187+
: b3Lang('invoice.footer.totalPayment', {
188+
total: `${currentToken}${selectedAccount}`,
189+
})}
182190
</Typography>
183191
<Box
184192
sx={{
@@ -190,6 +198,7 @@ function InvoiceFooter(props: InvoiceFooterProps) {
190198
>
191199
<Button
192200
variant="contained"
201+
disabled={hasMixedCurrency}
193202
sx={{
194203
marginLeft: isMobile ? 0 : '1rem',
195204
width: isMobile ? '100%' : 'auto',

apps/storefront/src/pages/Invoice/index.test.tsx

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import {
2121
import { when } from 'vitest-when';
2222

2323
import { permissionLevels } from '@/constants';
24-
import { snackbar } from '@/utils/b3Tip';
2524

2625
import { InvoiceStatusCode } from './components/InvoiceStatus';
2726
import { triggerPdfDownload } from './components/triggerPdfDownload';
@@ -30,9 +29,6 @@ import Invoice from '.';
3029
const { server } = startMockServer();
3130

3231
vi.mock('./components/triggerPdfDownload');
33-
vi.mock('@/utils/b3Tip', () => ({
34-
snackbar: { error: vi.fn() },
35-
}));
3632

3733
const buildInvoiceWith = builder(() => ({
3834
node: {
@@ -353,7 +349,7 @@ it('can pay for multiple invoices', async () => {
353349
});
354350
});
355351

356-
it('blocks selecting invoices that are in different currencies', async () => {
352+
it('disables the Pay invoices button when selected invoices are in different currencies', async () => {
357353
server.use(
358354
graphql.query('GetInvoices', () =>
359355
HttpResponse.json(
@@ -413,17 +409,27 @@ it('blocks selecting invoices that are in different currencies', async () => {
413409

414410
expect(screen.getByText('1 invoices selected')).toBeInTheDocument();
415411
expect(screen.getByRole('heading', { name: 'Total payment: $433.00' })).toBeInTheDocument();
412+
expect(screen.getByRole('button', { name: 'Pay invoices' })).toBeEnabled();
416413

417-
// attempting to also select the EUR invoice should be blocked
414+
// selecting the EUR invoice too is now allowed
418415
await userEvent.click(within(secondRowCells[0]).getByRole('checkbox'));
419416

420-
expect(within(secondRowCells[0]).getByRole('checkbox')).not.toBeChecked();
421-
expect(screen.getByText('1 invoices selected')).toBeInTheDocument();
422-
expect(screen.getByRole('heading', { name: 'Total payment: $433.00' })).toBeInTheDocument();
423-
expect(snackbar.error).toHaveBeenCalled();
417+
expect(within(secondRowCells[0]).getByRole('checkbox')).toBeChecked();
418+
expect(screen.getByText('2 invoices selected')).toBeInTheDocument();
419+
420+
// the misleading flattened total is replaced with a warning...
421+
expect(
422+
screen.getByRole('heading', {
423+
name: 'You cannot pay multiple invoices in different currencies',
424+
}),
425+
).toBeInTheDocument();
426+
expect(screen.queryByText(/Total payment:/)).not.toBeInTheDocument();
427+
428+
// ...and paying is blocked while the selection mixes currencies
429+
expect(screen.getByRole('button', { name: 'Pay invoices' })).toBeDisabled();
424430
});
425431

426-
it('blocks selecting all invoices when the page contains different currencies', async () => {
432+
it('disables the Pay invoices button when selecting all invoices with different currencies', async () => {
427433
server.use(
428434
graphql.query('GetInvoices', () =>
429435
HttpResponse.json(
@@ -477,15 +483,19 @@ it('blocks selecting all invoices when the page contains different currencies',
477483
const secondRow = within(table).getByRole('row', { name: /3325/ });
478484
const secondRowCells = within(secondRow).getAllByRole('cell');
479485

480-
// click the "select all" checkbox in the header, which would otherwise
481-
// select both the USD and EUR invoices at once
486+
// click the "select all" checkbox in the header
482487
await userEvent.click(within(columnHeaders[0]).getByRole('checkbox'));
483488

484-
// only the reference-currency invoice should end up selected
485-
expect(screen.getByText('1 invoices selected')).toBeInTheDocument();
486-
expect(screen.getByRole('heading', { name: 'Total payment: $433.00' })).toBeInTheDocument();
487-
expect(within(secondRowCells[0]).getByRole('checkbox')).not.toBeChecked();
488-
expect(snackbar.error).toHaveBeenCalled();
489+
// both invoices get selected even though currencies differ
490+
expect(within(secondRowCells[0]).getByRole('checkbox')).toBeChecked();
491+
expect(screen.getByText('2 invoices selected')).toBeInTheDocument();
492+
493+
expect(
494+
screen.getByRole('heading', {
495+
name: 'You cannot pay multiple invoices in different currencies',
496+
}),
497+
).toBeInTheDocument();
498+
expect(screen.getByRole('button', { name: 'Pay invoices' })).toBeDisabled();
489499
});
490500

491501
it('can specify an amount to pay for the invoices', async () => {

apps/storefront/src/pages/Invoice/index.tsx

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import InvoiceListType, {
3939
filterFormConfigsTranslationVariables,
4040
sortIdArr,
4141
} from './utils/config';
42-
import { filterInvoicesBySameCurrency, formattingNumericValues } from './utils/payment';
42+
import { formattingNumericValues } from './utils/payment';
4343
import { handlePrintPDF } from './utils/pdf';
4444
import { InvoiceItemCard } from './InvoiceItemCard';
4545

@@ -53,11 +53,10 @@ interface FilterSearchProps {
5353

5454
interface PaginationTableRefProps extends HTMLInputElement {
5555
getList: () => void;
56-
getCacheList: () => InvoiceListNode[];
56+
getCacheList: () => void;
5757
setCacheAllList: (items?: InvoiceList[]) => void;
5858
setList: (items?: InvoiceListNode[]) => void;
5959
getSelectedValue: () => void;
60-
setSelectCheckbox: (ids: Array<string | number>) => void;
6160
}
6261

6362
const initFilter = {
@@ -250,29 +249,20 @@ function Invoice() {
250249
if (selectCheckbox.length > 0) {
251250
const productList = paginationTableRef.current?.getCacheList() || [];
252251

253-
const checkedItems = selectCheckbox
254-
.map((item: number | string) => {
255-
const newItems = productList.find((product: InvoiceListNode) => {
256-
const { node } = product;
252+
const checkedItems = selectCheckbox.map((item: number | string) => {
253+
const newItems = productList.find((product: InvoiceListNode) => {
254+
const { node } = product;
257255

258-
return Number(node.id) === Number(item);
259-
});
260-
261-
return newItems;
262-
})
263-
.filter((item): item is InvoiceListNode => !!item && !item.node.disableCurrentCheckbox);
264-
265-
const { validInvoices, hasMixedCurrency } = filterInvoicesBySameCurrency(checkedItems);
256+
return Number(node.id) === Number(item);
257+
});
266258

267-
if (hasMixedCurrency) {
268-
snackbar.error(b3Lang('invoice.footer.differentCurrencyError'));
269-
const validIds = validInvoices.map((item) => item.node.id);
270-
paginationTableRef.current?.setSelectCheckbox(validIds);
271-
setCheckedArr([...validInvoices]);
272-
return;
273-
}
259+
return newItems;
260+
});
274261

275-
setCheckedArr([...checkedItems]);
262+
const newEnableItems = checkedItems.filter(
263+
(item: InvoiceListNode | undefined) => item && !item.node.disableCurrentCheckbox,
264+
);
265+
setCheckedArr([...newEnableItems]);
276266
} else {
277267
setCheckedArr([]);
278268
}

0 commit comments

Comments
 (0)