From a0630fae52d17c4b3d253c379809742cfbbc6d26 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Wed, 28 Oct 2026 16:07:21 +0530 Subject: [PATCH] feat(stoptb): save OPD prescription without dispensing by default Inventory is not part of this release, so the Stop TB OPD save no longer checks the camp store or stock, writes no patient issue and leaves stock unchanged. Drugs from the app's own list are saved as sent (DrugID, name, form, frequency, duration, instructions); quantity is worked out when the app sends 0. A new save for the visit replaces its earlier prescription, so an edit or retry leaves one copy. The existing dispensing path is kept behind stoptb.dispense.enabled (default false, no config needed) for the release that brings inventory. Co-Authored-By: Claude Opus 5.5 --- .../service/StopTBPrescriptionService.java | 77 ++++++++++++++++++- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/iemr/flw/service/StopTBPrescriptionService.java b/src/main/java/com/iemr/flw/service/StopTBPrescriptionService.java index 74005710..c61b4d4f 100644 --- a/src/main/java/com/iemr/flw/service/StopTBPrescriptionService.java +++ b/src/main/java/com/iemr/flw/service/StopTBPrescriptionService.java @@ -10,6 +10,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -48,6 +49,11 @@ public class StopTBPrescriptionService { @Autowired private BeneficiaryRepo beneficiaryRepo; + // Off by default: save the prescription only (no store/stock check, no patient issue, stock unchanged). + // true = check stock and dispense from the camp store. + @Value("${stoptb.dispense.enabled:false}") + private boolean dispenseEnabled; + private final Logger logger = LoggerFactory.getLogger(StopTBPrescriptionService.class); public static class InsufficientStockException extends RuntimeException { @@ -150,6 +156,10 @@ public void saveChiefComplaints(List> complaints, Long benef public Map prescribeAndDispense(Map prescription, Long beneficiaryRegID, BenVisitDetail visit, String submissionId, String createdBy, Integer vanID, Integer parkingPlaceID) { List> drugs = toList(prescription.get("drugs")); + if (!dispenseEnabled) { + return prescribeWithoutDispensing(prescription, drugs, beneficiaryRegID, visit, createdBy, vanID, + parkingPlaceID); + } Map result = new LinkedHashMap<>(); if (drugs.isEmpty()) { // Remarks without medicine are still kept, as MMU saves the visit instruction without drugs. @@ -176,7 +186,7 @@ public Map prescribeAndDispense(Map prescription Integer qty = toInt(d.get("qtyPrescribed")); if (qty == null || qty <= 0) { // No quantity from the app: tablets/capsules = doses per day x days, other forms = 1. - qty = calculateQuantity(d, items.get(drugID)); + qty = calculateQuantity(d, toInt(items.get(drugID)[7])); if (qty == null) { throw new IllegalArgumentException("qtyPrescribed is required for drugID " + drugID + " (tablet/capsule quantity needs frequency and duration)"); @@ -480,6 +490,68 @@ private Object[] getPatientDetails(Long beneficiaryRegID) { } } + /** + * Dispensing off (stoptb.dispense.enabled=false): the prescription is saved as the app sent it. No store or + * stock check and no patient issue, so drugs from the app's own list need not exist in this camp's store; + * DrugID, name and form are taken from the request. A new save replaces the visit's earlier prescription + * (same as chief complaints), so an edit or retry does not leave a second copy. + */ + private Map prescribeWithoutDispensing(Map prescription, + List> drugs, Long beneficiaryRegID, BenVisitDetail visit, String createdBy, + Integer vanID, Integer parkingPlaceID) { + for (Map d : drugs) { + if (toInt(d.get("drugID")) == null) throw new IllegalArgumentException("drugID is required for every drug"); + } + // Processed='N' so the removal is uploaded to central again (central updates rows it already has). + for (String table : new String[] { "t_prescribeddrug", "t_prescription" }) { + em.createNativeQuery("UPDATE db_iemr." + table + " SET Deleted = true, Processed = 'N', ModifiedBy = :user " + + "WHERE BeneficiaryRegID = :ben AND VisitCode = :visitCode AND Deleted = false") + .setParameter("user", createdBy) + .setParameter("ben", beneficiaryRegID) + .setParameter("visitCode", visit.getVisitCode()) + .executeUpdate(); + } + + Map result = new LinkedHashMap<>(); + String instruction = toStr(prescription.get("instruction")); + if (!drugs.isEmpty() || (instruction != null && !instruction.isBlank())) { + long prescriptionID = insertPrescription(beneficiaryRegID, visit, instruction, createdBy, vanID, + parkingPlaceID); + for (Map d : drugs) { + Integer qty = toInt(d.get("qtyPrescribed")); + if (qty == null || qty <= 0) qty = calculateQuantity(d, toInt(d.get("itemFormID"))); // null if unknown + em.createNativeQuery("INSERT INTO db_iemr.t_prescribeddrug (BeneficiaryRegID, BenVisitID, " + + "ProviderServiceMapID, VisitCode, PrescriptionID, DrugForm, DrugID, GenericDrugName, Dose, " + + "Frequency, Duration, DuartionUnit, SpecialInstruction, QtyPrescribed, Deleted, Processed, " + + "CreatedBy, VanID, ParkingPlaceID) VALUES (:ben, :visitID, :psm, :visitCode, " + + ":prescriptionID, :form, :drugID, :name, :dose, :frequency, :duration, :durationUnit, " + + ":instruction, :qty, false, 'N', :user, :van, :pp)") + .setParameter("ben", beneficiaryRegID) + .setParameter("visitID", visit.getBenVisitId()) + .setParameter("psm", visit.getProviderServiceMapID()) + .setParameter("visitCode", visit.getVisitCode()) + .setParameter("prescriptionID", prescriptionID) + .setParameter("form", toStr(d.get("drugForm"))) + .setParameter("drugID", toInt(d.get("drugID"))) + .setParameter("name", toStr(d.get("drugName"))) + .setParameter("dose", toStr(d.get("dose"))) + .setParameter("frequency", toStr(d.get("frequency"))) + .setParameter("duration", toStr(d.get("duration"))) + .setParameter("durationUnit", toStr(d.get("durationUnit"))) + .setParameter("instruction", toStr(d.get("instructions"))) + .setParameter("qty", qty) + .setParameter("user", createdBy) + .setParameter("van", vanID) + .setParameter("pp", parkingPlaceID) + .executeUpdate(); + stampVanSerialNo("t_prescribeddrug", "PrescribedDrugID", lastInsertId()); + } + result.put("prescriptionID", prescriptionID); + } + result.put("dispensed", new ArrayList>()); + return result; + } + // t_prescription row for the visit (Stop TB service line, same as the visit). private long insertPrescription(Long beneficiaryRegID, BenVisitDetail visit, String instruction, String createdBy, Integer vanID, Integer parkingPlaceID) { @@ -508,8 +580,7 @@ private long insertPrescription(Long beneficiaryRegID, BenVisitDetail visit, Str * one intake per 7 days. Other forms (syrup, cream, injection, drops...): 1 bottle/tube/vial. Null when a * tablet/capsule quantity cannot be worked out (unknown frequency or missing duration). */ - private Integer calculateQuantity(Map drug, Object[] item) { - Integer formID = item[7] != null ? ((Number) item[7]).intValue() : null; + private Integer calculateQuantity(Map drug, Integer formID) { if (formID == null || (formID != 1 && formID != 2)) return 1; // 1 = Tablet, 2 = Capsule double perIntake = formID == 2 ? 1 : tabletsPerIntake(toStr(drug.get("dose"))); String frequency = Optional.ofNullable(toStr(drug.get("frequency"))).orElse("").toUpperCase();