From c76b1a565ecc0a642678c7196bcca5013b63b0a8 Mon Sep 17 00:00:00 2001 From: Fai Luk Date: Sat, 21 Mar 2026 00:05:03 +0800 Subject: [PATCH] change the PO with m18 uom and qty, included stock in PO, putaway process and GRN --- .../scheduler/service/SchedulerService.kt | 8 +-- .../jobOrder/entity/JobOrderRepository.kt | 19 +++++ .../modules/jobOrder/service/PSService.kt | 7 +- .../service/PlasticBagPrinterService.kt | 28 +++++++- .../modules/jobOrder/web/PSController.kt | 7 +- .../modules/master/service/ItemUomService.kt | 21 ++++++ .../service/ProductionScheduleService.kt | 37 +++++++--- .../service/PurchaseOrderService.kt | 22 ++++-- .../fpsms/modules/stock/entity/StockInLine.kt | 9 +++ .../entity/projection/StockInLineInfo.kt | 6 +- .../stock/service/StockInLineService.kt | 70 +++++++++++++------ .../java/com/ffii/fpsms/py/PyController.kt | 9 ++- 12 files changed, 193 insertions(+), 50 deletions(-) diff --git a/src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt b/src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt index 9587754..6e9fdd6 100644 --- a/src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt +++ b/src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt @@ -218,15 +218,15 @@ open class SchedulerService( )*/ val tmr = today.plusDays(1L) var request = M18CommonRequest( - modifiedDateTo = tmr.format(dataStringFormat), - modifiedDateFrom = tmr.format(dataStringFormat) + dDateTo = tmr.format(dataStringFormat), + dDateFrom = tmr.format(dataStringFormat) ) m18PurchaseOrderService.savePurchaseOrders(request); //dDate from tmr to tmr var requestDO = M18CommonRequest( - modifiedDateTo = tmr.format(dataStringFormat), - modifiedDateFrom = tmr.format(dataStringFormat) + dDateTo = tmr.format(dataStringFormat), + dDateFrom = tmr.format(dataStringFormat) ) m18DeliveryOrderService.saveDeliveryOrders(requestDO); // logger.info("today: ${today.format(dataStringFormat)}") diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt index 8c78b73..28b1b6e 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt @@ -135,6 +135,25 @@ interface JobOrderRepository : AbstractRepository { planStartToExclusive: LocalDateTime, ): List + @Query( + """ + SELECT DISTINCT jo FROM JobOrder jo + JOIN jo.bom b + JOIN b.bomProcesses bp + JOIN bp.process p + WHERE jo.deleted = false + AND jo.planStart >= :planStartFrom + AND jo.planStart < :planStartToExclusive + AND p.name = :processName + ORDER BY jo.id ASC + """ + ) + fun findByDeletedFalseAndPlanStartBetweenAndBomProcessNameOrderByIdAsc( + planStartFrom: LocalDateTime, + planStartToExclusive: LocalDateTime, + processName: String, + ): List + @Query(""" SELECT jo FROM JobOrder jo WHERE jo.deleted = false diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PSService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PSService.kt index 247f965..337785f 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PSService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PSService.kt @@ -13,10 +13,11 @@ open class PSService( private val jdbcDao: JdbcDao, ) { - /** Default: past 30 days including today. */ + /** Default: 6 days before today to 1 day after today. */ fun getItemDailyOut(fromDate: LocalDate? = null, toDate: LocalDate? = null): List> { - val to = toDate ?: LocalDate.now() - val from = fromDate ?: to.minusDays(29) + val defaultToday = LocalDate.now() + val to = toDate ?: defaultToday.plusDays(1) + val from = fromDate ?: defaultToday.minusDays(6) val args = mapOf( "fromDate" to from.toString(), "toDate" to to.toString(), diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt index 34f6d59..80c05e2 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt @@ -159,7 +159,31 @@ open class PlasticBagPrinterService( require(normalizedJobOrders.isNotEmpty()) { "No job orders provided" } - val normalizedCodes = normalizedJobOrders + // Safety check: export only supports job orders whose BOM contains process "包裝". + val requestedJobOrderIds = normalizedJobOrders.map { it.jobOrderId }.distinct() + val allowedRows = jdbcDao.queryForList( + """ + SELECT DISTINCT jo.id AS jobOrderId + FROM job_order jo + JOIN bom b ON b.id = jo.bomId + JOIN bom_process bp ON bp.bomId = b.id + JOIN process p ON p.id = bp.processId + WHERE jo.id IN (:jobOrderIds) + AND jo.deleted = 0 + AND p.name = :processName + """.trimIndent(), + mapOf( + "jobOrderIds" to requestedJobOrderIds, + "processName" to "包裝", + ) + ) + val allowedJobOrderIds = allowedRows + .mapNotNull { (it["jobOrderId"] as? Number)?.toLong() } + .toSet() + val packagingJobOrders = normalizedJobOrders.filter { it.jobOrderId in allowedJobOrderIds } + require(packagingJobOrders.isNotEmpty()) { "No 包裝 process job orders found for export" } + + val normalizedCodes = packagingJobOrders .map { it.itemCode } .distinct() @@ -184,7 +208,7 @@ open class PlasticBagPrinterService( val baos = ByteArrayOutputStream() ZipOutputStream(baos).use { zos -> val addedEntries = linkedSetOf() - normalizedJobOrders.forEach { jobOrder -> + packagingJobOrders.forEach { jobOrder -> val filename = filenameByCode[jobOrder.itemCode].orEmpty() if (filename.isBlank()) return@forEach diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PSController.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PSController.kt index 107aab5..b7a648c 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PSController.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PSController.kt @@ -40,14 +40,15 @@ class PSController( return ResponseEntity.ok(results) } - /** 每日平均出貨量: itemCode, itemName, avgQtyLastMonth, dailyQty, isCoffee, isTea, isLemon. Default: past 30 days. */ + /** 每日平均出貨量: itemCode, itemName, avgQtyLastMonth, dailyQty, isCoffee, isTea, isLemon. Default: 6 days before today to 1 day after today. */ @GetMapping("/itemDailyOut.json") fun itemDailyOut( @RequestParam(required = false) fromDate: String?, @RequestParam(required = false) toDate: String?, ): ResponseEntity>> { - val to = toDate?.let { LocalDate.parse(it) } ?: LocalDate.now() - val from = fromDate?.let { LocalDate.parse(it) } ?: to.minusDays(29) + val defaultToday = LocalDate.now() + val to = toDate?.let { LocalDate.parse(it) } ?: defaultToday.plusDays(1) + val from = fromDate?.let { LocalDate.parse(it) } ?: defaultToday.minusDays(6) val results = psService.getItemDailyOut(from, to) return ResponseEntity.ok(results) } diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/ItemUomService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/ItemUomService.kt index 9455edb..21c0858 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/ItemUomService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/ItemUomService.kt @@ -178,6 +178,27 @@ open class ItemUomService( return stockQty.setScale(0, RoundingMode.UP) } + /** + * Convert source quantity from a specific UOM to this item's stock quantity. + * Same as convertQtyToStockQty but rounds down the final stock qty to integer. + */ + open fun convertQtyToStockQtyRoundDown(itemId: Long, uomId: Long, sourceQty: BigDecimal): BigDecimal { + val itemUom = findFirstByItemIdAndUomId(itemId, uomId) ?: return sourceQty + val stockUnit = findStockUnitByItemId(itemId) ?: return BigDecimal.ZERO + val one = BigDecimal.ONE + val calcScale = 10 + + val baseQty = sourceQty + .multiply(itemUom.ratioN ?: one) + .divide(itemUom.ratioD ?: one, calcScale, RoundingMode.HALF_UP) + + val stockQty = baseQty + .multiply(stockUnit.ratioD ?: one) + .divide(stockUnit.ratioN ?: one, calcScale, RoundingMode.HALF_UP) + + return stockQty.setScale(0, RoundingMode.DOWN) + } + // See if need to update the response open fun saveItemUom(request: ItemUomRequest): ItemUom { val itemUom = request.m18Id?.let { findByM18Id(it) } ?: request.id?.let { findById(it) } ?: ItemUom() diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt index d66d320..32ca2ad 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt @@ -628,7 +628,7 @@ open class ProductionScheduleService( } open fun getNeedQty(): List { - val fromDate = java.time.LocalDate.now().minusMonths(1) + val fromDate = java.time.LocalDate.now().minusDays(10) val toDate = java.time.LocalDate.now() val args = mapOf("fromDate" to fromDate.toString(), "toDate" to toDate.toString()) @@ -1832,16 +1832,34 @@ open class ProductionScheduleService( ) val sql = """ - WITH daily_needs AS ( + WITH latest_supplier_uom AS ( + SELECT x.materialId, x.uomIdM18 + FROM ( + SELECT + pol.itemId AS materialId, + pol.uomIdM18, + ROW_NUMBER() OVER ( + PARTITION BY pol.itemId + ORDER BY COALESCE(po.orderDate, po.created) DESC, pol.id DESC + ) AS rn + FROM purchase_order_line pol + JOIN purchase_order po ON po.id = pol.purchaseOrderId + WHERE pol.deleted = 0 + AND po.deleted = 0 + AND pol.uomIdM18 IS NOT NULL + ) x + WHERE x.rn = 1 + ), + daily_needs AS ( SELECT itm.id AS materialId, itm.code AS matCode, itm.name AS matName, - uomP.udfudesc AS uom, - ceil((iv.onHandQty * (itsm.ratioN / itsm.ratioD)) * (itum.ratioD / itum.ratioN)) as onHandQty, - ceil((iv.unavailableQty * (itsm.ratioN / itsm.ratioD)) * (itum.ratioD / itum.ratioN)) as unavailableQty, + COALESCE(uomM18.code, uomP.code) AS uom, + ceil((iv.onHandQty * (itsm.ratioN / itsm.ratioD)) * (COALESCE(ium18.ratioD, itum.ratioD) / COALESCE(ium18.ratioN, itum.ratioN))) as onHandQty, + ceil((iv.unavailableQty * (itsm.ratioN / itsm.ratioD)) * (COALESCE(ium18.ratioD, itum.ratioD) / COALESCE(ium18.ratioN, itum.ratioN))) as unavailableQty, COALESCE(( - SELECT SUM(pol.qty) + SELECT ceil(SUM(pol.qty * (itum.ratioN / itum.ratioD) * (COALESCE(ium18.ratioD, itum.ratioD) / COALESCE(ium18.ratioN, itum.ratioN)))) FROM purchase_order_line pol JOIN purchase_order po ON pol.purchaseOrderId = po.id WHERE pol.itemId = itm.id @@ -1849,7 +1867,7 @@ open class ProductionScheduleService( AND po.completeDate IS NULL ), 0) AS purchasedQty, DATE(ps.produceAt) AS produceDate, - ceil( SUM(bm.baseQty * psl.batchNeed) * (itum.ratioD / itum.ratioN) ) AS qtyNeeded + ceil( SUM(bm.baseQty * psl.batchNeed) * (COALESCE(ium18.ratioD, itum.ratioD) / COALESCE(ium18.ratioN, itum.ratioN)) ) AS qtyNeeded FROM production_schedule_line psl JOIN production_schedule ps ON psl.prodScheduleId = ps.id JOIN items it ON psl.itemId = it.id @@ -1857,7 +1875,10 @@ open class ProductionScheduleService( JOIN bom_material bm ON bom.id = bm.bomId JOIN items itm ON bm.itemId = itm.id JOIN item_uom itum ON itm.id = itum.itemId and itum.purchaseUnit = 1 - join uom_conversion uomP on itum.uomId = uomP.id + JOIN uom_conversion uomP ON itum.uomId = uomP.id + LEFT JOIN latest_supplier_uom lsu ON lsu.materialId = itm.id + LEFT JOIN item_uom ium18 ON ium18.itemId = itm.id AND ium18.uomId = lsu.uomIdM18 AND ium18.deleted = 0 + LEFT JOIN uom_conversion uomM18 ON uomM18.id = ium18.uomId JOIN item_uom itsm ON itm.id = itsm.itemId and itsm.stockUnit = 1 LEFT JOIN inventory iv ON itm.id = iv.itemId WHERE DATE(ps.produceAt) >= DATE_ADD(:fromDate, INTERVAL 1 DAY) diff --git a/src/main/java/com/ffii/fpsms/modules/purchaseOrder/service/PurchaseOrderService.kt b/src/main/java/com/ffii/fpsms/modules/purchaseOrder/service/PurchaseOrderService.kt index d403378..74f1244 100644 --- a/src/main/java/com/ffii/fpsms/modules/purchaseOrder/service/PurchaseOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/purchaseOrder/service/PurchaseOrderService.kt @@ -135,11 +135,11 @@ open fun getPoSummariesByIds(ids: List): List { " SEPARATOR ','" + " ) as itemName," + " group_concat(" + - " coalesce(uc.udfudesc, \"N/A\") " + + " coalesce(ucm18.udfudesc, uc.udfudesc, \"N/A\") " + " SEPARATOR ','" + " ) as itemUom," + " group_concat(" + - " coalesce(pol.qty, 0) " + + " coalesce(pol.qtyM18, pol.qty, 0) " + " SEPARATOR ','" + " ) as itemQty," + " group_concat(" + @@ -173,6 +173,7 @@ open fun getPoSummariesByIds(ids: List): List { " ) sil2 on sil2.purchaseOrderLineId = pol.id" + " left join item_uom iu on iu.itemId = pol.itemId and iu.purchaseUnit = true" + " left join uom_conversion uc on uc.id = iu.uomId" + + " left join uom_conversion ucm18 on ucm18.id = pol.uomIdM18" + " where po.deleted = false " + " and pol.deleted = false " ) @@ -264,11 +265,22 @@ open fun getPoSummariesByIds(ids: List): List { } ?: mutableListOf(); val purchaseUnit = thisPol.item?.id?.let { itemUomService.findPurchaseUnitByItemId(it) } val stockUnit = thisPol.item?.id?.let { itemUomService.findStockUnitByItemId(it).let { iu -> + val itemId = thisPol.item?.id + val qtyM18 = thisPol.qtyM18 + val uomM18Id = thisPol.uomM18?.id + val stockQtyFromM18 = if (itemId != null && qtyM18 != null && uomM18Id != null) { + itemUomService.convertQtyToStockQtyRoundDown(itemId, uomM18Id, qtyM18) + } else { + BigDecimal.ZERO + } StockUomForPoLine( id = iu?.id, stockUomCode = iu?.uom?.code, stockUomDesc = iu?.uom?.udfudesc, - stockQty = iu?.item?.id?.let { iId -> itemUomService.convertPurchaseQtyToStockQty(iId, (thisPol.qty ?: BigDecimal.ZERO)) } ?: BigDecimal.ZERO, + stockQty = if (stockQtyFromM18 > BigDecimal.ZERO) stockQtyFromM18 else { + // fallback to legacy behavior when M18 fields are missing + iu?.item?.id?.let { iId -> itemUomService.convertPurchaseQtyToStockQty(iId, (thisPol.qty ?: BigDecimal.ZERO)) } ?: BigDecimal.ZERO + }, stockRatioN = iu?.ratioN, stockRatioD = iu?.ratioD, purchaseRatioN = purchaseUnit?.ratioN, @@ -281,13 +293,13 @@ open fun getPoSummariesByIds(ids: List): List { itemId = thisPol.item!!.id!!, itemNo = thisPol.itemNo!!, itemName = thisPol.item!!.name, - qty = thisPol.qty!!, + qty = thisPol.qtyM18 ?: thisPol.qty!!, // processed = inLine.filter{ it.status == StockInLineStatus.COMPLETE.status}.sumOf { it.acceptedQty }, processed = inLine .filter { line -> line.putAwayLines?.any { it.qty?.let { qty -> qty > BigDecimal.ZERO } == true } ?: false } .sumOf { line -> line.putAwayLines?.sumOf { it.qty?.takeIf { qty -> qty > BigDecimal.ZERO } ?: BigDecimal.ZERO } ?: BigDecimal.ZERO}, receivedQty = thisPol.stockInLines.sumOf { it.acceptedQty ?: BigDecimal.ZERO }, - uom = thisPol.uom!!, + uom = thisPol.uomM18 ?: thisPol.uom!!, price = thisPol.price!!, status = thisPol.status!!.toString(), stockInLine = inLine, diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLine.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLine.kt index 19381dd..dfaf039 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLine.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLine.kt @@ -127,4 +127,13 @@ open class StockInLine : BaseEntity() { open var stockTransferRecord: StockTransferRecord? = null fun getReceivedQtyForPol(): BigDecimal? = purchaseOrderLine?.stockInLines?.sumOf { it.acceptedQty ?: BigDecimal.ZERO } + + /** + * Total received qty (in M18 unit) for the same Purchase Order Line. + * Used by QC UI so it shows PO qtyM18 / receivedQty in M18 terms. + */ + fun getReceivedQtyM18ForPol(): BigDecimal? = + purchaseOrderLine?.stockInLines?.sumOf { sil -> + sil.acceptedQtyM18?.let { BigDecimal.valueOf(it.toLong()) } ?: BigDecimal.ZERO + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt index 2aff3bd..b38210b 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt @@ -24,13 +24,13 @@ interface StockInLineInfo { val purchaseOrderId: Long? @get:Value("#{target.jobOrder?.id}") val jobOrderId: Long? - @get:Value("#{target.receivedQtyForPol}") + @get:Value("#{target.receivedQtyM18ForPol}") val receivedQty: BigDecimal? val demandQty: BigDecimal? val acceptedQty: BigDecimal @get:Value("#{target.acceptedQtyM18 != null ? new java.math.BigDecimal(target.acceptedQtyM18) : null}") val purchaseAcceptedQty: BigDecimal? - @get:Value("#{target.purchaseOrderLine?.qty}") + @get:Value("#{target.purchaseOrderLine?.qtyM18}") val qty: BigDecimal? val price: BigDecimal? val priceUnit: BigDecimal? @@ -46,7 +46,7 @@ interface StockInLineInfo { val supplier: String? @get:Value("#{target.item?.itemUoms.^[salesUnit == true && deleted == false]?.uom}") //TODO review val uom: UomConversion? - @get:Value("#{target.purchaseOrderLine?.uom?.udfudesc}") + @get:Value("#{target.purchaseOrderLine?.uomM18?.udfudesc}") val purchaseUomDesc: String? @get:Value("#{target.item?.itemUoms.^[stockUnit == true && deleted == false]?.uom?.udfudesc}") val stockUomDesc: String? diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt index 4419cf2..a179dd6 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt @@ -246,14 +246,24 @@ open class StockInLineService( itemNo = item.code this.stockIn = stockIn // PO-origin: - // 1) store user-input qty in acceptedQtyM18 (purchase unit) - // 2) calculate stock acceptedQty with round-down + // 1) store user-input qty in acceptedQtyM18 (M18 unit) + // 2) calculate stock acceptedQty by converting from M18 unit if (pol != null && item.id != null) { acceptedQtyM18 = request.acceptedQty.toInt() - acceptedQty = itemUomService.convertPurchaseQtyToStockQtyRoundDown( - item.id!!, - request.acceptedQty - ) + val m18UomId = pol.uomM18?.id + acceptedQty = if (m18UomId != null) { + itemUomService.convertQtyToStockQtyRoundDown( + item.id!!, + m18UomId, + request.acceptedQty + ) + } else { + // fallback to legacy: treat request.acceptedQty as purchase unit qty + itemUomService.convertPurchaseQtyToStockQtyRoundDown( + item.id!!, + request.acceptedQty + ) + } } else { // Non-PO flows: keep legacy behavior acceptedQty = request.acceptedQty @@ -264,8 +274,19 @@ open class StockInLineService( this.demandQty = jo?.bom?.outputQty } else if (pol != null && item.id != null) { - pol.qty?.let { polQty -> - this.demandQty = itemUomService.convertPurchaseQtyToStockQty(item.id!!, polQty) + val m18UomId = pol.uomM18?.id + val qtyM18 = pol.qtyM18 + this.demandQty = if (m18UomId != null && qtyM18 != null) { + itemUomService.convertQtyToStockQtyRoundDown( + item.id!!, + m18UomId, + qtyM18 + ) + } else { + // fallback to legacy: treat pol.qty as purchase unit qty + pol.qty?.let { polQty -> + itemUomService.convertPurchaseQtyToStockQty(item.id!!, polQty) + } } } dnNo = request.dnNo @@ -527,15 +548,11 @@ open class StockInLineService( val antValues = byPol.map { (_, silList) -> val sil = silList.first() val pol = sil.purchaseOrderLine!! - // M18 GRN ant expects purchase unit qty; acceptedQty on StockInLine is in stock unit - val totalStockQty = silList.sumOf { it.acceptedQty ?: BigDecimal.ZERO } - val itemId = sil.item?.id ?: pol.item?.id - val totalQtyInPurchaseUnit = if (itemId != null) { - itemUomService.convertStockQtyToPurchaseQty(itemId, totalStockQty) - } else { - logger.warn("[buildGoodsReceiptNoteRequest] No itemId for POL id=${pol.id}, using stock qty as fallback (may be wrong unit for M18)") - totalStockQty + // For PO-origin GRN, M18 ant qty must use the M18 UOM and received M18 qty. + val totalQtyM18 = silList.sumOf { + it.acceptedQtyM18?.let { qty -> BigDecimal.valueOf(qty.toLong()) } ?: BigDecimal.ZERO } + val unitIdM18 = pol.uomM18?.m18Id?.toInt() val unitIdFromDataLog = (pol.m18DataLog?.dataLog?.get("unitId") as? Number)?.toLong()?.toInt() val itemName = (sil.item?.name ?: pol.item?.name).orEmpty() // always non-null for M18 bDesc/bDesc_en GoodsReceiptNoteAntValue( @@ -544,13 +561,13 @@ open class StockInLineService( sourceLot = pol.m18Lot ?: "", proId = (sil.item?.m18Id ?: pol.item?.m18Id ?: 0L).toInt(), locId = 155, - unitId = unitIdFromDataLog ?: (pol.uom?.m18Id ?: 0L).toInt(), - qty = totalQtyInPurchaseUnit.toDouble(), + unitId = unitIdM18 ?: unitIdFromDataLog ?: (pol.uom?.m18Id ?: 0L).toInt(), + qty = totalQtyM18.toDouble(), up = pol.up?.toDouble() ?: 0.0, amt = CommonUtils.getAmt( up = pol.up ?: BigDecimal.ZERO, discount = pol.m18Discount ?: BigDecimal.ZERO, - qty = totalQtyInPurchaseUnit + qty = totalQtyM18 ), beId = beId, flowTypeId = flowTypeId, @@ -735,8 +752,19 @@ open class StockInLineService( if (this.jobOrder != null && this.jobOrder?.bom != null) { // For job orders, demandQty comes from BOM's outputQty this.demandQty = this.jobOrder?.bom?.outputQty ?: this.demandQty - } else if (this.purchaseOrderLine != null && this.item?.id != null && this.purchaseOrderLine?.qty != null) { - this.demandQty = itemUomService.convertPurchaseQtyToStockQty(this.item!!.id!!, this.purchaseOrderLine!!.qty!!) + } else if (this.purchaseOrderLine != null && this.item?.id != null) { + val itemId = this.item!!.id!! + val pol = this.purchaseOrderLine!! + val m18UomId = pol.uomM18?.id + val qtyM18 = pol.qtyM18 + this.demandQty = if (m18UomId != null && qtyM18 != null) { + itemUomService.convertQtyToStockQtyRoundDown(itemId, m18UomId, qtyM18) + } else if (pol.qty != null) { + // fallback to legacy fields when M18 fields are missing + itemUomService.convertPurchaseQtyToStockQty(itemId, pol.qty!!) + } else { + this.demandQty + } } // Don't overwrite demandQty with acceptQty from QC form this.invoiceNo = request.invoiceNo diff --git a/src/main/java/com/ffii/fpsms/py/PyController.kt b/src/main/java/com/ffii/fpsms/py/PyController.kt index 874f170..f020ae6 100644 --- a/src/main/java/com/ffii/fpsms/py/PyController.kt +++ b/src/main/java/com/ffii/fpsms/py/PyController.kt @@ -22,6 +22,9 @@ open class PyController( private val jobOrderRepository: JobOrderRepository, private val stockInLineRepository: StockInLineRepository, ) { + companion object { + private const val PACKAGING_PROCESS_NAME = "包裝" + } /** * List job orders by planStart date. @@ -36,7 +39,11 @@ open class PyController( val date = planStart ?: LocalDate.now() val dayStart = date.atStartOfDay() val dayEndExclusive = date.plusDays(1).atStartOfDay() - val orders = jobOrderRepository.findByDeletedFalseAndPlanStartFromBeforeExclusiveOrderByIdAsc(dayStart, dayEndExclusive) + val orders = jobOrderRepository.findByDeletedFalseAndPlanStartBetweenAndBomProcessNameOrderByIdAsc( + dayStart, + dayEndExclusive, + PACKAGING_PROCESS_NAME, + ) val list = orders.map { jo -> toListItem(jo) } return ResponseEntity.ok(list) }