Bladeren bron

WorkbenchGoodPickExecutionDetail UI 大改

WorkbenchTicketReleaseTable 加 user filter

DO release / FloorLanePanel:Truck X 票 + 搜尋依樓層
production
CANCERYS\kw093 17 uur geleden
bovenliggende
commit
173e6ce580
4 gewijzigde bestanden met toevoegingen van 152 en 4 verwijderingen
  1. +34
    -3
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt
  2. +56
    -1
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchMainService.kt
  3. +56
    -0
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchReleaseService.kt
  4. +6
    -0
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DoWorkbenchController.kt

+ 34
- 3
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt Bestand weergeven

@@ -706,8 +706,11 @@ class DoReleaseCoordinatorService(
val existing = candidates.firstOrNull { c ->
c.truckId == first.truckId &&
c.truckDepartureTime == first.truckDepartureTime &&
c.truckLanceCode == first.truckLanceCode
} ?: candidates.minByOrNull { it.id ?: Long.MAX_VALUE }
c.truckLanceCode == first.truckLanceCode &&
(storeId != null || doPickOrderMatchesTruckXPreferredFloor(c, first.preferredFloor))
} ?: candidates
.filter { storeId != null || doPickOrderMatchesTruckXPreferredFloor(it, first.preferredFloor) }
.minByOrNull { it.id ?: Long.MAX_VALUE }
if (existing != null) {
println(
" DEBUG: Merging batch into existing DoPickOrder id=${existing.id}, ticket=${existing.ticketNo} " +
@@ -754,6 +757,33 @@ class DoReleaseCoordinatorService(

println(" DEBUG: Created ${results.size} DoPickOrderLine records")
}

/**
* Truck X (`storeId` null): only merge when every linked DO supplier preferred floor
* matches [preferredFloor] (4F list wins; else 2F). Empty tickets are allowed.
*/
private fun doPickOrderMatchesTruckXPreferredFloor(
dpo: DoPickOrder,
preferredFloor: String?,
): Boolean {
val target = when (preferredFloor?.trim()?.uppercase(java.util.Locale.ROOT)?.replace("/", "")) {
"4F" -> "4F"
else -> "2F"
}
val id = dpo.id ?: return true
val lines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(id)
if (lines.isEmpty()) return true
val (_, s4f) = doFloorSupplierSettingsService.loadDoFloorSupplierLists()
fun floorOf(supplierCode: String?): String =
if (!supplierCode.isNullOrBlank() && s4f.contains(supplierCode.trim())) "4F" else "2F"
for (line in lines) {
val doId = line.doOrderId ?: continue
val dorder = deliveryOrderRepository.findById(doId).orElse(null) ?: continue
if (floorOf(dorder.supplier?.code) != target) return false
}
return true
}

private fun checkPickOrderHasStockIssues(doPickOrderId: Long): Boolean {
try {
// 1. 获取 do_pick_order 的所有 pick orders 数量
@@ -858,7 +888,8 @@ class DoReleaseCoordinatorService(
.firstOrNull {
it.requiredDeliveryDate == result.estimatedArrivalDate &&
it.truckDepartureTime == result.truckDepartureTime &&
it.truckLanceCode == result.truckLanceCode
it.truckLanceCode == result.truckLanceCode &&
(storeId != null || doPickOrderMatchesTruckXPreferredFloor(it, result.preferredFloor))
}
if (existingDoPickOrder != null) {


+ 56
- 1
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchMainService.kt Bestand weergeven

@@ -134,6 +134,7 @@ open class DoWorkbenchMainService(
private val itemsRepository: ItemsRepository,
private val transactionManager: PlatformTransactionManager,
private val doReplenishmentService: DoReplenishmentService,
private val doFloorSupplierSettingsService: DoFloorSupplierSettingsService,
) {
@PersistenceContext
private lateinit var entityManager: EntityManager
@@ -850,6 +851,9 @@ return MessageResponse(
)
}

// Truck X keeps storeId null; split count by supplier preferred floor (same as DO Search tabs).
val truckXFloorKey = actualStoreId.replace("/", "").trim().uppercase(java.util.Locale.ROOT)
val truckXFloorClause = truckXPreferredFloorExistsSql(truckXFloorKey)
val defaultCountSql = """
SELECT COUNT(DISTINCT dop.id) AS c
FROM fpsmsdb.delivery_order_pick_order dop
@@ -862,6 +866,7 @@ return MessageResponse(
SELECT 1 FROM fpsmsdb.pick_order po
WHERE po.deliveryOrderPickOrderId = dop.id AND po.deleted = 0
)
$truckXFloorClause
$releaseFilterClause
""".trimIndent()

@@ -1033,13 +1038,22 @@ return MessageResponse(
storeId: String?,
truck: String?,
releaseTypeFilter: String? = null,
floor: String? = null,
): List<ReleasedDoPickOrderListItem> =
queryWorkbenchReleasedDopoList(shopName, storeId, truck, beforeToday = true, releaseTypeFilter = releaseTypeFilter)
queryWorkbenchReleasedDopoList(
shopName,
storeId,
truck,
beforeToday = true,
releaseTypeFilter = releaseTypeFilter,
floor = floor,
)

/**
* @param requiredDeliveryDate when null, uses [LocalDate.now] (calendar today).
* When set, filters `dop.requiredDeliveryDate = :targetDate` (workbench date picker / select day).
* @param releaseTypeFilter when `isExtra` (case-insensitive), only `delivery_order_pick_order.releaseType = isExtra` rows.
* @param floor optional `2F`/`4F`: for Truck X (null storeId) filter by DO supplier preferred floor.
*/
open fun findWorkbenchReleasedDeliveryOrderPickOrdersForSelectionToday(
shopName: String?,
@@ -1047,6 +1061,7 @@ return MessageResponse(
truck: String?,
requiredDeliveryDate: LocalDate? = null,
releaseTypeFilter: String? = null,
floor: String? = null,
): List<ReleasedDoPickOrderListItem> =
queryWorkbenchReleasedDopoList(
shopName,
@@ -1055,6 +1070,7 @@ return MessageResponse(
beforeToday = false,
equalsDeliveryDate = requiredDeliveryDate,
releaseTypeFilter = releaseTypeFilter,
floor = floor,
)

/**
@@ -1648,6 +1664,39 @@ return MessageResponse(
)
}

/**
* EXISTS clause: Truck X ticket has at least one DO whose supplier preferred floor matches [floorKey]
* (`2F` / `4F`), and **no** linked DO on the other floor.
* Aligns with [DoFloorSupplierSettingsService.preferredStoreFloorForSupplier]
* (4F list wins; otherwise 2F). Empty string = no extra filter.
*/
private fun truckXPreferredFloorExistsSql(floorKey: String?): String {
val key = floorKey?.trim()?.uppercase(java.util.Locale.ROOT)?.replace("/", "").orEmpty()
if (key != "2F" && key != "4F") return ""
val (_, s4f) = doFloorSupplierSettingsService.loadDoFloorSupplierLists()
val in4 = s4f.joinToString(", ") { "'" + it.replace("'", "''") + "'" }
.ifEmpty { "'__NO_4F_SUPPLIER__'" }
val floorExpr = "CASE WHEN s.code IN ($in4) THEN '4F' ELSE '2F' END"
return """
AND EXISTS (
SELECT 1
FROM fpsmsdb.pick_order po
INNER JOIN fpsmsdb.delivery_order d ON d.id = po.doId AND d.deleted = 0
LEFT JOIN fpsmsdb.shop s ON s.id = d.supplierId AND s.deleted = 0
WHERE po.deliveryOrderPickOrderId = dop.id AND po.deleted = 0
AND ($floorExpr) = '$key'
)
AND NOT EXISTS (
SELECT 1
FROM fpsmsdb.pick_order po2
INNER JOIN fpsmsdb.delivery_order d2 ON d2.id = po2.doId AND d2.deleted = 0
LEFT JOIN fpsmsdb.shop s2 ON s2.id = d2.supplierId AND s2.deleted = 0
WHERE po2.deliveryOrderPickOrderId = dop.id AND po2.deleted = 0
AND (CASE WHEN s2.code IN ($in4) THEN '4F' ELSE '2F' END) <> '$key'
)
""".trimIndent()
}

private fun queryWorkbenchReleasedDopoList(
shopName: String?,
storeId: String?,
@@ -1655,6 +1704,7 @@ return MessageResponse(
beforeToday: Boolean,
equalsDeliveryDate: LocalDate? = null,
releaseTypeFilter: String? = null,
floor: String? = null,
): List<ReleasedDoPickOrderListItem> {
val today = LocalDate.now()
val params = mutableMapOf<String, Any>()
@@ -1709,6 +1759,11 @@ return MessageResponse(
if (rtNorm == "isExtra") {
sqlBuilder.append(" AND LOWER(COALESCE(dop.releaseType, '')) = 'isExtra' ")
}
// Optional floor: used for Truck X (null storeId) display split; does not write storeId.
val floorClause = truckXPreferredFloorExistsSql(floor)
if (floorClause.isNotBlank()) {
sqlBuilder.append(" ").append(floorClause).append(" ")
}
sqlBuilder.append(" ORDER BY dop.requiredDeliveryDate, dop.truckDepartureTime, dop.truckLanceCode, dop.id ")
val rows: List<Map<String, Any?>> = try {
jdbcDao.queryForList(sqlBuilder.toString(), params)


+ 56
- 0
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchReleaseService.kt Bestand weergeven

@@ -94,6 +94,7 @@ open class DoWorkbenchReleaseService(
private val jdbcDao: JdbcDao,
private val suggestedPickLotWorkbenchService: SuggestedPickLotWorkbenchService,
private val stockOutLineWorkbenchService: StockOutLineWorkbenchService,
private val doFloorSupplierSettingsService: DoFloorSupplierSettingsService,
) {
private val poolSize = Runtime.getRuntime().availableProcessors()
private val executor = Executors.newFixedThreadPool(kotlin.math.min(poolSize, 4))
@@ -523,7 +524,9 @@ open class DoWorkbenchReleaseService(
params: MutableMap<String, Any>,
) {
if (storeId.isNullOrBlank()) {
// Truck X: storeId stays null, but do not merge across supplier preferred floors (2F vs 4F).
sql.append(" AND (storeId IS NULL OR TRIM(COALESCE(storeId, '')) = '') ")
appendTruckXPreferredFloorMatchClauses(sql, first.preferredFloor)
} else {
sql.append(" AND storeId = :storeId ")
params["storeId"] = storeId
@@ -552,6 +555,59 @@ open class DoWorkbenchReleaseService(
}
}

/**
* For Truck X headers (`storeId` null): only merge into a ticket whose linked DO suppliers
* all resolve to the same preferred floor as [preferredFloor] (4F list wins; else 2F),
* matching normal lane separation by floor / [resolveStoreId].
*/
private fun appendTruckXPreferredFloorMatchClauses(
sql: StringBuilder,
preferredFloor: String?,
) {
val floor = when (preferredFloor?.trim()?.uppercase(java.util.Locale.ROOT)?.replace("/", "")) {
"4F" -> "4F"
else -> "2F"
}
val (_, s4f) = doFloorSupplierSettingsService.loadDoFloorSupplierLists()
val in4 = s4f.joinToString(", ") { "'" + it.replace("'", "''") + "'" }
.ifEmpty { "'__NO_4F_SUPPLIER__'" }
val floorExpr = "CASE WHEN s.code IN ($in4) THEN '4F' ELSE '2F' END"
// Reject tickets that already have any DO on a different floor.
sql.append(
"""
AND NOT EXISTS (
SELECT 1
FROM fpsmsdb.pick_order po
INNER JOIN fpsmsdb.delivery_order d ON d.id = po.doId AND d.deleted = 0
LEFT JOIN fpsmsdb.shop s ON s.id = d.supplierId AND s.deleted = 0
WHERE po.deliveryOrderPickOrderId = delivery_order_pick_order.id
AND po.deleted = 0
AND ($floorExpr) <> '$floor'
)
""".trimIndent()
)
// Prefer an existing same-floor ticket; allow empty header (no pick_order yet).
sql.append(
"""
AND (
NOT EXISTS (
SELECT 1 FROM fpsmsdb.pick_order po0
WHERE po0.deliveryOrderPickOrderId = delivery_order_pick_order.id AND po0.deleted = 0
)
OR EXISTS (
SELECT 1
FROM fpsmsdb.pick_order po
INNER JOIN fpsmsdb.delivery_order d ON d.id = po.doId AND d.deleted = 0
LEFT JOIN fpsmsdb.shop s ON s.id = d.supplierId AND s.deleted = 0
WHERE po.deliveryOrderPickOrderId = delivery_order_pick_order.id
AND po.deleted = 0
AND ($floorExpr) = '$floor'
)
)
""".trimIndent()
)
}

private fun mapRowToMergeableTicket(row: Map<String, Any?>): MergeableWorkbenchTicket? {
fun cell(name: String): String? {
val key = row.keys.find { it.equals(name, true) } ?: return null


+ 6
- 0
src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DoWorkbenchController.kt Bestand weergeven

@@ -115,12 +115,15 @@ class DoWorkbenchController(
@RequestParam(required = false) storeId: String?,
@RequestParam(required = false) truck: String?,
@RequestParam(required = false) releaseType: String?,
/** Optional `2F`/`4F`: Truck X list split by DO supplier preferred floor (storeId stays null). */
@RequestParam(required = false) floor: String?,
): List<ReleasedDoPickOrderListItem> {
return doWorkbenchMainService.findWorkbenchReleasedDeliveryOrderPickOrdersForSelection(
shopName,
storeId,
truck,
releaseTypeFilter = releaseType,
floor = floor,
)
}

@@ -131,6 +134,8 @@ class DoWorkbenchController(
@RequestParam(required = false) truck: String?,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) requiredDate: LocalDate?,
@RequestParam(required = false) releaseType: String?,
/** Optional `2F`/`4F`: Truck X list split by DO supplier preferred floor (storeId stays null). */
@RequestParam(required = false) floor: String?,
): List<ReleasedDoPickOrderListItem> {
return doWorkbenchMainService.findWorkbenchReleasedDeliveryOrderPickOrdersForSelectionToday(
shopName,
@@ -138,6 +143,7 @@ class DoWorkbenchController(
truck,
requiredDeliveryDate = requiredDate,
releaseTypeFilter = releaseType,
floor = floor,
)
}



Laden…
Annuleren
Opslaan