+
+
+
+
+
+
+ {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
+
+
+
+
+ {headers.map((header) => (
+
+ {header.header}
+
+ ))}
+
+
+
+ {rows.map((row) => (
+
+ {row.cells.map((cell) =>
+ cell.info.header !== 'actions' ? (
+ {cell.value}
+ ) : (
+
+
+ {
+ const selected = paymentModes.find((p) => p.uuid === row.id);
+ setSelectedPaymentMode(selected);
+ setIsDeleteModalOpen(true);
+ }}
+ />
+
+
+ ),
+ )}
+
+ ))}
+
+
+
+ )}
+
+
+
+
+ {/* Modal for Adding New Payment Mode */}
+
setIsModalOpen(false)}
+ onRequestSubmit={handleSubmit(onSubmit)}
+ primaryButtonText={t('save', 'Save')}
+ secondaryButtonText={t('cancel', 'Cancel')}
+ isPrimaryButtonDisabled={isSubmitting}>
+
+
+
+ {/* Modal for Deleting Payment Mode */}
+
setIsDeleteModalOpen(false)}
+ onRequestSubmit={handleDelete}
+ primaryButtonText={t('delete', 'Delete')}
+ secondaryButtonText={t('cancel', 'Cancel')}
+ primaryButtonDanger
+ danger>
+ {t('confirmDeleteMessage', 'Are you sure you want to delete this payment mode? Proceed cautiously.')}
+
+
+ );
+};
+
+export default PaymentModesConfig;
diff --git a/src/billable-services/payyment-modes/payment-modes-config.scss b/src/billable-services/payyment-modes/payment-modes-config.scss
new file mode 100644
index 0000000..6b7efc4
--- /dev/null
+++ b/src/billable-services/payyment-modes/payment-modes-config.scss
@@ -0,0 +1,23 @@
+@use '@carbon/layout';
+@use '@carbon/type';
+@use '@openmrs/esm-styleguide/src/vars' as *;
+
+.container {
+ padding: layout.$spacing-05;
+}
+
+.card {
+ width: 100%;
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: layout.$spacing-05;
+}
+
+.historyContainer {
+ margin-top: layout.$spacing-05;
+}
+
+.table {
+ width: 100%;
+ table-layout: auto;
+}
\ No newline at end of file
diff --git a/translations/en.json b/translations/en.json
index cc60651..673bbe4 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -2,6 +2,7 @@
"actions": "Actions",
"addBill": "Add bill item(s)",
"addBillableServices": "Add Billable Services",
+ "addCashPoint": "Add Cash Point",
"addNewBillableService": "Add new billable service",
"addNewService": "Add new service",
"addPaymentOptions": "Add payment option",
@@ -18,6 +19,7 @@
"billing": "Billing",
"billingForm": "Billing form",
"billingHistory": "Billing History",
+ "billingSettings": "Billing Settings",
"billItem": "Bill item",
"billItems": "Bill Items",
"billLineItemEmpty": "This bill has no line items",
@@ -34,11 +36,20 @@
"billWaiverError": "Bill waiver failed {{error}}",
"billWaiverSuccess": "Bill waiver successful",
"cancel": "Cancel",
+ "cashPointConfig": "Cash Point Config",
+ "cashPointHistory": "Cash Point History",
+ "cashPointLocation": "Cash Point Location",
+ "cashPointName": "Cash Point Name",
+ "cashPointNamePlaceholder": "e.g., Pharmacy Cash Point",
+ "cashPointSaved": "Cash point was successfully saved.",
+ "cashPointUuid": "Cash Point UUID",
+ "cashPointUuidPlaceholder": "Enter UUID",
"checkFilters": "Check the filters above",
"clearSearchInput": "Clear search input",
"clientBalance": "Client Balance",
"createdSuccessfully": "Billable service created successfully",
"currentPrice": "Current price",
+ "delete": "Delete",
"discard": "Discard",
"discount": "Discount",
"editBillableService": "Edit billable service",
@@ -49,8 +60,11 @@
"enterConcept": "Associated concept",
"enterReferenceNumber": "Enter ref. number",
"error": "Error",
+ "errorFetchingCashPoints": "An error occurred while fetching cash points.",
+ "errorFetchingLocations": "An error occurred while fetching locations.",
"errorLoadingBillServices": "Error loading bill services",
"errorLoadingPaymentModes": "Payment modes error",
+ "errorSavingCashPoint": "An error occurred while saving the cash point.",
"filterBy": "Filter by",
"filterTable": "Filter table",
"grandTotal": "Grand total",
@@ -68,6 +82,7 @@
"loading": "Loading data...",
"loadingBillingServices": "Loading billing services...",
"loadingDescription": "Loading",
+ "location": "Select Location",
"manageBillableServices": "Manage billable services",
"name": "Name",
"nextPage": "Next page",
@@ -87,6 +102,7 @@
"paymentMethod": "Payment method",
"paymentMethods": "Payment methods",
"paymentMode": "Payment Mode",
+ "paymentModesConfig": "Payment Modes Config",
"payments": "Payments",
"pleaseRequiredFields": "Please fill all required fields",
"policyNumber": "Policy number",
@@ -127,11 +143,13 @@
"status": "Service Status",
"stockItem": "Stock Item",
"submitting": "Submitting...",
+ "success": "Success",
"total": "Total",
"totalAmount": "Total Amount",
"totalTendered": "Total Tendered",
"unitPrice": "Unit price",
"updatedSuccessfully": "Billable service updated successfully",
+ "uuid": "UUID",
"visitTime": "Visit time",
"waiverForm": "Waiver form"
}
From 7b21b8abc5ef5bf8593cf06750da5a52d349743b Mon Sep 17 00:00:00 2001
From: AJAL ODORA JONATHAN <43242517+ODORA0@users.noreply.github.com>
Date: Wed, 11 Dec 2024 12:56:28 +0300
Subject: [PATCH 3/5] Prevent creating dublicates
---
.../cash-point-configuration.component.tsx | 17 +++++++++++++++++
.../payment-modes-config.component.tsx | 16 ++++++++++++++++
translations/en.json | 13 +++++++++++++
3 files changed, 46 insertions(+)
diff --git a/src/billable-services/cash-point/cash-point-configuration.component.tsx b/src/billable-services/cash-point/cash-point-configuration.component.tsx
index 7ebc621..3bc73e9 100644
--- a/src/billable-services/cash-point/cash-point-configuration.component.tsx
+++ b/src/billable-services/cash-point/cash-point-configuration.component.tsx
@@ -96,6 +96,23 @@ const CashPointConfiguration: React.FC = () => {
}, [fetchCashPoints, fetchLocations]);
const onSubmit = async (data: CashPointFormValues) => {
+ const isDuplicate = cashPoints.some(
+ (point) => point.name.toLowerCase() === data.name.toLowerCase() || point.uuid === data.uuid,
+ );
+
+ if (isDuplicate) {
+ showSnackbar({
+ title: t('error', 'Error'),
+ subtitle: t(
+ 'duplicateCashPointError',
+ 'A cash point with the same name or UUID already exists. Please use a unique name and UUID.',
+ ),
+ kind: 'error',
+ isLowContrast: false,
+ });
+ return;
+ }
+
try {
await axios.post(`${baseUrl}/billing/cashPoint`, {
name: data.name,
diff --git a/src/billable-services/payyment-modes/payment-modes-config.component.tsx b/src/billable-services/payyment-modes/payment-modes-config.component.tsx
index 2aa3330..671d645 100644
--- a/src/billable-services/payyment-modes/payment-modes-config.component.tsx
+++ b/src/billable-services/payyment-modes/payment-modes-config.component.tsx
@@ -72,6 +72,22 @@ const PaymentModesConfig: React.FC = () => {
}, [fetchPaymentModes]);
const onSubmit = async (data: PaymentModeFormValues) => {
+ // Check for duplicate payment mode name
+ const isDuplicate = paymentModes.some((mode) => mode.name.toLowerCase() === data.name.toLowerCase());
+
+ if (isDuplicate) {
+ showSnackbar({
+ title: t('error', 'Error'),
+ subtitle: t(
+ 'duplicatePaymentModeError',
+ 'A payment mode with the same name already exists. Please create another payment mode',
+ ),
+ kind: 'error',
+ isLowContrast: false,
+ });
+ return;
+ }
+
try {
await axios.post(`${baseUrl}/billing/paymentMode`, {
name: data.name,
diff --git a/translations/en.json b/translations/en.json
index 673bbe4..bb316e9 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -5,6 +5,7 @@
"addCashPoint": "Add Cash Point",
"addNewBillableService": "Add new billable service",
"addNewService": "Add new service",
+ "addPaymentMode": "Add Payment Mode",
"addPaymentOptions": "Add payment option",
"amount": "Amount",
"amountDue": "Amount Due",
@@ -47,9 +48,13 @@
"checkFilters": "Check the filters above",
"clearSearchInput": "Clear search input",
"clientBalance": "Client Balance",
+ "confirmDeleteMessage": "Are you sure you want to delete this payment mode? Proceed cautiously.",
"createdSuccessfully": "Billable service created successfully",
"currentPrice": "Current price",
"delete": "Delete",
+ "deletePaymentMode": "Delete Payment Mode",
+ "description": "Description",
+ "descriptionPlaceholder": "e.g., Used for all cash transactions",
"discard": "Discard",
"discount": "Discount",
"editBillableService": "Edit billable service",
@@ -60,11 +65,14 @@
"enterConcept": "Associated concept",
"enterReferenceNumber": "Enter ref. number",
"error": "Error",
+ "errorDeletingPaymentMode": "An error occurred while deleting the payment mode.",
"errorFetchingCashPoints": "An error occurred while fetching cash points.",
"errorFetchingLocations": "An error occurred while fetching locations.",
+ "errorFetchingPaymentModes": "An error occurred while fetching payment modes.",
"errorLoadingBillServices": "Error loading bill services",
"errorLoadingPaymentModes": "Payment modes error",
"errorSavingCashPoint": "An error occurred while saving the cash point.",
+ "errorSavingPaymentMode": "An error occurred while saving the payment mode.",
"filterBy": "Filter by",
"filterTable": "Filter table",
"grandTotal": "Grand total",
@@ -102,6 +110,11 @@
"paymentMethod": "Payment method",
"paymentMethods": "Payment methods",
"paymentMode": "Payment Mode",
+ "paymentModeDeleted": "Payment mode was successfully deleted.",
+ "paymentModeHistory": "Payment Mode History",
+ "paymentModeName": "Payment Mode Name",
+ "paymentModeNamePlaceholder": "e.g., Cash, Credit Card",
+ "paymentModeSaved": "Payment mode was successfully saved.",
"paymentModesConfig": "Payment Modes Config",
"payments": "Payments",
"pleaseRequiredFields": "Please fill all required fields",
From fdb2edc7f004564104f3e4c551fa366b65cafca0 Mon Sep 17 00:00:00 2001
From: AJAL ODORA JONATHAN <43242517+ODORA0@users.noreply.github.com>
Date: Fri, 13 Dec 2024 09:28:25 +0300
Subject: [PATCH 4/5] Change to openmrsFetch
---
.../cash-point-configuration.component.tsx | 52 ++++++++++++-------
.../payment-modes-config.component.tsx | 51 ++++++++++++------
translations/en.json | 2 +
3 files changed, 69 insertions(+), 36 deletions(-)
diff --git a/src/billable-services/cash-point/cash-point-configuration.component.tsx b/src/billable-services/cash-point/cash-point-configuration.component.tsx
index 3bc73e9..f378d65 100644
--- a/src/billable-services/cash-point/cash-point-configuration.component.tsx
+++ b/src/billable-services/cash-point/cash-point-configuration.component.tsx
@@ -17,11 +17,10 @@ import {
} from '@carbon/react';
import { Add } from '@carbon/react/icons';
import { useTranslation } from 'react-i18next';
-import axios from 'axios';
import { useForm, Controller } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
-import { showSnackbar } from '@openmrs/esm-framework';
+import { showSnackbar, openmrsFetch } from '@openmrs/esm-framework';
import { CardHeader } from '@openmrs/esm-patient-common-lib';
import styles from './cash-point-configuration.scss';
@@ -42,7 +41,6 @@ const CashPointConfiguration: React.FC = () => {
const [cashPoints, setCashPoints] = useState([]);
const [locations, setLocations] = useState([]);
const [isModalOpen, setIsModalOpen] = useState(false);
- const baseUrl = `${window.location.origin}/openmrs/ws/rest/v1`;
const {
control,
@@ -60,7 +58,7 @@ const CashPointConfiguration: React.FC = () => {
const fetchCashPoints = useCallback(async () => {
try {
- const response = await axios.get(`${baseUrl}/billing/cashPoint?v=full`);
+ const response = await openmrsFetch('/ws/rest/v1/billing/cashPoint?v=full');
setCashPoints(response.data.results || []);
} catch (err) {
showSnackbar({
@@ -70,11 +68,11 @@ const CashPointConfiguration: React.FC = () => {
isLowContrast: false,
});
}
- }, [baseUrl, t]);
+ }, [t]);
const fetchLocations = useCallback(async () => {
try {
- const response = await axios.get(`${baseUrl}/location?v=default`);
+ const response = await openmrsFetch('/ws/rest/v1/location?v=default');
const allLocations = response.data.results.map((loc) => ({
id: loc.uuid,
label: loc.display,
@@ -88,7 +86,7 @@ const CashPointConfiguration: React.FC = () => {
isLowContrast: false,
});
}
- }, [baseUrl, t]);
+ }, [t]);
useEffect(() => {
fetchCashPoints();
@@ -114,21 +112,37 @@ const CashPointConfiguration: React.FC = () => {
}
try {
- await axios.post(`${baseUrl}/billing/cashPoint`, {
- name: data.name,
- uuid: data.uuid,
- location: { uuid: data.location },
+ const response = await openmrsFetch('/ws/rest/v1/billing/cashPoint', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: {
+ name: data.name,
+ uuid: data.uuid,
+ location: { uuid: data.location },
+ },
});
- showSnackbar({
- title: t('success', 'Success'),
- subtitle: t('cashPointSaved', 'Cash point was successfully saved.'),
- kind: 'success',
- });
+ if (response.ok) {
+ showSnackbar({
+ title: t('success', 'Success'),
+ subtitle: t('cashPointSaved', 'Cash point was successfully saved.'),
+ kind: 'success',
+ });
- setIsModalOpen(false);
- reset({ name: '', uuid: '', location: '' });
- fetchCashPoints();
+ setIsModalOpen(false);
+ reset({ name: '', uuid: '', location: '' });
+ fetchCashPoints();
+ } else {
+ const errorData = response.data || {};
+ showSnackbar({
+ title: t('error', 'Error'),
+ subtitle: errorData.message || t('errorSavingCashPoint', 'An error occurred while saving the cash point.'),
+ kind: 'error',
+ isLowContrast: false,
+ });
+ }
} catch (err) {
showSnackbar({
title: t('error', 'Error'),
diff --git a/src/billable-services/payyment-modes/payment-modes-config.component.tsx b/src/billable-services/payyment-modes/payment-modes-config.component.tsx
index 671d645..113b6e9 100644
--- a/src/billable-services/payyment-modes/payment-modes-config.component.tsx
+++ b/src/billable-services/payyment-modes/payment-modes-config.component.tsx
@@ -16,11 +16,10 @@ import {
} from '@carbon/react';
import { Add } from '@carbon/react/icons';
import { useTranslation } from 'react-i18next';
-import axios from 'axios';
import { useForm, Controller } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
-import { showSnackbar } from '@openmrs/esm-framework';
+import { showSnackbar, openmrsFetch } from '@openmrs/esm-framework';
import { CardHeader } from '@openmrs/esm-patient-common-lib';
import styles from './payment-modes-config.scss';
@@ -38,7 +37,6 @@ const PaymentModesConfig: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [selectedPaymentMode, setSelectedPaymentMode] = useState(null);
- const baseUrl = `${window.location.origin}/openmrs/ws/rest/v1`;
const {
control,
@@ -55,7 +53,7 @@ const PaymentModesConfig: React.FC = () => {
const fetchPaymentModes = useCallback(async () => {
try {
- const response = await axios.get(`${baseUrl}/billing/paymentMode?v=full`);
+ const response = await openmrsFetch('/ws/rest/v1/billing/paymentMode?v=full');
setPaymentModes(response.data.results || []);
} catch (err) {
showSnackbar({
@@ -65,7 +63,7 @@ const PaymentModesConfig: React.FC = () => {
isLowContrast: false,
});
}
- }, [baseUrl, t]);
+ }, [t]);
useEffect(() => {
fetchPaymentModes();
@@ -89,20 +87,37 @@ const PaymentModesConfig: React.FC = () => {
}
try {
- await axios.post(`${baseUrl}/billing/paymentMode`, {
- name: data.name,
- description: data.description,
+ const response = await openmrsFetch('/ws/rest/v1/billing/paymentMode', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ name: data.name,
+ description: data.description,
+ }),
});
- showSnackbar({
- title: t('success', 'Success'),
- subtitle: t('paymentModeSaved', 'Payment mode was successfully saved.'),
- kind: 'success',
- });
+ if (response.ok) {
+ showSnackbar({
+ title: t('success', 'Success'),
+ subtitle: t('paymentModeSaved', 'Payment mode was successfully saved.'),
+ kind: 'success',
+ });
- setIsModalOpen(false);
- reset({ name: '', description: '' });
- fetchPaymentModes();
+ setIsModalOpen(false);
+ reset({ name: '', description: '' });
+ fetchPaymentModes();
+ } else {
+ const errorData = response.data || {};
+ showSnackbar({
+ title: t('error', 'Error'),
+ subtitle:
+ errorData.message || t('errorSavingPaymentMode', 'An error occurred while saving the payment mode.'),
+ kind: 'error',
+ isLowContrast: false,
+ });
+ }
} catch (err) {
showSnackbar({
title: t('error', 'Error'),
@@ -117,7 +132,9 @@ const PaymentModesConfig: React.FC = () => {
if (!selectedPaymentMode) return;
try {
- await axios.delete(`${baseUrl}/billing/paymentMode/${selectedPaymentMode.uuid}`);
+ await openmrsFetch(`/ws/rest/v1/billing/paymentMode/${selectedPaymentMode.uuid}`, {
+ method: 'DELETE',
+ });
showSnackbar({
title: t('success', 'Success'),
diff --git a/translations/en.json b/translations/en.json
index bb316e9..0e3dfdb 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -57,6 +57,8 @@
"descriptionPlaceholder": "e.g., Used for all cash transactions",
"discard": "Discard",
"discount": "Discount",
+ "duplicateCashPointError": "A cash point with the same name or UUID already exists. Please use a unique name and UUID.",
+ "duplicatePaymentModeError": "A payment mode with the same name already exists. Please create another payment mode",
"editBillableService": "Edit billable service",
"editBillableServices": "Edit Billable Services",
"editBillLineItem": "Edit bill line item?",
From 02b932ba32ea7a4964eeb4685f38a717276bd232 Mon Sep 17 00:00:00 2001
From: AJAL ODORA JONATHAN <43242517+ODORA0@users.noreply.github.com>
Date: Wed, 18 Dec 2024 16:01:37 +0300
Subject: [PATCH 5/5] Use restBaseUrl
---
.../cash-point/cash-point-configuration.component.tsx | 8 ++++----
.../payyment-modes/payment-modes-config.component.tsx | 8 ++++----
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/billable-services/cash-point/cash-point-configuration.component.tsx b/src/billable-services/cash-point/cash-point-configuration.component.tsx
index f378d65..6df96b3 100644
--- a/src/billable-services/cash-point/cash-point-configuration.component.tsx
+++ b/src/billable-services/cash-point/cash-point-configuration.component.tsx
@@ -20,7 +20,7 @@ import { useTranslation } from 'react-i18next';
import { useForm, Controller } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
-import { showSnackbar, openmrsFetch } from '@openmrs/esm-framework';
+import { showSnackbar, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
import { CardHeader } from '@openmrs/esm-patient-common-lib';
import styles from './cash-point-configuration.scss';
@@ -58,7 +58,7 @@ const CashPointConfiguration: React.FC = () => {
const fetchCashPoints = useCallback(async () => {
try {
- const response = await openmrsFetch('/ws/rest/v1/billing/cashPoint?v=full');
+ const response = await openmrsFetch(`${restBaseUrl}/billing/cashPoint?v=full`);
setCashPoints(response.data.results || []);
} catch (err) {
showSnackbar({
@@ -72,7 +72,7 @@ const CashPointConfiguration: React.FC = () => {
const fetchLocations = useCallback(async () => {
try {
- const response = await openmrsFetch('/ws/rest/v1/location?v=default');
+ const response = await openmrsFetch(`${restBaseUrl}/location?v=default`);
const allLocations = response.data.results.map((loc) => ({
id: loc.uuid,
label: loc.display,
@@ -112,7 +112,7 @@ const CashPointConfiguration: React.FC = () => {
}
try {
- const response = await openmrsFetch('/ws/rest/v1/billing/cashPoint', {
+ const response = await openmrsFetch(`${restBaseUrl}/billing/cashPoint`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
diff --git a/src/billable-services/payyment-modes/payment-modes-config.component.tsx b/src/billable-services/payyment-modes/payment-modes-config.component.tsx
index 113b6e9..647bacd 100644
--- a/src/billable-services/payyment-modes/payment-modes-config.component.tsx
+++ b/src/billable-services/payyment-modes/payment-modes-config.component.tsx
@@ -19,7 +19,7 @@ import { useTranslation } from 'react-i18next';
import { useForm, Controller } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
-import { showSnackbar, openmrsFetch } from '@openmrs/esm-framework';
+import { showSnackbar, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
import { CardHeader } from '@openmrs/esm-patient-common-lib';
import styles from './payment-modes-config.scss';
@@ -53,7 +53,7 @@ const PaymentModesConfig: React.FC = () => {
const fetchPaymentModes = useCallback(async () => {
try {
- const response = await openmrsFetch('/ws/rest/v1/billing/paymentMode?v=full');
+ const response = await openmrsFetch(`${restBaseUrl}/billing/paymentMode?v=full`);
setPaymentModes(response.data.results || []);
} catch (err) {
showSnackbar({
@@ -87,7 +87,7 @@ const PaymentModesConfig: React.FC = () => {
}
try {
- const response = await openmrsFetch('/ws/rest/v1/billing/paymentMode', {
+ const response = await openmrsFetch(`${restBaseUrl}/billing/paymentMode`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -132,7 +132,7 @@ const PaymentModesConfig: React.FC = () => {
if (!selectedPaymentMode) return;
try {
- await openmrsFetch(`/ws/rest/v1/billing/paymentMode/${selectedPaymentMode.uuid}`, {
+ await openmrsFetch(`${restBaseUrl}/billing/paymentMode/${selectedPaymentMode.uuid}`, {
method: 'DELETE',
});