Skip to content
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

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

ODORA0
Copy link

@ODORA0 ODORA0 commented Jun 7, 2024

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 paid

BILLABLE_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

@ODORA0 ODORA0 marked this pull request as draft June 7, 2024 05:13
@ODORA0 ODORA0 marked this pull request as ready for review June 12, 2024 06:23
Copy link
Member

@mks-d mks-d left a 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?

Copy link
Member

@mks-d mks-d left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks overall pretty good, though I'm confused as to why the whole thing may work when certain Spring beans haven't been defined as such...

Big miss though: documentation. We'll need a dedicated README for your new domain(s) and a couple of updates in the main README, here, here, here and here.

api-2.4/pom.xml Outdated
@@ -16,6 +16,7 @@

<properties>
<openmrsPlatformVersion>${openmrsVersion2.4}</openmrsPlatformVersion>
<billingVersion>1.1.0-SNAPSHOT</billingVersion>
Copy link
Member

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?

Copy link
Member

@mks-d mks-d left a 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 →

<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

readme/billing.md Outdated Show resolved Hide resolved
readme/billing.md Outdated Show resolved Hide resolved
Copy link
Member

@mks-d mks-d left a 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?

Comment on lines 22 to 36
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'
}
Copy link
Member

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.

Comment on lines 34 to 47
{
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);
}
Copy link
Member

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?

Copy link
Author

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.

Copy link
Member

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?

Copy link
Author

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?

Copy link
Member

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:

<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"/>

Comment on lines 38 to 41
String uuid = line.getString(HEADER_UUID);
if (StringUtils.isNotBlank(uuid)) {
paymentMode.setUuid(uuid);
}
Copy link
Member

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)?

Comment on lines 34 to 39
if (line.containsHeader(HEADER_UUID)) {
String uuid = line.getString(HEADER_UUID);
if (StringUtils.isNotBlank(uuid)) {
cashPoint.setUuid(uuid);
}
}
Copy link
Member

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.

Comment on lines 39 to 51
@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);
}
Copy link
Member

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.:

@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);
Copy link
Member

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Comment on lines +60 to +77
{
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());
}
Copy link
Member

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
...

@ODORA0
Copy link
Author

ODORA0 commented Jul 4, 2024

@Ruhanga Please review

Comment on lines 40 to 47
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);
}
}
Copy link
Member

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?

Copy link
Author

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
Copy link
Member

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));

Copy link
Member

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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

Comment on lines 37 to 41
// Process UUID (required)
String uuid = line.get(HEADER_UUID, true);
if (StringUtils.isNotBlank(uuid)) {
paymentMode.setUuid(uuid);
}
Copy link
Member

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?

Copy link
Author

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

Comment on lines +15 to +17
protected static final String HEADER_UUID = "uuid";

protected static final String HEADER_NAME = "name";
Copy link
Member

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.

Comment on lines +37 to +45
// 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);
Copy link
Member

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.

@mks-d mks-d requested a review from Ruhanga July 4, 2024 16:04
@mks-d mks-d changed the title Add Billing Domains to Iniz [#272] Added Billing-related Domains. Jul 4, 2024
Copy link
Member

@Ruhanga Ruhanga left a 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:

Comment on lines +68 to +73
<dependency>
<groupId>org.openmrs.module</groupId>
<artifactId>billing-omod</artifactId>
<version>${billingVersion}</version>
<scope>provided</scope>
</dependency>
Copy link
Member

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.

Copy link
Author

@ODORA0 ODORA0 Jul 4, 2024

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?

Copy link
Member

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.

Copy link
Member

@Ruhanga Ruhanga left a 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...

Comment on lines +61 to +76
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());
Copy link
Member

@Ruhanga Ruhanga Jul 4, 2024

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.

Comment on lines +68 to +73
<dependency>
<groupId>org.openmrs.module</groupId>
<artifactId>billing-omod</artifactId>
<version>${billingVersion}</version>
<scope>provided</scope>
</dependency>
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants