@digihmis/esm-administration-app 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/101.js +1 -0
- package/dist/101.js.map +1 -0
- package/dist/117.js +1 -0
- package/dist/117.js.map +1 -0
- package/dist/132.js +1 -0
- package/dist/132.js.map +1 -0
- package/dist/15.js +1 -0
- package/dist/15.js.map +1 -0
- package/dist/152.js +1 -0
- package/dist/152.js.map +1 -0
- package/dist/153.js +1 -0
- package/dist/153.js.map +1 -0
- package/dist/190.js +1 -0
- package/dist/190.js.map +1 -0
- package/dist/209.js +1 -0
- package/dist/209.js.map +1 -0
- package/dist/266.js +1 -0
- package/dist/266.js.map +1 -0
- package/dist/317.js +1 -0
- package/dist/317.js.map +1 -0
- package/dist/349.js +1 -0
- package/dist/349.js.map +1 -0
- package/dist/371.js +1 -0
- package/dist/371.js.map +1 -0
- package/dist/378.js +1 -0
- package/dist/378.js.map +1 -0
- package/dist/394.js +1 -0
- package/dist/394.js.map +1 -0
- package/dist/442.js +1 -0
- package/dist/442.js.map +1 -0
- package/dist/45.js +1 -0
- package/dist/45.js.map +1 -0
- package/dist/454.js +1 -0
- package/dist/454.js.map +1 -0
- package/dist/466.js +1 -0
- package/dist/466.js.map +1 -0
- package/dist/482.js +1 -0
- package/dist/482.js.map +1 -0
- package/dist/501.js +1 -0
- package/dist/501.js.map +1 -0
- package/dist/508.js +1 -0
- package/dist/508.js.map +1 -0
- package/dist/578.js +1 -0
- package/dist/578.js.map +1 -0
- package/dist/598.js +1 -0
- package/dist/598.js.map +1 -0
- package/dist/606.js +1 -0
- package/dist/606.js.map +1 -0
- package/dist/61.js +1 -0
- package/dist/61.js.map +1 -0
- package/dist/640.js +1 -0
- package/dist/640.js.map +1 -0
- package/dist/685.js +1 -0
- package/dist/685.js.map +1 -0
- package/dist/689.js +1 -0
- package/dist/689.js.map +1 -0
- package/dist/709.js +1 -0
- package/dist/709.js.map +1 -0
- package/dist/712.js +1 -0
- package/dist/712.js.map +1 -0
- package/dist/720.js +1 -0
- package/dist/720.js.map +1 -0
- package/dist/737.js +12 -0
- package/dist/737.js.map +1 -0
- package/dist/742.js +1 -0
- package/dist/742.js.map +1 -0
- package/dist/747.js +1 -0
- package/dist/747.js.map +1 -0
- package/dist/771.js +1 -0
- package/dist/771.js.map +1 -0
- package/dist/806.js +1 -0
- package/dist/806.js.map +1 -0
- package/dist/912.js +1 -0
- package/dist/912.js.map +1 -0
- package/dist/913.js +1 -0
- package/dist/913.js.map +1 -0
- package/dist/916.js +15 -0
- package/dist/916.js.map +1 -0
- package/dist/940.js +1 -0
- package/dist/940.js.map +1 -0
- package/dist/digihmis-esm-administration-app.js +6 -0
- package/dist/digihmis-esm-administration-app.js.buildmanifest.json +1100 -0
- package/dist/digihmis-esm-administration-app.js.map +1 -0
- package/dist/main.js +17 -0
- package/dist/main.js.map +1 -0
- package/dist/routes.json +1 -0
- package/increment-version.js +17 -0
- package/jest.config.js +26 -0
- package/package.json +51 -0
- package/release-version.js +16 -0
- package/rspack.config.js +1 -0
- package/src/bed-management/bed-management.component.tsx +19 -0
- package/src/bed-management/bed-management.scss +0 -0
- package/src/billing-management/billing-charge-items/billing-charge-items-modal/billing-charge-items.modal.tsx +79 -0
- package/src/billing-management/billing-charge-items/billing-charge-items-modal/billing-charge-items.resource.ts +11 -0
- package/src/billing-management/billing-charge-items/billing-charge-items-workspace/billing-charge-item.workspace.tsx +212 -0
- package/src/billing-management/billing-charge-items/billing-charge-items-workspace/billing-charge-items.resource.ts +51 -0
- package/src/billing-management/billing-charge-items/billing-charge-items-workspace/billing-charge-service.workspace.tsx +296 -0
- package/src/billing-management/billing-charge-items/billing-charge-items-workspace/components/concept-search.component.tsx +67 -0
- package/src/billing-management/billing-charge-items/billing-charge-items-workspace/components/price.component.tsx +81 -0
- package/src/billing-management/billing-charge-items/billing-charge-items-workspace/components/stock-search.component.tsx +107 -0
- package/src/billing-management/billing-charge-items/billing-charge-items-workspace/global/billing-charge-commodity.scss +61 -0
- package/src/billing-management/billing-charge-items/billing-charge-items-workspace/global/billing-charge-items.scss +59 -0
- package/src/billing-management/billing-charge-items/billing-charge-items-workspace/schemas/form-schemas.ts +33 -0
- package/src/billing-management/billing-charge-items/billing-charge-items.component.tsx +222 -0
- package/src/billing-management/billing-charge-items/billing-charge-items.resource.ts +20 -0
- package/src/billing-management/billing-charge-items/billing-charge-items.scss +84 -0
- package/src/billing-management/billing-charge-items/type/index.ts +88 -0
- package/src/billing-management/billing-charge-items/utils/index.ts +96 -0
- package/src/billing-management/billing-management.component.tsx +21 -0
- package/src/billing-management/billing-management.scss +0 -0
- package/src/billing-management/billing-payment-modes/billing-payment-modes-modal/billing-delete-payment-modes.modal.scss +28 -0
- package/src/billing-management/billing-payment-modes/billing-payment-modes-modal/billing-delete-payment-modes.modal.tsx +67 -0
- package/src/billing-management/billing-payment-modes/billing-payment-modes-modal/delete-payment-mode.test.tsx +56 -0
- package/src/billing-management/billing-payment-modes/billing-payment-modes-workspace/billing-payment-modes-form-schema.ts +52 -0
- package/src/billing-management/billing-payment-modes/billing-payment-modes-workspace/billing-payment-modes.resource.ts +35 -0
- package/src/billing-management/billing-payment-modes/billing-payment-modes-workspace/billing-payment-modes.workspace.scss +33 -0
- package/src/billing-management/billing-payment-modes/billing-payment-modes-workspace/billing-payment-modes.workspace.test.tsx +239 -0
- package/src/billing-management/billing-payment-modes/billing-payment-modes-workspace/billing-payment-modes.workspace.tsx +218 -0
- package/src/billing-management/billing-payment-modes/billing-payment-modes-workspace/payment-attributes/payment-mode-attributes.component.tsx +156 -0
- package/src/billing-management/billing-payment-modes/billing-payment-modes-workspace/payment-attributes/payment-mode-attributes.scss +10 -0
- package/src/billing-management/billing-payment-modes/billing-payment-modes.component.tsx +243 -0
- package/src/billing-management/billing-payment-modes/billing-payment-modes.resource.ts +23 -0
- package/src/billing-management/billing-payment-modes/billing-payment-modes.scss +140 -0
- package/src/billing-management/billing-payment-modes/type/index.ts +42 -0
- package/src/billing-management/billing-payment-points/billing-payment-point-modal/billing-delete-payment-modes.modal.scss +28 -0
- package/src/billing-management/billing-payment-points/billing-payment-point-modal/billing-delete-payment-point.modal.tsx +72 -0
- package/src/billing-management/billing-payment-points/billing-payment-point-modal/delete-payment-point.test.tsx +56 -0
- package/src/billing-management/billing-payment-points/billing-payment-point.component.tsx +202 -0
- package/src/billing-management/billing-payment-points/billing-payment-point.resource.ts +16 -0
- package/src/billing-management/billing-payment-points/billing-payment-point.scss +0 -0
- package/src/billing-management/billing-payment-points/billing-payment-points-workspace/billing-payment-point.resource.ts +39 -0
- package/src/billing-management/billing-payment-points/billing-payment-points-workspace/billing-payment-point.workspace.scss +33 -0
- package/src/billing-management/billing-payment-points/billing-payment-points-workspace/billing-payment-points.workspace.tsx +194 -0
- package/src/billing-management/billing-payment-points/type/index.ts +35 -0
- package/src/billing-management/billing-tabs/billing-tabs.component.scss +15 -0
- package/src/billing-management/billing-tabs/billing-tabs.component.tsx +36 -0
- package/src/components/custom-icon-wrapper/custom-icon-wrapper.component.tsx +17 -0
- package/src/components/custom-page-header/custom-page-header.component.tsx +38 -0
- package/src/components/custom-page-header/custom-page-header.scss +76 -0
- package/src/config-schema.ts +142 -0
- package/src/constant/index.ts +1 -0
- package/src/dashboard.meta.ts +47 -0
- package/src/declarations.d.ts +6 -0
- package/src/form-builder/form-builder.component.tsx +19 -0
- package/src/form-builder/form-builder.scss +0 -0
- package/src/hooks/useConcept.ts +13 -0
- package/src/hooks/useQueues.ts +22 -0
- package/src/hooks/useSystemSetting.ts +18 -0
- package/src/index.ts +133 -0
- package/src/left-panel/left-panel.component.tsx +49 -0
- package/src/left-panel/left-panel.scss +13 -0
- package/src/location-management/components/auto-suggest/autosuggest.component.tsx +149 -0
- package/src/location-management/components/auto-suggest/autosuggest.scss +61 -0
- package/src/location-management/components/auto-suggest/location-autosuggest.component.tsx +97 -0
- package/src/location-management/components/auto-suggest/location-autosuggest.scss +48 -0
- package/src/location-management/components/custom-results-tile/results-tile.component.tsx +43 -0
- package/src/location-management/components/custom-results-tile/results-tile.scss +86 -0
- package/src/location-management/forms/add-location/add-location.workspace.scss +71 -0
- package/src/location-management/forms/add-location/add-location.workspace.tsx +199 -0
- package/src/location-management/forms/search-location/search-location.workspace.scss +71 -0
- package/src/location-management/forms/search-location/search-location.workspace.tsx +214 -0
- package/src/location-management/helpers/index.ts +33 -0
- package/src/location-management/hooks/UseFacilityLocations.ts +12 -0
- package/src/location-management/hooks/useLocation.ts +18 -0
- package/src/location-management/hooks/useLocationTags.ts +15 -0
- package/src/location-management/location-management-dashboard.component.tsx +21 -0
- package/src/location-management/location-management-dashboard.scss +3 -0
- package/src/location-management/tables/locations-table.component.tsx +242 -0
- package/src/location-management/tables/locations-table.resource.ts +26 -0
- package/src/location-management/tables/locations-table.scss +114 -0
- package/src/location-management/types/index.ts +120 -0
- package/src/queue-management/queue-management-dashboard.component.tsx +23 -0
- package/src/queue-management/queue-management-dashboard.scss +8 -0
- package/src/queue-management/queue-management-table/queue-management-table.component.tsx +296 -0
- package/src/queue-management/queue-management-table/queue-management-table.scss +78 -0
- package/src/queue-management/queue-management-table/queue-management.resource.ts +39 -0
- package/src/queue-management/queue-management-workspace/queue-room/queue-room-form.scss +56 -0
- package/src/queue-management/queue-management-workspace/queue-room/queue-room-form.test.tsx +82 -0
- package/src/queue-management/queue-management-workspace/queue-room/queue-room-form.workspace.tsx +169 -0
- package/src/queue-management/queue-management-workspace/queue-room/queue-room.resource.ts +20 -0
- package/src/queue-management/queue-management-workspace/queue-service/queue-service-form.scss +61 -0
- package/src/queue-management/queue-management-workspace/queue-service/queue-service-form.test.tsx +127 -0
- package/src/queue-management/queue-management-workspace/queue-service/queue-service-form.workspace.tsx +178 -0
- package/src/queue-management/queue-management-workspace/queue-service/queue-service.resource.ts +55 -0
- package/src/queue-management/queue-types/index.ts +33 -0
- package/src/routes.json +157 -0
- package/src/setup-tests.ts +1 -0
- package/src/system-info/system-info.component.tsx +338 -0
- package/src/system-info/system-info.resource.ts +40 -0
- package/src/system-info/system-info.scss +622 -0
- package/src/type/index.ts +6 -0
- package/translations/am.json +111 -0
- package/translations/ar.json +115 -0
- package/translations/en.json +111 -0
- package/translations/es.json +112 -0
- package/translations/fr.json +112 -0
- package/translations/he.json +112 -0
- package/translations/km.json +110 -0
- package/translations/pt.json +112 -0
- package/translations/sw.json +111 -0
- package/translations/vi.json +110 -0
- package/translations/zh.json +110 -0
- package/tsconfig.json +5 -0
- package/version.js +27 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import DeletePaymentModeModal from './billing-delete-payment-modes.modal';
|
|
5
|
+
import { PaymentMethod } from '../type';
|
|
6
|
+
import { deletePaymentMode } from '../billing-payment-modes-workspace/billing-payment-modes.resource';
|
|
7
|
+
|
|
8
|
+
const mockDeletePaymentMode = jest.mocked(deletePaymentMode);
|
|
9
|
+
|
|
10
|
+
jest.mock('./payment-mode.resource', () => ({
|
|
11
|
+
deletePaymentMode: jest.fn(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
describe('DeletePaymentModeModal', () => {
|
|
15
|
+
test('should be able to delete payment mode when user clicks on delete button', async () => {
|
|
16
|
+
const user = userEvent.setup();
|
|
17
|
+
const mockCloseModal = jest.fn();
|
|
18
|
+
const mockPaymentMode = {
|
|
19
|
+
uuid: '123',
|
|
20
|
+
name: 'Test Payment Mode',
|
|
21
|
+
description: 'Test Description',
|
|
22
|
+
retired: false,
|
|
23
|
+
auditInfo: {
|
|
24
|
+
creator: {
|
|
25
|
+
uuid: '456',
|
|
26
|
+
display: 'Test Creator',
|
|
27
|
+
},
|
|
28
|
+
dateCreated: '2021-01-01',
|
|
29
|
+
},
|
|
30
|
+
} as PaymentMethod;
|
|
31
|
+
|
|
32
|
+
render(<DeletePaymentModeModal closeModal={mockCloseModal} paymentMode={mockPaymentMode} />);
|
|
33
|
+
|
|
34
|
+
const deleteButton = await screen.findByRole('button', { name: /Delete/i });
|
|
35
|
+
await user.click(deleteButton);
|
|
36
|
+
|
|
37
|
+
expect(mockDeletePaymentMode).toHaveBeenCalledWith(mockPaymentMode.uuid);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('should be able to cancel deletion of payment mode when user clicks on cancel button', async () => {
|
|
41
|
+
const user = userEvent.setup();
|
|
42
|
+
const mockCloseModal = jest.fn();
|
|
43
|
+
const mockPaymentMode = {
|
|
44
|
+
uuid: '123',
|
|
45
|
+
name: 'Test Payment Mode',
|
|
46
|
+
description: 'Test Description',
|
|
47
|
+
} as PaymentMethod;
|
|
48
|
+
|
|
49
|
+
render(<DeletePaymentModeModal closeModal={mockCloseModal} paymentMode={mockPaymentMode} />);
|
|
50
|
+
|
|
51
|
+
const cancelButton = await screen.findByRole('button', { name: /Cancel/i });
|
|
52
|
+
await user.click(cancelButton);
|
|
53
|
+
|
|
54
|
+
expect(mockCloseModal).toHaveBeenCalled();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
const usePaymentModeFormSchema = () => {
|
|
4
|
+
const attributeTypeSchema = z
|
|
5
|
+
.object({
|
|
6
|
+
name: z.string(),
|
|
7
|
+
description: z.string(),
|
|
8
|
+
retired: z.boolean().default(false),
|
|
9
|
+
retiredReason: z.string().optional(),
|
|
10
|
+
format: z.string().optional(),
|
|
11
|
+
regExp: z.union([z.string(), z.null()]).optional(),
|
|
12
|
+
required: z.boolean().default(false),
|
|
13
|
+
})
|
|
14
|
+
.refine(
|
|
15
|
+
(data) => {
|
|
16
|
+
if (data.retired === true) {
|
|
17
|
+
return !!data.retiredReason && data.retiredReason.trim().length > 0;
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
message: 'Retired reason is required when attribute type is retired',
|
|
23
|
+
path: ['retiredReason'],
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const paymentModeFormSchema = z.object({
|
|
28
|
+
name: z
|
|
29
|
+
.string({
|
|
30
|
+
required_error: 'Payment mode name is required',
|
|
31
|
+
invalid_type_error: 'Payment mode name is required',
|
|
32
|
+
})
|
|
33
|
+
.min(1, 'Payment mode name is required'),
|
|
34
|
+
description: z
|
|
35
|
+
.string({
|
|
36
|
+
required_error: 'Payment mode description is required',
|
|
37
|
+
invalid_type_error: 'Payment mode description is required',
|
|
38
|
+
})
|
|
39
|
+
.min(1, 'Payment mode description is required'),
|
|
40
|
+
retired: z
|
|
41
|
+
.boolean({
|
|
42
|
+
invalid_type_error: 'Retired must be a boolean',
|
|
43
|
+
})
|
|
44
|
+
.optional()
|
|
45
|
+
.default(false),
|
|
46
|
+
attributeTypes: z.array(attributeTypeSchema).optional(),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return { paymentModeFormSchema };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default usePaymentModeFormSchema;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import { PaymentMethod } from '../type';
|
|
3
|
+
import { mutate } from 'swr';
|
|
4
|
+
|
|
5
|
+
export const createPaymentMode = (paymentMode: Partial<PaymentMethod>) => {
|
|
6
|
+
return openmrsFetch(`${restBaseUrl}/cashier/paymentMode`, {
|
|
7
|
+
method: 'POST',
|
|
8
|
+
body: paymentMode,
|
|
9
|
+
headers: {
|
|
10
|
+
'Content-Type': 'application/json',
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const updatePaymentMode = (uuid: string, paymentMode: Partial<PaymentMethod>) => {
|
|
16
|
+
// OpenMRS REST API uses POST with the UUID in the URL to update resources
|
|
17
|
+
// The backend will recognize this as an update operation because the UUID is present
|
|
18
|
+
return openmrsFetch(`${restBaseUrl}/cashier/paymentMode/${uuid}`, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
body: paymentMode,
|
|
21
|
+
headers: {
|
|
22
|
+
'Content-Type': 'application/json',
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const deletePaymentMode = (uuid: string) => {
|
|
28
|
+
return openmrsFetch(`${restBaseUrl}/cashier/paymentMode/${uuid}`, {
|
|
29
|
+
method: 'DELETE',
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const handleMutation = (url: string) => {
|
|
34
|
+
mutate((key) => typeof key === 'string' && key.startsWith(url), undefined, { revalidate: true });
|
|
35
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
@use '@carbon/colors';
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@carbon/type';
|
|
4
|
+
|
|
5
|
+
.form {
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
justify-content: space-between;
|
|
9
|
+
height: 100%;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.formContainer {
|
|
13
|
+
margin: layout.$spacing-05;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.tablet {
|
|
17
|
+
padding: layout.$spacing-06 layout.$spacing-05;
|
|
18
|
+
background-color: colors.$white;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.desktop {
|
|
22
|
+
padding: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.paymentMethods {
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
row-gap: layout.$spacing-05;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.formStackControl {
|
|
32
|
+
row-gap: layout.$layout-01;
|
|
33
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { showSnackbar } from '@openmrs/esm-framework';
|
|
5
|
+
import PaymentModeWorkspace from './billing-payment-modes.workspace';
|
|
6
|
+
import { createPaymentMode } from './billing-payment-modes.resource';
|
|
7
|
+
import { PaymentMethod } from '../type';
|
|
8
|
+
|
|
9
|
+
const testProps = {
|
|
10
|
+
closeWorkspace: jest.fn(),
|
|
11
|
+
promptBeforeClosing: jest.fn(),
|
|
12
|
+
closeWorkspaceWithSavedChanges: jest.fn(),
|
|
13
|
+
setTitle: jest.fn(),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const mockCreatePaymentMode = jest.mocked(createPaymentMode);
|
|
17
|
+
|
|
18
|
+
jest.mock('./billing-payment-modes.resource', () => ({
|
|
19
|
+
...jest.requireActual('./billing-payment-modes.resource'),
|
|
20
|
+
createPaymentMode: jest.fn(),
|
|
21
|
+
updatePaymentMode: jest.fn(),
|
|
22
|
+
handleMutation: jest.fn(),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
describe('PaymentModeWorkspace', () => {
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
mockCreatePaymentMode.mockClear();
|
|
28
|
+
jest.clearAllMocks();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('should validate and submit correct form payload', async () => {
|
|
32
|
+
const user = userEvent.setup();
|
|
33
|
+
|
|
34
|
+
// Mock successful response
|
|
35
|
+
mockCreatePaymentMode.mockResolvedValue({ ok: true, data: {} } as any);
|
|
36
|
+
|
|
37
|
+
render(<PaymentModeWorkspace {...testProps} />);
|
|
38
|
+
const nameInput = screen.getByRole('textbox', { name: /Payment mode name/i });
|
|
39
|
+
const descriptionInput = screen.getByRole('textbox', { name: /Payment mode description/i });
|
|
40
|
+
const submitButton = screen.getByRole('button', { name: /Save & Close/i });
|
|
41
|
+
|
|
42
|
+
await user.click(submitButton);
|
|
43
|
+
|
|
44
|
+
// Should show validation error
|
|
45
|
+
expect(screen.getByText(/Payment mode name is required/i)).toBeInTheDocument();
|
|
46
|
+
expect(screen.getByText(/Payment mode description is required/i)).toBeInTheDocument();
|
|
47
|
+
|
|
48
|
+
await user.type(nameInput, 'Test Name');
|
|
49
|
+
await user.type(descriptionInput, 'Test Description');
|
|
50
|
+
await user.click(submitButton);
|
|
51
|
+
|
|
52
|
+
// Wait for validation to complete
|
|
53
|
+
await waitFor(() => {
|
|
54
|
+
expect(screen.queryByText(/Payment mode name is required/i)).not.toBeInTheDocument();
|
|
55
|
+
});
|
|
56
|
+
expect(screen.queryByText(/Payment mode description is required/i)).not.toBeInTheDocument();
|
|
57
|
+
|
|
58
|
+
await waitFor(() => {
|
|
59
|
+
expect(mockCreatePaymentMode).toHaveBeenCalledWith({
|
|
60
|
+
description: 'Test Description',
|
|
61
|
+
name: 'Test Name',
|
|
62
|
+
attributeTypes: [],
|
|
63
|
+
retired: false,
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('should show error message when submitting form fails', async () => {
|
|
69
|
+
const user = userEvent.setup();
|
|
70
|
+
|
|
71
|
+
// Mock error response
|
|
72
|
+
mockCreatePaymentMode.mockRejectedValue({
|
|
73
|
+
responseBody: {
|
|
74
|
+
error: {
|
|
75
|
+
message: 'An error occurred while creating the payment mode',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
render(<PaymentModeWorkspace {...testProps} />);
|
|
81
|
+
const nameInput = screen.getByRole('textbox', { name: /Payment mode name/i });
|
|
82
|
+
const descriptionInput = screen.getByRole('textbox', { name: /Payment mode description/i });
|
|
83
|
+
const submitButton = screen.getByRole('button', { name: /Save & Close/i });
|
|
84
|
+
|
|
85
|
+
await user.type(nameInput, 'Test Name');
|
|
86
|
+
await user.type(descriptionInput, 'Test Description');
|
|
87
|
+
await user.click(submitButton);
|
|
88
|
+
|
|
89
|
+
await waitFor(() => {
|
|
90
|
+
expect(showSnackbar).toHaveBeenCalledWith({
|
|
91
|
+
title: 'Payment mode save failed',
|
|
92
|
+
subtitle: 'An error occurred while saving the payment mode: An error occurred while creating the payment mode',
|
|
93
|
+
kind: 'error',
|
|
94
|
+
isLowContrast: true,
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('should submit payload with attributeTypes', async () => {
|
|
100
|
+
const user = userEvent.setup();
|
|
101
|
+
|
|
102
|
+
// Mock successful response
|
|
103
|
+
mockCreatePaymentMode.mockResolvedValue({ ok: true, data: {} } as any);
|
|
104
|
+
|
|
105
|
+
render(<PaymentModeWorkspace {...testProps} />);
|
|
106
|
+
|
|
107
|
+
// key in name, description and retired
|
|
108
|
+
const nameInput = screen.getByRole('textbox', { name: /Payment mode name/i });
|
|
109
|
+
const descriptionInput = screen.getByRole('textbox', { name: /Payment mode description/i });
|
|
110
|
+
const enablePaymentToggle = screen.getByRole('switch', { name: /Enable payment mode/i });
|
|
111
|
+
|
|
112
|
+
await user.type(nameInput, 'Test Name');
|
|
113
|
+
await user.type(descriptionInput, 'Test Description');
|
|
114
|
+
await user.click(enablePaymentToggle);
|
|
115
|
+
|
|
116
|
+
// Click to add attribute type
|
|
117
|
+
const addAttributeTypeButton = screen.getByRole('button', { name: /Add attribute type/i });
|
|
118
|
+
await user.click(addAttributeTypeButton);
|
|
119
|
+
|
|
120
|
+
// Key in attribute type name, description, required and format
|
|
121
|
+
const attributeTypeNameInput = screen.getByRole('textbox', { name: /Attribute name/i });
|
|
122
|
+
const attributeTypeDescriptionInput = screen.getByRole('textbox', { name: /Attribute description/i });
|
|
123
|
+
const attributeRegExpInput = screen.getByRole('textbox', { name: /Regular expression/i });
|
|
124
|
+
const attributeRetiredToggle = screen.getByRole('switch', { name: /Attribute retired/i });
|
|
125
|
+
const attributeRequiredToggle = screen.getByRole('switch', { name: /Attribute required/i });
|
|
126
|
+
|
|
127
|
+
// Key in attribute type details
|
|
128
|
+
await user.type(attributeTypeNameInput, 'Test Attribute Name');
|
|
129
|
+
await user.type(attributeTypeDescriptionInput, 'Test Attribute Description');
|
|
130
|
+
await user.type(attributeRegExpInput, 'Test Regular Expression');
|
|
131
|
+
await user.click(attributeRetiredToggle);
|
|
132
|
+
await user.click(attributeRequiredToggle);
|
|
133
|
+
|
|
134
|
+
// key retired reason
|
|
135
|
+
const attributeRetiredReasonInput = screen.getByRole('textbox', { name: /Retired reason/i });
|
|
136
|
+
await user.type(attributeRetiredReasonInput, 'Test Retired Reason');
|
|
137
|
+
|
|
138
|
+
// Click the dropdown to select format
|
|
139
|
+
const openDropdownButton = screen.getByRole('img', { name: /Open menu/i });
|
|
140
|
+
await user.click(openDropdownButton);
|
|
141
|
+
|
|
142
|
+
// Select the format
|
|
143
|
+
const formatOption = screen.getByText('java.lang.String');
|
|
144
|
+
await user.click(formatOption);
|
|
145
|
+
|
|
146
|
+
// Click save and close
|
|
147
|
+
const saveAndCloseButton = screen.getByRole('button', { name: /Save & Close/i });
|
|
148
|
+
await user.click(saveAndCloseButton);
|
|
149
|
+
|
|
150
|
+
await waitFor(() => {
|
|
151
|
+
expect(mockCreatePaymentMode).toHaveBeenCalledWith({
|
|
152
|
+
description: 'Test Description',
|
|
153
|
+
name: 'Test Name',
|
|
154
|
+
attributeTypes: [
|
|
155
|
+
{
|
|
156
|
+
name: 'Test Attribute Name',
|
|
157
|
+
description: 'Test Attribute Description',
|
|
158
|
+
retired: true,
|
|
159
|
+
required: true,
|
|
160
|
+
format: 'java.lang.String',
|
|
161
|
+
regExp: 'Test Regular Expression',
|
|
162
|
+
attributeOrder: 0,
|
|
163
|
+
foreignKey: null,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
retired: true,
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('should submit payload with attributeTypes and uuid when in edit mode', async () => {
|
|
172
|
+
const initialPaymentMode = {
|
|
173
|
+
uuid: '123',
|
|
174
|
+
name: 'Test Name',
|
|
175
|
+
description: 'Test Description',
|
|
176
|
+
retired: false,
|
|
177
|
+
retireReason: null,
|
|
178
|
+
sortOrder: 0,
|
|
179
|
+
auditInfo: {
|
|
180
|
+
creator: { uuid: 'creator-uuid', display: 'Admin' },
|
|
181
|
+
dateCreated: '2024-01-01T00:00:00.000Z',
|
|
182
|
+
changedBy: null,
|
|
183
|
+
dateChanged: null,
|
|
184
|
+
},
|
|
185
|
+
resourceVersion: '1.8',
|
|
186
|
+
attributeTypes: [
|
|
187
|
+
{
|
|
188
|
+
name: 'Test Attribute Name',
|
|
189
|
+
uuid: '456',
|
|
190
|
+
format: 'java.lang.String',
|
|
191
|
+
regExp: null,
|
|
192
|
+
attributeOrder: 0,
|
|
193
|
+
foreignKey: null,
|
|
194
|
+
description: 'Test Attribute Description',
|
|
195
|
+
retired: false,
|
|
196
|
+
required: false,
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
} as PaymentMethod;
|
|
200
|
+
|
|
201
|
+
const user = userEvent.setup();
|
|
202
|
+
|
|
203
|
+
// Import and mock updatePaymentMode for edit mode
|
|
204
|
+
const { updatePaymentMode } = require('./billing-payment-modes.resource');
|
|
205
|
+
const mockUpdatePaymentMode = jest.mocked(updatePaymentMode);
|
|
206
|
+
mockUpdatePaymentMode.mockResolvedValue({ ok: true, data: {} } as any);
|
|
207
|
+
|
|
208
|
+
render(<PaymentModeWorkspace {...testProps} initialPaymentMode={initialPaymentMode} />);
|
|
209
|
+
|
|
210
|
+
// make the attribute type required
|
|
211
|
+
const attributeRequiredToggle = screen.getByRole('switch', { name: /Attribute required/i });
|
|
212
|
+
await user.click(attributeRequiredToggle);
|
|
213
|
+
|
|
214
|
+
// click save and close
|
|
215
|
+
const saveAndCloseButton = screen.getByRole('button', { name: /Save & Close/i });
|
|
216
|
+
await user.click(saveAndCloseButton);
|
|
217
|
+
|
|
218
|
+
await waitFor(() => {
|
|
219
|
+
expect(mockUpdatePaymentMode).toHaveBeenCalledWith('123', {
|
|
220
|
+
description: 'Test Description',
|
|
221
|
+
name: 'Test Name',
|
|
222
|
+
attributeTypes: [
|
|
223
|
+
{
|
|
224
|
+
name: 'Test Attribute Name',
|
|
225
|
+
uuid: '456',
|
|
226
|
+
description: 'Test Attribute Description',
|
|
227
|
+
retired: false,
|
|
228
|
+
required: true,
|
|
229
|
+
format: 'java.lang.String',
|
|
230
|
+
regExp: null,
|
|
231
|
+
attributeOrder: 0,
|
|
232
|
+
foreignKey: null,
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
retired: false,
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
});
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import {
|
|
4
|
+
DefaultWorkspaceProps,
|
|
5
|
+
ResponsiveWrapper,
|
|
6
|
+
restBaseUrl,
|
|
7
|
+
showSnackbar,
|
|
8
|
+
useLayoutType,
|
|
9
|
+
} from '@openmrs/esm-framework';
|
|
10
|
+
import { Controller, FormProvider, useFieldArray, useForm } from 'react-hook-form';
|
|
11
|
+
import styles from './billing-payment-modes.workspace.scss';
|
|
12
|
+
import { TextInput, ButtonSet, Button, InlineLoading, Stack, Toggle } from '@carbon/react';
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
15
|
+
import classNames from 'classnames';
|
|
16
|
+
import { Add } from '@carbon/react/icons';
|
|
17
|
+
import usePaymentModeFormSchema from './billing-payment-modes-form-schema';
|
|
18
|
+
import { type PaymentMethod } from '../type';
|
|
19
|
+
import { createPaymentMode, updatePaymentMode, handleMutation } from './billing-payment-modes.resource';
|
|
20
|
+
import PaymentModeAttributeFields from './payment-attributes/payment-mode-attributes.component';
|
|
21
|
+
|
|
22
|
+
type PaymentModeWorkspaceProps = DefaultWorkspaceProps & {
|
|
23
|
+
initialPaymentMode?: PaymentMethod;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const PaymentModeWorkspace: React.FC<PaymentModeWorkspaceProps> = ({
|
|
27
|
+
closeWorkspace,
|
|
28
|
+
promptBeforeClosing,
|
|
29
|
+
closeWorkspaceWithSavedChanges,
|
|
30
|
+
initialPaymentMode = {} as PaymentMethod,
|
|
31
|
+
}) => {
|
|
32
|
+
const { t } = useTranslation();
|
|
33
|
+
const isTablet = useLayoutType() === 'tablet';
|
|
34
|
+
const { paymentModeFormSchema } = usePaymentModeFormSchema();
|
|
35
|
+
type PaymentModeFormSchema = z.infer<typeof paymentModeFormSchema>;
|
|
36
|
+
const formDefaultValues = Object.keys(initialPaymentMode).length > 0 ? initialPaymentMode : {};
|
|
37
|
+
|
|
38
|
+
const formMethods = useForm<PaymentModeFormSchema>({
|
|
39
|
+
resolver: zodResolver(paymentModeFormSchema),
|
|
40
|
+
mode: 'all',
|
|
41
|
+
defaultValues: formDefaultValues,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const { errors, isSubmitting, isDirty } = formMethods.formState;
|
|
45
|
+
|
|
46
|
+
// field array
|
|
47
|
+
const {
|
|
48
|
+
fields: attributeTypeFields,
|
|
49
|
+
append: appendAttributeType,
|
|
50
|
+
remove: removeAttributeType,
|
|
51
|
+
} = useFieldArray({
|
|
52
|
+
control: formMethods.control,
|
|
53
|
+
name: 'attributeTypes',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const mappedAttributeTypes = (attributes) => {
|
|
57
|
+
const uuid = initialPaymentMode?.uuid
|
|
58
|
+
? initialPaymentMode.attributeTypes.find((a) => a.name === attributes.name)?.uuid
|
|
59
|
+
: null;
|
|
60
|
+
|
|
61
|
+
const attributeType = {
|
|
62
|
+
name: attributes.name,
|
|
63
|
+
description: attributes.description,
|
|
64
|
+
retired: attributes.retired,
|
|
65
|
+
attributeOrder: attributes?.attributeOrder ?? 0,
|
|
66
|
+
format: attributes?.format ?? '',
|
|
67
|
+
foreignKey: attributes?.foreignKey ?? null,
|
|
68
|
+
regExp: attributes?.regExp ?? null,
|
|
69
|
+
required: attributes.required,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return uuid ? { ...attributeType, uuid } : attributeType;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const onSubmit = async (data: PaymentModeFormSchema) => {
|
|
76
|
+
const payload: Partial<PaymentMethod> = {
|
|
77
|
+
name: data.name,
|
|
78
|
+
description: data.description,
|
|
79
|
+
retired: data.retired,
|
|
80
|
+
attributeTypes: data.attributeTypes.map(mappedAttributeTypes),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const response = initialPaymentMode?.uuid
|
|
85
|
+
? await updatePaymentMode(initialPaymentMode.uuid, payload)
|
|
86
|
+
: await createPaymentMode(payload);
|
|
87
|
+
|
|
88
|
+
if (response.ok) {
|
|
89
|
+
const successMessage = initialPaymentMode?.uuid
|
|
90
|
+
? t('paymentModeUpdated', 'Payment mode updated successfully')
|
|
91
|
+
: t('paymentModeCreated', 'Payment mode created successfully');
|
|
92
|
+
|
|
93
|
+
const successSubtitle = initialPaymentMode?.uuid
|
|
94
|
+
? t('paymentModeUpdatedSubtitle', 'The payment mode has been updated successfully')
|
|
95
|
+
: t('paymentModeCreatedSubtitle', 'The payment mode has been created successfully');
|
|
96
|
+
|
|
97
|
+
showSnackbar({
|
|
98
|
+
title: successMessage,
|
|
99
|
+
subtitle: successSubtitle,
|
|
100
|
+
kind: 'success',
|
|
101
|
+
isLowContrast: true,
|
|
102
|
+
});
|
|
103
|
+
closeWorkspaceWithSavedChanges();
|
|
104
|
+
handleMutation(`${restBaseUrl}/cashier/paymentMode?v=full`);
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
const errorObject = error?.responseBody?.error;
|
|
108
|
+
const errorMessage = errorObject?.message ?? 'An error occurred while saving the payment mode';
|
|
109
|
+
showSnackbar({
|
|
110
|
+
title: t('paymentModeSaveFailed', 'Payment mode save failed'),
|
|
111
|
+
subtitle: t(
|
|
112
|
+
'paymentModeSaveFailedSubtitle',
|
|
113
|
+
'An error occurred while saving the payment mode: {{errorMessage}}',
|
|
114
|
+
{ errorMessage },
|
|
115
|
+
),
|
|
116
|
+
kind: 'error',
|
|
117
|
+
isLowContrast: true,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const handleError = (error) => {
|
|
123
|
+
showSnackbar({
|
|
124
|
+
title: t('paymentModeSaveFailed', 'Payment mode save failed'),
|
|
125
|
+
subtitle: t(
|
|
126
|
+
'paymentModeSaveFailedSubtitle',
|
|
127
|
+
'An error occurred while saving the payment mode: {{errorMessage}}',
|
|
128
|
+
{ errorMessage: JSON.stringify(error, null, 2) },
|
|
129
|
+
),
|
|
130
|
+
kind: 'error',
|
|
131
|
+
isLowContrast: true,
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
if (isDirty) {
|
|
137
|
+
promptBeforeClosing(() => isDirty);
|
|
138
|
+
}
|
|
139
|
+
}, [isDirty, promptBeforeClosing]);
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<FormProvider {...formMethods}>
|
|
143
|
+
<form onSubmit={formMethods.handleSubmit(onSubmit, handleError)} className={styles.form}>
|
|
144
|
+
<div className={styles.formContainer}>
|
|
145
|
+
<Stack className={styles.formStackControl} gap={7}>
|
|
146
|
+
<ResponsiveWrapper>
|
|
147
|
+
<Controller
|
|
148
|
+
name="name"
|
|
149
|
+
control={formMethods.control}
|
|
150
|
+
render={({ field }) => (
|
|
151
|
+
<TextInput
|
|
152
|
+
{...field}
|
|
153
|
+
id="name"
|
|
154
|
+
type="text"
|
|
155
|
+
labelText={t('paymentModeName', 'Payment mode name')}
|
|
156
|
+
placeholder={t('paymentModeNamePlaceholder', 'Enter payment mode name')}
|
|
157
|
+
invalid={!!errors.name}
|
|
158
|
+
invalidText={errors.name?.message}
|
|
159
|
+
/>
|
|
160
|
+
)}
|
|
161
|
+
/>
|
|
162
|
+
</ResponsiveWrapper>
|
|
163
|
+
<ResponsiveWrapper>
|
|
164
|
+
<Controller
|
|
165
|
+
name="description"
|
|
166
|
+
control={formMethods.control}
|
|
167
|
+
render={({ field }) => (
|
|
168
|
+
<TextInput
|
|
169
|
+
{...field}
|
|
170
|
+
id="description"
|
|
171
|
+
type="text"
|
|
172
|
+
labelText={t('paymentModeDescription', 'Payment mode description')}
|
|
173
|
+
placeholder={t('paymentModeDescriptionPlaceholder', 'Enter payment mode description')}
|
|
174
|
+
invalid={!!errors.description}
|
|
175
|
+
invalidText={errors.description?.message}
|
|
176
|
+
/>
|
|
177
|
+
)}
|
|
178
|
+
/>
|
|
179
|
+
</ResponsiveWrapper>
|
|
180
|
+
<Button size="sm" kind="tertiary" renderIcon={Add} onClick={() => appendAttributeType({})}>
|
|
181
|
+
{t('addAttributeType', 'Add attribute type')}
|
|
182
|
+
</Button>
|
|
183
|
+
{attributeTypeFields.map((field, index) => (
|
|
184
|
+
<PaymentModeAttributeFields
|
|
185
|
+
key={field.id}
|
|
186
|
+
field={field}
|
|
187
|
+
index={index}
|
|
188
|
+
control={formMethods.control}
|
|
189
|
+
removeAttributeType={removeAttributeType}
|
|
190
|
+
errors={errors}
|
|
191
|
+
/>
|
|
192
|
+
))}
|
|
193
|
+
</Stack>
|
|
194
|
+
</div>
|
|
195
|
+
<ButtonSet className={classNames({ [styles.tablet]: isTablet, [styles.desktop]: !isTablet })}>
|
|
196
|
+
<Button style={{ maxWidth: '50%' }} kind="secondary" onClick={() => closeWorkspace()}>
|
|
197
|
+
{t('cancel', 'Cancel')}
|
|
198
|
+
</Button>
|
|
199
|
+
<Button
|
|
200
|
+
disabled={isSubmitting || Object.keys(errors).length > 0}
|
|
201
|
+
style={{ maxWidth: '50%' }}
|
|
202
|
+
kind="primary"
|
|
203
|
+
type="submit">
|
|
204
|
+
{isSubmitting ? (
|
|
205
|
+
<span style={{ display: 'flex', justifyItems: 'center' }}>
|
|
206
|
+
{t('submitting', 'Submitting...')} <InlineLoading status="active" iconDescription="Loading" />
|
|
207
|
+
</span>
|
|
208
|
+
) : (
|
|
209
|
+
t('saveAndClose', 'Save & close')
|
|
210
|
+
)}
|
|
211
|
+
</Button>
|
|
212
|
+
</ButtonSet>
|
|
213
|
+
</form>
|
|
214
|
+
</FormProvider>
|
|
215
|
+
);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export default PaymentModeWorkspace;
|