From 4aad2ce4c7fb85827271c9f0eae9ca33b9289da1 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Thu, 15 Oct 2026 09:16:59 +0530 Subject: [PATCH 1/3] feat(AMM-2491): low stock and out of stock report for a store POST /crmReportController/getLowStockReport {facilityID}: items mapped to the store whose usable (not expired) stock is 0, or at/below the item's Min_Stock. Reads live stock from db_iemr, not the reporting copy. Co-Authored-By: Claude Opus 5.5 --- .../report/CRMReportController.java | 19 +++++ .../model/report/LowStockReport.java | 76 +++++++++++++++++++ .../repo/report/ItemStockReportRepo.java | 15 ++++ .../service/report/CRMReportService.java | 2 + .../service/report/CRMReportServiceImpl.java | 24 ++++++ 5 files changed, 136 insertions(+) create mode 100644 src/main/java/com/iemr/inventory/model/report/LowStockReport.java diff --git a/src/main/java/com/iemr/inventory/controller/report/CRMReportController.java b/src/main/java/com/iemr/inventory/controller/report/CRMReportController.java index 3627768d..3ed55557 100644 --- a/src/main/java/com/iemr/inventory/controller/report/CRMReportController.java +++ b/src/main/java/com/iemr/inventory/controller/report/CRMReportController.java @@ -86,6 +86,25 @@ public String getExpiryReport(@RequestBody String request) { return response.toString(); } + @ApiOperation(value = "Get low stock and out of stock report for a store", consumes = "application/json", produces = "application/json") + @RequestMapping(value = "/getLowStockReport", headers = "Authorization", method = { + RequestMethod.POST }, produces = { "application/json" }) + public String getLowStockReport(@RequestBody String request) { + logger.info("getLowStockReport request " + request.toString()); + OutputResponse response = new OutputResponse(); + try { + + ItemStockEntryReport report = InputMapper.gson().fromJson(request, ItemStockEntryReport.class); + String res = crmReportService.getLowStockReport(report); + response.setResponse(res); + logger.info("getLowStockReport response " + response.toString()); + } catch (Exception e) { + logger.error(e.getMessage()); + response.setError(e); + } + return response.toString(); + } + @ApiOperation(value = "Get consumption report", consumes = "application/json", produces = "application/json") @RequestMapping(value = "/getConsumptionReport", headers = "Authorization", method = { RequestMethod.POST }, produces = { "application/json" }) diff --git a/src/main/java/com/iemr/inventory/model/report/LowStockReport.java b/src/main/java/com/iemr/inventory/model/report/LowStockReport.java new file mode 100644 index 00000000..7b8e95b5 --- /dev/null +++ b/src/main/java/com/iemr/inventory/model/report/LowStockReport.java @@ -0,0 +1,76 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ +package com.iemr.inventory.model.report; + +import com.google.gson.GsonBuilder; +import com.google.gson.LongSerializationPolicy; + +import lombok.Data; + +@Data +public class LowStockReport { + + private Long slNo; + + private String facilityName; + + private Integer itemID; + + private String itemName; + + private String itemCategory; + + private String strength; + + private Integer minStock; + + private Integer quantityInHand; + + // "Out of stock" (nothing usable left) or "Low stock" (at or below the item's minimum stock) + private String stockStatus; + + public LowStockReport() { + + } + + public LowStockReport(String facilityName, Integer itemID, String itemName, String itemCategory, String strength, + Integer minStock, Integer quantityInHand) { + + this.facilityName = facilityName; + this.itemID = itemID; + this.itemName = itemName; + this.itemCategory = itemCategory; + this.strength = strength; + this.minStock = minStock; + this.quantityInHand = quantityInHand; + this.stockStatus = (quantityInHand == null || quantityInHand <= 0) ? "Out of stock" : "Low stock"; + } + + @Override + public String toString() { + + return new GsonBuilder().setLongSerializationPolicy(LongSerializationPolicy.STRING).serializeNulls().create() + .toJson(this); + + } + +} diff --git a/src/main/java/com/iemr/inventory/repo/report/ItemStockReportRepo.java b/src/main/java/com/iemr/inventory/repo/report/ItemStockReportRepo.java index 99a9ca67..efcdaef8 100644 --- a/src/main/java/com/iemr/inventory/repo/report/ItemStockReportRepo.java +++ b/src/main/java/com/iemr/inventory/repo/report/ItemStockReportRepo.java @@ -57,6 +57,21 @@ List getItemStockEntryReportByFacilityID(@Param("startDate List getExpiryReportByFacilityID(@Param("startDate") Date startDate, @Param("endDate") Date endDate, @Param("facilityID") Integer facilityID); + // Live stock (db_iemr, not the reporting copy): items mapped to the store whose usable (not expired) stock + // is 0 or at/below the item's Min_Stock. + @Query(value = "SELECT f.FacilityName, i.ItemID, i.ItemName, c.ItemCategoryName, i.Strength, i.Min_Stock, " + + "COALESCE((SELECT SUM(e.QuantityInHand) FROM db_iemr.t_itemstockentry e WHERE e.FacilityID = f.FacilityID " + + " AND e.ItemID = i.ItemID AND e.Deleted = false AND (e.ExpiryDate IS NULL OR e.ExpiryDate >= CURDATE())), 0) AS inHand " + + "FROM db_iemr.m_facility f " + + "JOIN db_iemr.m_item i ON i.ItemID IN (SELECT m.ItemID FROM db_iemr.m_itemfacilitymapping m " + + " WHERE m.FacilityID = f.FacilityID AND m.Deleted = false) AND i.Deleted = false " + + " AND (i.Discontinued IS NULL OR i.Discontinued = false) " + + "LEFT JOIN db_iemr.m_itemcategory c ON c.ItemCategoryID = i.ItemCategoryID " + + "WHERE f.FacilityID = :facilityID " + + "HAVING inHand <= 0 OR (i.Min_Stock > 0 AND inHand <= i.Min_Stock) " + + "ORDER BY inHand ASC, i.ItemName ASC", nativeQuery = true) + List getLowStockReportByFacilityID(@Param("facilityID") Integer facilityID); + @Query(value="Select ISEx.*, db.BeneficiaryID, concat(IFNULL(db.Title,''),' ', IFNULL(db.FirstName,''),' ', " + "IFNULL(db.MiddleName,''),' ', IFNULL(db.LastName,'')) Name from db_reporting.fact_itemstockexit ISEx left join db_iemr.t_patientissue PI " + "on ISEx.ExitTypeID = PI.VanSerialNo and ISEx.VanID = PI.VanID " diff --git a/src/main/java/com/iemr/inventory/service/report/CRMReportService.java b/src/main/java/com/iemr/inventory/service/report/CRMReportService.java index 5e6d3dc6..e53977e7 100644 --- a/src/main/java/com/iemr/inventory/service/report/CRMReportService.java +++ b/src/main/java/com/iemr/inventory/service/report/CRMReportService.java @@ -33,6 +33,8 @@ public interface CRMReportService { String getExpiryReport(ItemStockEntryReport entryReport); + String getLowStockReport(ItemStockEntryReport entryReport) throws Exception; + String getConsumptionReport(ItemStockExitReport exitReport); String getBenDrugIssueReport(PatientIssueExitReport exitReport); diff --git a/src/main/java/com/iemr/inventory/service/report/CRMReportServiceImpl.java b/src/main/java/com/iemr/inventory/service/report/CRMReportServiceImpl.java index b41a6403..91ddd852 100644 --- a/src/main/java/com/iemr/inventory/service/report/CRMReportServiceImpl.java +++ b/src/main/java/com/iemr/inventory/service/report/CRMReportServiceImpl.java @@ -45,6 +45,7 @@ import com.iemr.inventory.model.report.DailyStockSummary; import com.iemr.inventory.model.report.ExpiryReport; import com.iemr.inventory.model.report.InwardStockReport; +import com.iemr.inventory.model.report.LowStockReport; import com.iemr.inventory.model.report.MonthlyReport; import com.iemr.inventory.model.report.TransitReport; import com.iemr.inventory.model.report.YearlyReport; @@ -203,6 +204,29 @@ public String getExpiryReport(ItemStockEntryReport entryReport) { return reportList.toString(); } + @Override + public String getLowStockReport(ItemStockEntryReport entryReport) throws Exception { + + if (entryReport == null || entryReport.getFacilityID() == null) { + throw new Exception("facilityID is required"); + } + List list = itemStockReportRepo.getLowStockReportByFacilityID(entryReport.getFacilityID()); + List reportList = new ArrayList(); + Long slNo = 1L; + for (Object[] object : list) { + if (object != null) { + LowStockReport report = new LowStockReport(text(object[0]), + object[1] != null ? ((Number) object[1]).intValue() : null, text(object[2]), text(object[3]), + text(object[4]), object[5] != null ? ((Number) object[5]).intValue() : null, + object[6] != null ? ((Number) object[6]).intValue() : 0); + report.setSlNo(slNo++); + reportList.add(report); + } + } + + return reportList.toString(); + } + @Override public String getConsumptionReport(ItemStockExitReport exitReport) { From dbb86a768b3142919d0224596fa67d8a92de1c3d Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Tue, 20 Oct 2026 10:21:50 +0530 Subject: [PATCH 2/3] feat(AMM-2491): StopTB laptop uploads a batch again when its stock changes On StopTB camp laptops (stoptb.enforce.vanid=true) a batch whose QuantityInHand changes (stock adjustment, stock exit/dispense, patient return, indent issue) is marked Processed='N', so the next upload sends its current stock and central's camp-stock reports match the camp. Other deployments unchanged. Co-Authored-By: Claude Opus 5.5 --- .../repo/stockEntry/ItemStockEntryRepo.java | 10 ++++ .../service/indent/IndentServiceImpl.java | 5 ++ .../PatientReturnServiceImpl.java | 5 ++ .../stockEntry/StockEntryServiceImpl.java | 5 ++ .../service/stockEntry/StockUploadMarker.java | 53 +++++++++++++++++++ .../StockAdjustmentServiceImpl.java | 5 ++ 6 files changed, 83 insertions(+) create mode 100644 src/main/java/com/iemr/inventory/service/stockEntry/StockUploadMarker.java diff --git a/src/main/java/com/iemr/inventory/repo/stockEntry/ItemStockEntryRepo.java b/src/main/java/com/iemr/inventory/repo/stockEntry/ItemStockEntryRepo.java index 915c2cb7..95495a80 100644 --- a/src/main/java/com/iemr/inventory/repo/stockEntry/ItemStockEntryRepo.java +++ b/src/main/java/com/iemr/inventory/repo/stockEntry/ItemStockEntryRepo.java @@ -94,6 +94,16 @@ List findByItemIDInQuantityInHandGreaterThanForFacilityID(@Param("item @Query("UPDATE ItemStockEntry c SET c.quantityInHand = c.quantityInHand + :quant WHERE c.itemStockEntryID = :id") Integer addStock(@Param("id") Long id, @Param("quant") Integer quant); + @Transactional + @Modifying + @Query("UPDATE ItemStockEntry c SET c.processed = 'N' WHERE c.itemStockEntryID = :id") + Integer markForUpload(@Param("id") Long id); + + @Transactional + @Modifying + @Query("UPDATE ItemStockEntry c SET c.processed = 'N' WHERE c.vanSerialNo = :vanSerialNo and c.facilityID = :facilityID") + Integer markForUploadByVanSerialNo(@Param("vanSerialNo") Long vanSerialNo, @Param("facilityID") Integer facilityID); + @Transactional @Modifying @Query("UPDATE ItemStockEntry c SET c.quantityInHand = c.quantityInHand - :quant WHERE c.itemStockEntryID = :id") diff --git a/src/main/java/com/iemr/inventory/service/indent/IndentServiceImpl.java b/src/main/java/com/iemr/inventory/service/indent/IndentServiceImpl.java index e7dd4d3f..e354ad16 100644 --- a/src/main/java/com/iemr/inventory/service/indent/IndentServiceImpl.java +++ b/src/main/java/com/iemr/inventory/service/indent/IndentServiceImpl.java @@ -40,6 +40,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import com.iemr.inventory.service.stockEntry.StockUploadMarker; import org.springframework.stereotype.Service; import com.iemr.inventory.data.indent.Indent; @@ -59,6 +60,9 @@ @Service public class IndentServiceImpl implements IndentService{ + @Autowired + private StockUploadMarker stockUploadMarker; + private static final Logger logger = LoggerFactory.getLogger(IndentServiceImpl.class); @PersistenceContext @@ -237,6 +241,7 @@ public String issueIndent(IndentIssue[] array) { { indentOrderRepo.updateQuantityInStock(indentIssue.getIssuedQty(), indentIssue.getItemStockEntryID(), indentIssue.getFromFacilityID()); + stockUploadMarker.batchChanged(indentIssue.getItemStockEntryID()); indentIssue.setCreatedDate(new Timestamp(System.currentTimeMillis())); indentIssue.setIssueDate(new Timestamp(System.currentTimeMillis())); diff --git a/src/main/java/com/iemr/inventory/service/patientreturn/PatientReturnServiceImpl.java b/src/main/java/com/iemr/inventory/service/patientreturn/PatientReturnServiceImpl.java index e5fcc014..276cac3c 100644 --- a/src/main/java/com/iemr/inventory/service/patientreturn/PatientReturnServiceImpl.java +++ b/src/main/java/com/iemr/inventory/service/patientreturn/PatientReturnServiceImpl.java @@ -31,6 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import com.iemr.inventory.service.stockEntry.StockUploadMarker; import org.springframework.stereotype.Service; import com.iemr.inventory.data.patientreturn.ItemDetailModel; @@ -44,6 +45,9 @@ @Service public class PatientReturnServiceImpl implements PatientReturnService{ + @Autowired + private StockUploadMarker stockUploadMarker; + private static final Logger logger = LoggerFactory.getLogger(PatientReturnServiceImpl.class); @Autowired(required=false) @@ -113,6 +117,7 @@ public String updateQuantityReturned(ItemDetailModel[] itemDetailModel) for(ItemDetailModel itemDetail : list) { int result = patientReturnRepo.updateQuantityReturned(itemDetail.getReturnQuantity(), itemDetail.getItemStockEntryID()); + stockUploadMarker.batchChanged(itemDetail.getItemStockEntryID()); patientReturnRepo.updateIssuedQuantity(itemDetail.getReturnQuantity(), itemDetail.getItemStockExitID()); ItemReturnEntry itemReturnEntry = new ItemReturnEntry(); itemReturnEntry.setCreatedBy(itemDetail.getCreatedBy()); diff --git a/src/main/java/com/iemr/inventory/service/stockEntry/StockEntryServiceImpl.java b/src/main/java/com/iemr/inventory/service/stockEntry/StockEntryServiceImpl.java index 73602950..c67bf0c3 100644 --- a/src/main/java/com/iemr/inventory/service/stockEntry/StockEntryServiceImpl.java +++ b/src/main/java/com/iemr/inventory/service/stockEntry/StockEntryServiceImpl.java @@ -55,6 +55,9 @@ @Service public class StockEntryServiceImpl implements StockEntryService { + @Autowired + private StockUploadMarker stockUploadMarker; + @Autowired private PhysicalStockEntryRepo physicalStockEntryRepo; @@ -129,6 +132,8 @@ public Integer updateStocks(List itemissueListUpdated) { for (ItemStockExit itemStockExit : itemissueListUpdated) { cnt = cnt + itemStockEntryRepo.updateStock(itemStockExit.getFacilityID(), itemStockExit.getItemStockEntryID(), itemStockExit.getQuantity()); + stockUploadMarker.batchChangedByVanSerialNo(itemStockExit.getItemStockEntryID(), + itemStockExit.getFacilityID()); } return cnt; diff --git a/src/main/java/com/iemr/inventory/service/stockEntry/StockUploadMarker.java b/src/main/java/com/iemr/inventory/service/stockEntry/StockUploadMarker.java new file mode 100644 index 00000000..2d531312 --- /dev/null +++ b/src/main/java/com/iemr/inventory/service/stockEntry/StockUploadMarker.java @@ -0,0 +1,53 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ +package com.iemr.inventory.service.stockEntry; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.iemr.inventory.repo.stockEntry.ItemStockEntryRepo; + +/** + * StopTB camp laptops (stoptb.enforce.vanid=true): the camp's stock is reported on central from uploaded rows, so a + * batch whose QuantityInHand changes (adjustment, stock exit, patient return, indent issue) is marked + * Processed='N' and uploaded again; central updates its copy by VanSerialNo + store. Other deployments: no change. + */ +@Component +public class StockUploadMarker { + + @Value("${stoptb.enforce.vanid:false}") + private boolean stopTBLaptop; + + @Autowired + private ItemStockEntryRepo itemStockEntryRepo; + + public void batchChanged(Long itemStockEntryID) { + if (stopTBLaptop && itemStockEntryID != null) + itemStockEntryRepo.markForUpload(itemStockEntryID); + } + + public void batchChangedByVanSerialNo(Long vanSerialNo, Integer facilityID) { + if (stopTBLaptop && vanSerialNo != null && facilityID != null) + itemStockEntryRepo.markForUploadByVanSerialNo(vanSerialNo, facilityID); + } +} diff --git a/src/main/java/com/iemr/inventory/service/stockadjustment/StockAdjustmentServiceImpl.java b/src/main/java/com/iemr/inventory/service/stockadjustment/StockAdjustmentServiceImpl.java index fe002032..eb9457a1 100644 --- a/src/main/java/com/iemr/inventory/service/stockadjustment/StockAdjustmentServiceImpl.java +++ b/src/main/java/com/iemr/inventory/service/stockadjustment/StockAdjustmentServiceImpl.java @@ -29,6 +29,7 @@ import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; +import com.iemr.inventory.service.stockEntry.StockUploadMarker; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -51,6 +52,9 @@ @Service public class StockAdjustmentServiceImpl implements StockAdjustmentService { + @Autowired + private StockUploadMarker stockUploadMarker; + @Autowired(required=false) StockAdjustmentDraftRepo stockAdjustmentDraftRepo; @@ -184,6 +188,7 @@ public StockAdjustment savetransaction(StockAdjustment stockAdjustment) throws I } else { itemStockEntryRepo.subtractStock(action.getItemStockEntryID(), action.getAdjustedQuantity()); } + stockUploadMarker.batchChanged(action.getItemStockEntryID()); }); From bf55fb6d44feeb9f76cdb7b5a7cf16dfccd7de47 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Tue, 20 Oct 2026 10:27:04 +0530 Subject: [PATCH 3/3] chore: set version to 3.12.0 Co-Authored-By: Claude Opus 5.5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5b2ebce2..a7f2a440 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.iemr.inventory inventory-api - 3.9.0 + 3.12.0 war Inventory-API Inventory Page