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

Merged
merged 30 commits into from
Aug 13, 2024
Merged

Conversation

ODORA0
Copy link
Contributor

@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
Contributor 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
Contributor 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 53
@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
Contributor 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
Contributor 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
Contributor 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
Contributor 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 38
// 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:

api-2.4/pom.xml Outdated
Comment on lines 68 to 75
<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
Contributor 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.

api-2.4/pom.xml Outdated
Comment on lines 68 to 75
<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.

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, @ODORA0, for contributing additional tests. For all loader integration tests, our goals should encompass at least three verifications: 1.) Creating entities, 2.) Modifying existing entities, and 3.) Retiring/un-retiring of existing entities via CSV lines. You can refer to this example here for guidance.

Comment on lines 44 to 47
PaymentMode paymentMode = paymentModeService.getByUuid("526bf278-ba81-4436-b867-c2f6641d060a");
assertNotNull(paymentMode);
assertEquals("Cash", paymentMode.getName());
assertEquals("Cash payment mode", paymentMode.getDescription());
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't validate that the csv file configuration/billableserviceprices/ServicePrices.csv was loaded. Validation would be fine if what's in setup() of this same file was removed.

Comment on lines 52 to 58
CashPoint cashPoint = iCashPointService.getByUuid("54065383-b4d4-42d2-af4d-d250a1fd2590");
assertNotNull(cashPoint);
assertEquals("OPD Cash Point", cashPoint.getName());
assertEquals("Opd cash point for billing", cashPoint.getDescription());

Location location = locationService.getLocationByUuid("8d6c7b96-29de-102b-86b0-7a5022ba4115");
assertEquals(location, cashPoint.getLocation());
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't validate that the csv file configuration/billableserviceprices/cashPoints.csv was loaded. Validation would be fine if what's in setup() of this same file was removed.

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.

@ODORA0, please remember to provide separate dedicated README documentation for each of the added domains. This should include definitions for all the domain specific fields (See this example).

Additionally, update the existing documentation as highlighted below.

README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
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.

For all loader integration tests, our goals should encompass at least three verifications: 1.) Creating entities, 2.) Modifying existing entities, and 3.) Retiring/un-retiring of existing entities via CSV lines. You can refer to this example here for guidance.

Thanks for the changes @ODORA0. Let's remember to address the above comment too.

README.md Outdated Show resolved Hide resolved
@ODORA0 ODORA0 force-pushed the billingMeta branch 2 times, most recently from 426bfe4 to 849e318 Compare July 12, 2024 07:28
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.

@ODORA0 could you please push the changes without using force push for now? It complicates the review process, especially when some comments need to be properly resolved.

@@ -0,0 +1,2 @@
uuid, name, price, paymentMode, item, billableService
526bf278-ba81-4436-b867-c2f6641d060a, Antenatal Cash Item, 10000, Cash, , Antenatal Care
Copy link
Member

Choose a reason for hiding this comment

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

One line is insufficient to validate at least three scenarios: creation, editing, and retirement.

@@ -0,0 +1,2 @@
uuid,name,description,location
54065383-b4d4-42d2-af4d-d250a1fd2590, OPD Cash Point, Opd cash point for billing, ART Clinic
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...

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.

@ODORA0 overall, this looks good. Let's refine a few details below.

import org.openmrs.module.initializer.api.CsvParser;
import org.springframework.beans.factory.annotation.Autowired;

@OpenmrsProfile(modules = { "billing:1.1.0" })
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
@OpenmrsProfile(modules = { "billing:1.1.0" })
@OpenmrsProfile(modules = { "billing:1.1.0 - 9.*" })

import org.openmrs.module.initializer.api.loaders.BaseCsvLoader;
import org.springframework.beans.factory.annotation.Autowired;

@OpenmrsProfile(modules = { "billing:1.1.0" })
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...

/**
* This is the first level line processor for Billable Services
*/
@OpenmrsProfile(modules = { "billing:1.1.0" })
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.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

@OpenmrsProfile(modules = { "billing:1.1.0" })
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.

import org.openmrs.module.initializer.api.loaders.BaseCsvLoader;
import org.springframework.beans.factory.annotation.Autowired;

@OpenmrsProfile(modules = { "billing:1.1.0" })
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.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

@OpenmrsProfile(modules = { "billing:1.1.0" })
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.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

@OpenmrsProfile(modules = { "billing:1.1.0" })
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.

import org.openmrs.module.initializer.api.loaders.BaseCsvLoader;
import org.springframework.beans.factory.annotation.Autowired;

@OpenmrsProfile(modules = { "billing:1.1.0" })
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 54 to 59
BillableService service = billableItemsService.getByUuid("16435ab4-27c3-4d91-b21e-52819bd654d8");
service.setName("Nutrition counseling updated");
service.setServiceStatus(BillableServiceStatus.DISABLED);
billableItemsService.save(service);

loader.load();
Copy link
Member

Choose a reason for hiding this comment

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

This should be done through the setup and loader.loader() run once above on line 30. The same is true for the other test cases including those in other domain loader tests.

Comment on lines 66 to 84
// Retire and un-retire an existing entity via CSV (assuming retirement is done by setting status to DISABLED)
{
BillableService service = billableItemsService.getByUuid("16435ab4-27c3-4d91-b21e-52819bd654d8");
service.setServiceStatus(BillableServiceStatus.DISABLED);
billableItemsService.save(service);

loader.load();

BillableService retiredService = billableItemsService.getByUuid("16435ab4-27c3-4d91-b21e-52819bd654d8");
Assert.assertEquals(BillableServiceStatus.DISABLED, retiredService.getServiceStatus());

retiredService.setServiceStatus(BillableServiceStatus.ENABLED);
billableItemsService.save(retiredService);

loader.load();

BillableService unretiredService = billableItemsService.getByUuid("16435ab4-27c3-4d91-b21e-52819bd654d8");
Assert.assertEquals(BillableServiceStatus.ENABLED, unretiredService.getServiceStatus());
}
Copy link
Member

@Ruhanga Ruhanga Jul 16, 2024

Choose a reason for hiding this comment

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

This should assert that retiredService.getRetired() == true, the same is true for similar test cases on the other domains.

Comment on lines 40 to 79
public void setup() throws Exception {
executeDataSet("testdata/test-metadata.xml");
loader.load();
}

@Test
public void shouldCreateCashPointsFromCSV() {
// Verify creation for all CashPoints
CashPoint cashPoint1 = iCashPointService.getByUuid("54065383-b4d4-42d2-af4d-d250a1fd2590");
assertNotNull(cashPoint1);
assertEquals("OPD Cash Point", cashPoint1.getName());
assertEquals("Opd cash point for billing", cashPoint1.getDescription());

Location location1 = locationService.getLocation("ART Clinic");
assertNotNull(location1);
assertEquals("ART Clinic", location1.getName());
assertEquals(location1, cashPoint1.getLocation());

CashPoint cashPoint2 = iCashPointService.getByUuid("c56a108f-e3c5-4881-a5e8-a796601883b9");
assertNotNull(cashPoint2);
assertEquals("IPD Cash Point", cashPoint2.getName());
assertEquals("IPD cash point for billing", cashPoint2.getDescription());

Location location2 = locationService.getLocation("Inpatient Ward");
assertNotNull(location2);
assertEquals("Inpatient Ward", location2.getName());
assertEquals(location2, cashPoint2.getLocation());

CashPoint cashPoint3 = iCashPointService.getByUuid("8e48e0be-1a31-4bd3-a54d-ace82653f8b8");
assertNotNull(cashPoint3);
assertEquals("MCH Cash Point", cashPoint3.getName());
assertEquals("MCH cash point for billing", cashPoint3.getDescription());

Location location3 = locationService.getLocation("MCH Clinic");
assertNotNull(location3);
assertEquals("MCH Clinic", location3.getName());
assertEquals(location3, cashPoint3.getLocation());
}
Copy link
Member

Choose a reason for hiding this comment

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

This is kind of change I was expecting seen on all the loader integration tests @ODORA0.

Suggested change
public void setup() throws Exception {
executeDataSet("testdata/test-metadata.xml");
loader.load();
}
@Test
public void shouldCreateCashPointsFromCSV() {
// Verify creation for all CashPoints
CashPoint cashPoint1 = iCashPointService.getByUuid("54065383-b4d4-42d2-af4d-d250a1fd2590");
assertNotNull(cashPoint1);
assertEquals("OPD Cash Point", cashPoint1.getName());
assertEquals("Opd cash point for billing", cashPoint1.getDescription());
Location location1 = locationService.getLocation("ART Clinic");
assertNotNull(location1);
assertEquals("ART Clinic", location1.getName());
assertEquals(location1, cashPoint1.getLocation());
CashPoint cashPoint2 = iCashPointService.getByUuid("c56a108f-e3c5-4881-a5e8-a796601883b9");
assertNotNull(cashPoint2);
assertEquals("IPD Cash Point", cashPoint2.getName());
assertEquals("IPD cash point for billing", cashPoint2.getDescription());
Location location2 = locationService.getLocation("Inpatient Ward");
assertNotNull(location2);
assertEquals("Inpatient Ward", location2.getName());
assertEquals(location2, cashPoint2.getLocation());
CashPoint cashPoint3 = iCashPointService.getByUuid("8e48e0be-1a31-4bd3-a54d-ace82653f8b8");
assertNotNull(cashPoint3);
assertEquals("MCH Cash Point", cashPoint3.getName());
assertEquals("MCH cash point for billing", cashPoint3.getDescription());
Location location3 = locationService.getLocation("MCH Clinic");
assertNotNull(location3);
assertEquals("MCH Clinic", location3.getName());
assertEquals(location3, cashPoint3.getLocation());
}
public void setup() throws Exception {
executeDataSet("testdata/test-metadata.xml");
}
@Test
public void shouldCreateCashPointsFromCSV() {
// Replay
loader.load();
// Verify creation for all CashPoints
CashPoint cashPoint1 = iCashPointService.getByUuid("54065383-b4d4-42d2-af4d-d250a1fd2590");
assertNotNull(cashPoint1);
assertEquals("OPD Cash Point", cashPoint1.getName());
assertEquals("Opd cash point for billing", cashPoint1.getDescription());
Location location1 = locationService.getLocation("ART Clinic");
assertNotNull(location1);
assertEquals("ART Clinic", location1.getName());
assertEquals(location1, cashPoint1.getLocation());
CashPoint cashPoint2 = iCashPointService.getByUuid("c56a108f-e3c5-4881-a5e8-a796601883b9");
assertNotNull(cashPoint2);
assertEquals("IPD Cash Point", cashPoint2.getName());
assertEquals("IPD cash point for billing", cashPoint2.getDescription());
Location location2 = locationService.getLocation("Inpatient Ward");
assertNotNull(location2);
assertEquals("Inpatient Ward", location2.getName());
assertEquals(location2, cashPoint2.getLocation());
CashPoint cashPoint3 = iCashPointService.getByUuid("8e48e0be-1a31-4bd3-a54d-ace82653f8b8");
assertNotNull(cashPoint3);
assertEquals("MCH Cash Point", cashPoint3.getName());
assertEquals("MCH cash point for billing", cashPoint3.getDescription());
Location location3 = locationService.getLocation("MCH Clinic");
assertNotNull(location3);
assertEquals("MCH Clinic", location3.getName());
assertEquals(location3, cashPoint3.getLocation());
}

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.

A few outstanding issues need to be addressed @ODORA0. Let’s try to make sure to implement proper assertions as recommended below.

Comment on lines 59 to 62
BillableService service = billableItemsService.getByUuid("16435ab4-27c3-4d91-b21e-52819bd654d8");
service.setName("Nutrition counseling updated");
service.setServiceStatus(BillableServiceStatus.DISABLED);
billableItemsService.save(service);
Copy link
Member

Choose a reason for hiding this comment

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

This should be accomplished by a .csv line corresponding to a pre-existing record, likely saved during the setup() method. When loader.load() is called, the existing record should be updated with the relevant line from the .csv file. After that, you can verify that the .csv line was loaded correctly.

Copy link
Contributor Author

@ODORA0 ODORA0 Jul 18, 2024

Choose a reason for hiding this comment

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

@Ruhanga Please expound on this, I don't follow

Comment on lines 71 to 73
BillableService service = billableItemsService.getByUuid("16435ab4-27c3-4d91-b21e-52819bd654d8");
service.setServiceStatus(BillableServiceStatus.DISABLED);
billableItemsService.save(service);
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 for retiring.

Comment on lines 78 to 83
// Unretire the entity
retiredService.setServiceStatus(BillableServiceStatus.ENABLED);
billableItemsService.save(retiredService);

BillableService unretiredService = billableItemsService.getByUuid("16435ab4-27c3-4d91-b21e-52819bd654d8");
Assert.assertEquals(BillableServiceStatus.ENABLED, unretiredService.getServiceStatus());
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 83 to 86
// Modify an existing entity in the CSV
CashPoint cashPoint = iCashPointService.getByUuid("54065383-b4d4-42d2-af4d-d250a1fd2590");
cashPoint.setName("OPD Cash Point Updated");
iCashPointService.save(cashPoint);
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 94 to 104
// Retire an existing entity in the CSV
CashPoint cashPoint = iCashPointService.getByUuid("c56a108f-e3c5-4881-a5e8-a796601883b9");
cashPoint.setRetired(true);
iCashPointService.save(cashPoint);

CashPoint retiredCashPoint = iCashPointService.getByUuid("c56a108f-e3c5-4881-a5e8-a796601883b9");
assertTrue(retiredCashPoint.getRetired());

// Unretire the entity
retiredCashPoint.setRetired(false);
iCashPointService.save(retiredCashPoint);
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 for retiring and un-retiring

Comment on lines 57 to 60
// Modify an existing entity in the CSV
PaymentMode paymentMode = paymentModeService.getByUuid("526bf278-ba81-4436-b867-c2f6641d060a");
paymentMode.setName("Cash Updated");
paymentModeService.save(paymentMode);
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 68 to 78
// Retire an existing entity in the CSV
PaymentMode paymentMode = paymentModeService.getByUuid("2b1b9aae-5d35-43dd-9214-3fd370fd7737");
paymentMode.setRetired(true);
paymentModeService.save(paymentMode);

PaymentMode retiredPaymentMode = paymentModeService.getByUuid("2b1b9aae-5d35-43dd-9214-3fd370fd7737");
Assert.assertTrue(retiredPaymentMode.getRetired());

// Unretire the entity
retiredPaymentMode.setRetired(false);
paymentModeService.save(retiredPaymentMode);
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 for retiring and un-retiring...

@Ruhanga
Copy link
Member

Ruhanga commented Jul 19, 2024

@ODORA0 do the tests you added run successfully when you run mvn clean install?

@Ruhanga Ruhanga force-pushed the billingMeta branch 2 times, most recently from eedf490 to 8a629ab Compare July 26, 2024 12:44
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.

@ODORA0, I’ve successfully set up an appropriate test environment and resolved the issues with the Billable Services Domain integration tests. Could you take it from here and proceed with the other domain tests, using the Billable Services Domain integration tests as a reference?

Copy link
Contributor

@corneliouzbett corneliouzbett left a comment

Choose a reason for hiding this comment

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

Left few comments.
Generally LGTM. I'll let @Ruhanga have final say in this one.

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.

Generally LGTM!

readme/billableservies.md Outdated Show resolved Hide resolved
readme/billableservies.md Outdated Show resolved Hide resolved
readme/paymentmodes.md Outdated Show resolved Hide resolved
/**
* This is the first level line processor for Billable Services
*/
@OpenmrsProfile(modules = { "billing:1.1.0 - 9.*" })
Copy link
Member

Choose a reason for hiding this comment

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

@ODORA0 @Ruhanga why not just

@OpenmrsProfile(modules = { "billing:1.1.0" })

?
That should, normally, mean what we're intending no?

Copy link
Member

@Ruhanga Ruhanga Aug 12, 2024

Choose a reason for hiding this comment

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

It’s quite restrictive to specify version 1.1.0 as the required version. We would prefer to set 1.1.0 as the minimum version instead. Version 9.* provides a more flexible range for usage, accommodating future updates until potential incompatibility issues arise.

@Ruhanga Ruhanga requested a review from mks-d August 13, 2024 05:26
@mks-d mks-d merged commit 0b73171 into mekomsolutions:main Aug 13, 2024
2 checks passed
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.

5 participants