From 3814cd4c79b0815de582de3e5d16c3301ad8b9c4 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Mon, 29 Jun 2026 14:11:50 +0800 Subject: [PATCH] =?UTF-8?q?jo=20pick=20order=20resuggest=20reject=20W402?= =?UTF-8?q?=20warehosue=20consumable=20pickOrder=20new=20ui=20like=20do=20?= =?UTF-8?q?consumable=20if=20user=20is=20=E4=BB=BB=E6=96=87=E8=8F=AF,=20su?= =?UTF-8?q?ggest=20and=20reusggest=20will=20reject=20W402=20warehosue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/DoWorkbenchMainService.kt | 47 ++++++++++-- .../service/JoWorkbenchMainService.kt | 31 +------- .../service/JoWorkbenchPickConstants.kt | 36 +++++++++ .../ConsumableWorkbenchPickConstants.kt | 18 +++++ .../service/PickOrderWorkbenchService.kt | 76 ++++++++++++++----- 5 files changed, 155 insertions(+), 53 deletions(-) create mode 100644 src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoWorkbenchPickConstants.kt create mode 100644 src/main/java/com/ffii/fpsms/modules/pickOrder/service/ConsumableWorkbenchPickConstants.kt diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchMainService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchMainService.kt index a593d2d..5629356 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchMainService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchMainService.kt @@ -30,6 +30,7 @@ import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus import com.ffii.fpsms.modules.pickOrder.enums.PickOrderType import com.ffii.fpsms.modules.pickOrder.service.PickOrderService +import com.ffii.fpsms.modules.pickOrder.service.ConsumableWorkbenchPickConstants import com.ffii.fpsms.modules.pickOrder.service.assembleHierarchicalFgPayload import com.ffii.fpsms.modules.stock.entity.Inventory import com.ffii.fpsms.modules.stock.entity.InventoryLotLine @@ -61,6 +62,7 @@ import com.ffii.fpsms.modules.deliveryOrder.web.models.ReleasedDoPickOrderListIt import com.ffii.fpsms.modules.deliveryOrder.web.models.WorkbenchTicketReleaseTableResponse import com.ffii.fpsms.modules.user.service.UserService import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrderRepository +import com.ffii.fpsms.modules.jobOrder.service.JoWorkbenchPickConstants import com.ffii.fpsms.modules.pickOrder.entity.TruckRepository import kotlin.system.measureTimeMillis import org.slf4j.LoggerFactory @@ -239,6 +241,29 @@ open class DoWorkbenchMainService( joPickOrderRepository.save(jpo) } + /** + * Warehouse exclude list for workbench **re-suggest** after scan-pick (shortfall / lot split). + * - JO: same list as assign ([JoWorkbenchPickConstants.DEFAULT_EXCLUDE_WAREHOUSE_CODES]). + * - Consumable: hardcoded user [ConsumableWorkbenchPickConstants.HARDCODED_EXCLUDE_USER_ID] → JO list; else `null`. + * - DO / other: pass through request (`null` → service default excludes). + */ + private fun workbenchResuggestExcludeWarehouseCodes( + pickOrderId: Long?, + poType: PickOrderType?, + userId: Long?, + requestExcludeWarehouseCodes: List?, + ): List? { + val resolvedType = poType + ?: pickOrderId?.let { pickOrderRepository.findById(it).orElse(null)?.type } + if (resolvedType == PickOrderType.JOB_ORDER) { + return JoWorkbenchPickConstants.DEFAULT_EXCLUDE_WAREHOUSE_CODES.toList() + } + if (resolvedType == PickOrderType.Consumable && userId != null) { + return ConsumableWorkbenchPickConstants.resolveExcludeWarehouseCodes(userId) + } + return requestExcludeWarehouseCodes + } + /** * Workbench scan-pick (DO FG): * 1) Post outbound on scanned inventory lot line first; on failure return a clear message. @@ -599,10 +624,12 @@ val saveSolMs = lapMs() val pickOrderId = pol.pickOrder?.id val poType = pol.pickOrder?.type -val effectiveExcludeWarehouseCodes = when (poType) { - PickOrderType.JOB_ORDER -> request.excludeWarehouseCodes ?: emptyList() - else -> request.excludeWarehouseCodes // null → DO 走 default;有傳則整份取代 default -} +val effectiveExcludeWarehouseCodes = workbenchResuggestExcludeWarehouseCodes( + pickOrderId = pickOrderId, + poType = poType, + userId = request.userId, + requestExcludeWarehouseCodes = request.excludeWarehouseCodes, +) val ledgerMs = measureTimeMillis { createWorkbenchPickLedger(sol, effectiveDelta) } registerAfterCommit { runInNewTransaction { @@ -2127,6 +2154,12 @@ return MessageResponse( var postMs = 0L try { if (pickOrderId != null) { + val resuggestExcludeWarehouseCodes = workbenchResuggestExcludeWarehouseCodes( + pickOrderId = pickOrderId, + poType = null, + userId = userId, + requestExcludeWarehouseCodes = effectiveExcludeWarehouseCodes, + ) if (hasExplicitQty) { rebuildMs = measureTimeMillis { if (explicitRemainder > BigDecimal.ZERO) { @@ -2135,13 +2168,13 @@ return MessageResponse( targetQty = explicitRemainder, storeId = requestStoreId, excludeInventoryLotLineId = scannedIllId, - excludeWarehouseCodes = effectiveExcludeWarehouseCodes, + excludeWarehouseCodes = resuggestExcludeWarehouseCodes, ) } else { suggestedPickLotWorkbenchService.setNoHoldSuggestionsForPickOrderLineExactQty( pickOrderLineId = polId, targetQty = BigDecimal.ZERO, - excludeWarehouseCodes = effectiveExcludeWarehouseCodes, + excludeWarehouseCodes = resuggestExcludeWarehouseCodes, ) } } @@ -2168,7 +2201,7 @@ return MessageResponse( suggestedPickLotWorkbenchService.rebuildNoHoldSuggestionsForPickOrderLine( pickOrderLineId = polId, storeId = requestStoreId, - excludeWarehouseCodes = effectiveExcludeWarehouseCodes, + excludeWarehouseCodes = resuggestExcludeWarehouseCodes, ) } ensureMs = measureTimeMillis { diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoWorkbenchMainService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoWorkbenchMainService.kt index f4ef72f..03c66be 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoWorkbenchMainService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoWorkbenchMainService.kt @@ -50,35 +50,8 @@ open class JoWorkbenchMainService( private val suggestedPickLotWorkbenchService: SuggestedPickLotWorkbenchService, private val stockOutLineWorkbenchService: StockOutLineWorkbenchService, ) { - // Keep aligned with SuggestedPickLotWorkbenchService default excludes for diagnosis logs. - private val workbenchDefaultExcludeWarehouseCodes: Set = setOf( - //"2F-W202-01-00", - //"2F-W200-#A-00", - "4F-W402-01-00", -"4F-W402-02-00", -"4F-W402-03-00", -"4F-W402-04-00", -"4F-W402-05-00", -"4F-W402-#A-00", -"4F-W402-#B-00", -"4F-W402-#C-00", -"4F-W402-#D-00", -"4F-W402-#E-00", -"4F-W402-#F-00", -"4F-W402-#G-00", -"4F-W402-#H-00", -"4F-W402-#I-00", -"4F-W402-#J-00", -"4F-W402-#K-00", -"4F-W402-#L-00", -"4F-W402-#M-00", -"4F-W402-#N-00", -"4F-W402-#O-00", -"4F-W402-#P-00", -"4F-W402-#Q-00", -"4F-W402-#R-00", -"4F-W402-#S-00" - ) + private val workbenchDefaultExcludeWarehouseCodes: Set = + JoWorkbenchPickConstants.DEFAULT_EXCLUDE_WAREHOUSE_CODES private fun debugPrintSuggestionNullReasons(pickOrderId: Long) { val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) ?: return diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoWorkbenchPickConstants.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoWorkbenchPickConstants.kt new file mode 100644 index 0000000..76480c9 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoWorkbenchPickConstants.kt @@ -0,0 +1,36 @@ +package com.ffii.fpsms.modules.jobOrder.service + +/** + * JO workbench pick constants. + * + * [DEFAULT_EXCLUDE_WAREHOUSE_CODES] applies on **assign / first prime** ([JoWorkbenchMainService]) + * and on **scan-pick re-suggest** ([com.ffii.fpsms.modules.deliveryOrder.service.DoWorkbenchMainService]). + */ +object JoWorkbenchPickConstants { + val DEFAULT_EXCLUDE_WAREHOUSE_CODES: Set = setOf( + "4F-W402-01-00", + "4F-W402-02-00", + "4F-W402-03-00", + "4F-W402-04-00", + "4F-W402-05-00", + "4F-W402-#A-00", + "4F-W402-#B-00", + "4F-W402-#C-00", + "4F-W402-#D-00", + "4F-W402-#E-00", + "4F-W402-#F-00", + "4F-W402-#G-00", + "4F-W402-#H-00", + "4F-W402-#I-00", + "4F-W402-#J-00", + "4F-W402-#K-00", + "4F-W402-#L-00", + "4F-W402-#M-00", + "4F-W402-#N-00", + "4F-W402-#O-00", + "4F-W402-#P-00", + "4F-W402-#Q-00", + "4F-W402-#R-00", + "4F-W402-#S-00", + ) +} diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/ConsumableWorkbenchPickConstants.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/ConsumableWorkbenchPickConstants.kt new file mode 100644 index 0000000..21c29c1 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/ConsumableWorkbenchPickConstants.kt @@ -0,0 +1,18 @@ +package com.ffii.fpsms.modules.pickOrder.service + +import com.ffii.fpsms.modules.jobOrder.service.JoWorkbenchPickConstants + +/** + * Temporary consumable workbench exclude list until per-user DB config (Scheme A) lands. + * + * - [HARDCODED_EXCLUDE_USER_ID] → same warehouses as JO ([JoWorkbenchPickConstants]). + * - All other users → `null` → DO 2F default excludes in [SuggestedPickLotWorkbenchService]. + */ +object ConsumableWorkbenchPickConstants { + const val HARDCODED_EXCLUDE_USER_ID: Long = 246L + + fun resolveExcludeWarehouseCodes(userId: Long): List? { + if (userId != HARDCODED_EXCLUDE_USER_ID) return null + return JoWorkbenchPickConstants.DEFAULT_EXCLUDE_WAREHOUSE_CODES.toList() + } +} diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderWorkbenchService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderWorkbenchService.kt index 9d9c0e5..e35f250 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderWorkbenchService.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderWorkbenchService.kt @@ -5,6 +5,7 @@ import com.ffii.fpsms.modules.master.web.models.MessageResponse import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus +import com.ffii.fpsms.modules.pickOrder.enums.PickOrderType import com.ffii.fpsms.modules.pickOrder.web.models.PickOrderLineLotDetailResponse import com.ffii.fpsms.modules.stock.service.StockOutLineWorkbenchService import com.ffii.fpsms.modules.stock.service.SuggestedPickLotWorkbenchService @@ -169,43 +170,76 @@ open class PickOrderWorkbenchService( sol.id AS stockOutLineId, sol.status AS stockOutLineStatus, COALESCE(sol.qty, 0) AS stockOutLineQty, + sol.inventoryLotLineId AS inventoryLotLineId, ill.id AS lotId, il.lotNo AS lotNo, il.stockInLineId AS stockInLineId, DATE_FORMAT(il.expiryDate, '%Y-%m-%d') AS expiryDate, - w.name AS location, + w.code AS location, COALESCE(ill.inQty,0) AS inQty, COALESCE(ill.outQty,0) AS outQty, COALESCE(ill.holdQty,0) AS holdQty, - (COALESCE(ill.inQty,0) - COALESCE(ill.outQty,0)) AS availableQty + (COALESCE(ill.inQty,0) - COALESCE(ill.outQty,0)) AS availableQty, + ill.status AS lotStatus, + spl.suggestedPickLotId AS suggestedPickLotId, + COALESCE(spl.splQty, sol.qty, 0) AS suggestedQty FROM fpsmsdb.stock_out_line sol LEFT JOIN fpsmsdb.inventory_lot_line ill ON ill.id = sol.inventoryLotLineId LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId + LEFT JOIN ( + SELECT + s.stockOutLineId AS solId, + MAX(s.id) AS suggestedPickLotId, + SUM(COALESCE(s.qty, 0)) AS splQty + FROM fpsmsdb.suggested_pick_lot s + WHERE s.pickOrderLineId = :pickOrderLineId + AND s.deleted = false + AND s.stockOutLineId IS NOT NULL + GROUP BY s.stockOutLineId + ) spl ON spl.solId = sol.id WHERE sol.pickOrderLineId = :pickOrderLineId AND sol.deleted = false ORDER BY sol.id """.trimIndent() val lotRows = jdbcDao.queryForList(sql, mapOf("pickOrderLineId" to polId)) + val zero = BigDecimal.ZERO val lots = lotRows.map { r -> + val isNoLot = r["inventoryLotLineId"] == null + val inQty = toBigDecimal(r["inQty"]) ?: zero + val outQty = toBigDecimal(r["outQty"]) ?: zero + val availableQty = toBigDecimal(r["availableQty"]) ?: inQty.subtract(outQty) + val status = (r["stockOutLineStatus"]?.toString() ?: "").lowercase() + val lotStatusRaw = (r["lotStatus"]?.toString() ?: "").lowercase() + val suggestedQty = toBigDecimal(r["suggestedQty"]) ?: zero + val stockOutLineQty = toBigDecimal(r["stockOutLineQty"]) ?: zero + val lotAvailability = when { + isNoLot && status != "completed" -> "insufficient_stock" + status == "rejected" -> "rejected" + !isNoLot && lotStatusRaw == "unavailable" -> "status_unavailable" + !isNoLot && availableQty <= zero && status != "completed" -> "insufficient_stock" + else -> "available" + } mapOf( "id" to r["lotId"], "lotNo" to r["lotNo"], "expiryDate" to r["expiryDate"], "location" to r["location"], "stockUnit" to (pol.uom?.udfudesc ?: pol.uom?.code ?: ""), - "availableQty" to r["availableQty"], - "requiredQty" to pol.qty, - "actualPickQty" to r["stockOutLineQty"], - "inQty" to r["inQty"], - "outQty" to r["outQty"], - "holdQty" to r["holdQty"], - "lotStatus" to "available", - "lotAvailability" to "available", + "availableQty" to if (isNoLot) null else availableQty, + "requiredQty" to suggestedQty, + "actualPickQty" to stockOutLineQty, + "inQty" to if (isNoLot) null else inQty, + "outQty" to if (isNoLot) null else outQty, + "holdQty" to if (isNoLot) null else toBigDecimal(r["holdQty"]), + "lotStatus" to if (isNoLot) "unavailable" else r["lotStatus"], + "lotAvailability" to lotAvailability, + "noLot" to isNoLot, "processingStatus" to (r["stockOutLineStatus"] ?: "pending"), + "suggestedPickLotId" to r["suggestedPickLotId"], "stockOutLineId" to r["stockOutLineId"], "stockOutLineStatus" to r["stockOutLineStatus"], - "stockOutLineQty" to r["stockOutLineQty"], + "stockOutLineQty" to stockOutLineQty, "stockInLineId" to r["stockInLineId"], ) } @@ -360,6 +394,7 @@ open class PickOrderWorkbenchService( """.trimIndent() val noLotRows = jdbcDao.queryForList(noLotSql, mapOf("pickOrderLineId" to pickOrderLineId)) val noLot = noLotRows.map { r -> + val stockOutLineQty = toBigDecimal(r["stockOutLineQty"]) ?: zero PickOrderLineLotDetailResponse( lotId = null, lotNo = null, @@ -369,19 +404,19 @@ open class PickOrderWorkbenchService( stockInLineId = null, stockUnit = uomDesc, availableQty = null, - requiredQty = pol.qty ?: zero, + requiredQty = stockOutLineQty, inQty = null, outQty = null, holdQty = null, - actualPickQty = toBigDecimal(r["stockOutLineQty"]) ?: zero, + actualPickQty = stockOutLineQty, suggestedPickLotId = null, - lotStatus = null, + lotStatus = "unavailable", stockOutLineId = toLong(r["stockOutLineId"]), stockOutLineStatus = r["stockOutLineStatus"]?.toString(), - stockOutLineQty = toBigDecimal(r["stockOutLineQty"]) ?: zero, + stockOutLineQty = stockOutLineQty, totalPickedByAllPickOrders = null, remainingAfterAllPickOrders = null, - lotAvailability = "available", + lotAvailability = "insufficient_stock", noLot = true, ) } @@ -407,10 +442,17 @@ open class PickOrderWorkbenchService( errorPosition = null ) + val excludeWarehouseCodes = + if (pickOrder.type == PickOrderType.Consumable) { + ConsumableWorkbenchPickConstants.resolveExcludeWarehouseCodes(userId) + } else { + null + } + val suggestSummary = suggestedPickLotWorkbenchService.primeNextSingleLotSuggestionsForPickOrder( pickOrderId = pickOrderId, storeId = null, - excludeWarehouseCodes = null, + excludeWarehouseCodes = excludeWarehouseCodes, ) val stockOutSummary = stockOutLineWorkbenchService.ensureStockOutLinesForPickOrderNoHold( pickOrderId = pickOrderId,