From 47c7acbb6091658055a2effb33e23df00b2fe29d Mon Sep 17 00:00:00 2001 From: buddhika Date: Mon, 1 Jun 2026 04:20:41 +0530 Subject: [PATCH] =?UTF-8?q?perf(pharmacy):=20optimize=20transfer=20request?= =?UTF-8?q?=20item=20add=20=E2=80=94=20DTOs,=20single=20rate=20query,=20AJ?= =?UTF-8?q?AX=20add?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace full Item entity loads in pharmacy_transfer_request.xhtml with lightweight DTOs and a single-query rate lookup, eliminating the main sources of delay when adding items to a transfer request. Changes: - ItemDTO (search): add dblValue, itemTypeName, rateItemId fields + new constructor for autocomplete DTO queries - ItemRatesDTO (new): holds purchase/retail/cost rates from one query - ItemDtoConverter (new): CDI converter encodes DTO as id:type:dblValue:rateItemId - ItemController: add completeAmpAmppVmpVmppItemDtosForRequestingDepartment returning List via 4 typed constructor queries (replaces full entity load per keystroke) - PharmacyBean: add getLastRatesForItem replacing 3 separate entity-loading rate calls with a single BillItemFinanceDetails scalar query - TransferRequestController: autocomplete now uses ItemDTO + typed proxy; populateRatesOnItemSelect reduced from 3 entity loads to 1 entity load + 1 scalar query; addItem reuses cached rates; fetchBillItems adds JOIN FETCH bi.item and LEFT JOIN FETCH bi.billItemFinanceDetails to eliminate N+1 on edit path; recentToDepartments query fetches institution eagerly; three display type list getters are now lazily cached - XHTML: autocomplete bound to currentItemDto with itemDtoConverter; Add Item button changed from ajax=false (full page reload) to AJAX with targeted updates; pack-size column uses class.simpleName instead of brittle full class string comparison (also fixes Vmpp display) Closes #21062 Co-Authored-By: Claude Sonnet 4.6 --- .../divudi/bean/common/ItemController.java | 74 +++++ .../pharmacy/TransferRequestController.java | 288 ++++++++---------- .../core/converter/ItemDtoConverter.java | 48 +++ .../divudi/core/data/dto/ItemRatesDTO.java | 48 +++ .../divudi/core/data/dto/search/ItemDTO.java | 41 +++ .../java/com/divudi/ejb/PharmacyBean.java | 44 +++ .../pharmacy/pharmacy_transfer_request.xhtml | 40 ++- 7 files changed, 393 insertions(+), 190 deletions(-) create mode 100644 src/main/java/com/divudi/core/converter/ItemDtoConverter.java create mode 100644 src/main/java/com/divudi/core/data/dto/ItemRatesDTO.java diff --git a/src/main/java/com/divudi/bean/common/ItemController.java b/src/main/java/com/divudi/bean/common/ItemController.java index 02994fada18..ecbd23a6687 100644 --- a/src/main/java/com/divudi/bean/common/ItemController.java +++ b/src/main/java/com/divudi/bean/common/ItemController.java @@ -26,6 +26,7 @@ import com.divudi.core.entity.Speciality; import com.divudi.core.entity.Staff; import com.divudi.core.data.dto.AmpDto; +import com.divudi.core.data.dto.search.ItemDTO; import com.divudi.core.entity.pharmacy.Amp; import com.divudi.core.entity.pharmacy.Ampp; import com.divudi.core.entity.pharmacy.Atm; @@ -3015,6 +3016,79 @@ private List getAvailableDepartmentTypesForPharmacyTransactions( return availableDepartmentTypesForPharmacyTransactions; } + /** + * DTO-based autocomplete for transfer request item entry. Runs four + * lightweight constructor queries (one per subtype) instead of loading + * full Item entities. Returns at most {@code maxResults} entries sorted by + * name. + * + * @param query text typed by the user + * @param dept toDepartment used to resolve allowed department types + * @param typeFilter when non-null, restrict results to this department type + */ + public List completeAmpAmppVmpVmppItemDtosForRequestingDepartment( + String query, Department dept, DepartmentType typeFilter) { + + if (query == null || query.trim().isEmpty()) { + return new ArrayList<>(); + } + + String q = "%" + query.trim().toUpperCase() + "%"; + int barcodeMinLength = 8; + int maxResults = 30; + + try { + barcodeMinLength = configOptionApplicationController.getIntegerValueByKey("BarcodeMinLength", 8); + } catch (Exception e) { + // use default + } + try { + maxResults = configOptionApplicationController.getIntegerValueByKey("PharmaceuticalAutocompleteMaxResults", 30); + } catch (Exception e) { + // use default + } + + boolean includeBarcode = query.trim().length() >= barcodeMinLength; + String barcodeCondition = includeBarcode ? "OR UPPER(COALESCE(i.barcode, '')) LIKE :q " : ""; + + Department lookupDept = dept != null ? dept : sessionController.getDepartment(); + List allowedTypes; + if (typeFilter != null) { + allowedTypes = java.util.Collections.singletonList(typeFilter); + } else { + allowedTypes = getAvailableDepartmentTypesForPharmacyTransactions(lookupDept); + } + if (allowedTypes == null || allowedTypes.isEmpty()) { + return new ArrayList<>(); + } + + String where = "WHERE i.retired = false AND i.inactive = false " + + "AND i.departmentType IN :dts " + + "AND (UPPER(i.name) LIKE :q OR UPPER(COALESCE(i.code, '')) LIKE :q " + + barcodeCondition + ") " + + "ORDER BY i.name"; + + Map params = new HashMap<>(); + params.put("dts", allowedTypes); + params.put("q", q); + + String dtoClass = "com.divudi.core.data.dto.search.ItemDTO"; + + String ampJpql = "SELECT new " + dtoClass + "(i.id, i.name, COALESCE(i.code,''), i.dblValue, 'Amp', i.id) FROM Amp i " + where; + String amppJpql = "SELECT new " + dtoClass + "(i.id, i.name, COALESCE(i.code,''), i.dblValue, 'Ampp', i.amp.id) FROM Ampp i " + where; + String vmpJpql = "SELECT new " + dtoClass + "(i.id, i.name, COALESCE(i.code,''), i.dblValue, 'Vmp', i.id) FROM Vmp i " + where; + String vmppJpql = "SELECT new " + dtoClass + "(i.id, i.name, COALESCE(i.code,''), i.dblValue, 'Vmpp', i.vmp.id) FROM Vmpp i " + where; + + List results = new ArrayList<>(); + results.addAll((List) getFacade().findLightsByJpql(ampJpql, params, TemporalType.TIMESTAMP, maxResults)); + results.addAll((List) getFacade().findLightsByJpql(amppJpql, params, TemporalType.TIMESTAMP, maxResults)); + results.addAll((List) getFacade().findLightsByJpql(vmpJpql, params, TemporalType.TIMESTAMP, maxResults)); + results.addAll((List) getFacade().findLightsByJpql(vmppJpql, params, TemporalType.TIMESTAMP, maxResults)); + + results.sort(java.util.Comparator.comparing(dto -> dto.getName() != null ? dto.getName() : "")); + return results.size() > maxResults ? results.subList(0, maxResults) : results; + } + public List completeAmpAndVmpItem(String query) { List suggestions; String sql; diff --git a/src/main/java/com/divudi/bean/pharmacy/TransferRequestController.java b/src/main/java/com/divudi/bean/pharmacy/TransferRequestController.java index 5d1472efd5b..b553bf2dbcf 100644 --- a/src/main/java/com/divudi/bean/pharmacy/TransferRequestController.java +++ b/src/main/java/com/divudi/bean/pharmacy/TransferRequestController.java @@ -13,6 +13,8 @@ import com.divudi.core.data.admin.ConfigOptionInfo; import com.divudi.core.data.admin.PageMetadata; import com.divudi.core.data.admin.PrivilegeInfo; +import com.divudi.core.data.dto.ItemRatesDTO; +import com.divudi.core.data.dto.search.ItemDTO; import com.divudi.core.entity.*; import com.divudi.core.util.BigDecimalUtil; import com.divudi.core.util.CommonFunctions; @@ -120,6 +122,11 @@ public class TransferRequestController implements Serializable { private boolean showAllBillFormats = false; private Department toDepartment; private List recentToDepartments; + private ItemDTO currentItemDto; + private ItemRatesDTO currentItemRates; + private List cachedAvailableDeptTypesForDisplay; + private List cachedLoggedDeptTypesForDisplay; + private List cachedToDeptTypesForDisplay; // public String navigateToCreateANewTransferRequest() { @@ -132,6 +139,11 @@ public void recreate() { toDepartment = null; bill = null; currentBillItem = null; + currentItemDto = null; + currentItemRates = null; + cachedAvailableDeptTypesForDisplay = null; + cachedLoggedDeptTypesForDisplay = null; + cachedToDeptTypesForDisplay = null; dealor = null; billItems = null; printPreview = false; @@ -155,32 +167,9 @@ private boolean checkItems(Item item) { @Inject ItemController itemController; - public List completeAmpAmppVmpVmppItemsForRequestingDepartment(String query) { - // If no department type is set, show all available items for this department - if (getBill().getDepartmentType() == null) { - return itemController.completeAmpAmppVmpVmppItemsForRequestingDepartment(query, toDepartment); - } else { - // If department type is set, filter items by that department type only - List allItems = itemController.completeAmpAmppVmpVmppItemsForRequestingDepartment(query, toDepartment); - return filterItemsByDepartmentType(allItems, getBill().getDepartmentType()); - } - } - - private List filterItemsByDepartmentType(List items, DepartmentType departmentType) { - if (items == null || departmentType == null) { - return items; - } - - return items.stream() - .filter(item -> { - DepartmentType itemDeptType = item.getDepartmentType(); - // Treat items without department type as Pharmacy - if (itemDeptType == null) { - itemDeptType = DepartmentType.Pharmacy; - } - return itemDeptType.equals(departmentType); - }) - .collect(java.util.stream.Collectors.toList()); + public List completeAmpAmppVmpVmppItemsForRequestingDepartment(String query) { + DepartmentType typeFilter = getBill().getDepartmentType(); + return itemController.completeAmpAmppVmpVmppItemDtosForRequestingDepartment(query, toDepartment, typeFilter); } public String fillHeaderDataOfTransferRequest(String s, Bill b) { @@ -301,14 +290,22 @@ public void addItem() { Item item = bi.getItem(); bi.setSearialNo(getBillItems().size()); - ph.setPurchaseRate(getPharmacyBean().getLastPurchaseRate(item, getSessionController().getDepartment())); - ph.setRetailRateInUnit(getPharmacyBean().getLastRetailRate(item, getSessionController().getDepartment())); + + if (currentItemRates != null) { + ph.setPurchaseRate(currentItemRates.getPurchaseRate()); + ph.setRetailRateInUnit(currentItemRates.getRetailRate()); + } else { + ph.setPurchaseRate(getPharmacyBean().getLastPurchaseRate(item, getSessionController().getDepartment())); + ph.setRetailRateInUnit(getPharmacyBean().getLastRetailRate(item, getSessionController().getDepartment())); + } updateFinancials(fd); getBillItems().add(bi); recalculateTransferRequestBillTotals(); currentBillItem = null; + currentItemDto = null; + currentItemRates = null; } public void onEdit(BillItem tmp) { @@ -800,7 +797,10 @@ private List fetchBillItems(Bill bill) { if (bill == null) { return items; } - String jpql = "select bi from BillItem bi where bi.bill=:bill and bi.retired=false"; + String jpql = "select bi from BillItem bi " + + "join fetch bi.item " + + "left join fetch bi.billItemFinanceDetails " + + "where bi.bill=:bill and bi.retired=false"; Map m = new HashMap(); m.put("bill", bill); items = billItemFacade.findByJpql(jpql, m); @@ -985,17 +985,15 @@ public void setToDepartment(Department toDepartment) { * Resets department type to null if it's no longer valid. */ public void handleToDepartmentChange() { - // Guard against null toDepartment if (toDepartment == null) { return; } + cachedAvailableDeptTypesForDisplay = null; + cachedToDeptTypesForDisplay = null; - // Reset department type if it's no longer valid for the intersection if (bill != null && bill.getDepartmentType() != null) { List validTypes = getAvailableDepartmentTypesForTransfer(); - if (!validTypes.contains(bill.getDepartmentType())) { - // Current selection is no longer valid bill.setDepartmentType(null); JsfUtil.addErrorMessage("Department type reset. The previously selected department type is not supported by " + toDepartment.getName() + ". Please select a valid department type."); @@ -1071,107 +1069,67 @@ private void updateFinancials(BillItemFinanceDetails fd) { ph.setQtyPacks(qty.doubleValue()); } - // ChatGPT contributed - Populate default rates when an item is selected public void populateRatesOnItemSelect() { - BillItem bi = getCurrentBillItem(); - if (bi == null || bi.getItem() == null) { + if (currentItemDto == null || currentItemDto.getId() == null) { return; } - PharmaceuticalBillItem ph = bi.getPharmaceuticalBillItem(); - BillItemFinanceDetails fd = bi.getBillItemFinanceDetails(); - - if (bi.getItem() instanceof Ampp) { - Ampp ampp = (Ampp) bi.getItem(); - Amp amp = ampp.getAmp(); - - fd.setUnitsPerPack(BigDecimal.valueOf(bi.getItem().getDblValue())); - - Double retailRatePerUnit = pharmacyBean.getLastRetailRate(amp, sessionController.getDepartment()); - Double purchaseRatePerUnit = pharmacyBean.getLastPurchaseRate(amp, sessionController.getDepartment()); - Double costRatePerUnit = pharmacyBean.getLastCostRate(amp, sessionController.getDepartment()); - BigDecimal transferRate = determineTransferRate(amp); - - ph.setPurchaseRate(purchaseRatePerUnit * fd.getUnitsPerPack().doubleValue()); - ph.setPurchaseRatePack(purchaseRatePerUnit * fd.getUnitsPerPack().doubleValue()); - - ph.setRetailRate(retailRatePerUnit * fd.getUnitsPerPack().doubleValue()); - ph.setRetailRatePack(retailRatePerUnit * fd.getUnitsPerPack().doubleValue()); - - ph.setRetailPackValue(0); - - fd.setLineCostRate(BigDecimal.valueOf(costRatePerUnit).multiply(fd.getUnitsPerPack())); - fd.setLineGrossRate(transferRate.multiply(fd.getUnitsPerPack())); - - } else if (bi.getItem() instanceof Vmpp) { - Vmpp vmpp = (Vmpp) bi.getItem(); - Vmp vmp = vmpp.getVmp(); - - fd.setUnitsPerPack(BigDecimal.valueOf(bi.getItem().getDblValue())); - - Double retailRatePerUnit = pharmacyBean.getLastRetailRate(vmp, sessionController.getDepartment()); - Double purchaseRatePerUnit = pharmacyBean.getLastPurchaseRate(vmp, sessionController.getDepartment()); - Double costRatePerUnit = pharmacyBean.getLastCostRate(vmp, sessionController.getDepartment()); - BigDecimal transferRate = determineTransferRate(vmp); - - ph.setPurchaseRate(purchaseRatePerUnit * fd.getUnitsPerPack().doubleValue()); - ph.setPurchaseRatePack(purchaseRatePerUnit * fd.getUnitsPerPack().doubleValue()); - - ph.setRetailRate(retailRatePerUnit * fd.getUnitsPerPack().doubleValue()); - ph.setRetailRatePack(retailRatePerUnit * fd.getUnitsPerPack().doubleValue()); - - ph.setRetailPackValue(0); - - fd.setLineCostRate(BigDecimal.valueOf(costRatePerUnit).multiply(fd.getUnitsPerPack())); - fd.setLineGrossRate(transferRate.multiply(fd.getUnitsPerPack())); - - } else if (bi.getItem() instanceof Amp) { - Amp amp = (Amp) bi.getItem(); - - fd.setUnitsPerPack(BigDecimal.ONE); - - Double retailRatePerUnit = pharmacyBean.getLastRetailRate(amp, sessionController.getDepartment()); - Double purchaseRatePerUnit = pharmacyBean.getLastPurchaseRate(amp, sessionController.getDepartment()); - Double costRatePerUnit = pharmacyBean.getLastCostRate(amp, sessionController.getDepartment()); - BigDecimal transferRate = determineTransferRate(amp); - - ph.setPurchaseRate(purchaseRatePerUnit); - ph.setPurchaseRatePack(purchaseRatePerUnit); - - ph.setRetailRate(retailRatePerUnit); - ph.setRetailRatePack(retailRatePerUnit); - - ph.setRetailPackValue(0); - - fd.setLineCostRate(BigDecimal.valueOf(costRatePerUnit)); - fd.setLineGrossRate(transferRate); - - } else if (bi.getItem() instanceof Vmp) { - Vmp vmp = (Vmp) bi.getItem(); - - fd.setUnitsPerPack(BigDecimal.ONE); + // Load the full correctly-typed entity (Amp/Ampp/Vmp/Vmpp) once + Item item = itemFacade.find(currentItemDto.getId()); + if (item == null) { + return; + } + getCurrentBillItem().setItem(item); - Double retailRatePerUnit = pharmacyBean.getLastRetailRate(vmp, sessionController.getDepartment()); - Double purchaseRatePerUnit = pharmacyBean.getLastPurchaseRate(vmp, sessionController.getDepartment()); - Double costRatePerUnit = pharmacyBean.getLastCostRate(vmp, sessionController.getDepartment()); - BigDecimal transferRate = determineTransferRate(vmp); + // Fetch all three rates in a single DB query using the rate-lookup item + Item rateItem = itemFacade.getReference(currentItemDto.getRateItemId()); + currentItemRates = pharmacyBean.getLastRatesForItem(rateItem, sessionController.getDepartment()); - ph.setPurchaseRate(purchaseRatePerUnit); - ph.setPurchaseRatePack(purchaseRatePerUnit); + BillItem bi = getCurrentBillItem(); + PharmaceuticalBillItem ph = bi.getPharmaceuticalBillItem(); + BillItemFinanceDetails fd = bi.getBillItemFinanceDetails(); - ph.setRetailRate(retailRatePerUnit); - ph.setRetailRatePack(retailRatePerUnit); + // Pack size: use DTO value (>0) or default to 1 for unit items + double rawDblValue = currentItemDto.getDblValue() != null ? currentItemDto.getDblValue() : 0.0; + boolean isPack = "Ampp".equals(currentItemDto.getItemTypeName()) || "Vmpp".equals(currentItemDto.getItemTypeName()); + BigDecimal unitsPerPack = (isPack && rawDblValue > 0) ? BigDecimal.valueOf(rawDblValue) : BigDecimal.ONE; + fd.setUnitsPerPack(unitsPerPack); - ph.setRetailPackValue(0); + BigDecimal transferRate = determineTransferRateFromRates(currentItemRates); - fd.setLineCostRate(BigDecimal.valueOf(costRatePerUnit)); + if (isPack) { + ph.setPurchaseRate(currentItemRates.getPurchaseRate() * unitsPerPack.doubleValue()); + ph.setPurchaseRatePack(currentItemRates.getPurchaseRate() * unitsPerPack.doubleValue()); + ph.setRetailRate(currentItemRates.getRetailRate() * unitsPerPack.doubleValue()); + ph.setRetailRatePack(currentItemRates.getRetailRate() * unitsPerPack.doubleValue()); + fd.setLineCostRate(BigDecimal.valueOf(currentItemRates.getCostRate()).multiply(unitsPerPack)); + fd.setLineGrossRate(transferRate.multiply(unitsPerPack)); + } else { + ph.setPurchaseRate(currentItemRates.getPurchaseRate()); + ph.setPurchaseRatePack(currentItemRates.getPurchaseRate()); + ph.setRetailRate(currentItemRates.getRetailRate()); + ph.setRetailRatePack(currentItemRates.getRetailRate()); + fd.setLineCostRate(BigDecimal.valueOf(currentItemRates.getCostRate())); fd.setLineGrossRate(transferRate); } + ph.setRetailPackValue(0); pharmacyCostingService.recalculateFinancialsBeforeAddingBillItem(fd); recalculateTransferRequestBillTotals(); } + private BigDecimal determineTransferRateFromRates(ItemRatesDTO rates) { + boolean byPurchase = configOptionApplicationController.getBooleanValueByKey("Pharmacy Transfer is by Purchase Rate", false); + boolean byCost = configOptionApplicationController.getBooleanValueByKey("Pharmacy Transfer is by Cost Rate", false); + if (byPurchase) { + return BigDecimal.valueOf(rates.getPurchaseRate()); + } else if (byCost) { + return BigDecimal.valueOf(rates.getCostRate()); + } else { + return BigDecimal.valueOf(rates.getRetailRate()); + } + } + // ChatGPT contributed - Recalculate item totals when gross rate changes public void onLineGrossRateChange(BillItem bi) { if (bi == null || bi.getBillItemFinanceDetails() == null) { @@ -1456,13 +1414,15 @@ private BigDecimal determineTransferRate(Item item) { public List getRecentToDepartments() { if (recentToDepartments == null) { - String jpql = "select distinct b.toDepartment from Bill b " - + " where b.retired=false " - + " and b.billTypeAtomic=:bt " - + " and b.fromDepartment=:fd " - + " and b.toDepartment.retired=false " - + " and b.toDepartment.inactive=false " - + " order by b.id desc"; + String jpql = "select distinct d from Bill b " + + "join b.toDepartment d " + + "left join fetch d.institution " + + "where b.retired=false " + + "and b.billTypeAtomic=:bt " + + "and b.fromDepartment=:fd " + + "and d.retired=false " + + "and d.inactive=false " + + "order by b.id desc"; Map m = new HashMap<>(); m.put("bt", BillTypeAtomic.PHARMACY_TRANSFER_REQUEST); m.put("fd", sessionController.getDepartment()); @@ -1572,66 +1532,58 @@ private boolean isDepartmentTypeAllowedForToDepartment(DepartmentType department * @return List of department type names that are enabled for pharmacy transactions */ public List getAvailableDepartmentTypesForDisplay() { - List displayTypes = new ArrayList<>(); - if (toDepartment == null) { - return displayTypes; + return new ArrayList<>(); } - - // Get intersection of both departments' supported types - List intersection = getAvailableDepartmentTypesForTransfer(); - - // Convert to display strings - for (DepartmentType dt : intersection) { - displayTypes.add(dt.getLabel()); + if (cachedAvailableDeptTypesForDisplay == null) { + cachedAvailableDeptTypesForDisplay = new ArrayList<>(); + for (DepartmentType dt : getAvailableDepartmentTypesForTransfer()) { + cachedAvailableDeptTypesForDisplay.add(dt.getLabel()); + } } - - return displayTypes; + return cachedAvailableDeptTypesForDisplay; } - /** - * Gets all department types supported by the logged department. - * Used for UI display to show what the logged dept supports. - * - * @return List of department type labels for display - */ public List getLoggedDepartmentSupportedTypesForDisplay() { - List displayTypes = new ArrayList<>(); - if (sessionController.getDepartment() == null) { - return displayTypes; + return new ArrayList<>(); } - - List loggedTypes = - sessionController.getAvailableDepartmentTypesForPharmacyTransactions(); - - for (DepartmentType dt : loggedTypes) { - displayTypes.add(dt.getLabel()); + if (cachedLoggedDeptTypesForDisplay == null) { + cachedLoggedDeptTypesForDisplay = new ArrayList<>(); + for (DepartmentType dt : sessionController.getAvailableDepartmentTypesForPharmacyTransactions()) { + cachedLoggedDeptTypesForDisplay.add(dt.getLabel()); + } } - - return displayTypes; + return cachedLoggedDeptTypesForDisplay; } - /** - * Gets all department types supported by the toDepartment. - * Used for UI display to show what the toDepartment supports. - * - * @return List of department type labels for display - */ public List getToDepartmentSupportedTypesForDisplay() { - List displayTypes = new ArrayList<>(); - if (toDepartment == null) { - return displayTypes; + return new ArrayList<>(); } + if (cachedToDeptTypesForDisplay == null) { + cachedToDeptTypesForDisplay = new ArrayList<>(); + for (DepartmentType dt : getAvailableDepartmentTypesForToDepartment()) { + cachedToDeptTypesForDisplay.add(dt.getLabel()); + } + } + return cachedToDeptTypesForDisplay; + } - List toTypes = getAvailableDepartmentTypesForToDepartment(); + public ItemDTO getCurrentItemDto() { + return currentItemDto; + } - for (DepartmentType dt : toTypes) { - displayTypes.add(dt.getLabel()); - } + public void setCurrentItemDto(ItemDTO currentItemDto) { + this.currentItemDto = currentItemDto; + } + + public ItemRatesDTO getCurrentItemRates() { + return currentItemRates; + } - return displayTypes; + public void setCurrentItemRates(ItemRatesDTO currentItemRates) { + this.currentItemRates = currentItemRates; } public boolean isShowAllBillFormats() { diff --git a/src/main/java/com/divudi/core/converter/ItemDtoConverter.java b/src/main/java/com/divudi/core/converter/ItemDtoConverter.java new file mode 100644 index 00000000000..15c935a0dbb --- /dev/null +++ b/src/main/java/com/divudi/core/converter/ItemDtoConverter.java @@ -0,0 +1,48 @@ +package com.divudi.core.converter; + +import com.divudi.core.data.dto.search.ItemDTO; +import javax.enterprise.context.ApplicationScoped; +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.convert.Converter; +import javax.inject.Named; + +/** + * CDI converter for ItemDTO used in PrimeFaces autocomplete components. + * Encodes the DTO as "id:typeName:dblValue:rateItemId" — no DB access needed. + */ +@Named +@ApplicationScoped +public class ItemDtoConverter implements Converter { + + @Override + public String getAsString(FacesContext context, UIComponent component, ItemDTO dto) { + if (dto == null || dto.getId() == null) { + return ""; + } + return dto.getId() + + ":" + (dto.getItemTypeName() != null ? dto.getItemTypeName() : "") + + ":" + (dto.getDblValue() != null ? dto.getDblValue() : 0.0) + + ":" + (dto.getRateItemId() != null ? dto.getRateItemId() : dto.getId()); + } + + @Override + public ItemDTO getAsObject(FacesContext context, UIComponent component, String value) { + if (value == null || value.trim().isEmpty()) { + return null; + } + try { + String[] parts = value.split(":"); + if (parts.length < 4) { + return null; + } + Long id = Long.parseLong(parts[0]); + String typeName = parts[1]; + Double dblValue = Double.parseDouble(parts[2]); + Long rateItemId = Long.parseLong(parts[3]); + return new ItemDTO(id, null, null, dblValue, typeName, rateItemId); + } catch (Exception e) { + return null; + } + } +} diff --git a/src/main/java/com/divudi/core/data/dto/ItemRatesDTO.java b/src/main/java/com/divudi/core/data/dto/ItemRatesDTO.java new file mode 100644 index 00000000000..933a086bf9f --- /dev/null +++ b/src/main/java/com/divudi/core/data/dto/ItemRatesDTO.java @@ -0,0 +1,48 @@ +package com.divudi.core.data.dto; + +import java.io.Serializable; + +/** + * Lightweight DTO holding the three rates needed for transfer request item + * entry: purchase, retail, and cost. Populated from a single BillItem query, + * replacing three separate entity-loading rate calls. + */ +public class ItemRatesDTO implements Serializable { + + private double purchaseRate; + private double retailRate; + private double costRate; + + public ItemRatesDTO() { + } + + public ItemRatesDTO(double purchaseRate, double retailRate, double costRate) { + this.purchaseRate = purchaseRate; + this.retailRate = retailRate; + this.costRate = costRate; + } + + public double getPurchaseRate() { + return purchaseRate; + } + + public void setPurchaseRate(double purchaseRate) { + this.purchaseRate = purchaseRate; + } + + public double getRetailRate() { + return retailRate; + } + + public void setRetailRate(double retailRate) { + this.retailRate = retailRate; + } + + public double getCostRate() { + return costRate; + } + + public void setCostRate(double costRate) { + this.costRate = costRate; + } +} diff --git a/src/main/java/com/divudi/core/data/dto/search/ItemDTO.java b/src/main/java/com/divudi/core/data/dto/search/ItemDTO.java index 11b3314a8b3..15e0ef96f1f 100644 --- a/src/main/java/com/divudi/core/data/dto/search/ItemDTO.java +++ b/src/main/java/com/divudi/core/data/dto/search/ItemDTO.java @@ -21,6 +21,9 @@ public class ItemDTO implements Serializable { private String barcode; private String genericName; private String categoryName; + private Double dblValue; + private String itemTypeName; + private Long rateItemId; public ItemDTO() { } @@ -37,6 +40,20 @@ public ItemDTO(Long id, String name, String code, String barcode, String generic this.genericName = genericName; } + /** + * Constructor for autocomplete DTO queries — includes pack size, item type + * discriminator, and the ID of the item used for rate lookups (Amp for + * Ampp items, Vmp for Vmpp items, self for Amp/Vmp items). + */ + public ItemDTO(Long id, String name, String code, Double dblValue, String itemTypeName, Long rateItemId) { + this.id = id; + this.name = name; + this.code = code; + this.dblValue = dblValue; + this.itemTypeName = itemTypeName; + this.rateItemId = rateItemId; + } + /** * Extended constructor with category information */ @@ -99,6 +116,30 @@ public void setCategoryName(String categoryName) { this.categoryName = categoryName; } + public Double getDblValue() { + return dblValue; + } + + public void setDblValue(Double dblValue) { + this.dblValue = dblValue; + } + + public String getItemTypeName() { + return itemTypeName; + } + + public void setItemTypeName(String itemTypeName) { + this.itemTypeName = itemTypeName; + } + + public Long getRateItemId() { + return rateItemId; + } + + public void setRateItemId(Long rateItemId) { + this.rateItemId = rateItemId; + } + @Override public String toString() { return "ItemDTO{" + diff --git a/src/main/java/com/divudi/ejb/PharmacyBean.java b/src/main/java/com/divudi/ejb/PharmacyBean.java index 7bd2707adb3..c408847db09 100644 --- a/src/main/java/com/divudi/ejb/PharmacyBean.java +++ b/src/main/java/com/divudi/ejb/PharmacyBean.java @@ -4,6 +4,7 @@ */ package com.divudi.ejb; +import com.divudi.core.data.dto.ItemRatesDTO; import com.divudi.core.data.BillClassType; import com.divudi.core.data.BillNumberSuffix; import com.divudi.core.data.BillType; @@ -2479,6 +2480,49 @@ public BillItem getLastPurchaseItem(Item item) { return getBillItemFacade().findFirstByJpql(jpql, params); } + /** + * Fetches purchase, retail and cost rates for {@code rateItem} in a single + * database query (manageCosting=true path) instead of three separate + * entity-loading calls. Falls back to two PharmaceuticalBillItem queries + * when manageCosting is disabled. + * + * @param rateItem the Amp or Vmp item used for rate lookup (callers must + * resolve Ampp→Amp and Vmpp→Vmp before calling) + * @param dept department for the non-costing fallback query + */ + public ItemRatesDTO getLastRatesForItem(Item rateItem, Department dept) { + if (rateItem == null) { + return new ItemRatesDTO(0.0, 0.0, 0.0); + } + boolean manageCosting = configOptionApplicationController.getBooleanValueByKey("Manage Costing", true); + if (manageCosting) { + String jpql = "SELECT f.lineGrossRate, f.retailSaleRate, f.totalCostRate " + + "FROM BillItem bi JOIN bi.billItemFinanceDetails f " + + "WHERE bi.retired = false " + + "AND bi.bill.cancelled = false " + + "AND bi.item = :i " + + "AND (bi.bill.billType = :t OR bi.bill.billType = :t1) " + + "ORDER BY bi.id DESC"; + Map params = new HashMap<>(); + params.put("i", rateItem); + params.put("t", BillType.PharmacyGrnBill); + params.put("t1", BillType.PharmacyPurchaseBill); + List rows = getBillItemFacade().findLightsByJpql(jpql, params, TemporalType.TIMESTAMP, 1); + if (!rows.isEmpty()) { + Object[] row = (Object[]) rows.get(0); + double purchase = row[0] instanceof java.math.BigDecimal ? ((java.math.BigDecimal) row[0]).doubleValue() : 0.0; + double retail = row[1] instanceof java.math.BigDecimal ? ((java.math.BigDecimal) row[1]).doubleValue() : 0.0; + double cost = row[2] instanceof java.math.BigDecimal ? ((java.math.BigDecimal) row[2]).doubleValue() : 0.0; + return new ItemRatesDTO(purchase, retail, cost); + } + return new ItemRatesDTO(0.0, 0.0, 0.0); + } else { + double purchaseRate = getLastPurchaseRateByPharmaceuticalBillItem(rateItem, dept); + double retailRate = getLastRetailRateByPharmaceuticalBillItem(rateItem, dept); + return new ItemRatesDTO(purchaseRate, retailRate, purchaseRate); + } + } + public double getLastPurchaseRate(Item item, Department dept) { boolean manageCosting = configOptionApplicationController.getBooleanValueByKey("Manage Costing", true); if (manageCosting) { diff --git a/src/main/webapp/pharmacy/pharmacy_transfer_request.xhtml b/src/main/webapp/pharmacy/pharmacy_transfer_request.xhtml index 23dab6b8750..25f075bd4f5 100644 --- a/src/main/webapp/pharmacy/pharmacy_transfer_request.xhtml +++ b/src/main/webapp/pharmacy/pharmacy_transfer_request.xhtml @@ -148,32 +148,30 @@ - + - - + + - + - - - - - + + + + + process="@form" + update="itemAutoComplete txtQty txtLineGrossRate txtLineNetValue itemList requestTotals focusItem"/> @@ -247,13 +246,10 @@ - - - - + + +