Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ List<ItemStockEntry> findByFacilityIDAndItemIDAndQuantityInHandGreaterThanAndDel

@Transactional
@Modifying
// Previously matched on vanSerialNo which is a mutable sync field; using the
// stable primary key itemStockEntryID ensures the correct batch is always updated.
@Query("UPDATE ItemStockEntry c SET c.quantityInHand = c.quantityInHand - :dispQuant "
+ " WHERE c.vanSerialNo = :itemStockEntryId and c.facilityID = :facilityID")
+ " WHERE c.itemStockEntryID = :itemStockEntryId and c.facilityID = :facilityID")
Integer updateStock(@Param("facilityID") Integer facilityID, @Param("itemStockEntryId") Long itemStockEntryId,
@Param("dispQuant") Integer dispQuant);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

List<ItemDetailModel> getItemDetailByBen(ItemDetailModel itemDetailModel);

String updateQuantityReturned(ItemDetailModel[] itemDetailModel);
String updateQuantityReturned(ItemDetailModel[] itemDetailModel) throws Exception;

Check warning on line 38 in src/main/java/com/iemr/inventory/service/patientreturn/PatientReturnService.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace generic exceptions with specific library exceptions or a custom exception.

See more on https://sonarcloud.io/project/issues?id=PSMRI_Inventory-API&issues=AZ4EbYAp8-ee4mAVnM_N&open=AZ4EbYAp8-ee4mAVnM_N&pullRequest=126

List<ReturnHistoryModel> getBenReturnHistory(ItemReturnEntry itemReturnEntry);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.iemr.inventory.data.patientreturn.ItemDetailModel;
import com.iemr.inventory.data.patientreturn.PatientReturnModel;
Expand Down Expand Up @@ -104,15 +105,26 @@
return list;
}
@Override
public String updateQuantityReturned(ItemDetailModel[] itemDetailModel)
@Transactional(rollbackFor = Exception.class)
public String updateQuantityReturned(ItemDetailModel[] itemDetailModel) throws Exception
{
logger.info("updateQuantityReturned - Start");
List<ItemDetailModel> list = Arrays.asList(itemDetailModel);
List<ItemReturnEntry> returnList=null;
returnList = new ArrayList<ItemReturnEntry>();
for(ItemDetailModel itemDetail : list)
List<ItemReturnEntry> returnList = new ArrayList<ItemReturnEntry>();
for (ItemDetailModel itemDetail : list)
{
int result = patientReturnRepo.updateQuantityReturned(itemDetail.getReturnQuantity(), itemDetail.getItemStockEntryID());
// Guard: returnQuantity must not exceed the originally issued quantity.
// Without this check a caller could inflate stock beyond what was dispensed.
if (itemDetail.getReturnQuantity() == null || itemDetail.getReturnQuantity() <= 0) {
throw new Exception("Return quantity must be greater than zero for ItemID: " + itemDetail.getItemID());

Check warning on line 119 in src/main/java/com/iemr/inventory/service/patientreturn/PatientReturnServiceImpl.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace generic exceptions with specific library exceptions or a custom exception.

See more on https://sonarcloud.io/project/issues?id=PSMRI_Inventory-API&issues=AZ4EbYAe8-ee4mAVnM_L&open=AZ4EbYAe8-ee4mAVnM_L&pullRequest=126
}
if (itemDetail.getIssuedQuantity() != null
&& itemDetail.getReturnQuantity() > itemDetail.getIssuedQuantity()) {
throw new Exception("Return quantity (" + itemDetail.getReturnQuantity()

Check warning on line 123 in src/main/java/com/iemr/inventory/service/patientreturn/PatientReturnServiceImpl.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace generic exceptions with specific library exceptions or a custom exception.

See more on https://sonarcloud.io/project/issues?id=PSMRI_Inventory-API&issues=AZ4EbYAe8-ee4mAVnM_M&open=AZ4EbYAe8-ee4mAVnM_M&pullRequest=126
+ ") exceeds issued quantity (" + itemDetail.getIssuedQuantity()
+ ") for ItemID: " + itemDetail.getItemID());
}
patientReturnRepo.updateQuantityReturned(itemDetail.getReturnQuantity(), itemDetail.getItemStockEntryID());
patientReturnRepo.updateIssuedQuantity(itemDetail.getReturnQuantity(), itemDetail.getItemStockExitID());
ItemReturnEntry itemReturnEntry = new ItemReturnEntry();
itemReturnEntry.setCreatedBy(itemDetail.getCreatedBy());
Expand All @@ -123,7 +135,6 @@
itemReturnEntry.setProviderServiceMapID(itemDetail.getProviderServiceMapID());
itemReturnEntry.setVisitID(itemDetail.getVisitID());
itemReturnEntry.setVisitCode(itemDetail.getVisitCode());

returnList.add(itemReturnEntry);
}
itemReturnEntryRepo.saveAll(returnList);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
package com.iemr.inventory.service.stockEntry;

import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
Expand Down Expand Up @@ -162,24 +164,29 @@ public List<AllocateItemMap> getItemStockFromItemID(Integer facilityID, List<Ite
List<ItemStockEntry> stockList = new ArrayList<ItemStockEntry>();
String method = item.getItemCategory().getIssueType();
Integer itemID = itemStockExit.getItemID();
Date nowdate = new Date();

// Use java.time to compute the expiry threshold.
// The deprecated Date.setDate/setMonth do not handle month-boundary overflow
// (e.g. Jan 31 + 1 month wraps to Mar 3) and ignore DST, producing an
// incorrect cutoff that can serve expired stock or reject valid batches.
LocalDate nowLocal = LocalDate.now();
if (itemStockExit.getDuration() != null && itemStockExit.getDuration() > 0
&& itemStockExit.getDurationUnit() != null) {
switch (itemStockExit.getDurationUnit()) {
case "Day(s)":
nowdate.setDate(nowdate.getDate() + itemStockExit.getDuration());
nowLocal = nowLocal.plusDays(itemStockExit.getDuration());
break;
case "Month(s)":
nowdate.setMonth(nowdate.getMonth() + itemStockExit.getDuration());
nowLocal = nowLocal.plusMonths(itemStockExit.getDuration());
break;
case "Week(s)":
nowdate.setDate(nowdate.getDate() + (7 * itemStockExit.getDuration()));
nowLocal = nowLocal.plusWeeks(itemStockExit.getDuration());
break;
default:
break;
}
}
Date nowdate = Date.from(nowLocal.atStartOfDay(ZoneId.systemDefault()).toInstant());

if (method == null) {
stockList = getItemStockForStoreIDOrderByEntryDateAsc(facilityID, itemID, nowdate);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ public interface StockExitService {
List<ItemStockExit> getItemStockAndValidate(List<ItemStockExit> itemissueList, Integer facilityID,
String createdBy,Long vanID,Long ppID);

Integer storeSelfConsumption(StoreSelfConsumption storeSelfConsumption);
Integer storeTransfer(T_StockTransfer stockTransfer);
Integer storeSelfConsumption(StoreSelfConsumption storeSelfConsumption) throws InventoryException;

Integer storeTransfer(T_StockTransfer stockTransfer) throws InventoryException;

// List<ItemStockEntry> getItemStockFromItemID(Integer facilityID,List<ItemStockExit> itemStockExit);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,23 +113,36 @@
+ itemissueListUpdated.size());
logger.info("itemissueList " + itemissueList.toString());
logger.info("itemissueListUpdated " + itemissueListUpdated.toString());
if (itemissueList.size() == itemissueListUpdated.size()) {
patientIssue.setSyncFacilityID(patientIssue.getFacilityID());
patientIssueRepo.save(patientIssue);
patientIssueRepo.updateVanSerialNo();

returnvalue = saveItemExit(itemissueListUpdated, patientIssue.getPatientIssueID(), "T_PatientIssue");
if (returnvalue == 1) {
if (patientIssue != null && patientIssue.getBenRegID() != null
&& patientIssue.getVisitCode() != null) {
int i = updateBenFlowAfterPharmaTransaction(patientIssue);
if (i > 0)
returnvalue = 1;
else
returnvalue = 0;
} else {
if (itemissueList.size() != itemissueListUpdated.size()) {
// Identify which items failed validation (insufficient stock) so the caller
// gets actionable feedback instead of a silent 0 return.
List<Integer> validatedItemIDs = itemissueListUpdated.stream()
.map(ItemStockExit::getItemID)
.collect(Collectors.toList());
List<String> failedItems = itemissueList.stream()
.filter(item -> !validatedItemIDs.contains(item.getItemID()))
.map(item -> "ItemID " + item.getItemID()

Check failure on line 124 in src/main/java/com/iemr/inventory/service/stockExit/StockExitServiceImpl.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "ItemID " 3 times.

See more on https://sonarcloud.io/project/issues?id=PSMRI_Inventory-API&issues=AZ4EbX-R8-ee4mAVnM_I&open=AZ4EbX-R8-ee4mAVnM_I&pullRequest=126
+ " (requested: " + item.getQuantity()

Check failure on line 125 in src/main/java/com/iemr/inventory/service/stockExit/StockExitServiceImpl.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal " (requested: " 3 times.

See more on https://sonarcloud.io/project/issues?id=PSMRI_Inventory-API&issues=AZ4EbX-R8-ee4mAVnM_K&open=AZ4EbX-R8-ee4mAVnM_K&pullRequest=126
+ ", available: " + item.getQuantityInHand() + ")")

Check failure on line 126 in src/main/java/com/iemr/inventory/service/stockExit/StockExitServiceImpl.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal ", available: " 3 times.

See more on https://sonarcloud.io/project/issues?id=PSMRI_Inventory-API&issues=AZ4EbX-R8-ee4mAVnM_J&open=AZ4EbX-R8-ee4mAVnM_J&pullRequest=126
.collect(Collectors.toList());
throw new InventoryException(
"Insufficient stock for the following item(s): " + String.join(", ", failedItems));
}
patientIssue.setSyncFacilityID(patientIssue.getFacilityID());
patientIssueRepo.save(patientIssue);
patientIssueRepo.updateVanSerialNo();

returnvalue = saveItemExit(itemissueListUpdated, patientIssue.getPatientIssueID(), "T_PatientIssue");
if (returnvalue == 1) {
if (patientIssue != null && patientIssue.getBenRegID() != null
&& patientIssue.getVisitCode() != null) {
int i = updateBenFlowAfterPharmaTransaction(patientIssue);
if (i > 0)
returnvalue = 1;
else
returnvalue = 0;
}
} else {
returnvalue = 0;
}
}

Expand Down Expand Up @@ -194,31 +207,39 @@

@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
@Override
public Integer storeSelfConsumption(StoreSelfConsumption storeSelfConsumption) {
public Integer storeSelfConsumption(StoreSelfConsumption storeSelfConsumption) throws InventoryException {
Integer returnvalue = 0;
if (true) {
List<ItemStockExit> itemissueList = storeSelfConsumption.getItemStockExit();

List<ItemStockExit> itemissueListUpdated = getItemStockAndValidate(itemissueList,
storeSelfConsumption.getFacilityID(), storeSelfConsumption.getCreatedBy(),
storeSelfConsumption.getVanID(), storeSelfConsumption.getParkingPlaceID());
if (itemissueList.size() == itemissueListUpdated.size()) {
storeSelfConsumption.setSyncFacilityID(storeSelfConsumption.getFacilityID());
storeSelfConsumptionRepo.save(storeSelfConsumption);
storeSelfConsumptionRepo.updateVanSerialNo();

returnvalue = saveItemExit(itemissueListUpdated, storeSelfConsumption.getConsumptionID(),
"StoreSelfConsumption");
}
List<ItemStockExit> itemissueList = storeSelfConsumption.getItemStockExit();

List<ItemStockExit> itemissueListUpdated = getItemStockAndValidate(itemissueList,
storeSelfConsumption.getFacilityID(), storeSelfConsumption.getCreatedBy(),
storeSelfConsumption.getVanID(), storeSelfConsumption.getParkingPlaceID());
if (itemissueList.size() != itemissueListUpdated.size()) {
List<Integer> validatedItemIDs = itemissueListUpdated.stream()
.map(ItemStockExit::getItemID)
.collect(Collectors.toList());
List<String> failedItems = itemissueList.stream()
.filter(item -> !validatedItemIDs.contains(item.getItemID()))
.map(item -> "ItemID " + item.getItemID()
+ " (requested: " + item.getQuantity()
+ ", available: " + item.getQuantityInHand() + ")")
.collect(Collectors.toList());
throw new InventoryException(
"Insufficient stock for the following item(s): " + String.join(", ", failedItems));
}
storeSelfConsumption.setSyncFacilityID(storeSelfConsumption.getFacilityID());
storeSelfConsumptionRepo.save(storeSelfConsumption);
storeSelfConsumptionRepo.updateVanSerialNo();

returnvalue = saveItemExit(itemissueListUpdated, storeSelfConsumption.getConsumptionID(),
"StoreSelfConsumption");

return returnvalue;
}

@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
@Override
public Integer storeTransfer(T_StockTransfer stockTransfer) {
public Integer storeTransfer(T_StockTransfer stockTransfer) throws InventoryException {
Integer returnvalue = 0;
Long toVanID = stockTransferRepo.findVanIDByFacID(stockTransfer.getTransferToFacilityID());
stockTransfer.setToVanID(toVanID);
Expand All @@ -228,18 +249,29 @@
stockTransfer.getTransferFromFacilityID(), stockTransfer.getCreatedBy(), stockTransfer.getVanID(),
null);

if (itemissueList.size() == itemissueListUpdated.size()) {
stockTransfer.setSyncFacilityID(stockTransfer.getTransferFromFacilityID());
stockTransferRepo.save(stockTransfer);
stockTransferRepo.updateVanSerialNo();
if (itemissueList.size() != itemissueListUpdated.size()) {
List<Integer> validatedItemIDs = itemissueListUpdated.stream()
.map(ItemStockExit::getItemID)
.collect(Collectors.toList());
List<String> failedItems = itemissueList.stream()
.filter(item -> !validatedItemIDs.contains(item.getItemID()))
.map(item -> "ItemID " + item.getItemID()
+ " (requested: " + item.getQuantity()
+ ", available: " + item.getQuantityInHand() + ")")
.collect(Collectors.toList());
throw new InventoryException(
"Insufficient stock for the following item(s): " + String.join(", ", failedItems));
}
stockTransfer.setSyncFacilityID(stockTransfer.getTransferFromFacilityID());
stockTransferRepo.save(stockTransfer);
stockTransferRepo.updateVanSerialNo();

saveItemExit(itemissueListUpdated, stockTransfer.getStockTransferID(), "T_StockTransfer");
saveItemExit(itemissueListUpdated, stockTransfer.getStockTransferID(), "T_StockTransfer");

stockEntryService.saveItemStockFromStockTransfer(itemissueListUpdated, stockTransfer.getStockTransferID(),
"T_StockTransfer", stockTransfer.getTransferFromFacilityID(),
stockTransfer.getTransferToFacilityID(), toVanID);
returnvalue = 1;
}
stockEntryService.saveItemStockFromStockTransfer(itemissueListUpdated, stockTransfer.getStockTransferID(),
"T_StockTransfer", stockTransfer.getTransferFromFacilityID(),
stockTransfer.getTransferToFacilityID(), toVanID);
returnvalue = 1;

return returnvalue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ public StockAdjustmentDraft saveDraft(StockAdjustmentDraft stockAdjustmentDraft)
}

Long stockdraftid = stockdraft.getStockAdjustmentDraftID();
itemdraft.parallelStream().forEach(action -> {
// Sequential: repo lookups inside a parallel stream run on separate threads,
// bypassing the @Transactional context and risking inconsistent reads.
itemdraft.forEach(action -> {
if (action.getSADraftItemMapID() != null) {
StockAdjustmentItemDraft stockAdjustmentItemDraft = stockAdjustmentItemDraftRepo
.findById(action.getSADraftItemMapID()).get();
Expand All @@ -103,7 +105,6 @@ public StockAdjustmentDraft saveDraft(StockAdjustmentDraft stockAdjustmentDraft)
action.setProcessed(stockAdjustmentItemDraft.getProcessed());
}
action.setStockAdjustmentDraftID(stockdraftid);

});
itemdraft = (List<StockAdjustmentItemDraft>) stockAdjustmentItemDraftRepo.saveAll(itemdraft);
stockdraft.setStockAdjustmentItemDraft(itemdraft);
Expand Down Expand Up @@ -150,7 +151,10 @@ public StockAdjustment savetransaction(StockAdjustment stockAdjustment) throws I

List<Long> comapreid = new ArrayList<>();
final Integer facID = stockAdjustment.getFacilityID();
sd.parallelStream().forEach(action -> {
// Sequential stream: ArrayList is not thread-safe, and mutating shared objects
// or calling @Modifying JPA queries from a parallel stream bypasses the
// @Transactional boundary, risking lost updates and corrupted stock counts.
sd.forEach(action -> {
comapreid.add(action.getItemStockEntryID());
action.setFacilityID(facID);
});
Expand All @@ -177,14 +181,16 @@ public StockAdjustment savetransaction(StockAdjustment stockAdjustment) throws I
stockAdjustmentRepo.updateVanSerialNo();

Long saID = stockAdjustment.getStockAdjustmentID();
sd.parallelStream().forEach(action -> {
// Sequential: @Modifying JPA queries must run within the active transaction.
// A parallel stream spawns threads that execute outside the transaction
// coordinator, defeating rollback guarantees and causing partial stock updates.
sd.forEach(action -> {
action.setStockAdjustmentID(saID);
if (action.getIsAdded()) {
itemStockEntryRepo.addStock(action.getItemStockEntryID(), action.getAdjustedQuantity());
} else {
itemStockEntryRepo.subtractStock(action.getItemStockEntryID(), action.getAdjustedQuantity());
}

});

stockAdjustmentItemRepo.saveAll(sd);
Expand Down