Problem
AbstractIndexingAdvice<T extends BaseOpenmrsData> (querystore-api org.openmrs.module.querystore.bridge.AbstractIndexingAdvice) constrains the generic type parameter to BaseOpenmrsData. This rules out indexing any entity that extends BaseOpenmrsMetadata or BaseChangeableOpenmrsMetadata — i.e., catalog and reference data.
In practice this shuts out a broad class of legitimate search targets:
BillableService (the billing module's service catalog — concrete pain point we hit; we had to drop catalog indexing entirely)
PaymentMode, CashPoint, BillableServicePrice
Drug, OrderType, LocationTag, and presumably similar catalog types in other modules
The serializer SPI (AbstractRecordSerializer<T>) has no such bound. Only the advice does, and only because it relies on BaseOpenmrsData.getVoided() to route voided entities through the delete path (the per-node voided policy).
What we tried
In the billing module's querystore slice we built a full BillableServiceRecordSerializer + BillableServiceIndexingAdvice + BillableServiceResourceTypeProvider, then deleted them when the type bound surfaced at compile time. Catalog search ("find every Lab-department service") remains unaddressed and there's no workaround within the existing SPI.
Suggested approach
Two reasonable shapes:
-
Split the abstract. AbstractDataIndexingAdvice<T extends BaseOpenmrsData> for the current behavior, plus AbstractMetadataIndexingAdvice<T extends BaseOpenmrsMetadata> that routes retired entities through the delete path instead of voided. Slight code duplication, clean type system.
-
Widen to OpenmrsObject. Drop the bound, replace entity.getVoided() with a strategy hook (protected boolean isInactive(T entity)) that data subclasses implement as getVoided() and metadata subclasses implement as getRetired(). Less duplication, slightly more contract for subclasses.
Either way, the document writes / SPI surface / consumer queries don't change — only the advice's type constraint.
Failure mode prevented
Without this, every module with catalog search needs (billing, stockmanagement, FHIR, formentry…) hand-rolls its own index outside the querystore, fragmenting the search surface that querystore exists to centralize.
Surfaced during a billing module slice that adds Bill / BillRefund / BillDiscount / Timesheet to the querystore. See openmrs/openmrs-module-billing#176 (the ADR) for the full context.
Problem
AbstractIndexingAdvice<T extends BaseOpenmrsData>(querystore-apiorg.openmrs.module.querystore.bridge.AbstractIndexingAdvice) constrains the generic type parameter toBaseOpenmrsData. This rules out indexing any entity that extendsBaseOpenmrsMetadataorBaseChangeableOpenmrsMetadata— i.e., catalog and reference data.In practice this shuts out a broad class of legitimate search targets:
BillableService(the billing module's service catalog — concrete pain point we hit; we had to drop catalog indexing entirely)PaymentMode,CashPoint,BillableServicePriceDrug,OrderType,LocationTag, and presumably similar catalog types in other modulesThe serializer SPI (
AbstractRecordSerializer<T>) has no such bound. Only the advice does, and only because it relies onBaseOpenmrsData.getVoided()to route voided entities through the delete path (the per-node voided policy).What we tried
In the billing module's querystore slice we built a full
BillableServiceRecordSerializer+BillableServiceIndexingAdvice+BillableServiceResourceTypeProvider, then deleted them when the type bound surfaced at compile time. Catalog search ("find every Lab-department service") remains unaddressed and there's no workaround within the existing SPI.Suggested approach
Two reasonable shapes:
Split the abstract.
AbstractDataIndexingAdvice<T extends BaseOpenmrsData>for the current behavior, plusAbstractMetadataIndexingAdvice<T extends BaseOpenmrsMetadata>that routes retired entities through the delete path instead of voided. Slight code duplication, clean type system.Widen to
OpenmrsObject. Drop the bound, replaceentity.getVoided()with a strategy hook (protected boolean isInactive(T entity)) that data subclasses implement asgetVoided()and metadata subclasses implement asgetRetired(). Less duplication, slightly more contract for subclasses.Either way, the document writes / SPI surface / consumer queries don't change — only the advice's type constraint.
Failure mode prevented
Without this, every module with catalog search needs (billing, stockmanagement, FHIR, formentry…) hand-rolls its own index outside the querystore, fragmenting the search surface that querystore exists to centralize.
Surfaced during a billing module slice that adds Bill / BillRefund / BillDiscount / Timesheet to the querystore. See openmrs/openmrs-module-billing#176 (the ADR) for the full context.