-
Notifications
You must be signed in to change notification settings - Fork 72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[#272] Added Billing-related Domains. #269
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @ODORA0. Since this is not connected to any Iniz GitHub issue, I'll ask you to provide a comprehensive description of what all this PR bring in, could you do that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
api-2.4/pom.xml
Outdated
@@ -16,6 +16,7 @@ | |||
|
|||
<properties> | |||
<openmrsPlatformVersion>${openmrsVersion2.4}</openmrsPlatformVersion> | |||
<billingVersion>1.1.0-SNAPSHOT</billingVersion> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We assume that there is plans for a v1.1.0
release?
.../src/main/java/org/openmrs/module/initializer/api/billing/BillableServicesLineProcessor.java
Outdated
Show resolved
Hide resolved
api-2.4/src/main/java/org/openmrs/module/initializer/api/billing/CashPointCsvParser.java
Show resolved
Hide resolved
api-2.4/src/main/java/org/openmrs/module/initializer/api/billing/CashPointLoader.java
Show resolved
Hide resolved
.../src/main/java/org/openmrs/module/initializer/api/billing/BillableServicesLineProcessor.java
Outdated
Show resolved
Hide resolved
...2.4/src/main/java/org/openmrs/module/initializer/api/billing/ServicePricesLineProcessor.java
Outdated
Show resolved
Hide resolved
...2.4/src/main/java/org/openmrs/module/initializer/api/billing/ServicePricesLineProcessor.java
Outdated
Show resolved
Hide resolved
...src/test/java/org/openmrs/module/initializer/api/BillableServicesLoaderIntergrationTest.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make it work with Validator, this will prove that all is well.
See this as an example (with IDGen) of what's required to be done →
openmrs-module-initializer/validator/pom.xml
Lines 186 to 192 in 3b5a541
<dependency> | |
<groupId>org.openmrs.module</groupId> | |
<artifactId>idgen-api</artifactId> | |
<version>${idgenVersion}</version> | |
<scope>runtime</scope> | |
<type>jar</type> | |
</dependency> |
Then, you should validate that you can make a dry run of a piece of billing config, see → https://github.com/mekomsolutions/openmrs-module-initializer/blob/master/readme/validator.md#how-to-make-a-dry-run
api-2.4/src/main/java/org/openmrs/module/initializer/api/billing/CashPointCsvParser.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ruhanga could you check that all is well with this new domain with the Validator?
public abstract class BillableServicesLoaderIntergrationTest extends DomainBaseModuleContextSensitive_2_3_Test { | ||
|
||
public BillableServicesLoaderIntergrationTest() { | ||
super(); | ||
{ | ||
Module mod = new Module("", "billing", "", "", "", "1.1.0-SNAPSHOT"); | ||
mod.setFile(new File("")); | ||
ModuleFactory.getStartedModulesMap().put(mod.getModuleId(), mod); | ||
} | ||
} | ||
|
||
@Override | ||
public void updateSearchIndex() { | ||
// to prevent Data Filter's 'Illegal Record Access' | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @ODORA0.
Thank you for the PR. We should include some integration tests here. You can refer to this example as a guide.
{ | ||
Concept concept = new Concept(); | ||
concept.setShortName(new ConceptName("Antenatal Services", Locale.ENGLISH)); | ||
concept.setConceptClass(conceptService.getConceptClassByName("Misc")); | ||
concept.setDatatype(conceptService.getConceptDatatypeByName("N/A")); | ||
concept = conceptService.saveConcept(concept); | ||
} | ||
{ | ||
Concept concept = new Concept(); | ||
concept.setShortName(new ConceptName("Orthopedic Services", Locale.ENGLISH)); | ||
concept.setConceptClass(conceptService.getConceptClassByName("Misc")); | ||
concept.setDatatype(conceptService.getConceptDatatypeByName("N/A")); | ||
concept = conceptService.saveConcept(concept); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ODORA0 did you need to create new test concepts, why not use some from the various test datasets?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mks-d I am picking this from the CSV.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ODORA0 you mean you're showing that loading the CSV will modify those concepts accordingly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mks-d I don't follow, please explain. Which datasets did you mean earlier?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Within OpenMRS' Spring tests you don't necessarily need to create concepts, you have access to this bunch if you need concepts.
You can even load and use the following extra dataset shipped with Iniz if you ever needed more:
openmrs-module-initializer/api/src/test/resources/testdata/test-concepts.xml
Lines 7 to 22 in 6449d0b
<concept CONCEPT_ID="5497" UUID="a09ab2c5-878e-4905-b25d-5784167d0216" RETIRED="false" DATE_CREATED="2004-08-12 00:00:00.0" VERSION="0.1" DATE_CHANGED="2006-06-09 14:57:33.0" IS_SET="false" DATATYPE_ID="1" CLASS_ID="1" CHANGED_BY="1" CREATOR="1"/> | |
<concept CONCEPT_ID="5498" UUID="4421da0d-42d0-410d-8ffd-47ec6f155d8f" RETIRED="false" DATE_CREATED="2019-12-11 16:59:47.0" DATE_CHANGED="2019-12-11 16:59:47.0" IS_SET="false" DATATYPE_ID="3" CLASS_ID="11" CHANGED_BY="1" CREATOR="1"/> | |
<concept CONCEPT_ID="5499" UUID="276c5861-cd46-429f-9665-e067ddeca8e3" RETIRED="false" DATE_CREATED="2019-12-11 16:59:47.0" DATE_CHANGED="2019-12-11 16:59:47.0" IS_SET="false" DATATYPE_ID="3" CLASS_ID="11" CHANGED_BY="1" CREATOR="1"/> | |
<concept CONCEPT_ID="5500" UUID="401d17f2-6a53-4d9e-8df8-08010a837970" RETIRED="false" DATE_CREATED="2019-12-11 16:59:47.0" DATE_CHANGED="2019-12-11 16:59:47.0" IS_SET="false" DATATYPE_ID="3" CLASS_ID="11" CHANGED_BY="1" CREATOR="1"/> | |
<concept CONCEPT_ID="5501" UUID="542c18f3-a837-41d4-92e9-be53dc825302" RETIRED="false" DATE_CREATED="2019-12-11 16:59:47.0" DATE_CHANGED="2019-12-11 16:59:47.0" IS_SET="false" DATATYPE_ID="3" CLASS_ID="11" CHANGED_BY="1" CREATOR="1"/> | |
<concept CONCEPT_ID="5502" UUID="13f62545-d0de-4f7b-a86f-04b91afee2d3" RETIRED="false" DATE_CREATED="2019-12-11 16:59:47.0" DATE_CHANGED="2019-12-11 16:59:47.0" IS_SET="false" DATATYPE_ID="3" CLASS_ID="11" CHANGED_BY="1" CREATOR="1"/> | |
<concept CONCEPT_ID="5503" UUID="d803e973-1010-4415-8659-c011dec707c0" RETIRED="false" DATE_CREATED="2019-12-11 16:59:48.0" VERSION="1.0" DATE_CHANGED="2019-12-11 16:59:48.0" IS_SET="true" DATATYPE_ID="3" CLASS_ID="11" CHANGED_BY="1" CREATOR="1"/> | |
<concept CONCEPT_ID="5504" UUID="4280217a-eb93-4e2f-9684-28bed4690e7b" RETIRED="false" DATE_CREATED="2019-12-11 16:59:48.0" DATE_CHANGED="2019-12-11 16:59:48.0" IS_SET="false" DATATYPE_ID="1" CLASS_ID="11" CHANGED_BY="1" CREATOR="1"/> | |
<concept CONCEPT_ID="5505" UUID="b0b15817-79d6-4c33-b7e9-bfa079d46f5f" RETIRED="false" DATE_CREATED="2019-12-11 16:59:48.0" DATE_CHANGED="2019-12-11 16:59:48.0" IS_SET="false" DATATYPE_ID="13" CLASS_ID="11" CHANGED_BY="1" CREATOR="1"/> | |
<concept CONCEPT_ID="5506" UUID="7517abe5-8a70-4347-af17-c7e31fa75579" RETIRED="false" DATE_CREATED="2019-12-11 16:59:48.0" VERSION="1.7" DATE_CHANGED="2019-12-11 16:59:48.0" IS_SET="false" DATATYPE_ID="3" CLASS_ID="11" CHANGED_BY="1" CREATOR="1"/> | |
<concept CONCEPT_ID="5507" UUID="995ceb0e-d397-4560-bbf1-7642d143a0a2" RETIRED="false" DATE_CREATED="2019-12-11 16:59:48.0" DATE_CHANGED="2019-12-11 16:59:48.0" IS_SET="false" DATATYPE_ID="3" CLASS_ID="11" CHANGED_BY="1" CREATOR="1"/> | |
<concept CONCEPT_ID="5508" UUID="4c93c34e-37c2-11ea-bd28-d70ffe7aa802" RETIRED="true" DATE_CREATED="2019-12-11 16:59:48.0" DATE_CHANGED="2019-12-11 16:59:48.0" IS_SET="false" DATATYPE_ID="3" CLASS_ID="11" CHANGED_BY="1" CREATOR="1"/> | |
<concept CONCEPT_ID="5509" UUID="fda5cc2a-6245-4d91-be17-446d27aab33b" RETIRED="false" DATE_CREATED="2019-12-11 16:59:48.0" DATE_CHANGED="2019-12-11 16:59:48.0" IS_SET="false" DATATYPE_ID="3" CLASS_ID="11" CHANGED_BY="1" CREATOR="1"/> | |
<concept CONCEPT_ID="5510" UUID="4cfe07b0-3061-11ec-8d2b-0242ac110002" RETIRED="false" DATE_CREATED="2019-12-11 16:59:48.0" DATE_CHANGED="2019-12-11 16:59:48.0" IS_SET="false" DATATYPE_ID="3" CLASS_ID="11" CHANGED_BY="1" CREATOR="1"/> | |
<concept CONCEPT_ID="5511" UUID="61214827-303f-11ec-8d2b-0242ac110002" RETIRED="false" DATE_CREATED="2019-12-11 16:59:48.0" DATE_CHANGED="2019-12-11 16:59:48.0" IS_SET="false" DATATYPE_ID="3" CLASS_ID="11" CHANGED_BY="1" CREATOR="1"/> | |
<concept CONCEPT_ID="5512" UUID="1ddb8255-00d5-45e8-8830-f9567919a382" RETIRED="false" DATE_CREATED="2019-12-11 16:59:48.0" DATE_CHANGED="2019-12-11 16:59:48.0" IS_SET="false" DATATYPE_ID="3" CLASS_ID="11" CHANGED_BY="1" CREATOR="1"/> |
String uuid = line.getString(HEADER_UUID); | ||
if (StringUtils.isNotBlank(uuid)) { | ||
paymentMode.setUuid(uuid); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the UUID not required (like the name is)?
if (line.containsHeader(HEADER_UUID)) { | ||
String uuid = line.getString(HEADER_UUID); | ||
if (StringUtils.isNotBlank(uuid)) { | ||
cashPoint.setUuid(uuid); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not
cashPoint.setUuid(line.getString(HEADER_UUID));
?
... if the UUID is not required of course.
@Test | ||
public void testBootstrapWithExistingService() { | ||
String uuid = "44ebd6cd-04ad-4eba-8ce1-0de4564bfd17"; | ||
CsvLine csvLine = new CsvLine(new String[] { "Uuid" }, new String[] { uuid }); | ||
|
||
BillableService existingService = new BillableService(); | ||
when(billableServiceResource.getByUniqueId(uuid)).thenReturn(existingService); | ||
|
||
BillableService result = parser.bootstrap(csvLine); | ||
|
||
assertNotNull(result); | ||
assertEquals(existingService, result); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Starting with this one, divide all your test methods/cases as best as can following the given-when-then pattern. Eg.:
Lines 37 to 48 in 6449d0b
@Test | |
public void bootstrap_shouldBootstrapObjectGivenUuidPresentAndObjectNotVoid() { | |
// Setup | |
CsvLine line = new CsvLine(new String[] { "uuid", "void/retire" }, | |
new String[] { "d9e04a9d-d534-4a02-9c40-1c173f3d1d4b", "False" }); | |
// Replay | |
OpenmrsObject obj = displayParser.bootstrap(line); | |
// Verify | |
verify(someParser, times(1)).bootstrap(line); | |
} |
Also the method name follows a convention, see also the example above: bootstrap_shouldBootstrapObjectGivenUuidPresentAndObjectNotVoid
.
As in: "method being tested" + "_" + "expected outcome".
public void setup() { | ||
{ | ||
Concept concept = conceptService.getConceptByUuid("a09ab2c5-878e-4905-b25d-5784167d0216"); | ||
Assert.assertNotNull("Concept should not be null", concept); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this concept is in the test dataset you don't need to assert this, IMO. Also you don't need to craft a special message. If somehow the test dataset is broken, then it's another story, it will be detected and fixed accordingly.
} | ||
{ | ||
Concept concept = conceptService.getConceptByUuid("4421da0d-42d0-410d-8ffd-47ec6f155d8f"); | ||
Assert.assertNotNull("Concept should not be null", concept); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
...4/src/test/java/org/openmrs/module/initializer/api/BillableServiceLoaderIntegrationTest.java
Show resolved
Hide resolved
{ | ||
BillableService service = billableServiceResource.getByUniqueId("44ebd6cd-04ad-4eba-8ce1-0de4564bfd17"); | ||
Assert.assertNotNull(service); | ||
Assert.assertEquals(conceptService.getConceptByUuid("a09ab2c5-878e-4905-b25d-5784167d0216"), service.getConcept()); | ||
Assert.assertEquals(BillableServiceStatus.ENABLED, service.getServiceStatus()); | ||
} | ||
{ | ||
BillableService service = billableServiceResource.getByUniqueId("a0f7d8a1-4fa2-418c-aa8a-9b358f43d605"); | ||
Assert.assertNotNull(service); | ||
Assert.assertEquals(conceptService.getConceptByUuid("4421da0d-42d0-410d-8ffd-47ec6f155d8f"), service.getConcept()); | ||
Assert.assertEquals(BillableServiceStatus.ENABLED, service.getServiceStatus()); | ||
} | ||
{ | ||
BillableService service = billableServiceResource.getByUniqueId("16435ab4-27c3-4d91-b21e-52819bd654d8"); | ||
Assert.assertNotNull(service); | ||
Assert.assertEquals("Updated Service", service.getName()); | ||
Assert.assertEquals(BillableServiceStatus.DISABLED, service.getServiceStatus()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Precedes this with "// verify
":
// verify
...
@Ruhanga Please review |
if (line.containsHeader(HEADER_LOCATION)) { | ||
String location = line.getString(HEADER_LOCATION); | ||
if (StringUtils.isNotBlank(location)) { | ||
cashPoint.setLocation(Utils.fetchLocation(location, locationService)); | ||
} else { | ||
cashPoint.setLocation(null); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure you can't compactify all this into just
String location = line.getString(HEADER_LOCATION);
cashPoint.setLocation(Utils.fetchLocation(location, locationService));
?
Did you check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
@Override | ||
public CashPoint fill(CashPoint cashPoint, CsvLine line) throws IllegalArgumentException { | ||
// UUID is required |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may remove this line is in fact the UUID is not required.
public CashPoint fill(CashPoint cashPoint, CsvLine line) throws IllegalArgumentException { | ||
// UUID is required | ||
cashPoint.setUuid(line.get(HEADER_UUID)); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may remove this empty line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
// Process UUID (required) | ||
String uuid = line.get(HEADER_UUID, true); | ||
if (StringUtils.isNotBlank(uuid)) { | ||
paymentMode.setUuid(uuid); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the UUID required for payment modes but not for cash points?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are actually required, changing that for cash points
protected static final String HEADER_UUID = "uuid"; | ||
|
||
protected static final String HEADER_NAME = "name"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those two are already defined in BaseLineProcessor
, you get them for free.
// Process UUID (required) | ||
paymentMode.setUuid(line.get(HEADER_UUID, true)); | ||
// Process Name (required) | ||
paymentMode.setName(line.get(HEADER_NAME, true)); | ||
// Process other optional attributes | ||
processAttribute(line, HEADER_PRICE, paymentMode); | ||
processAttribute(line, HEADER_PAYMENT_MODE, paymentMode); | ||
processAttribute(line, HEADER_ITEM, paymentMode); | ||
processAttribute(line, HEADER_BILLABLE_SERVICE, paymentMode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we don't need to document in the code which is optional and which is not, this is done in the READMEs and should be self-explanatory through the use of the throwable version or not.
So let's get rid of the comments, you didn't set them in CashPointsLineProcessor
anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for adding tests, @ODORA0. Moving forward, you'll add more integration tests for each of the newly introduced domain loaders. Currently, we only have one, BillableServiceLoaderIntegrationTest
. Here are a few other specific points to address:
<dependency> | ||
<groupId>org.openmrs.module</groupId> | ||
<artifactId>billing-omod</artifactId> | ||
<version>${billingVersion}</version> | ||
<scope>provided</scope> | ||
</dependency> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ODORA0, we should rather depend on the sub-modules' api packages than the omod web api package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ruhanga Please advise, given the constraint that BillableServiceResource
is only available in the billing-omod module and not in the billing-api module, transitioning to use billing-api will limit the direct operations we can perform on BillableService entities since these operations are currently done through the BillableServiceResource.
Also are you suggesting adding integration tests for all the other domain in this PR as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ODORA0 Iniz doesn't need any REST resources, it only needs the Java API of the modules it depends on. You can indeed switch to depending on the -api
submodule and, if all your Spring tests pass, you're good.
Also are you suggesting adding integration tests for all the other domain in this PR as well?
Yes indeed. You need to bring as a test resource a typical, real-world looking, CSV file and demonstrate through your tests that the outcome of loading each CSV file is as expected and can be asserted.
Each *Loader
class should have its corresponding Spring test (context sensitive) class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One other obvious observation...
BillableService service = billableServiceResource.getByUniqueId("44ebd6cd-04ad-4eba-8ce1-0de4564bfd17"); | ||
Assert.assertNotNull(service); | ||
Assert.assertEquals(conceptService.getConceptByUuid("a09ab2c5-878e-4905-b25d-5784167d0216"), service.getConcept()); | ||
Assert.assertEquals(BillableServiceStatus.ENABLED, service.getServiceStatus()); | ||
} | ||
{ | ||
BillableService service = billableServiceResource.getByUniqueId("a0f7d8a1-4fa2-418c-aa8a-9b358f43d605"); | ||
Assert.assertNotNull(service); | ||
Assert.assertEquals(conceptService.getConceptByUuid("4421da0d-42d0-410d-8ffd-47ec6f155d8f"), service.getConcept()); | ||
Assert.assertEquals(BillableServiceStatus.ENABLED, service.getServiceStatus()); | ||
} | ||
{ | ||
BillableService service = billableServiceResource.getByUniqueId("16435ab4-27c3-4d91-b21e-52819bd654d8"); | ||
Assert.assertNotNull(service); | ||
Assert.assertEquals("Updated Service", service.getName()); | ||
Assert.assertEquals(BillableServiceStatus.DISABLED, service.getServiceStatus()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is actually testing the loading of the billable services csv file located at resources/testAppDataDir/configuration/billableservices/services.csv
since what's being retrieved is what's being saved inside setup()
above. Else you'd have to remove setup()
altogether or use it to save billable services to be edited or voided by contents of services.csv
, which are valid update use-cases to test.
<dependency> | ||
<groupId>org.openmrs.module</groupId> | ||
<artifactId>billing-omod</artifactId> | ||
<version>${billingVersion}</version> | ||
<scope>provided</scope> | ||
</dependency> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ODORA0 Iniz doesn't need any REST resources, it only needs the Java API of the modules it depends on. You can indeed switch to depending on the -api
submodule and, if all your Spring tests pass, you're good.
Also are you suggesting adding integration tests for all the other domain in this PR as well?
Yes indeed. You need to bring as a test resource a typical, real-world looking, CSV file and demonstrate through your tests that the outcome of loading each CSV file is as expected and can be asserted.
Each *Loader
class should have its corresponding Spring test (context sensitive) class.
This PR takes care of adding the OpenMRS Billing Module domains to iniz
BILLABLE_SERVICES
that represents services that can be billed within the billing module with attributes such as the name, short name, concept, service type, service category, service prices, and service status, handling creation of billable services in the billing module e.g. Covid Vaccination Service that might be a billable service in a facility under the Vaccination Services, Ultra Sound Scanning Services under the Antenatal Services, Complete Blood Count that can be under Clinical Consultation Services etc.CASH_POINTS
which represents a locations such as OPD Clinic, ART Clinic within the OpenMRS billing module where bills can be created and paidBILLABLE_SERVICE_PRICES
which are payment modes that represents different modes of payment (such as cash, mobile money or credit card) within the billing module which also allows for customisable attribute types, enabling users to define additional properties for each payment mode like the pricing of the different payments