| @@ -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)}") | |||
| @@ -135,6 +135,25 @@ interface JobOrderRepository : AbstractRepository<JobOrder, Long> { | |||
| planStartToExclusive: LocalDateTime, | |||
| ): List<JobOrder> | |||
| @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<JobOrder> | |||
| @Query(""" | |||
| SELECT jo FROM JobOrder jo | |||
| WHERE jo.deleted = false | |||
| @@ -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<Map<String, Any>> { | |||
| 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(), | |||
| @@ -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<String>() | |||
| normalizedJobOrders.forEach { jobOrder -> | |||
| packagingJobOrders.forEach { jobOrder -> | |||
| val filename = filenameByCode[jobOrder.itemCode].orEmpty() | |||
| if (filename.isBlank()) return@forEach | |||
| @@ -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<List<Map<String, Any>>> { | |||
| 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) | |||
| } | |||
| @@ -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() | |||
| @@ -628,7 +628,7 @@ open class ProductionScheduleService( | |||
| } | |||
| open fun getNeedQty(): List<NeedQtyRecord> { | |||
| 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) | |||
| @@ -135,11 +135,11 @@ open fun getPoSummariesByIds(ids: List<Long>): List<PurchaseOrderSummary> { | |||
| " 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<Long>): List<PurchaseOrderSummary> { | |||
| " ) 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<Long>): List<PurchaseOrderSummary> { | |||
| } ?: 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<Long>): List<PurchaseOrderSummary> { | |||
| 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, | |||
| @@ -127,4 +127,13 @@ open class StockInLine : BaseEntity<Long>() { | |||
| 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 | |||
| } | |||
| } | |||
| @@ -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? | |||
| @@ -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 | |||
| @@ -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) | |||
| } | |||