Skip to content

Commit

Permalink
OP-1197 | Validate ward Inventory (#1456)
Browse files Browse the repository at this point in the history
* Add new column on medicalinventory

* add validate ward inventory row function

* Remove uneccessary format code

* Remove uneccessary format code

* Write unit test for validateWardinventory

* Add the standard license header block

* Remove reason attribut

* Add actualize inventory row function

* update validateMedicalWardInventoryRow function

* Update src/main/java/org/isf/medicalinventory/manager/MedicalInventoryManager.java

* Applying suggestion for change

* Fix test coverage

* Update test validation medical ward inventory row

* Add actualize inventory row test

---------

Co-authored-by: Alessandro Domanico <[email protected]>
  • Loading branch information
JantBogard and mwithi authored Jan 14, 2025
1 parent 125822a commit 43581de
Show file tree
Hide file tree
Showing 3 changed files with 464 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
import org.isf.medstockmovtype.model.MovementType;
import org.isf.supplier.manager.SupplierBrowserManager;
import org.isf.supplier.model.Supplier;
import org.isf.medicalstockward.manager.MovWardBrowserManager;
import org.isf.medicalstockward.model.MedicalWard;
import org.isf.medicalstockward.model.MovementWard;
import org.isf.utils.exception.OHDataValidationException;
import org.isf.utils.exception.OHServiceException;
import org.isf.utils.exception.model.OHExceptionMessage;
Expand Down Expand Up @@ -74,20 +77,23 @@ public class MedicalInventoryManager {
private final SupplierBrowserManager supplierManager;

private final WardBrowserManager wardManager;

protected Map<String, String> statusHashMap;

private MovWardBrowserManager movWardBrowserManager;

public MedicalInventoryManager(MedicalInventoryIoOperation medicalInventoryIoOperation, MedicalInventoryRowManager medicalInventoryRowManager,
MedicalDsrStockMovementTypeBrowserManager medicalDsrStockMovementTypeBrowserManager,
SupplierBrowserManager supplierManager, MovStockInsertingManager movStockInsertingManager, WardBrowserManager wardManager,
MovBrowserManager movBrowserManager) {
MedicalDsrStockMovementTypeBrowserManager medicalDsrStockMovementTypeBrowserManager,
SupplierBrowserManager supplierManager, MovStockInsertingManager movStockInsertingManager, WardBrowserManager wardManager,
MovBrowserManager movBrowserManager, MovWardBrowserManager movWardBrowserManager) {
this.ioOperations = medicalInventoryIoOperation;
this.medicalInventoryRowManager = medicalInventoryRowManager;
this.medicalDsrStockMovementTypeBrowserManager = medicalDsrStockMovementTypeBrowserManager;
this.supplierManager = supplierManager;
this.movStockInsertingManager = movStockInsertingManager;
this.wardManager = wardManager;
this.movBrowserManager = movBrowserManager;
this.movWardBrowserManager = movWardBrowserManager;
}

/**
Expand Down Expand Up @@ -360,6 +366,101 @@ public void validateMedicalInventoryRow(MedicalInventory inventory, List<Medical
}
}

/**
* Validate the Inventory rows of inventory ward.
*
* @param inventory the {@link MedicalInventory}
* @param inventoryRowSearchList the list of {@link MedicalInventory}
* @throws OHServiceException
*/
public void validateMedicalWardInventoryRow(MedicalInventory inventory, List<MedicalInventoryRow> inventoryRowSearchList) throws OHServiceException {
LocalDateTime movFrom = inventory.getInventoryDate();
LocalDateTime movTo = TimeTools.getNow();
StringBuilder medDescriptionForLotUpdated = new StringBuilder("\n"); // initial new line
StringBuilder medDescriptionForNewLot = new StringBuilder("\n"); // initial new line
StringBuilder medDescriptionForNewMedical = new StringBuilder("\n"); // initial new line
boolean lotUpdated = false;
boolean lotAdded = false;
boolean medicalAdded = false;

List<MovementWard> movementWards = new ArrayList<>(movWardBrowserManager.getMovementWard(inventory.getWard(), movFrom, movTo));
List<Movement> movementToWards = new ArrayList<>(movBrowserManager.getMovements(inventory.getWard(), movFrom, movTo));
List<Medical> inventoryMedicalsList = inventoryRowSearchList.stream().map(MedicalInventoryRow::getMedical).distinct().toList();

// Get all the lots from ward movements
List<Lot> lotOfMovements = new ArrayList<>(movementWards.stream().map(MovementWard::getLot).toList());
// Get all the lots from main store movements
lotOfMovements.addAll(movementToWards.stream().map(Movement::getLot).toList());
// Remove duplicates by converting the list to a set
Set<Lot> uniqueLots = new HashSet<>(lotOfMovements);
// Convert the set back to a list
List<Lot> uniqueLotList = new ArrayList<>(uniqueLots);
// Cycle fetched movements to see if they impact inventoryRowSearchList
for (Lot lot : uniqueLotList) {
String lotCode = lot.getCode();
String lotExpiringDate = TimeTools.formatDateTime(lot.getDueDate(), TimeTools.DD_MM_YYYY);
String lotInfo = GeneralData.AUTOMATICLOT_IN ? lotExpiringDate : lotCode;
Medical medical = lot.getMedical();
String medicalDesc = medical.getDescription();

Optional<MedicalWard> optMedicalWard = movWardBrowserManager.getMedicalsWard(inventory.getWard(), medical.getCode(), false).stream()
.filter(m -> m.getLot().getCode().equals(lotCode)).findFirst();

double wardStoreQty = optMedicalWard.isPresent() ? optMedicalWard.get().getQty() : 0.0;

// Search for the specific Lot and Medical in inventoryRowSearchList
Optional<MedicalInventoryRow> matchingRow = inventoryRowSearchList.stream()
.filter(row -> row.getLot().getCode().equals(lotCode)).findFirst();

if (matchingRow.isPresent()) {
MedicalInventoryRow medicalInventoryRow = matchingRow.get();
double theoQty = medicalInventoryRow.getTheoreticQty();
if (wardStoreQty != theoQty) {
lotUpdated = true;
medDescriptionForLotUpdated
.append("\n")
.append(MessageBundle.formatMessage("angal.inventory.theoreticalqtyhavebeenupdatedforsomemedical.detail.fmt.msg",
medicalDesc, lotInfo, theoQty, wardStoreQty, wardStoreQty - theoQty));
}
} else {
// TODO: to decide if to give control to the user about this
if (!inventoryMedicalsList.contains(medical)) {
// New medical
medicalAdded = true;
medDescriptionForNewMedical
.append("\n")
.append(MessageBundle.formatMessage("angal.inventory.newmedicalshavebeenfound.detail.fmt.msg",
medicalDesc, lotInfo, wardStoreQty));
} else {
// New Lot
lotAdded = true;
medDescriptionForNewLot
.append("\n")
.append(MessageBundle.formatMessage("angal.inventory.newlotshavebeenaddedforsomemedical.detail.fmt.msg",
medicalDesc, lotInfo, wardStoreQty));
}
}
}
List<OHExceptionMessage> errors = new ArrayList<>();
if (lotUpdated) {
errors.add(new OHExceptionMessage(MessageBundle.getMessage("angal.inventory.validate.btn"),
MessageBundle.formatMessage("angal.inventory.theoreticalqtyhavebeenupdatedforsomemedicalward.fmt.msg", medDescriptionForLotUpdated),
OHSeverityLevel.INFO));
}
if (lotAdded) {
errors.add(new OHExceptionMessage(MessageBundle.getMessage("angal.inventory.validate.btn"),
MessageBundle.formatMessage("angal.inventory.newlotshavebeenaddedforsomemedicalward.fmt.msg", medDescriptionForNewLot),
OHSeverityLevel.INFO));
}
if (medicalAdded) {
errors.add(new OHExceptionMessage(MessageBundle.getMessage("angal.inventory.validate.btn"),
MessageBundle.formatMessage("angal.inventory.newmedicalshavebeenfoundward.fmt.msg", medDescriptionForNewMedical), OHSeverityLevel.INFO));
}
if (!errors.isEmpty()) {
throw new OHDataValidationException(errors);
}
}

/**
* Marks an inventory as deleted by changing its status.
*
Expand Down Expand Up @@ -519,7 +620,7 @@ public MedicalInventory actualizeMedicalInventoryRow(MedicalInventory inventory)
}
return this.updateMedicalInventory(inventory, true);
}

private void buildStatusHashMap() {
statusHashMap = new HashMap<>(4);
statusHashMap.put("canceled", MessageBundle.getMessage("angal.inventory.status.canceled.txt"));
Expand All @@ -545,7 +646,7 @@ public List<String> getStatusList() {
statusList.sort(new DefaultSorter(MessageBundle.getMessage("angal.inventory.status.draft.txt")));
return statusList;
}

/**
* Return a value of the key on statusHashMap.
* @param key - the key of status
Expand All @@ -562,4 +663,61 @@ public String getStatusByKey(String key) {
}
return "";
}

/**
* Actualize the {@link MedicalInventory}'s ward.
*
* @param inventory the {@link MedicalInventory}
* @return {@link MedicalInventory}. It could be {@code null}.
* @throws OHServiceException
*/
public MedicalInventory actualizeMedicalWardInventoryRow(MedicalInventory inventory) throws OHServiceException {
LocalDateTime movFrom = inventory.getInventoryDate();
LocalDateTime movTo = TimeTools.getNow();

List<MovementWard> movementWards = new ArrayList<>(movWardBrowserManager.getMovementWard(inventory.getWard(), movFrom, movTo));
List<Movement> movementToWards = new ArrayList<>(movBrowserManager.getMovements(inventory.getWard(), movFrom, movTo));
List<MedicalInventoryRow> inventoryRowList = medicalInventoryRowManager.getMedicalInventoryRowByInventoryId(inventory.getId());

// Get all the lots from the ward movements
List<Lot> lotOfMovements = new ArrayList<>(movementWards.stream().map(MovementWard::getLot).toList());
// Get all the lots from the main store movements
lotOfMovements.addAll(movementToWards.stream().map(Movement::getLot).toList());
// Remove duplicates by converting the list to a set
Set<Lot> uniqueLots = new HashSet<>(lotOfMovements);
// Convert the set back to a list
List<Lot> uniqueLotList = new ArrayList<>(uniqueLots);
// Cycle fetched movements to see if they impact inventoryRowSearchList
for (Lot lot : uniqueLotList) {
String lotCode = lot.getCode();
Medical medical = lot.getMedical();
Integer medicalCode = medical.getCode();
// Fetch also empty lots because some movements may have discharged them completely
Optional<MedicalWard> optMedicalWard = movWardBrowserManager.getMedicalsWard(inventory.getWard(), medical.getCode(), false).stream()
.filter(m -> m.getLot().getCode().equals(lotCode)).findFirst();
double wardStoreQty = optMedicalWard.isPresent() ? optMedicalWard.get().getQty() : 0.0;

// Search for the specific Lot and Medical in inventoryRowSearchList (Lot should be enough)
Optional<MedicalInventoryRow> matchingRow = inventoryRowList.stream()
.filter(row -> row.getLot().getCode().equals(lotCode) && row.getMedical().getCode().equals(medicalCode))
.findFirst();

if (matchingRow.isPresent()) {
MedicalInventoryRow medicalInventoryRow = matchingRow.get();
double theoQty = medicalInventoryRow.getTheoreticQty();
if (wardStoreQty != theoQty) {
// Update Lot
medicalInventoryRow.setTheoreticQty(wardStoreQty);
medicalInventoryRow.setRealqty(wardStoreQty);
medicalInventoryRowManager.updateMedicalInventoryRow(medicalInventoryRow);
}
} else {
// TODO: to decide if to give control to the user about this
double realQty = wardStoreQty;
MedicalInventoryRow newMedicalInventoryRow = new MedicalInventoryRow(null, wardStoreQty, realQty, inventory, medical, lot);
medicalInventoryRowManager.newMedicalInventoryRow(newMedicalInventoryRow);
}
}
return this.updateMedicalInventory(inventory, true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Open Hospital (www.open-hospital.org)
* Copyright © 2006-2024 Informatici Senza Frontiere ([email protected])
*
* Open Hospital is a free and open source software for healthcare data management.
*
* 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.
*
* https://www.gnu.org/licenses/gpl-3.0-standalone.html
*
* 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 org.isf.medicalsinventory;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.within;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

import org.isf.medicalinventory.model.InventoryStatus;
import org.isf.medicalinventory.model.InventoryType;
import org.isf.medicalinventory.model.MedicalInventory;
import org.isf.utils.exception.OHException;
import org.isf.utils.time.TimeTools;
import org.isf.ward.model.Ward;

public class TestMedicalWardInventory {
private int id = 1;
private String status = InventoryStatus.draft.toString();
private LocalDateTime inventoryDate = TimeTools.getNow();
private String user = "admin";
private String inventoryReference = "REFERENCE";
private String inventoryType = InventoryType.ward.toString();
private String ward = "Z";

public MedicalInventory setup(Ward ward, boolean usingSet) throws OHException {
MedicalInventory medicalInventory;

if (usingSet) {
medicalInventory = new MedicalInventory();
setParameters(medicalInventory);
} else {
// create MedicalInventory with all parameters
medicalInventory = new MedicalInventory(id, status, inventoryDate, user, inventoryReference, inventoryType, ward.getCode());
}
return medicalInventory;
}

public void setParameters(MedicalInventory medicalInventory) {
medicalInventory.setId(id);
medicalInventory.setStatus(status);
medicalInventory.setInventoryDate(inventoryDate);
medicalInventory.setUser(user);
medicalInventory.setInventoryReference(inventoryReference);
medicalInventory.setInventoryType(inventoryType);
medicalInventory.setWard(ward);
}

public void check(MedicalInventory medicalInventory, int id) {
assertThat(medicalInventory.getId()).isEqualTo(id);
assertThat(medicalInventory.getStatus()).isEqualTo(status);
assertThat(medicalInventory.getInventoryDate()).isCloseTo(inventoryDate, within(1, ChronoUnit.SECONDS));
assertThat(medicalInventory.getUser()).isEqualTo(user);
assertThat(medicalInventory.getInventoryReference()).isEqualTo(inventoryReference);
assertThat(medicalInventory.getInventoryType()).isEqualTo(inventoryType);
assertThat(medicalInventory.getWard()).isEqualTo(ward);
}
}
Loading

0 comments on commit 43581de

Please sign in to comment.