From 8bb498ce4fd42cf34be95afe3c2391112d828cbb Mon Sep 17 00:00:00 2001 From: Dr M H B Ariyaratne Date: Wed, 27 May 2026 06:03:25 +0530 Subject: [PATCH] fix(pharmacy-report): show net consumption in Disposal Consumption Report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Disposal Consumption Report mixed two sign conventions that coexist on disposal-issue bills: `billItem.qty` is stored positive on both issue and return bills, while `billItemFinanceDetails.valueAt*Rate` (and the bill-level `billFinanceDetails.total*Value`) use a stock-direction sign — negative when stock leaves, positive when it comes back. See `DataAdministrationController.isFinanceValueNegative` for the canonical rule. The byBill, byBillItem, and summary aggregations summed those fields raw, so: - quantity columns counted issued and returned units both positive, inflating totals (e.g. 8 issued + 8 returned showed as 16 instead of 0); - purchase/cost/retail summary columns netted to ~0 because issue and return rows cancelled, even when net consumption was non-zero, while the netTotal column (already signed correctly) was the only column that looked right. Translate each row to consumption direction (issue +, return/cancel −) before aggregating: negate the stored valueAt*Rate (its sign is opposite to consumption) and negate qty on return/cancel rows. netTotal already carries the right sign and is left alone. The byBill and byBillItem table now expose the per-row consumption-direction values on PharmacyRow so totals and rows agree, and the PDF export reads the same fields. Closes #21025 Co-Authored-By: Claude Opus 4.7 --- .../bean/pharmacy/PharmacyController.java | 120 ++++++++++++++---- .../com/divudi/core/data/PharmacyRow.java | 37 ++++++ .../inventoryReports/consumption.xhtml | 31 +++-- 3 files changed, 148 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/divudi/bean/pharmacy/PharmacyController.java b/src/main/java/com/divudi/bean/pharmacy/PharmacyController.java index 9424af207a4..bbdb8948cbc 100644 --- a/src/main/java/com/divudi/bean/pharmacy/PharmacyController.java +++ b/src/main/java/com/divudi/bean/pharmacy/PharmacyController.java @@ -3827,6 +3827,50 @@ public void generateConsumptionReportTableByBill(BillType billType) { } } + /** + * Multiplier to convert a stored {@code valueAt*Rate} into consumption-direction value. + * + *

Stored {@code billItemFinanceDetails.valueAt*Rate} (and the matching + * {@code billFinanceDetails.total*Value}) use a stock-direction sign: negative when + * stock leaves the source department, positive when it comes back. Consumption + * accounting is the inverse — an issue adds to consumption, a return/cancel subtracts. + * For every disposal-issue bill in this convention the contribution to consumption + * value is therefore {@code -valueAt*Rate}.

+ * + *

See {@code DataAdministrationController.isFinanceValueNegative} for the canonical + * sign rules.

+ */ + private static double consumptionValueSign(BillTypeAtomic bta) { + if (bta == null) { + return 1.0; + } + switch (bta) { + case PHARMACY_DISPOSAL_ISSUE: + case PHARMACY_DISPOSAL_ISSUE_RETURN: + case PHARMACY_DISPOSAL_ISSUE_CANCELLED: + return -1.0; + default: + return 1.0; + } + } + + /** + * Multiplier to convert a stored {@code billItem.qty} into consumption-direction qty. + * Issues add to consumption (+1); returns and cancellations subtract (-1). + */ + private static double consumptionQtySign(BillTypeAtomic bta) { + if (bta == null) { + return 1.0; + } + switch (bta) { + case PHARMACY_DISPOSAL_ISSUE_RETURN: + case PHARMACY_DISPOSAL_ISSUE_CANCELLED: + return -1.0; + default: + return 1.0; + } + } + public void generateConsumptionReportTableByBill(List billTypeAtomics) { try { bills = new ArrayList<>(); @@ -3897,10 +3941,26 @@ public void generateConsumptionReportTableByBill(List billTypeAt PharmacyRow row = new PharmacyRow(); row.setBill(b); - // Simply aggregate the values displayed in the columns without manipulation - totalPurchase += b.getBillFinanceDetails().getTotalPurchaseValue() != null ? b.getBillFinanceDetails().getTotalPurchaseValue().doubleValue() : 0.0; - totalCostValue += b.getBillFinanceDetails().getTotalCostValue() != null ? b.getBillFinanceDetails().getTotalCostValue().doubleValue() : 0.0; - totalRetailValue += b.getBillFinanceDetails().getTotalRetailSaleValue() != null ? b.getBillFinanceDetails().getTotalRetailSaleValue().doubleValue() : 0.0; + // Apply consumption-direction sign so returns/cancellations subtract + // from net consumption rather than adding (issue #21025). + if (b.getBillFinanceDetails() != null) { + double sign = consumptionValueSign(b.getBillTypeAtomic()); + + double rowPurchase = b.getBillFinanceDetails().getTotalPurchaseValue() != null + ? sign * b.getBillFinanceDetails().getTotalPurchaseValue().doubleValue() : 0.0; + double rowCost = b.getBillFinanceDetails().getTotalCostValue() != null + ? sign * b.getBillFinanceDetails().getTotalCostValue().doubleValue() : 0.0; + double rowRetail = b.getBillFinanceDetails().getTotalRetailSaleValue() != null + ? sign * b.getBillFinanceDetails().getTotalRetailSaleValue().doubleValue() : 0.0; + + row.setConsumptionPurchaseValue(rowPurchase); + row.setConsumptionCostValue(rowCost); + row.setConsumptionRetailValue(rowRetail); + + totalPurchase += rowPurchase; + totalCostValue += rowCost; + totalRetailValue += rowRetail; + } pharmacyRows.add(row); @@ -4083,23 +4143,32 @@ public void generateConsumptionReportTableByBillItems(List billT totalRetailValue = 0.0; for (PharmacyRow row : pharmacyRows) { - // Simply aggregate the values displayed in the columns without manipulation + // Apply consumption-direction sign so returns/cancellations subtract + // from net consumption rather than adding (issue #21025). if (row.getBillItem() != null && row.getBillItem().getBillItemFinanceDetails() != null) { BigDecimal valueAtPurchase = row.getBillItem().getBillItemFinanceDetails().getValueAtPurchaseRate(); BigDecimal valueAtCost = row.getBillItem().getBillItemFinanceDetails().getValueAtCostRate(); BigDecimal valueAtRetail = row.getBillItem().getBillItemFinanceDetails().getValueAtRetailRate(); - if (valueAtPurchase != null) { - totalPurchase += valueAtPurchase.doubleValue(); - } + BillTypeAtomic bta = row.getBillItem().getBill() != null + ? row.getBillItem().getBill().getBillTypeAtomic() + : null; + double valueSign = consumptionValueSign(bta); + double qtySign = consumptionQtySign(bta); - if (valueAtCost != null) { - totalCostValue += valueAtCost.doubleValue(); - } + double rowPurchase = valueAtPurchase != null ? valueSign * valueAtPurchase.doubleValue() : 0.0; + double rowCost = valueAtCost != null ? valueSign * valueAtCost.doubleValue() : 0.0; + double rowRetail = valueAtRetail != null ? valueSign * valueAtRetail.doubleValue() : 0.0; + double rowQty = qtySign * (row.getBillItem().getQty() != null ? row.getBillItem().getQty() : 0.0); - if (valueAtRetail != null) { - totalRetailValue += valueAtRetail.doubleValue(); - } + row.setConsumptionPurchaseValue(rowPurchase); + row.setConsumptionCostValue(rowCost); + row.setConsumptionRetailValue(rowRetail); + row.setConsumptionQty(rowQty); + + totalPurchase += rowPurchase; + totalCostValue += rowCost; + totalRetailValue += rowRetail; } } @@ -4489,17 +4558,23 @@ public List generateConsumptionReportTableByDepartm try { List results = getBillItemFacade().findObjectsArrayByJpql(jpql, parameters, TemporalType.TIMESTAMP); + // Apply consumption-direction sign so returns/cancellations subtract + // from net consumption rather than adding (issue #21025). netTotal is + // already stored with the correct sign, so it does not need flipping. + double valueSign = consumptionValueSign(billType); + double qtySign = consumptionQtySign(billType); + // Convert Object[] to DepartmentCategoryWiseItems for (Object[] row : results) { Department mainDept = (Department) row[0]; Department consumptionDept = (Department) row[1]; Item item = (Item) row[2]; Category category = (item != null) ? item.getCategory() : null; - Double purchaseValue = row[3] != null ? ((Number) row[3]).doubleValue() : 0.0; - Double costValue = row[4] != null ? ((Number) row[4]).doubleValue() : 0.0; - Double retailValue = row[5] != null ? ((Number) row[5]).doubleValue() : 0.0; + Double purchaseValue = row[3] != null ? valueSign * ((Number) row[3]).doubleValue() : 0.0; + Double costValue = row[4] != null ? valueSign * ((Number) row[4]).doubleValue() : 0.0; + Double retailValue = row[5] != null ? valueSign * ((Number) row[5]).doubleValue() : 0.0; Double netTotal = row[6] != null ? ((Number) row[6]).doubleValue() : 0.0; - Double qty = row[7] != null ? ((Number) row[7]).doubleValue() : 0.0; + Double qty = row[7] != null ? qtySign * ((Number) row[7]).doubleValue() : 0.0; DepartmentCategoryWiseItems dtoItem = new DepartmentCategoryWiseItems( mainDept, consumptionDept, item, category, @@ -12294,20 +12369,17 @@ public void exportConsumptionReportByBillToPdf() { table.addCell(new PdfPCell(new Phrase(bill.getInvoiceNumber() != null ? bill.getInvoiceNumber() : "", normalFont))); PdfPCell purchaseCell = new PdfPCell(new Phrase( - bill.getBillFinanceDetails() != null && bill.getBillFinanceDetails().getTotalPurchaseValue() != null - ? decimalFormat.format(bill.getBillFinanceDetails().getTotalPurchaseValue()) : "0.00", normalFont)); + decimalFormat.format(row.getConsumptionPurchaseValue()), normalFont)); purchaseCell.setHorizontalAlignment(Element.ALIGN_RIGHT); table.addCell(purchaseCell); PdfPCell costCell = new PdfPCell(new Phrase( - bill.getBillFinanceDetails() != null && bill.getBillFinanceDetails().getTotalCostValue() != null - ? decimalFormat.format(bill.getBillFinanceDetails().getTotalCostValue()) : "0.00", normalFont)); + decimalFormat.format(row.getConsumptionCostValue()), normalFont)); costCell.setHorizontalAlignment(Element.ALIGN_RIGHT); table.addCell(costCell); PdfPCell retailCell = new PdfPCell(new Phrase( - bill.getBillFinanceDetails() != null && bill.getBillFinanceDetails().getTotalRetailSaleValue() != null - ? decimalFormat.format(bill.getBillFinanceDetails().getTotalRetailSaleValue()) : "0.00", normalFont)); + decimalFormat.format(row.getConsumptionRetailValue()), normalFont)); retailCell.setHorizontalAlignment(Element.ALIGN_RIGHT); table.addCell(retailCell); diff --git a/src/main/java/com/divudi/core/data/PharmacyRow.java b/src/main/java/com/divudi/core/data/PharmacyRow.java index 126061db050..4ea123ebd1b 100644 --- a/src/main/java/com/divudi/core/data/PharmacyRow.java +++ b/src/main/java/com/divudi/core/data/PharmacyRow.java @@ -194,6 +194,11 @@ public class PharmacyRow implements Serializable { private BigDecimal valueOfStocksAtPurchaseRate = BigDecimal.ZERO; private BigDecimal valueOfStocksAtRetailSaleRate = BigDecimal.ZERO; + private double consumptionQty; + private double consumptionPurchaseValue; + private double consumptionCostValue; + private double consumptionRetailValue; + private BigDecimal grossSaleRate = BigDecimal.ZERO; private BigDecimal discountRate = BigDecimal.ZERO; private BigDecimal marginRate = BigDecimal.ZERO; @@ -1754,4 +1759,36 @@ public BigDecimal getValueOfStocksAtRetailSaleRate() { public void setValueOfStocksAtRetailSaleRate(BigDecimal valueOfStocksAtRetailSaleRate) { this.valueOfStocksAtRetailSaleRate = valueOfStocksAtRetailSaleRate; } + + public double getConsumptionQty() { + return consumptionQty; + } + + public void setConsumptionQty(double consumptionQty) { + this.consumptionQty = consumptionQty; + } + + public double getConsumptionPurchaseValue() { + return consumptionPurchaseValue; + } + + public void setConsumptionPurchaseValue(double consumptionPurchaseValue) { + this.consumptionPurchaseValue = consumptionPurchaseValue; + } + + public double getConsumptionCostValue() { + return consumptionCostValue; + } + + public void setConsumptionCostValue(double consumptionCostValue) { + this.consumptionCostValue = consumptionCostValue; + } + + public double getConsumptionRetailValue() { + return consumptionRetailValue; + } + + public void setConsumptionRetailValue(double consumptionRetailValue) { + this.consumptionRetailValue = consumptionRetailValue; + } } diff --git a/src/main/webapp/reports/inventoryReports/consumption.xhtml b/src/main/webapp/reports/inventoryReports/consumption.xhtml index b90e5a8d610..898fcaba195 100644 --- a/src/main/webapp/reports/inventoryReports/consumption.xhtml +++ b/src/main/webapp/reports/inventoryReports/consumption.xhtml @@ -518,8 +518,8 @@ width="4em" style="text-align: right;" filterMatchMode="contains" - sortBy="#{b.bill.billFinanceDetails.totalPurchaseValue}"> - + sortBy="#{b.consumptionPurchaseValue}"> + @@ -533,8 +533,8 @@ width="4em" style="text-align: right;" filterMatchMode="contains" - sortBy="#{b.bill.billFinanceDetails.totalCostValue}"> - + sortBy="#{b.consumptionCostValue}"> + @@ -548,8 +548,8 @@ width="4em" style="text-align: right;" filterMatchMode="contains" - sortBy="#{b.bill.billFinanceDetails.totalRetailSaleValue}"> - + sortBy="#{b.consumptionRetailValue}"> + @@ -658,9 +658,9 @@ width="4em" style="text-align: right; padding-right: 20px;" filterMatchMode="contains" - sortBy="#{i.billItem.qty}" - filterBy="#{i.billItem.qty}"> - + sortBy="#{i.consumptionQty}" + filterBy="#{i.consumptionQty}"> + @@ -732,9 +732,8 @@ width="6em" style="text-align: right;" filterMatchMode="contains" - sortBy="#{i.billItem.billItemFinanceDetails.valueAtPurchaseRate}"> - + sortBy="#{i.consumptionPurchaseValue}"> + @@ -759,8 +758,8 @@ width="6em" style="text-align: right;" filterMatchMode="contains" - sortBy="#{i.billItem.billItemFinanceDetails.valueAtRetailRate}"> - + sortBy="#{i.consumptionRetailValue}"> + @@ -785,8 +784,8 @@ width="6em" style="text-align: right;" filterMatchMode="contains" - sortBy="#{i.billItem.billItemFinanceDetails.valueAtCostRate}"> - + sortBy="#{i.consumptionCostValue}"> +