Sfoglia il codice sorgente

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 1 giorno fa
parent
commit
3814cd4c79
5 ha cambiato i file con 155 aggiunte e 53 eliminazioni
  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 Vedi File

@@ -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<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):
* 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 {


+ 2
- 29
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoWorkbenchMainService.kt Vedi File

@@ -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<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) {
val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) ?: return


+ 36
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoWorkbenchPickConstants.kt Vedi File

@@ -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 Vedi File

@@ -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 Vedi File

@@ -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,


Caricamento…
Annulla
Salva