From c74c494e55ea634e8ba15960741d3ddaf3308c5f Mon Sep 17 00:00:00 2001 From: Alisher Musurmonov Date: Thu, 26 Sep 2024 20:31:08 +0500 Subject: [PATCH 01/11] UINV-557: Improve error message when attempting to save an invoice line when no budget for the fiscal year exists --- CHANGELOG.md | 1 + .../InvoiceDetails/InvoiceDetailsContainer.js | 88 +++++++++++-------- .../InvoiceDetailsContainer.test.js | 4 + src/invoices/InvoiceDetails/utils.js | 40 ++++++--- src/invoices/InvoiceDetails/utils.test.js | 69 +++++++++++---- .../InvoiceLineFormContainer.js | 19 ++-- .../InvoiceLineFormContainer.test.js | 4 + translations/ui-invoice/en.json | 8 +- 8 files changed, 157 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c247c2e6..f54eddcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Use Save & close button label stripes-component translation key. Refs UINV-540. * Add indexes to improve the performance of getting composite orders by poNumber. Refs UINV-546. * Duplicate invoice and invoice lines. Refs UINV-552. +* Improve error message when attempting to save an invoice line when no budget for the fiscal year exists. Refs UINV-557. ## [6.0.4](https://github.com/folio-org/ui-invoice/tree/v6.0.4) (2024-08-21) [Full Changelog](https://github.com/folio-org/ui-invoice/compare/v6.0.3...v6.0.4) diff --git a/src/invoices/InvoiceDetails/InvoiceDetailsContainer.js b/src/invoices/InvoiceDetails/InvoiceDetailsContainer.js index f4e7b8b0..77bd0b6f 100644 --- a/src/invoices/InvoiceDetails/InvoiceDetailsContainer.js +++ b/src/invoices/InvoiceDetails/InvoiceDetailsContainer.js @@ -12,7 +12,10 @@ import { import ReactRouterPropTypes from 'react-router-prop-types'; import { LoadingPane } from '@folio/stripes/components'; -import { stripesConnect } from '@folio/stripes/core'; +import { + stripesConnect, + useOkapiKy, +} from '@folio/stripes/core'; import { baseManifest, batchFetch, @@ -56,6 +59,7 @@ export function InvoiceDetailsContainer({ // eslint-disable-next-line react-hooks/exhaustive-deps const mutator = useMemo(() => originMutator, [id]); const showCallout = useShowCallout(); + const ky = useOkapiKy(); const [isLoading, setIsLoading] = useState(true); const [invoice, setInvoice] = useState({}); const [invoiceLines, setInvoiceLines] = useState({}); @@ -221,14 +225,15 @@ export function InvoiceDetailsContainer({ }, Promise.resolve()); await fetchInvoiceData(); } catch (response) { - showUpdateInvoiceError( + showUpdateInvoiceError({ response, showCallout, - 'saveLine', - 'ui-invoice.invoice.actions.saveLine.error', - mutator.expenseClass, - mutator.fund, - ); + action: 'saveLine', + defaultErrorMessageId: 'ui-invoice.invoice.actions.saveLine.error', + expenseClassMutator: mutator.expenseClass, + fundMutator: mutator.fund, + ky, + }); } setIsLoading(false); @@ -277,14 +282,15 @@ export function InvoiceDetailsContainer({ return fetchInvoiceData(); }, ({ response }) => ( - showUpdateInvoiceError( + showUpdateInvoiceError({ response, showCallout, - 'cancel', - 'ui-invoice.invoice.actions.cancel.error', - mutator.expenseClass, - mutator.fund, - ) + action: 'cancel', + defaultErrorMessageId: 'ui-invoice.invoice.actions.cancel.error', + expenseClassMutator: mutator.expenseClass, + fundMutator: mutator.fund, + ky, + }) )) .finally(setIsLoading); }, @@ -305,14 +311,15 @@ export function InvoiceDetailsContainer({ return fetchInvoiceData(); }, ({ response }) => ( - showUpdateInvoiceError( + showUpdateInvoiceError({ response, showCallout, - 'approve', - 'ui-invoice.invoice.actions.approve.error', - mutator.expenseClass, - mutator.fund, - ) + action: 'approve', + defaultErrorMessageId: 'ui-invoice.invoice.actions.approve.error', + expenseClassMutator: mutator.expenseClass, + fundMutator: mutator.fund, + ky, + }) )) .finally(setIsLoading); }, @@ -332,14 +339,15 @@ export function InvoiceDetailsContainer({ refreshList(); return fetchInvoiceData(); - }, ({ response }) => showUpdateInvoiceError( + }, ({ response }) => showUpdateInvoiceError({ response, showCallout, - 'pay', - 'ui-invoice.invoice.actions.pay.error', - mutator.expenseClass, - mutator.fund, - )) + action: 'pay', + defaultErrorMessageId: 'ui-invoice.invoice.actions.pay.error', + expenseClassMutator: mutator.expenseClass, + fundMutator: mutator.fund, + ky, + })) .finally(setIsLoading); }, // eslint-disable-next-line react-hooks/exhaustive-deps @@ -356,14 +364,15 @@ export function InvoiceDetailsContainer({ return mutateInvoice({ ...invoiceResponse, status: INVOICE_STATUS.paid }); }) .catch(({ response }) => { - showUpdateInvoiceError( + showUpdateInvoiceError({ response, showCallout, - 'approveAndPay', - 'ui-invoice.invoice.actions.approveAndPay.error', - mutator.expenseClass, - mutator.fund, - ); + action: 'approveAndPay', + defaultErrorMessageId: 'ui-invoice.invoice.actions.approveAndPay.error', + expenseClassMutator: mutator.expenseClass, + fundMutator: mutator.fund, + ky, + }); throw new Error('approveAndPay error'); }) @@ -398,6 +407,7 @@ export function InvoiceDetailsContainer({ fund: mutator.fund, }, showCallout, + ky, })).then(async ({ invoiceId: newInvoiceId }) => { showCallout({ messageId: 'ui-invoice.invoice.actions.duplicate.success.message' }); refreshList(); @@ -408,14 +418,15 @@ export function InvoiceDetailsContainer({ }); }) .catch((error) => { - showUpdateInvoiceError( - error?.response, + showUpdateInvoiceError({ + response: error?.response, showCallout, - 'duplicate', - 'ui-invoice.invoice.actions.duplicate.error.message', - mutator.expenseClass, - mutator.fund, - ); + action: 'duplicate', + defaultErrorMessageId: 'ui-invoice.invoice.actions.duplicate.error.message', + expenseClassMutator: mutator.expenseClass, + fundMutator: mutator.fund, + ky, + }); }) .finally(() => setIsLoading(false)); }, [ @@ -429,6 +440,7 @@ export function InvoiceDetailsContainer({ refreshList, history, location.search, + ky, ]); const updateInvoice = useCallback( diff --git a/src/invoices/InvoiceDetails/InvoiceDetailsContainer.test.js b/src/invoices/InvoiceDetails/InvoiceDetailsContainer.test.js index e7128d97..e4f72706 100644 --- a/src/invoices/InvoiceDetails/InvoiceDetailsContainer.test.js +++ b/src/invoices/InvoiceDetails/InvoiceDetailsContainer.test.js @@ -22,6 +22,10 @@ import { createInvoiceLineFromPOL, showUpdateInvoiceError } from './utils'; import InvoiceDetails from './InvoiceDetails'; import { InvoiceDetailsContainer } from './InvoiceDetailsContainer'; +jest.mock('@folio/stripes/core', () => ({ + ...jest.requireActual('@folio/stripes/core'), + useOkapiKy: jest.fn().mockReturnValue({}), +})); jest.mock('../../common/hooks', () => ({ useInvoiceMutation: jest.fn().mockReturnValue({}), useInvoiceLineMutation: jest.fn().mockReturnValue({}), diff --git a/src/invoices/InvoiceDetails/utils.js b/src/invoices/InvoiceDetails/utils.js index 510b3b2d..10f86f83 100644 --- a/src/invoices/InvoiceDetails/utils.js +++ b/src/invoices/InvoiceDetails/utils.js @@ -1,11 +1,14 @@ +import { noop } from 'lodash'; + import { EXPENSE_CLASSES_API, FUNDS_API, } from '@folio/stripes-acq-components'; import { - INVOICE_STATUS, ERROR_CODES, + FISCAL_YEARS_API, + INVOICE_STATUS, } from '../../common/constants'; import { convertToInvoiceLineFields, @@ -21,7 +24,7 @@ export const createInvoiceLineFromPOL = (poLine, invoiceId, vendor) => { }; }; -export const showUpdateInvoiceError = async ( +export const showUpdateInvoiceError = async ({ response, showCallout, action, @@ -29,7 +32,8 @@ export const showUpdateInvoiceError = async ( expenseClassMutator, fundMutator, messageValues = {}, -) => { + ky = {}, +}) => { let error; try { @@ -135,6 +139,7 @@ export const showUpdateInvoiceError = async ( case ERROR_CODES.budgetNotFoundByFundIdAndFiscalYearId: { const errors = error?.errors?.[0]?.parameters; let fundId = errors?.find(({ key }) => key === 'fundId')?.value; + const fiscalYearId = errors?.find(({ key }) => key === 'fiscalYearId')?.value; if (!fundId) { fundId = errors?.find(({ key }) => key === 'fund')?.value; @@ -142,12 +147,19 @@ export const showUpdateInvoiceError = async ( if (fundId) { fundMutator.GET({ path: `${FUNDS_API}/${fundId}` }) - .then(({ fund }) => { + .then(async ({ fund }) => { + let fiscalYear = {}; + + if (fiscalYearId) { + fiscalYear = await ky.get(`${FISCAL_YEARS_API}/${fiscalYearId}`).json(); + } + showCallout({ messageId: `ui-invoice.invoice.actions.${action}.error.${ERROR_CODES[code]}`, type: 'error', values: { fundCode: fund?.code, + fiscalYear: fiscalYear?.code, }, }); }, () => { @@ -179,6 +191,7 @@ export const handleInvoiceLineErrors = async ({ requestData = [], responses = [], showCallout, + ky = noop, }) => { const errors = responses.filter(({ status }) => status === 'rejected'); @@ -189,15 +202,16 @@ export const handleInvoiceLineErrors = async ({ const errorRequests = errors.map(({ reason }, index) => { const invoiceLineNumber = requestData[index]?.invoiceLineNumber; - return showUpdateInvoiceError( - reason?.response, + return showUpdateInvoiceError({ + response: reason, showCallout, - 'saveLine', - 'ui-invoice.errors.invoiceLine.duplicate', - mutator.expenseClass, - mutator.fund, - { invoiceLineNumber }, - ); + action: 'saveLine', + defaultErrorMessageId: 'ui-invoice.errors.invoiceLine.duplicate', + expenseClassMutator: mutator.expenseClass, + fundMutator: mutator.fund, + messageValues: { invoiceLineNumber }, + ky, + }); }); return Promise.all(errorRequests); @@ -209,6 +223,7 @@ export const handleInvoiceLinesCreation = async ({ createInvoiceLines, showCallout, mutator, + ky, }) => { if (!invoiceLines.length) { return { @@ -235,6 +250,7 @@ export const handleInvoiceLinesCreation = async ({ expenseClass: mutator.expenseClass, fund: mutator.fund, }, + ky, }); return ({ diff --git a/src/invoices/InvoiceDetails/utils.test.js b/src/invoices/InvoiceDetails/utils.test.js index 2d7937bb..b867cec9 100644 --- a/src/invoices/InvoiceDetails/utils.test.js +++ b/src/invoices/InvoiceDetails/utils.test.js @@ -6,6 +6,7 @@ import { } from './utils'; const showCallout = jest.fn(); +const ky = jest.fn(() => ({ get: jest.fn().mockReturnValue({ json: jest.fn() }) })); const action = 'action'; const defaultErrorMessageId = 'defaultErrorMessageId'; const expenseClassMutator = { GET: jest.fn() }; @@ -21,9 +22,15 @@ describe('showUpdateInvoiceError', () => { }); it('should return default error message', async () => { - await showUpdateInvoiceError( - undefined, showCallout, action, defaultErrorMessageId, expenseClassMutator, fundMutator, - ); + await showUpdateInvoiceError({ + response: undefined, + showCallout, + action, + defaultErrorMessageId, + expenseClassMutator, + fundMutator, + ky, + }); expect(showCallout).toHaveBeenCalledWith({ messageId: defaultErrorMessageId, type: 'error', values: {} }); }); @@ -43,6 +50,15 @@ describe('showUpdateInvoiceError', () => { response, showCallout, action, defaultErrorMessageId, expenseClassMutator, fundMutator, ); + await showUpdateInvoiceError({ + response, + showCallout, + action, + defaultErrorMessageId, + expenseClassMutator, + fundMutator, + }); + expect(showCallout).toHaveBeenCalledWith({ messageId: defaultErrorMessageId, type: 'error' }); }); @@ -62,9 +78,14 @@ describe('showUpdateInvoiceError', () => { }), }; - await showUpdateInvoiceError( - response, showCallout, action, defaultErrorMessageId, expenseClassMutator, fundMutator, - ); + await showUpdateInvoiceError({ + response, + showCallout, + action, + defaultErrorMessageId, + expenseClassMutator, + fundMutator, + }); expect(showCallout).toHaveBeenCalledWith({ messageId: 'ui-invoice.invoice.actions.action.error.inactiveExpenseClass', @@ -88,9 +109,14 @@ describe('showUpdateInvoiceError', () => { }), }; - await showUpdateInvoiceError( - response, showCallout, action, defaultErrorMessageId, expenseClassMutator, fundMutator, - ); + await showUpdateInvoiceError({ + response, + showCallout, + action, + defaultErrorMessageId, + expenseClassMutator, + fundMutator, + }); expect(showCallout).toHaveBeenCalledWith({ messageId: 'ui-invoice.invoice.actions.action.error.inactiveExpenseClass', @@ -110,9 +136,14 @@ describe('showUpdateInvoiceError', () => { }), }; - await showUpdateInvoiceError( - response, showCallout, action, defaultErrorMessageId, expenseClassMutator, fundMutator, - ); + await showUpdateInvoiceError({ + response, + showCallout, + action, + defaultErrorMessageId, + expenseClassMutator, + fundMutator, + }); expect(showCallout).toHaveBeenCalledWith({ messageId: 'ui-invoice.invoice.actions.approve.error.outdatedFundIdInEncumbrance', @@ -141,6 +172,7 @@ describe('showUpdateInvoiceError', () => { expenseClass: jest.fn(), fund: jest.fn(), }, + ky, }); expect(result.invoiceLines).toEqual([{ status: 'fulfilled' }]); @@ -167,6 +199,7 @@ describe('showUpdateInvoiceError', () => { expenseClass: jest.fn(), fund: jest.fn(), }, + ky, }); expect(result).toEqual([]); @@ -214,9 +247,15 @@ describe('showUpdateInvoiceError', () => { GET: jest.fn().mockResolvedValue({ fund: { code: 'value' } }), }; - await showUpdateInvoiceError( - mockResponse, showCallout, mockActionName, defaultErrorMessageId, expenseClassMutator, mockFundMutator, - ); + await showUpdateInvoiceError({ + response: mockResponse, + showCallout, + action: mockActionName, + defaultErrorMessageId, + expenseClassMutator, + fundMutator: mockFundMutator, + ky, + }); expect(showCallout).toHaveBeenCalledWith({ messageId, diff --git a/src/invoices/InvoiceLineForm/InvoiceLineFormContainer.js b/src/invoices/InvoiceLineForm/InvoiceLineFormContainer.js index 5aa2b187..536d3a09 100644 --- a/src/invoices/InvoiceLineForm/InvoiceLineFormContainer.js +++ b/src/invoices/InvoiceLineForm/InvoiceLineFormContainer.js @@ -7,7 +7,10 @@ import ReactRouterPropTypes from 'react-router-prop-types'; import { LoadingView, } from '@folio/stripes/components'; -import { stripesConnect } from '@folio/stripes/core'; +import { + stripesConnect, + useOkapiKy, +} from '@folio/stripes/core'; import { baseManifest, VENDORS_API, @@ -30,6 +33,7 @@ export function InvoiceLineFormContainerComponent({ resources, showCallout, }) { + const ky = useOkapiKy(); const [invoiceLine, setInvoiceLine] = useState(); const [invoice, setInvoice] = useState(); const [vendor, setVendor] = useState(); @@ -92,14 +96,15 @@ export function InvoiceLineFormContainerComponent({ onClose(); }) .catch((response) => { - showUpdateInvoiceError( + showUpdateInvoiceError({ response, showCallout, - 'saveLine', - 'ui-invoice.errors.invoiceLineHasNotBeenSaved', - mutator.expenseClass, - mutator.fund, - ); + action: 'saveLine', + defaultErrorMessageId: 'ui-invoice.errors.invoiceLineHasNotBeenSaved', + expenseClassMutator: mutator.expenseClass, + fundMutator: mutator.fund, + ky, + }); return { id: 'Unable to save invoice line' }; }); diff --git a/src/invoices/InvoiceLineForm/InvoiceLineFormContainer.test.js b/src/invoices/InvoiceLineForm/InvoiceLineFormContainer.test.js index 80a6d2f1..97893649 100644 --- a/src/invoices/InvoiceLineForm/InvoiceLineFormContainer.test.js +++ b/src/invoices/InvoiceLineForm/InvoiceLineFormContainer.test.js @@ -13,6 +13,10 @@ import { showUpdateInvoiceError } from '../InvoiceDetails/utils'; import InvoiceLineForm from './InvoiceLineForm'; import { InvoiceLineFormContainerComponent } from './InvoiceLineFormContainer'; +jest.mock('@folio/stripes/core', () => ({ + ...jest.requireActual('@folio/stripes/core'), + useOkapiKy: jest.fn().mockReturnValue({}), +})); jest.mock('./InvoiceLineForm', () => jest.fn().mockReturnValue('InvoiceLineForm')); jest.mock('../InvoiceDetails/utils', () => ({ ...jest.requireActual('../InvoiceDetails/utils'), diff --git a/translations/ui-invoice/en.json b/translations/ui-invoice/en.json index c86f8ab2..e3a2bb56 100644 --- a/translations/ui-invoice/en.json +++ b/translations/ui-invoice/en.json @@ -369,7 +369,7 @@ "invoice.actions.approve.error.lockCalculatedTotalsMismatch": "Invoice cannot be approved. The Lock total amount of the invoice does not match the Calculated total amount of its invoice lines and adjustments.", "invoice.actions.approve.error.organizationIsNotVendor": "Invoice cannot be approved because the associated Vendor Id belongs to a non-vendor organization.", "invoice.actions.approve.error.budgetNotFoundByFundId": "Invoice cannot be approved because Fund {fundCode} has no current budget.", - "invoice.actions.approve.error.budgetNotFoundByFundIdAndFiscalYearId": "Invoice cannot be approved because Fund {fundCode} has no current budget.", + "invoice.actions.approve.error.budgetNotFoundByFundIdAndFiscalYearId": "Invoice cannot be approved because Fund {fundCode} has no current budget for fiscal year {fiscalYear}.", "invoice.actions.approve.error.adjustmentFundDistributionsNotPresent": "At least one fund distribution should be present for every non-prorated adjustment", "invoice.actions.approve.error.lineFundDistributionsSummaryMismatch": "Fund distributions summary should be 100 % or equal to subtotal for every associated invoice lines", "invoice.actions.approve.error.adjustmentFundDistributionsSummaryMismatch": "Fund distributions summary should be 100 % or equal to subtotal for every non-prorated adjustment", @@ -383,18 +383,18 @@ "invoice.actions.approve.error.outdatedFundIdInEncumbrance": "Invoice could not be approved. Encumbrance fund id does not match the fund id in the invoice line. Error must be corrected in database.", "invoice.actions.pay.error.inactiveExpenseClass": "Invoice can not be Payed because expense class {expenseClass} is inactive.", "invoice.actions.pay.error.budgetNotFoundByFundId": "Invoice cannot be paid because Fund {fundCode} has no current budget.", - "invoice.actions.pay.error.budgetNotFoundByFundIdAndFiscalYearId": "Invoice cannot be paid because Fund {fundCode} has no current budget.", + "invoice.actions.pay.error.budgetNotFoundByFundIdAndFiscalYearId": "Invoice cannot be paid because Fund {fundCode} has no current budget for fiscal year {fiscalYear}.", "invoice.actions.approveAndPay.error.budgetExpenseClassNotFound": "{expenseClassName} expense class not found on {fundCode} Fund", "invoice.actions.approve.error.incorrectFundDistributionTotal": "Invoice could not be approved or paid. The fund distribution total must be distributed by 100 percent in invoice line {invoiceLineNumber}. Please update fund distribution details for that invoice line to continue.", "invoice.actions.approveAndPay.error.inactiveExpenseClass": "Invoice can not be Approved and Payed because expense class {expenseClass} is inactive.", "invoice.actions.approveAndPay.error.budgetNotFoundByFundId": "Invoice cannot be approved and paid because Fund {fundCode} has no current budget.", - "invoice.actions.approveAndPay.error.budgetNotFoundByFundIdAndFiscalYearId": "Invoice cannot be approved and paid because Fund {fundCode} has no current budget.", + "invoice.actions.approveAndPay.error.budgetNotFoundByFundIdAndFiscalYearId": "Invoice cannot be approved and paid because Fund {fundCode} has no current budget for fiscal year {fiscalYear}.", "invoice.actions.approve.error.transactionCreationFailure": "One or more transactions record(s) failed to be created", "invoice.actions.approve.confirmation.heading": "Approve invoice", "invoice.actions.approve.confirmation.message": "Are you sure you want to approve invoice?", "invoice.actions.saveLine.error.budgetExpenseClassNotFound": "{expenseClassName} expense class not found on {fundCode} Fund", "invoice.actions.saveLine.error.budgetNotFoundByFundId": "Invoice line cannot be saved because Fund {fundCode} has no current budget.", - "invoice.actions.saveLine.error.budgetNotFoundByFundIdAndFiscalYearId": "Invoice line cannot be saved because Fund {fundCode} has no current budget.", + "invoice.actions.saveLine.error.budgetNotFoundByFundIdAndFiscalYearId": "Invoice line cannot be saved because Fund {fundCode} has no current budget for fiscal year {fiscalYear}.", "invoice.actions.saveLine.error.inactiveExpenseClass": "Invoice line can not be saved because expense class {expenseClass} is inactive.", "invoice.actions.saveLine.error": "Invoice line was not saved", "invoice.actions.error.userHasNoPermission": "Operation is not permitted because user is not a member of the specified acquisition unit", From 29aa8c20fd771da60fdb2b60eac1203a422bd731 Mon Sep 17 00:00:00 2001 From: Alisher Musurmonov Date: Thu, 26 Sep 2024 20:43:13 +0500 Subject: [PATCH 02/11] upgrade actions/upload-artifact@v2 to actions/upload-artifact@v4 --- .github/workflows/build-npm-release.yml | 6 +++--- .github/workflows/build-npm.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-npm-release.yml b/.github/workflows/build-npm-release.yml index 8d7a17f7..bbd8f531 100644 --- a/.github/workflows/build-npm-release.yml +++ b/.github/workflows/build-npm-release.yml @@ -152,7 +152,7 @@ jobs: comment_title: Jest Unit Test Statistics - name: Publish Jest coverage report - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: always() with: name: jest-coverage-report @@ -170,7 +170,7 @@ jobs: comment_title: BigTest Unit Test Statistics - name: Publish BigTest coverage report - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: always() with: name: bigtest-coverage-report @@ -178,7 +178,7 @@ jobs: retention-days: 30 - name: Publish yarn.lock - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: failure() with: name: yarn.lock diff --git a/.github/workflows/build-npm.yml b/.github/workflows/build-npm.yml index 47e43de7..72949a15 100644 --- a/.github/workflows/build-npm.yml +++ b/.github/workflows/build-npm.yml @@ -95,7 +95,7 @@ jobs: comment_title: Jest Unit Test Statistics - name: Publish Jest coverage report - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: always() with: name: jest-coverage-report @@ -113,7 +113,7 @@ jobs: comment_title: BigTest Unit Test Statistics - name: Publish BigTest coverage report - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: always() with: name: bigtest-coverage-report @@ -121,7 +121,7 @@ jobs: retention-days: 30 - name: Publish yarn.lock - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: failure() with: name: yarn.lock From 05d085f0bdddb10f4adf055362139735890ec222 Mon Sep 17 00:00:00 2001 From: Alisher Musurmonov Date: Fri, 27 Sep 2024 15:21:46 +0500 Subject: [PATCH 03/11] test: update snapshot tests --- .../AdjustmentsForm.test.js.snap | 24 +--- src/invoices/InvoiceDetails/utils.test.js | 11 +- .../InvoiceDocumentsForm.test.js.snap | 1 - src/invoices/InvoiceForm/InvoiceForm.test.js | 3 + .../__snapshots__/InvoiceForm.test.js.snap | 117 +----------------- .../InvoiceLineForm.test.js.snap | 12 +- .../FiscalYearFilter/FiscalYearFilter.test.js | 3 +- .../InvoicesListFilters.test.js.snap | 32 +---- .../VoucherEditForm.test.js.snap | 2 - .../BatchGroupConfigurationForm.test.js.snap | 2 - 10 files changed, 24 insertions(+), 183 deletions(-) diff --git a/src/invoices/AdjustmentsForm/__snapshots__/AdjustmentsForm.test.js.snap b/src/invoices/AdjustmentsForm/__snapshots__/AdjustmentsForm.test.js.snap index a64da2a1..70a4b842 100644 --- a/src/invoices/AdjustmentsForm/__snapshots__/AdjustmentsForm.test.js.snap +++ b/src/invoices/AdjustmentsForm/__snapshots__/AdjustmentsForm.test.js.snap @@ -91,16 +91,7 @@ exports[`AdjustmentsForm should render correct structure with defined adjustment id="downshift-:r1:-menu" role="listbox" style="max-height: 174px;" - > -
  • -
    -
  • - + /> @@ -500,7 +491,6 @@ exports[`AdjustmentsForm should render correct structure with defined adjustment -
    `; @@ -594,16 +584,7 @@ exports[`AdjustmentsForm should render correct structure without adjustments 1`] id="downshift-:r3:-menu" role="listbox" style="max-height: 174px;" - > -
  • -
    -
  • - + />
    @@ -632,6 +613,5 @@ exports[`AdjustmentsForm should render correct structure without adjustments 1`] -
    `; diff --git a/src/invoices/InvoiceDetails/utils.test.js b/src/invoices/InvoiceDetails/utils.test.js index b867cec9..34afefa9 100644 --- a/src/invoices/InvoiceDetails/utils.test.js +++ b/src/invoices/InvoiceDetails/utils.test.js @@ -46,9 +46,14 @@ describe('showUpdateInvoiceError', () => { }), }; - await showUpdateInvoiceError( - response, showCallout, action, defaultErrorMessageId, expenseClassMutator, fundMutator, - ); + await showUpdateInvoiceError({ + response, + showCallout, + action, + defaultErrorMessageId, + expenseClassMutator, + fundMutator, + }); await showUpdateInvoiceError({ response, diff --git a/src/invoices/InvoiceForm/InvoiceDocumentsForm/__snapshots__/InvoiceDocumentsForm.test.js.snap b/src/invoices/InvoiceForm/InvoiceDocumentsForm/__snapshots__/InvoiceDocumentsForm.test.js.snap index 6f5cddce..9e6a66d7 100644 --- a/src/invoices/InvoiceForm/InvoiceDocumentsForm/__snapshots__/InvoiceDocumentsForm.test.js.snap +++ b/src/invoices/InvoiceForm/InvoiceDocumentsForm/__snapshots__/InvoiceDocumentsForm.test.js.snap @@ -125,6 +125,5 @@ exports[`InvoiceDocumentsForm should render correct structure with defined adjus
    -
    `; diff --git a/src/invoices/InvoiceForm/InvoiceForm.test.js b/src/invoices/InvoiceForm/InvoiceForm.test.js index 021d6241..23b9954b 100644 --- a/src/invoices/InvoiceForm/InvoiceForm.test.js +++ b/src/invoices/InvoiceForm/InvoiceForm.test.js @@ -126,6 +126,7 @@ describe('InvoiceForm component', () => { const accountLabel = `${accountNo} (${appSystemNo})`; expect(container.querySelector('#selected-accounting-code-selection-item')).toHaveTextContent(''); + await userEvent.click(screen.getAllByText('stripes-components.selection.controlLabel')[4]); await userEvent.click(screen.getByText(accountLabel)); @@ -251,6 +252,7 @@ describe('InvoiceForm component', () => { expect(fiscalYearLabel).toBeInTheDocument(); + await userEvent.click(screen.getAllByText('stripes-components.selection.controlLabel')[0]); const fiscalYearOptions = container.querySelectorAll(optionIdQuery); expect(fiscalYearOptions.length).toBe(optionLengthWithEmptyLine); @@ -264,6 +266,7 @@ describe('InvoiceForm component', () => { const fiscalYearLabel = await screen.findByText(labelId); expect(fiscalYearLabel.innerHTML).toBe(labelIdRequired); + await userEvent.click(screen.getAllByText('stripes-components.selection.controlLabel')[0]); const fiscalYearOptions = container.querySelectorAll(optionIdQuery); diff --git a/src/invoices/InvoiceForm/__snapshots__/InvoiceForm.test.js.snap b/src/invoices/InvoiceForm/__snapshots__/InvoiceForm.test.js.snap index 349e56ea..67a97df5 100644 --- a/src/invoices/InvoiceForm/__snapshots__/InvoiceForm.test.js.snap +++ b/src/invoices/InvoiceForm/__snapshots__/InvoiceForm.test.js.snap @@ -327,29 +327,7 @@ exports[`InvoiceForm component should render correct structure 1`] = ` id="downshift-:r1:-menu" role="listbox" style="max-height: 174px;" - > -
  • -
    -
  • -
  • -
    - FY2023 -
    -
  • - + />
    @@ -447,34 +425,7 @@ exports[`InvoiceForm component should render correct structure 1`] = ` id="downshift-:r3:-menu" role="listbox" style="max-height: 174px;" - > -
  • -
    - ui-invoice.invoice.status.open -
    -
  • -
  • -
    - ui-invoice.invoice.status.reviewed -
    -
  • - + /> @@ -736,16 +687,7 @@ exports[`InvoiceForm component should render correct structure 1`] = ` id="downshift-:r5:-menu" role="listbox" style="max-height: 174px;" - > -
  • -
    -
  • - + /> @@ -864,17 +806,7 @@ exports[`InvoiceForm component should render correct structure 1`] = ` id="downshift-:r7:-menu" role="listbox" style="max-height: 174px;" - > -
  • - - -- - -
  • - + /> @@ -1351,16 +1283,7 @@ exports[`InvoiceForm component should render correct structure 1`] = ` id="downshift-:r9:-menu" role="listbox" style="max-height: 174px;" - > -
  • -
    -
  • - + /> @@ -1802,34 +1725,7 @@ exports[`InvoiceForm component should render correct structure 1`] = ` id="downshift-:rb:-menu" role="listbox" style="max-height: 174px;" - > -
  • -
    - BYN (BYN) -
    -
  • -
  • -
    - USD (USD) -
    -
  • - + /> @@ -2182,6 +2078,5 @@ exports[`InvoiceForm component should render correct structure 1`] = ` -
    `; diff --git a/src/invoices/InvoiceLineForm/__snapshots__/InvoiceLineForm.test.js.snap b/src/invoices/InvoiceLineForm/__snapshots__/InvoiceLineForm.test.js.snap index 44f79818..00c31dd4 100644 --- a/src/invoices/InvoiceLineForm/__snapshots__/InvoiceLineForm.test.js.snap +++ b/src/invoices/InvoiceLineForm/__snapshots__/InvoiceLineForm.test.js.snap @@ -786,16 +786,7 @@ exports[`InvoiceLineForm component should render correct structure 1`] = ` id="downshift-:r1:-menu" role="listbox" style="max-height: 174px;" - > -
  • -
    -
  • - + />
    @@ -1139,6 +1130,5 @@ exports[`InvoiceLineForm component should render correct structure 1`] = ` -
    `; diff --git a/src/invoices/InvoicesList/InvoicesListFilters/FiscalYearFilter/FiscalYearFilter.test.js b/src/invoices/InvoicesList/InvoicesListFilters/FiscalYearFilter/FiscalYearFilter.test.js index 43099fcf..0d079257 100644 --- a/src/invoices/InvoicesList/InvoicesListFilters/FiscalYearFilter/FiscalYearFilter.test.js +++ b/src/invoices/InvoicesList/InvoicesListFilters/FiscalYearFilter/FiscalYearFilter.test.js @@ -1,5 +1,5 @@ import { noop } from 'lodash'; -import { render, screen } from '@folio/jest-config-stripes/testing-library/react'; +import { fireEvent, render, screen } from '@folio/jest-config-stripes/testing-library/react'; import { useFiscalYears } from '../../../../common/hooks'; import { FiscalYearFilter } from './FiscalYearFilter'; @@ -33,6 +33,7 @@ describe('FiscalYearFilter', () => { renderFilter(); expect(screen.getByText(labelId)).toBeInTheDocument(); + fireEvent.click(screen.getByText('stripes-components.selection.controlLabel')); expect(screen.getByText(fiscalYearsMock[0].code)).toBeInTheDocument(); expect(screen.getByText(fiscalYearsMock[1].code)).toBeInTheDocument(); }); diff --git a/src/invoices/InvoicesList/InvoicesListFilters/__snapshots__/InvoicesListFilters.test.js.snap b/src/invoices/InvoicesList/InvoicesListFilters/__snapshots__/InvoicesListFilters.test.js.snap index c26ce290..a47b7084 100644 --- a/src/invoices/InvoicesList/InvoicesListFilters/__snapshots__/InvoicesListFilters.test.js.snap +++ b/src/invoices/InvoicesList/InvoicesListFilters/__snapshots__/InvoicesListFilters.test.js.snap @@ -712,21 +712,7 @@ exports[`InvoicesListFilters should render correct structure 1`] = ` id="downshift-:r1:-menu" role="listbox" style="max-height: 174px;" - > -
  • -
    - stripes-acq-components.filter.acqUnit.noAcqUnit -
    -
  • - + />
    @@ -4219,21 +4205,7 @@ exports[`InvoicesListFilters should render correct structure when disabled 1`] = id="downshift-:r3:-menu" role="listbox" style="max-height: 174px;" - > -
  • -
    - stripes-acq-components.filter.acqUnit.noAcqUnit -
    -
  • - + /> diff --git a/src/invoices/Voucher/VoucherEditForm/__snapshots__/VoucherEditForm.test.js.snap b/src/invoices/Voucher/VoucherEditForm/__snapshots__/VoucherEditForm.test.js.snap index dad58803..51470546 100644 --- a/src/invoices/Voucher/VoucherEditForm/__snapshots__/VoucherEditForm.test.js.snap +++ b/src/invoices/Voucher/VoucherEditForm/__snapshots__/VoucherEditForm.test.js.snap @@ -327,7 +327,6 @@ exports[`VoucherEditForm should render correct voucher form structure 1`] = ` -
    `; @@ -673,6 +672,5 @@ exports[`VoucherEditForm should render correct voucher form structure with edita
    -
    `; diff --git a/src/settings/BatchGroupConfigurationSettings/__snapshots__/BatchGroupConfigurationForm.test.js.snap b/src/settings/BatchGroupConfigurationSettings/__snapshots__/BatchGroupConfigurationForm.test.js.snap index 55a6e9da..b52d8b9b 100644 --- a/src/settings/BatchGroupConfigurationSettings/__snapshots__/BatchGroupConfigurationForm.test.js.snap +++ b/src/settings/BatchGroupConfigurationSettings/__snapshots__/BatchGroupConfigurationForm.test.js.snap @@ -6,7 +6,6 @@ exports[`BatchGroupConfigurationForm component should render correct structure 1 class="pane focusIndicator" data-test-batch-group-configuration-settings="true" id="pane-batch-group-configuration" - tabindex="-1" >
    -
    `; From 7a2aa0959d27268db9768fb139efe9909a9ec937 Mon Sep 17 00:00:00 2001 From: Alisher Musurmonov Date: Mon, 7 Oct 2024 12:47:09 +0500 Subject: [PATCH 04/11] refactor: update code quality and test cases --- .../InvoiceDetails/utils/errorHandlers.js | 95 +++++++++++++++++++ src/invoices/InvoiceDetails/utils/index.js | 1 + .../InvoiceDetails/{ => utils}/utils.js | 94 +++++------------- src/invoices/InvoiceForm/InvoiceForm.test.js | 2 +- .../FiscalYearFilter/FiscalYearFilter.test.js | 10 +- 5 files changed, 127 insertions(+), 75 deletions(-) create mode 100644 src/invoices/InvoiceDetails/utils/errorHandlers.js create mode 100644 src/invoices/InvoiceDetails/utils/index.js rename src/invoices/InvoiceDetails/{ => utils}/utils.js (69%) diff --git a/src/invoices/InvoiceDetails/utils/errorHandlers.js b/src/invoices/InvoiceDetails/utils/errorHandlers.js new file mode 100644 index 00000000..9b027027 --- /dev/null +++ b/src/invoices/InvoiceDetails/utils/errorHandlers.js @@ -0,0 +1,95 @@ +import { + EXPENSE_CLASSES_API, + FUNDS_API, +} from '@folio/stripes-acq-components'; + +import { + ERROR_CODES, + FISCAL_YEARS_API, +} from '../../../common/constants'; + +export const handleBudgetNotFoundByFundIdAndFiscalYearId = async ({ + action, + code, + defaultErrorMessageId, + error, + fundMutator, + ky, + showCallout, +}) => { + const errors = error?.errors?.[0]?.parameters; + let fundId = errors?.find(({ key }) => key === 'fundId')?.value; + const fiscalYearId = errors?.find(({ key }) => key === 'fiscalYearId')?.value; + + if (!fundId) { + fundId = errors?.find(({ key }) => key === 'fund')?.value; + } + + if (fundId) { + return fundMutator.GET({ path: `${FUNDS_API}/${fundId}` }) + .then(async ({ fund }) => { + let fiscalYear = {}; + + if (fiscalYearId) { + try { + fiscalYear = await ky.get(`${FISCAL_YEARS_API}/${fiscalYearId}`).json(); + } catch { + fiscalYear = {}; + } + } + + showCallout({ + messageId: `ui-invoice.invoice.actions.${action}.error.${ERROR_CODES[code]}`, + type: 'error', + values: { + fundCode: fund?.code, + fiscalYear: fiscalYear?.code, + }, + }); + }, () => { + showCallout({ + messageId: defaultErrorMessageId, + type: 'error', + }); + }); + } else { + return showCallout({ + messageId: defaultErrorMessageId, + type: 'error', + }); + } +}; + +export const handleInactiveExpenseClass = async ({ + action, + code, + defaultErrorMessageId, + error, + expenseClassMutator, + showCallout, +}) => { + const expenseClassId = error?.errors?.[0]?.parameters?.find(({ key }) => key === 'expenseClassId')?.value; + const expenseClassName = error?.errors?.[0]?.parameters?.find(({ key }) => key === 'expenseClassName')?.value; + + if (expenseClassId || expenseClassName) { + const expenseClassPromise = expenseClassName + ? Promise.resolve({ name: expenseClassName }) + : expenseClassMutator.GET({ path: `${EXPENSE_CLASSES_API}/${expenseClassId}` }); + + expenseClassPromise + .then(({ name }) => { + const values = { expenseClass: name }; + + showCallout({ + messageId: `ui-invoice.invoice.actions.${action}.error.${ERROR_CODES[code]}`, + type: 'error', + values, + }); + }); + } else { + showCallout({ + messageId: defaultErrorMessageId, + type: 'error', + }); + } +}; diff --git a/src/invoices/InvoiceDetails/utils/index.js b/src/invoices/InvoiceDetails/utils/index.js new file mode 100644 index 00000000..04bca77e --- /dev/null +++ b/src/invoices/InvoiceDetails/utils/index.js @@ -0,0 +1 @@ +export * from './utils'; diff --git a/src/invoices/InvoiceDetails/utils.js b/src/invoices/InvoiceDetails/utils/utils.js similarity index 69% rename from src/invoices/InvoiceDetails/utils.js rename to src/invoices/InvoiceDetails/utils/utils.js index 10f86f83..b2e44075 100644 --- a/src/invoices/InvoiceDetails/utils.js +++ b/src/invoices/InvoiceDetails/utils/utils.js @@ -1,19 +1,15 @@ -import { noop } from 'lodash'; - -import { - EXPENSE_CLASSES_API, - FUNDS_API, -} from '@folio/stripes-acq-components'; +import noop from 'lodash/noop'; import { ERROR_CODES, - FISCAL_YEARS_API, INVOICE_STATUS, -} from '../../common/constants'; +} from '../../../common/constants'; +import { convertToInvoiceLineFields } from '../../utils'; +import { ACQ_ERROR_TYPE } from '../constants'; import { - convertToInvoiceLineFields, -} from '../utils'; -import { ACQ_ERROR_TYPE } from './constants'; + handleBudgetNotFoundByFundIdAndFiscalYearId, + handleInactiveExpenseClass, +} from './errorHandlers'; export const createInvoiceLineFromPOL = (poLine, invoiceId, vendor) => { return { @@ -109,71 +105,29 @@ export const showUpdateInvoiceError = async ({ break; } case ERROR_CODES.inactiveExpenseClass: { - const expenseClassId = error?.errors?.[0]?.parameters?.find(({ key }) => key === 'expenseClassId')?.value; - const expenseClassName = error?.errors?.[0]?.parameters?.find(({ key }) => key === 'expenseClassName')?.value; - - if (expenseClassId || expenseClassName) { - const expenseClassPromise = expenseClassName - ? Promise.resolve({ name: expenseClassName }) - : expenseClassMutator.GET({ path: `${EXPENSE_CLASSES_API}/${expenseClassId}` }); - - expenseClassPromise - .then(({ name }) => { - const values = { expenseClass: name }; + await handleInactiveExpenseClass({ + action, + code, + defaultErrorMessageId, + error, + expenseClassMutator, + showCallout, + }); - showCallout({ - messageId: `ui-invoice.invoice.actions.${action}.error.${ERROR_CODES[code]}`, - type: 'error', - values, - }); - }); - } else { - showCallout({ - messageId: defaultErrorMessageId, - type: 'error', - }); - } break; } case ERROR_CODES.budgetNotFoundByFundId: case ERROR_CODES.budgetNotFoundByFundIdAndFiscalYearId: { - const errors = error?.errors?.[0]?.parameters; - let fundId = errors?.find(({ key }) => key === 'fundId')?.value; - const fiscalYearId = errors?.find(({ key }) => key === 'fiscalYearId')?.value; - - if (!fundId) { - fundId = errors?.find(({ key }) => key === 'fund')?.value; - } - - if (fundId) { - fundMutator.GET({ path: `${FUNDS_API}/${fundId}` }) - .then(async ({ fund }) => { - let fiscalYear = {}; - - if (fiscalYearId) { - fiscalYear = await ky.get(`${FISCAL_YEARS_API}/${fiscalYearId}`).json(); - } + await handleBudgetNotFoundByFundIdAndFiscalYearId({ + action, + code, + defaultErrorMessageId, + error, + fundMutator, + ky, + showCallout, + }); - showCallout({ - messageId: `ui-invoice.invoice.actions.${action}.error.${ERROR_CODES[code]}`, - type: 'error', - values: { - fundCode: fund?.code, - fiscalYear: fiscalYear?.code, - }, - }); - }, () => { - showCallout({ - messageId: defaultErrorMessageId, - type: 'error', - }); - }); - } else { - showCallout({ - messageId: defaultErrorMessageId, - type: 'error', - }); - } break; } default: { diff --git a/src/invoices/InvoiceForm/InvoiceForm.test.js b/src/invoices/InvoiceForm/InvoiceForm.test.js index 23b9954b..d881fdd4 100644 --- a/src/invoices/InvoiceForm/InvoiceForm.test.js +++ b/src/invoices/InvoiceForm/InvoiceForm.test.js @@ -126,7 +126,7 @@ describe('InvoiceForm component', () => { const accountLabel = `${accountNo} (${appSystemNo})`; expect(container.querySelector('#selected-accounting-code-selection-item')).toHaveTextContent(''); - await userEvent.click(screen.getAllByText('stripes-components.selection.controlLabel')[4]); + await userEvent.click(container.querySelector('#accounting-code-selection')); await userEvent.click(screen.getByText(accountLabel)); diff --git a/src/invoices/InvoicesList/InvoicesListFilters/FiscalYearFilter/FiscalYearFilter.test.js b/src/invoices/InvoicesList/InvoicesListFilters/FiscalYearFilter/FiscalYearFilter.test.js index 0d079257..49e926c2 100644 --- a/src/invoices/InvoicesList/InvoicesListFilters/FiscalYearFilter/FiscalYearFilter.test.js +++ b/src/invoices/InvoicesList/InvoicesListFilters/FiscalYearFilter/FiscalYearFilter.test.js @@ -1,5 +1,7 @@ -import { noop } from 'lodash'; -import { fireEvent, render, screen } from '@folio/jest-config-stripes/testing-library/react'; +import noop from 'lodash/noop'; + +import { render, screen } from '@folio/jest-config-stripes/testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import { useFiscalYears } from '../../../../common/hooks'; import { FiscalYearFilter } from './FiscalYearFilter'; @@ -29,11 +31,11 @@ describe('FiscalYearFilter', () => { useFiscalYears.mockClear().mockReturnValue({ fiscalYears: fiscalYearsMock, isLoading: false }); }); - it('should display filter title', () => { + it('should display filter title', async () => { renderFilter(); expect(screen.getByText(labelId)).toBeInTheDocument(); - fireEvent.click(screen.getByText('stripes-components.selection.controlLabel')); + await userEvent.click(screen.getByText('stripes-components.selection.controlLabel')); expect(screen.getByText(fiscalYearsMock[0].code)).toBeInTheDocument(); expect(screen.getByText(fiscalYearsMock[1].code)).toBeInTheDocument(); }); From 58ab567a211310f01f48983e3433c6282bbcf3f5 Mon Sep 17 00:00:00 2001 From: Alisher Musurmonov Date: Mon, 7 Oct 2024 13:19:49 +0500 Subject: [PATCH 05/11] test: update snapshot tests --- .../__snapshots__/InvoiceDetails.test.js.snap | 1691 +++++++------- .../__snapshots__/InvoiceForm.test.js.snap | 266 ++- .../InvoiceLineForm.test.js.snap | 159 +- .../InvoicesListFilters.test.js.snap | 1976 +++++++++-------- 4 files changed, 2189 insertions(+), 1903 deletions(-) diff --git a/src/invoices/InvoiceDetails/__snapshots__/InvoiceDetails.test.js.snap b/src/invoices/InvoiceDetails/__snapshots__/InvoiceDetails.test.js.snap index 08eb2ed8..443a3d03 100644 --- a/src/invoices/InvoiceDetails/__snapshots__/InvoiceDetails.test.js.snap +++ b/src/invoices/InvoiceDetails/__snapshots__/InvoiceDetails.test.js.snap @@ -170,41 +170,44 @@ exports[`InvoiceDetails should render correct structure for Approved invoice 1`] id="information" >
    -

    - -

    + + +
    -

    - -

    -
    -
    - InvoiceLinesActions + + +
    +
    + InvoiceLinesActions +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    -
    -
    - InvoiceLinesActions + + +
    +
    + InvoiceLinesActions +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    -
    -
    - InvoiceLinesActions + + +
    +
    + InvoiceLinesActions +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    -
    -
    - InvoiceLinesActions + + +
    +
    + InvoiceLinesActions +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    -
    -
    - InvoiceLinesActions + + +
    +
    + InvoiceLinesActions +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    -
    -
    - InvoiceLinesActions + + +
    +
    + InvoiceLinesActions +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    - -

    + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    -

    -

    - - + + +
    Date: Mon, 7 Oct 2024 13:58:54 +0500 Subject: [PATCH 06/11] test: updated test cases --- src/invoices/InvoiceDetails/utils.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/invoices/InvoiceDetails/utils.test.js b/src/invoices/InvoiceDetails/utils.test.js index 34afefa9..5a495e86 100644 --- a/src/invoices/InvoiceDetails/utils.test.js +++ b/src/invoices/InvoiceDetails/utils.test.js @@ -219,7 +219,7 @@ describe('showUpdateInvoiceError', () => { ['userHasNoPermission', 'ui-invoice.invoice.actions.error.userHasNoPermission', []], ['userNotAMemberOfTheAcq', 'ui-invoice.invoice.actions.error.userNotAMemberOfTheAcq', []], ['fundCannotBePaid', 'ui-invoice.invoice.actions.approve.error.fundCannotBePaid', ['funds']], - ['budgetNotFoundByFundIdAndFiscalYearId', 'ui-invoice.invoice.actions.approve.error.budgetNotFoundByFundIdAndFiscalYearId', ['fundId']], + ['budgetNotFoundByFundIdAndFiscalYearId', 'ui-invoice.invoice.actions.approve.error.budgetNotFoundByFundIdAndFiscalYearId', ['fundId', 'fiscalYearId']], ])('should get %s error message', async (code, messageId, key) => { const mockActionName = 'approve'; const parameters = key.map(k => ({ key: k, value: 'value' })); @@ -230,7 +230,7 @@ describe('showUpdateInvoiceError', () => { } else if (code === 'fundCannotBePaid') { values = { values: { fundCodes: 'value' } }; } else if (code === 'budgetNotFoundByFundIdAndFiscalYearId') { - values = { values: { fundCode: 'value' } }; + values = { values: { fundCode: 'value', fiscalYear: undefined } }; } else if (code === 'userNotAMemberOfTheAcq' || code === 'userHasNoPermission') { values = {}; } From a5120fcd259c0fe147733801f3950fe24b53c722 Mon Sep 17 00:00:00 2001 From: Yury Saukou Date: Mon, 7 Oct 2024 14:38:09 +0400 Subject: [PATCH 07/11] apply strategies pattern for some errors handling --- .../InvoiceDetails/utils/errorHandlers.js | 118 +++++++++--------- src/invoices/InvoiceDetails/utils/utils.js | 32 ++--- 2 files changed, 73 insertions(+), 77 deletions(-) diff --git a/src/invoices/InvoiceDetails/utils/errorHandlers.js b/src/invoices/InvoiceDetails/utils/errorHandlers.js index 9b027027..e414b98d 100644 --- a/src/invoices/InvoiceDetails/utils/errorHandlers.js +++ b/src/invoices/InvoiceDetails/utils/errorHandlers.js @@ -8,88 +8,90 @@ import { FISCAL_YEARS_API, } from '../../../common/constants'; -export const handleBudgetNotFoundByFundIdAndFiscalYearId = async ({ +export const handleBudgetNotFoundByFundIdAndFiscalYearId = ({ action, code, defaultErrorMessageId, - error, fundMutator, ky, showCallout, }) => { - const errors = error?.errors?.[0]?.parameters; - let fundId = errors?.find(({ key }) => key === 'fundId')?.value; - const fiscalYearId = errors?.find(({ key }) => key === 'fiscalYearId')?.value; + const handle = async (errorsContainer) => { + const error = errorsContainer.getError(); + const fundId = error.getParameter('fundId') || error.getParameter('fund'); + const fiscalYearId = error.getParameter('fiscalYearId'); - if (!fundId) { - fundId = errors?.find(({ key }) => key === 'fund')?.value; - } + if (fundId) { + return fundMutator.GET({ path: `${FUNDS_API}/${fundId}` }) + .then(async ({ fund }) => { + let fiscalYear = {}; - if (fundId) { - return fundMutator.GET({ path: `${FUNDS_API}/${fundId}` }) - .then(async ({ fund }) => { - let fiscalYear = {}; - - if (fiscalYearId) { - try { - fiscalYear = await ky.get(`${FISCAL_YEARS_API}/${fiscalYearId}`).json(); - } catch { - fiscalYear = {}; + if (fiscalYearId) { + try { + fiscalYear = await ky.get(`${FISCAL_YEARS_API}/${fiscalYearId}`).json(); + } catch { + fiscalYear = {}; + } } - } - showCallout({ - messageId: `ui-invoice.invoice.actions.${action}.error.${ERROR_CODES[code]}`, - type: 'error', - values: { - fundCode: fund?.code, - fiscalYear: fiscalYear?.code, - }, - }); - }, () => { - showCallout({ - messageId: defaultErrorMessageId, - type: 'error', + showCallout({ + messageId: `ui-invoice.invoice.actions.${action}.error.${ERROR_CODES[code]}`, + type: 'error', + values: { + fundCode: fund?.code, + fiscalYear: fiscalYear?.code, + }, + }); + }, () => { + showCallout({ + messageId: defaultErrorMessageId, + type: 'error', + }); }); + } else { + return showCallout({ + messageId: defaultErrorMessageId, + type: 'error', }); - } else { - return showCallout({ - messageId: defaultErrorMessageId, - type: 'error', - }); - } + } + }; + + return { handle }; }; -export const handleInactiveExpenseClass = async ({ +export const handleInactiveExpenseClass = ({ action, code, defaultErrorMessageId, - error, expenseClassMutator, showCallout, }) => { - const expenseClassId = error?.errors?.[0]?.parameters?.find(({ key }) => key === 'expenseClassId')?.value; - const expenseClassName = error?.errors?.[0]?.parameters?.find(({ key }) => key === 'expenseClassName')?.value; + const handle = async (errorsContainer) => { + const expenseClassId = errorsContainer.getError().getParameter('expenseClassId'); + const expenseClassName = errorsContainer.getError().getParameter('expenseClassName'); - if (expenseClassId || expenseClassName) { - const expenseClassPromise = expenseClassName - ? Promise.resolve({ name: expenseClassName }) - : expenseClassMutator.GET({ path: `${EXPENSE_CLASSES_API}/${expenseClassId}` }); + if (expenseClassId || expenseClassName) { + const expenseClassPromise = expenseClassName + ? Promise.resolve({ name: expenseClassName }) + : expenseClassMutator.GET({ path: `${EXPENSE_CLASSES_API}/${expenseClassId}` }); - expenseClassPromise - .then(({ name }) => { - const values = { expenseClass: name }; + return expenseClassPromise + .then(({ name }) => { + const values = { expenseClass: name }; - showCallout({ - messageId: `ui-invoice.invoice.actions.${action}.error.${ERROR_CODES[code]}`, - type: 'error', - values, + showCallout({ + messageId: `ui-invoice.invoice.actions.${action}.error.${ERROR_CODES[code]}`, + type: 'error', + values, + }); }); + } else { + return showCallout({ + messageId: defaultErrorMessageId, + type: 'error', }); - } else { - showCallout({ - messageId: defaultErrorMessageId, - type: 'error', - }); - } + } + }; + + return { handle }; }; diff --git a/src/invoices/InvoiceDetails/utils/utils.js b/src/invoices/InvoiceDetails/utils/utils.js index b2e44075..76315320 100644 --- a/src/invoices/InvoiceDetails/utils/utils.js +++ b/src/invoices/InvoiceDetails/utils/utils.js @@ -1,5 +1,7 @@ import noop from 'lodash/noop'; +import { ResponseErrorsContainer } from '@folio/stripes-acq-components'; + import { ERROR_CODES, INVOICE_STATUS, @@ -30,15 +32,9 @@ export const showUpdateInvoiceError = async ({ messageValues = {}, ky = {}, }) => { - let error; - - try { - error = await response.clone().json(); - } catch (parsingException) { - error = response; - } + const { handler } = await ResponseErrorsContainer.create(response); - const errorCode = error?.errors?.[0]?.code; + const errorCode = handler.getError().code; const code = ERROR_CODES[errorCode]; switch (code) { @@ -68,7 +64,7 @@ export const showUpdateInvoiceError = async ({ } case ERROR_CODES.userHasNoPermission: case ERROR_CODES.userNotAMemberOfTheAcq: { - const acqErrorType = error?.errors?.[0]?.parameters?.filter(({ key }) => key === 'type')[0]?.value; + const acqErrorType = handler.getError().getParameter('type'); const messageId = `ui-invoice.invoice.actions.error.${ERROR_CODES[code]}`; if (acqErrorType === ACQ_ERROR_TYPE.order) { @@ -86,47 +82,45 @@ export const showUpdateInvoiceError = async ({ break; } case ERROR_CODES.fundCannotBePaid: { - const fundCodes = error?.errors?.[0]?.parameters?.filter(({ key }) => key === 'funds')[0]?.value; + const fundCodes = handler.getError().getParameter('funds'); showCallout({ messageId: `ui-invoice.invoice.actions.approve.error.${ERROR_CODES[code]}`, type: 'error', values: { fundCodes } }); break; } case ERROR_CODES.incorrectFundDistributionTotal: { - const invoiceLineNumber = error?.errors?.[0]?.parameters?.filter(({ key }) => key === 'invoiceLineNumber')[0]?.value; + const invoiceLineNumber = handler.getError().getParameter('invoiceLineNumber'); showCallout({ messageId: `ui-invoice.invoice.actions.approve.error.${ERROR_CODES[code]}`, type: 'error', values: { invoiceLineNumber } }); break; } case ERROR_CODES.budgetExpenseClassNotFound: { - const fundCode = error?.errors?.[0]?.parameters?.filter(({ key }) => key === 'fundCode')[0]?.value; - const expenseClassName = error?.errors[0]?.parameters?.filter(({ key }) => key === 'expenseClassName')[0]?.value; + const fundCode = handler.getError().getParameter('fundCode'); + const expenseClassName = handler.getError().getParameter('expenseClassName'); showCallout({ messageId: `ui-invoice.invoice.actions.${action}.error.${code}`, type: 'error', values: { fundCode, expenseClassName } }); break; } case ERROR_CODES.inactiveExpenseClass: { - await handleInactiveExpenseClass({ + await handler.handle(handleInactiveExpenseClass({ action, code, defaultErrorMessageId, - error, expenseClassMutator, showCallout, - }); + })); break; } case ERROR_CODES.budgetNotFoundByFundId: case ERROR_CODES.budgetNotFoundByFundIdAndFiscalYearId: { - await handleBudgetNotFoundByFundIdAndFiscalYearId({ + await handler.handle(handleBudgetNotFoundByFundIdAndFiscalYearId({ action, code, defaultErrorMessageId, - error, fundMutator, ky, showCallout, - }); + })); break; } From 1c012fdf1c50173abfd97bae9b038d846e4633cf Mon Sep 17 00:00:00 2001 From: Yury Saukou Date: Mon, 7 Oct 2024 14:46:22 +0400 Subject: [PATCH 08/11] update function names for strategies --- src/invoices/InvoiceDetails/utils/errorHandlers.js | 4 ++-- src/invoices/InvoiceDetails/utils/utils.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/invoices/InvoiceDetails/utils/errorHandlers.js b/src/invoices/InvoiceDetails/utils/errorHandlers.js index e414b98d..1b3f9bff 100644 --- a/src/invoices/InvoiceDetails/utils/errorHandlers.js +++ b/src/invoices/InvoiceDetails/utils/errorHandlers.js @@ -8,7 +8,7 @@ import { FISCAL_YEARS_API, } from '../../../common/constants'; -export const handleBudgetNotFoundByFundIdAndFiscalYearId = ({ +export const noBudgetForFiscalYearStrategy = ({ action, code, defaultErrorMessageId, @@ -59,7 +59,7 @@ export const handleBudgetNotFoundByFundIdAndFiscalYearId = ({ return { handle }; }; -export const handleInactiveExpenseClass = ({ +export const inactiveExpenseClassStrategy = ({ action, code, defaultErrorMessageId, diff --git a/src/invoices/InvoiceDetails/utils/utils.js b/src/invoices/InvoiceDetails/utils/utils.js index 76315320..6a2aa781 100644 --- a/src/invoices/InvoiceDetails/utils/utils.js +++ b/src/invoices/InvoiceDetails/utils/utils.js @@ -9,8 +9,8 @@ import { import { convertToInvoiceLineFields } from '../../utils'; import { ACQ_ERROR_TYPE } from '../constants'; import { - handleBudgetNotFoundByFundIdAndFiscalYearId, - handleInactiveExpenseClass, + inactiveExpenseClassStrategy, + noBudgetForFiscalYearStrategy, } from './errorHandlers'; export const createInvoiceLineFromPOL = (poLine, invoiceId, vendor) => { @@ -101,7 +101,7 @@ export const showUpdateInvoiceError = async ({ break; } case ERROR_CODES.inactiveExpenseClass: { - await handler.handle(handleInactiveExpenseClass({ + await handler.handle(inactiveExpenseClassStrategy({ action, code, defaultErrorMessageId, @@ -113,7 +113,7 @@ export const showUpdateInvoiceError = async ({ } case ERROR_CODES.budgetNotFoundByFundId: case ERROR_CODES.budgetNotFoundByFundIdAndFiscalYearId: { - await handler.handle(handleBudgetNotFoundByFundIdAndFiscalYearId({ + await handler.handle(noBudgetForFiscalYearStrategy({ action, code, defaultErrorMessageId, From 147ebc2fc588e2c0a04ced64f2704a29d947d99f Mon Sep 17 00:00:00 2001 From: Alisher Musurmonov Date: Mon, 7 Oct 2024 18:55:36 +0500 Subject: [PATCH 09/11] test: fix failing tests --- src/invoices/InvoiceForm/InvoiceForm.test.js | 2 -- .../FiscalYearFilter/FiscalYearFilter.test.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/invoices/InvoiceForm/InvoiceForm.test.js b/src/invoices/InvoiceForm/InvoiceForm.test.js index c315abaf..e650e340 100644 --- a/src/invoices/InvoiceForm/InvoiceForm.test.js +++ b/src/invoices/InvoiceForm/InvoiceForm.test.js @@ -129,7 +129,6 @@ describe('InvoiceForm component', () => { const accountLabel = `${accountNo} (${appSystemNo})`; expect(container.querySelector('#selected-accounting-code-selection-item')).toHaveTextContent(''); - await userEvent.click(container.querySelector('#accounting-code-selection')); await userEvent.click(screen.getByRole('button', { name: /invoice.accountingCode/ })); await userEvent.click(screen.getByText(accountLabel)); @@ -271,7 +270,6 @@ describe('InvoiceForm component', () => { const fiscalYearLabel = await screen.findByText(labelId); expect(fiscalYearLabel.innerHTML).toBe(labelIdRequired); - await userEvent.click(screen.getAllByText('stripes-components.selection.controlLabel')[0]); await userEvent.click(screen.getByRole('button', { name: /details.information.fiscalYear / })); diff --git a/src/invoices/InvoicesList/InvoicesListFilters/FiscalYearFilter/FiscalYearFilter.test.js b/src/invoices/InvoicesList/InvoicesListFilters/FiscalYearFilter/FiscalYearFilter.test.js index 14a860d7..010016c6 100644 --- a/src/invoices/InvoicesList/InvoicesListFilters/FiscalYearFilter/FiscalYearFilter.test.js +++ b/src/invoices/InvoicesList/InvoicesListFilters/FiscalYearFilter/FiscalYearFilter.test.js @@ -37,7 +37,7 @@ describe('FiscalYearFilter', () => { await userEvent.click(screen.getByRole('button', { name: '' })); expect(screen.getByText(labelId)).toBeInTheDocument(); - await userEvent.click(screen.getByText('stripes-components.selection.controlLabel')); + expect(screen.getByText(fiscalYearsMock[0].code)).toBeInTheDocument(); expect(screen.getByText(fiscalYearsMock[1].code)).toBeInTheDocument(); }); From 088df9512811000205776d42553b3b3994652ded Mon Sep 17 00:00:00 2001 From: Alisher Musurmonov Date: Mon, 7 Oct 2024 19:05:19 +0500 Subject: [PATCH 10/11] test: move test into utils folder --- src/invoices/InvoiceDetails/{ => utils}/utils.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/invoices/InvoiceDetails/{ => utils}/utils.test.js (99%) diff --git a/src/invoices/InvoiceDetails/utils.test.js b/src/invoices/InvoiceDetails/utils/utils.test.js similarity index 99% rename from src/invoices/InvoiceDetails/utils.test.js rename to src/invoices/InvoiceDetails/utils/utils.test.js index 5a495e86..309d2efe 100644 --- a/src/invoices/InvoiceDetails/utils.test.js +++ b/src/invoices/InvoiceDetails/utils/utils.test.js @@ -1,4 +1,4 @@ -import { ERROR_CODES } from '../../common/constants'; +import { ERROR_CODES } from '../../../common/constants'; import { handleInvoiceLineErrors, handleInvoiceLinesCreation, From a424a918cc55f8ab9f5bc892bb6f97baf5f65fc0 Mon Sep 17 00:00:00 2001 From: Alisher Musurmonov Date: Tue, 8 Oct 2024 11:33:25 +0500 Subject: [PATCH 11/11] remove noop --- src/invoices/InvoiceDetails/utils/utils.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/invoices/InvoiceDetails/utils/utils.js b/src/invoices/InvoiceDetails/utils/utils.js index 6a2aa781..98ded804 100644 --- a/src/invoices/InvoiceDetails/utils/utils.js +++ b/src/invoices/InvoiceDetails/utils/utils.js @@ -1,5 +1,3 @@ -import noop from 'lodash/noop'; - import { ResponseErrorsContainer } from '@folio/stripes-acq-components'; import { @@ -139,7 +137,7 @@ export const handleInvoiceLineErrors = async ({ requestData = [], responses = [], showCallout, - ky = noop, + ky = {}, }) => { const errors = responses.filter(({ status }) => status === 'rejected');