Преглед изворни кода

jo pick order resuggest reject W402 warehosue

consumable pickOrder new ui like do
consumable if user is 任文華, suggest and reusggest will reject W402 warehosue
production
CANCERYS\kw093 пре 2 дана
родитељ
комит
3814cd4c79
5 измењених фајлова са 155 додато и 53 уклоњено
  1. +40
    -7
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchMainService.kt
  2. +2
    -29
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoWorkbenchMainService.kt
  3. +36
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoWorkbenchPickConstants.kt
  4. +18
    -0
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/ConsumableWorkbenchPickConstants.kt
  5. +59
    -17
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderWorkbenchService.kt

+ 40
- 7
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.PickOrderStatus
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderType import com.ffii.fpsms.modules.pickOrder.enums.PickOrderType
import com.ffii.fpsms.modules.pickOrder.service.PickOrderService 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.pickOrder.service.assembleHierarchicalFgPayload
import com.ffii.fpsms.modules.stock.entity.Inventory import com.ffii.fpsms.modules.stock.entity.Inventory
import com.ffii.fpsms.modules.stock.entity.InventoryLotLine 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.deliveryOrder.web.models.WorkbenchTicketReleaseTableResponse
import com.ffii.fpsms.modules.user.service.UserService import com.ffii.fpsms.modules.user.service.UserService
import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrderRepository 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 com.ffii.fpsms.modules.pickOrder.entity.TruckRepository
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@@ -239,6 +241,29 @@ open class DoWorkbenchMainService(
joPickOrderRepository.save(jpo) 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<String>?,
): List<String>? {
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): * Workbench scan-pick (DO FG):
* 1) Post outbound on scanned inventory lot line first; on failure return a clear message. * 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 pickOrderId = pol.pickOrder?.id
val poType = pol.pickOrder?.type 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) } val ledgerMs = measureTimeMillis { createWorkbenchPickLedger(sol, effectiveDelta) }
registerAfterCommit { registerAfterCommit {
runInNewTransaction { runInNewTransaction {
@@ -2127,6 +2154,12 @@ return MessageResponse(
var postMs = 0L var postMs = 0L
try { try {
if (pickOrderId != null) { if (pickOrderId != null) {
val resuggestExcludeWarehouseCodes = workbenchResuggestExcludeWarehouseCodes(
pickOrderId = pickOrderId,
poType = null,
userId = userId,
requestExcludeWarehouseCodes = effectiveExcludeWarehouseCodes,
)
if (hasExplicitQty) { if (hasExplicitQty) {
rebuildMs = measureTimeMillis { rebuildMs = measureTimeMillis {
if (explicitRemainder > BigDecimal.ZERO) { if (explicitRemainder > BigDecimal.ZERO) {
@@ -2135,13 +2168,13 @@ return MessageResponse(
targetQty = explicitRemainder, targetQty = explicitRemainder,
storeId = requestStoreId, storeId = requestStoreId,
excludeInventoryLotLineId = scannedIllId, excludeInventoryLotLineId = scannedIllId,
excludeWarehouseCodes = effectiveExcludeWarehouseCodes,
excludeWarehouseCodes = resuggestExcludeWarehouseCodes,
) )
} else { } else {
suggestedPickLotWorkbenchService.setNoHoldSuggestionsForPickOrderLineExactQty( suggestedPickLotWorkbenchService.setNoHoldSuggestionsForPickOrderLineExactQty(
pickOrderLineId = polId, pickOrderLineId = polId,
targetQty = BigDecimal.ZERO, targetQty = BigDecimal.ZERO,
excludeWarehouseCodes = effectiveExcludeWarehouseCodes,
excludeWarehouseCodes = resuggestExcludeWarehouseCodes,
) )
} }
} }
@@ -2168,7 +2201,7 @@ return MessageResponse(
suggestedPickLotWorkbenchService.rebuildNoHoldSuggestionsForPickOrderLine( suggestedPickLotWorkbenchService.rebuildNoHoldSuggestionsForPickOrderLine(
pickOrderLineId = polId, pickOrderLineId = polId,
storeId = requestStoreId, storeId = requestStoreId,
excludeWarehouseCodes = effectiveExcludeWarehouseCodes,
excludeWarehouseCodes = resuggestExcludeWarehouseCodes,
) )
} }
ensureMs = measureTimeMillis { ensureMs = measureTimeMillis {


+ 2
- 29
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoWorkbenchMainService.kt Прегледај датотеку

@@ -50,35 +50,8 @@ open class JoWorkbenchMainService(
private val suggestedPickLotWorkbenchService: SuggestedPickLotWorkbenchService, private val suggestedPickLotWorkbenchService: SuggestedPickLotWorkbenchService,
private val stockOutLineWorkbenchService: StockOutLineWorkbenchService, private val stockOutLineWorkbenchService: StockOutLineWorkbenchService,
) { ) {
// Keep aligned with SuggestedPickLotWorkbenchService default excludes for diagnosis logs.
private val workbenchDefaultExcludeWarehouseCodes: Set<String> = 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<String> =
JoWorkbenchPickConstants.DEFAULT_EXCLUDE_WAREHOUSE_CODES


private fun debugPrintSuggestionNullReasons(pickOrderId: Long) { private fun debugPrintSuggestionNullReasons(pickOrderId: Long) {
val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) ?: return val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) ?: return


+ 36
- 0
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<String> = 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",
)
}

+ 18
- 0
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<String>? {
if (userId != HARDCODED_EXCLUDE_USER_ID) return null
return JoWorkbenchPickConstants.DEFAULT_EXCLUDE_WAREHOUSE_CODES.toList()
}
}

+ 59
- 17
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.PickOrderLineRepository
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus 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.pickOrder.web.models.PickOrderLineLotDetailResponse
import com.ffii.fpsms.modules.stock.service.StockOutLineWorkbenchService import com.ffii.fpsms.modules.stock.service.StockOutLineWorkbenchService
import com.ffii.fpsms.modules.stock.service.SuggestedPickLotWorkbenchService import com.ffii.fpsms.modules.stock.service.SuggestedPickLotWorkbenchService
@@ -169,43 +170,76 @@ open class PickOrderWorkbenchService(
sol.id AS stockOutLineId, sol.id AS stockOutLineId,
sol.status AS stockOutLineStatus, sol.status AS stockOutLineStatus,
COALESCE(sol.qty, 0) AS stockOutLineQty, COALESCE(sol.qty, 0) AS stockOutLineQty,
sol.inventoryLotLineId AS inventoryLotLineId,
ill.id AS lotId, ill.id AS lotId,
il.lotNo AS lotNo, il.lotNo AS lotNo,
il.stockInLineId AS stockInLineId, il.stockInLineId AS stockInLineId,
DATE_FORMAT(il.expiryDate, '%Y-%m-%d') AS expiryDate, 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.inQty,0) AS inQty,
COALESCE(ill.outQty,0) AS outQty, COALESCE(ill.outQty,0) AS outQty,
COALESCE(ill.holdQty,0) AS holdQty, 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 FROM fpsmsdb.stock_out_line sol
LEFT JOIN fpsmsdb.inventory_lot_line ill ON ill.id = sol.inventoryLotLineId 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.inventory_lot il ON il.id = ill.inventoryLotId
LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId 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 WHERE sol.pickOrderLineId = :pickOrderLineId
AND sol.deleted = false AND sol.deleted = false
ORDER BY sol.id ORDER BY sol.id
""".trimIndent() """.trimIndent()
val lotRows = jdbcDao.queryForList(sql, mapOf("pickOrderLineId" to polId)) val lotRows = jdbcDao.queryForList(sql, mapOf("pickOrderLineId" to polId))
val zero = BigDecimal.ZERO
val lots = lotRows.map { r -> 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( mapOf(
"id" to r["lotId"], "id" to r["lotId"],
"lotNo" to r["lotNo"], "lotNo" to r["lotNo"],
"expiryDate" to r["expiryDate"], "expiryDate" to r["expiryDate"],
"location" to r["location"], "location" to r["location"],
"stockUnit" to (pol.uom?.udfudesc ?: pol.uom?.code ?: ""), "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"), "processingStatus" to (r["stockOutLineStatus"] ?: "pending"),
"suggestedPickLotId" to r["suggestedPickLotId"],
"stockOutLineId" to r["stockOutLineId"], "stockOutLineId" to r["stockOutLineId"],
"stockOutLineStatus" to r["stockOutLineStatus"], "stockOutLineStatus" to r["stockOutLineStatus"],
"stockOutLineQty" to r["stockOutLineQty"],
"stockOutLineQty" to stockOutLineQty,
"stockInLineId" to r["stockInLineId"], "stockInLineId" to r["stockInLineId"],
) )
} }
@@ -360,6 +394,7 @@ open class PickOrderWorkbenchService(
""".trimIndent() """.trimIndent()
val noLotRows = jdbcDao.queryForList(noLotSql, mapOf("pickOrderLineId" to pickOrderLineId)) val noLotRows = jdbcDao.queryForList(noLotSql, mapOf("pickOrderLineId" to pickOrderLineId))
val noLot = noLotRows.map { r -> val noLot = noLotRows.map { r ->
val stockOutLineQty = toBigDecimal(r["stockOutLineQty"]) ?: zero
PickOrderLineLotDetailResponse( PickOrderLineLotDetailResponse(
lotId = null, lotId = null,
lotNo = null, lotNo = null,
@@ -369,19 +404,19 @@ open class PickOrderWorkbenchService(
stockInLineId = null, stockInLineId = null,
stockUnit = uomDesc, stockUnit = uomDesc,
availableQty = null, availableQty = null,
requiredQty = pol.qty ?: zero,
requiredQty = stockOutLineQty,
inQty = null, inQty = null,
outQty = null, outQty = null,
holdQty = null, holdQty = null,
actualPickQty = toBigDecimal(r["stockOutLineQty"]) ?: zero,
actualPickQty = stockOutLineQty,
suggestedPickLotId = null, suggestedPickLotId = null,
lotStatus = null,
lotStatus = "unavailable",
stockOutLineId = toLong(r["stockOutLineId"]), stockOutLineId = toLong(r["stockOutLineId"]),
stockOutLineStatus = r["stockOutLineStatus"]?.toString(), stockOutLineStatus = r["stockOutLineStatus"]?.toString(),
stockOutLineQty = toBigDecimal(r["stockOutLineQty"]) ?: zero,
stockOutLineQty = stockOutLineQty,
totalPickedByAllPickOrders = null, totalPickedByAllPickOrders = null,
remainingAfterAllPickOrders = null, remainingAfterAllPickOrders = null,
lotAvailability = "available",
lotAvailability = "insufficient_stock",
noLot = true, noLot = true,
) )
} }
@@ -407,10 +442,17 @@ open class PickOrderWorkbenchService(
errorPosition = null errorPosition = null
) )


val excludeWarehouseCodes =
if (pickOrder.type == PickOrderType.Consumable) {
ConsumableWorkbenchPickConstants.resolveExcludeWarehouseCodes(userId)
} else {
null
}

val suggestSummary = suggestedPickLotWorkbenchService.primeNextSingleLotSuggestionsForPickOrder( val suggestSummary = suggestedPickLotWorkbenchService.primeNextSingleLotSuggestionsForPickOrder(
pickOrderId = pickOrderId, pickOrderId = pickOrderId,
storeId = null, storeId = null,
excludeWarehouseCodes = null,
excludeWarehouseCodes = excludeWarehouseCodes,
) )
val stockOutSummary = stockOutLineWorkbenchService.ensureStockOutLinesForPickOrderNoHold( val stockOutSummary = stockOutLineWorkbenchService.ensureStockOutLinesForPickOrderNoHold(
pickOrderId = pickOrderId, pickOrderId = pickOrderId,


Loading…
Откажи
Сачувај