Sfoglia il codice sorgente

isextra truck X ticket fix

production
CANCERYS\kw093 13 ore fa
parent
commit
b12b9a499f
4 ha cambiato i file con 127 aggiunte e 36 eliminazioni
  1. +7
    -3
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchDopoAssignmentService.kt
  2. +106
    -29
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchMainService.kt
  3. +13
    -3
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/WorkbenchReleaseTypeSupport.kt
  4. +1
    -1
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/DoDetailResponse.kt

+ 7
- 3
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchDopoAssignmentService.kt Vedi File

@@ -95,6 +95,7 @@ open class DoWorkbenchDopoAssignmentService(
} }


/** /**
* FP-MTMS Version Checklist | Functions Ref. No. 37 | v1.0.0 | 2026-07-27
* Same UX as [DoPickOrderAssignmentService.assignByLane] but candidates come from * Same UX as [DoPickOrderAssignmentService.assignByLane] but candidates come from
* [delivery_order_pick_order] (+ unassigned [pick_order]), not [do_pick_order]. * [delivery_order_pick_order] (+ unassigned [pick_order]), not [do_pick_order].
*/ */
@@ -145,7 +146,7 @@ open class DoWorkbenchDopoAssignmentService(
params["loadingSequence"] = request.loadingSequence params["loadingSequence"] = request.loadingSequence
} }
if (isisExtraReleaseType(request.releaseType)) { if (isisExtraReleaseType(request.releaseType)) {
sql.append(WorkbenchReleaseTypeSupport.legacyIsExtraSql())
sql.append(WorkbenchReleaseTypeSupport.etraFamilySql())
} else { } else {
sql.append(WorkbenchReleaseTypeSupport.assignFilterSql(request.releaseType)) sql.append(WorkbenchReleaseTypeSupport.assignFilterSql(request.releaseType))
} }
@@ -206,7 +207,10 @@ open class DoWorkbenchDopoAssignmentService(
) )
} }


/** Legacy lane assign (V1): old FG-style (no atomic conflict guard). */
/**
* FP-MTMS Version Checklist | Functions Ref. No. 37 | v1.0.0 | 2026-07-27
* Legacy lane assign (V1): old FG-style (no atomic conflict guard).
*/
@Transactional @Transactional
open fun assignByLaneForWorkbenchV1(request: AssignByLaneRequest): MessageResponse { open fun assignByLaneForWorkbenchV1(request: AssignByLaneRequest): MessageResponse {
val user = userRepository.findById(request.userId).orElse(null) val user = userRepository.findById(request.userId).orElse(null)
@@ -253,7 +257,7 @@ open class DoWorkbenchDopoAssignmentService(
params["loadingSequence"] = request.loadingSequence params["loadingSequence"] = request.loadingSequence
} }
if (isisExtraReleaseType(request.releaseType)) { if (isisExtraReleaseType(request.releaseType)) {
sql.append(WorkbenchReleaseTypeSupport.legacyIsExtraSql())
sql.append(WorkbenchReleaseTypeSupport.etraFamilySql())
} else { } else {
sql.append(WorkbenchReleaseTypeSupport.assignFilterSql(request.releaseType)) sql.append(WorkbenchReleaseTypeSupport.assignFilterSql(request.releaseType))
} }


+ 106
- 29
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchMainService.kt Vedi File

@@ -887,43 +887,114 @@ return MessageResponse(
} }


/** /**
* Workbench Etra view: all `delivery_order_pick_order` with `releaseType` = isExtra (case-insensitive),
* for one [requiredDeliveryDate], grouped by shop then by truck / time / loading sequence.
* FP-MTMS Version Checklist | Functions Ref. No. 37 | v1.0.0 | 2026-07-27
* Workbench Etra view: `isExtra` / `isExtrabatch` / `isExtrasingle` tickets for one day,
* grouped by shop then truck / time / loading sequence.
* Truck X (`車線-X`, DB `storeId` null) is included and split by supplier preferred floor
* into display `storeId` `2/F`/`4/F` (response only; not written to DB).
*/ */
open fun getWorkbenchEtraLaneSummary(requiredDate: LocalDate?): List<WorkbenchEtraShopLaneGroup> { open fun getWorkbenchEtraLaneSummary(requiredDate: LocalDate?): List<WorkbenchEtraShopLaneGroup> {
val targetDate = requiredDate ?: LocalDate.now() val targetDate = requiredDate ?: LocalDate.now()
val defaultTruck = truckRepository.findById(5577L).orElse(null) val defaultTruck = truckRepository.findById(5577L).orElse(null)
val defaultTruckLaneCode = defaultTruck?.truckLanceCode ?: ""
val defaultTruckLaneCode = (defaultTruck?.truckLanceCode ?: "車線-X").trim()
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"
val etraReleaseFilter = WorkbenchReleaseTypeSupport.etraFamilySql("dop.releaseType")


val sql = """ val sql = """
SELECT SELECT
dop.shopCode AS shopCode,
dop.shopName AS shopName,
dop.storeId AS storeId,
dop.truckDepartureTime AS truckDepartureTime,
dop.truckLanceCode AS truckLanceCode,
dop.loadingSequence AS loadingSequence,
COUNT(DISTINCT dop.id) AS total_cnt,
SUM(CASE WHEN dop.handledBy IS NULL THEN 1 ELSE 0 END) AS unassigned_cnt,
base.shopCode AS shopCode,
base.shopName AS shopName,
base.displayStoreId AS storeId,
base.truckDepartureTime AS truckDepartureTime,
base.truckLanceCode AS truckLanceCode,
base.loadingSequence AS loadingSequence,
COUNT(DISTINCT base.id) AS total_cnt,
SUM(CASE WHEN base.handledBy IS NULL THEN 1 ELSE 0 END) AS unassigned_cnt,
GROUP_CONCAT( GROUP_CONCAT(
DISTINCT NULLIF(TRIM(dop.handlerName), '')
ORDER BY dop.handlerName
DISTINCT NULLIF(TRIM(base.handlerName), '')
ORDER BY base.handlerName
SEPARATOR ', ' SEPARATOR ', '
) AS handler_names ) AS handler_names
FROM fpsmsdb.delivery_order_pick_order dop
WHERE dop.deleted = 0
AND LOWER(COALESCE(dop.releaseType, '')) = 'isExtra'
AND dop.requiredDeliveryDate = :requiredDate
AND dop.ticketStatus IN ('pending', 'released')
AND EXISTS (
SELECT 1 FROM fpsmsdb.pick_order po
WHERE po.deliveryOrderPickOrderId = dop.id AND po.deleted = 0
)
GROUP BY dop.shopCode, dop.shopName, dop.storeId, dop.truckDepartureTime, dop.truckLanceCode, dop.loadingSequence
FROM (
SELECT
dop.id AS id,
dop.shopCode AS shopCode,
dop.shopName AS shopName,
dop.handledBy AS handledBy,
dop.handlerName AS handlerName,
dop.truckDepartureTime AS truckDepartureTime,
dop.truckLanceCode AS truckLanceCode,
dop.loadingSequence AS loadingSequence,
CASE
WHEN (dop.storeId IS NULL OR TRIM(dop.storeId) = '')
AND TRIM(dop.truckLanceCode) = :defaultTruckLane
THEN CASE
WHEN 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) = '4F'
)
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) <> '4F'
)
THEN '4/F'
WHEN 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) = '2F'
)
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) <> '2F'
)
THEN '2/F'
ELSE NULL
END
ELSE dop.storeId
END AS displayStoreId
FROM fpsmsdb.delivery_order_pick_order dop
WHERE dop.deleted = 0
$etraReleaseFilter
AND dop.requiredDeliveryDate = :requiredDate
AND dop.ticketStatus IN ('pending', 'released')
AND EXISTS (
SELECT 1 FROM fpsmsdb.pick_order po
WHERE po.deliveryOrderPickOrderId = dop.id AND po.deleted = 0
)
) base
WHERE base.displayStoreId IS NOT NULL
OR TRIM(COALESCE(base.truckLanceCode, '')) <> :defaultTruckLane
GROUP BY
base.shopCode, base.shopName, base.displayStoreId,
base.truckDepartureTime, base.truckLanceCode, base.loadingSequence
""".trimIndent() """.trimIndent()


val rawRows: List<Map<String, Any?>> = try { val rawRows: List<Map<String, Any?>> = try {
jdbcDao.queryForList(sql, mapOf("requiredDate" to targetDate))
jdbcDao.queryForList(
sql,
mapOf(
"requiredDate" to targetDate,
"defaultTruckLane" to defaultTruckLaneCode,
),
)
} catch (e: Exception) { } catch (e: Exception) {
println("❌ getWorkbenchEtraLaneSummary: ${e.message}") println("❌ getWorkbenchEtraLaneSummary: ${e.message}")
emptyList() emptyList()
@@ -966,8 +1037,11 @@ return MessageResponse(


val aggs = rawRows.mapNotNull { row -> val aggs = rawRows.mapNotNull { row ->
val lance = cellStr(row, "truckLanceCode") ?: return@mapNotNull null val lance = cellStr(row, "truckLanceCode") ?: return@mapNotNull null
if (lance == defaultTruckLaneCode) return@mapNotNull null
val storeIdCol = cellStr(row, "storeId") val storeIdCol = cellStr(row, "storeId")
// Truck X must have derived display floor (2/F or 4/F); drop mixed/unknown.
if (lance == defaultTruckLaneCode && storeIdCol.isNullOrBlank()) {
return@mapNotNull null
}
val ttKey = row.keys.find { it.equals("truckDepartureTime", true) } val ttKey = row.keys.find { it.equals("truckDepartureTime", true) }
val ttVal = ttKey?.let { row[it] } val ttVal = ttKey?.let { row[it] }
val sortTime = when (ttVal) { val sortTime = when (ttVal) {
@@ -1001,13 +1075,14 @@ return MessageResponse(
} }


return byShop.entries return byShop.entries
.map { (key, group) ->
.map { (_, group) ->
val head = group.first() val head = group.first()
val lanes = group val lanes = group
.sortedWith( .sortedWith(
compareBy<EtraAgg> { it.sortTime } compareBy<EtraAgg> { it.sortTime }
.thenBy { it.lance } .thenBy { it.lance }
.thenBy { it.loadingSequence ?: 999 } .thenBy { it.loadingSequence ?: 999 }
.thenBy { it.storeId ?: "" }
) )
.map { .map {
val is4F = it.storeId?.replace("/", "")?.trim()?.equals("4F", ignoreCase = true) == true val is4F = it.storeId?.replace("/", "")?.trim()?.equals("4F", ignoreCase = true) == true
@@ -1052,7 +1127,7 @@ return MessageResponse(
/** /**
* @param requiredDeliveryDate when null, uses [LocalDate.now] (calendar today). * @param requiredDeliveryDate when null, uses [LocalDate.now] (calendar today).
* When set, filters `dop.requiredDeliveryDate = :targetDate` (workbench date picker / select day). * 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 releaseTypeFilter when `isExtra`/`etra`/`etraFamily`, filter Etra family (`isExtra`/`isExtrabatch`/`isExtrasingle`).
* @param floor optional `2F`/`4F`: for Truck X (null storeId) filter by DO supplier preferred floor. * @param floor optional `2F`/`4F`: for Truck X (null storeId) filter by DO supplier preferred floor.
*/ */
open fun findWorkbenchReleasedDeliveryOrderPickOrdersForSelectionToday( open fun findWorkbenchReleasedDeliveryOrderPickOrdersForSelectionToday(
@@ -1697,6 +1772,7 @@ return MessageResponse(
""".trimIndent() """.trimIndent()
} }


/** FP-MTMS Version Checklist | Functions Ref. No. 37 | v1.0.0 | 2026-07-27 */
private fun queryWorkbenchReleasedDopoList( private fun queryWorkbenchReleasedDopoList(
shopName: String?, shopName: String?,
storeId: String?, storeId: String?,
@@ -1756,8 +1832,9 @@ return MessageResponse(
params["shopPat"] = "%${shopName.trim()}%" params["shopPat"] = "%${shopName.trim()}%"
} }
val rtNorm = releaseTypeFilter?.trim()?.lowercase().orEmpty() val rtNorm = releaseTypeFilter?.trim()?.lowercase().orEmpty()
if (rtNorm == "isExtra") {
sqlBuilder.append(" AND LOWER(COALESCE(dop.releaseType, '')) = 'isExtra' ")
if (rtNorm == "isextra" || rtNorm == "etra" || rtNorm == "etrafamily") {
// Etra modal: standalone isExtra + merged isExtrabatch / isExtrasingle
sqlBuilder.append(WorkbenchReleaseTypeSupport.etraFamilySql("dop.releaseType"))
} }
// Optional floor: used for Truck X (null storeId) display split; does not write storeId. // Optional floor: used for Truck X (null storeId) display split; does not write storeId.
val floorClause = truckXPreferredFloorExistsSql(floor) val floorClause = truckXPreferredFloorExistsSql(floor)


+ 13
- 3
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/WorkbenchReleaseTypeSupport.kt Vedi File

@@ -2,7 +2,7 @@ package com.ffii.fpsms.modules.deliveryOrder.service


/** /**
* Workbench [delivery_order_pick_order.releaseType] values and SQL filters. * Workbench [delivery_order_pick_order.releaseType] values and SQL filters.
* Legacy `isExtra` tickets are excluded from merge; only used for legacy Etra views.
* Etra UI uses [etraFamilySql] (standalone `isExtra` + merged `isExtrabatch` / `isExtrasingle`).
*/ */
object WorkbenchReleaseTypeSupport { object WorkbenchReleaseTypeSupport {
const val BATCH = "batch" const val BATCH = "batch"
@@ -15,20 +15,23 @@ object WorkbenchReleaseTypeSupport {


fun singleFamilyTypes(): List<String> = listOf(SINGLE, IS_EXTRA_SINGLE) fun singleFamilyTypes(): List<String> = listOf(SINGLE, IS_EXTRA_SINGLE)


/** FP-MTMS Version Checklist | Functions Ref. No. 37 | v1.0.0 | 2026-07-27 */
fun summaryFilterSql(releaseType: String, column: String = "dop.releaseType"): String = fun summaryFilterSql(releaseType: String, column: String = "dop.releaseType"): String =
when (releaseType.trim().lowercase()) { when (releaseType.trim().lowercase()) {
"batch" -> batchFamilySql(column) "batch" -> batchFamilySql(column)
"single" -> singleFamilySql(column) "single" -> singleFamilySql(column)
"isextra" -> legacyIsExtraSql(column)
// Etra UI / list: standalone + merged extra family
"isextra", "etra", "etrafamily" -> etraFamilySql(column)
else -> "" else -> ""
} }


/** FP-MTMS Version Checklist | Functions Ref. No. 37 | v1.0.0 | 2026-07-27 */
fun assignFilterSql(releaseType: String?, column: String = "dop.releaseType"): String { fun assignFilterSql(releaseType: String?, column: String = "dop.releaseType"): String {
val n = releaseType?.trim()?.lowercase().orEmpty() val n = releaseType?.trim()?.lowercase().orEmpty()
return when (n) { return when (n) {
"batch" -> batchFamilySql(column) "batch" -> batchFamilySql(column)
"single" -> singleFamilySql(column) "single" -> singleFamilySql(column)
"isextra" -> legacyIsExtraSql(column)
"isextra", "etra", "etrafamily" -> etraFamilySql(column)
else -> "" else -> ""
} }
} }
@@ -42,6 +45,13 @@ object WorkbenchReleaseTypeSupport {
fun legacyIsExtraSql(column: String = "dop.releaseType"): String = fun legacyIsExtraSql(column: String = "dop.releaseType"): String =
" AND LOWER(COALESCE($column, '')) = 'isextra' " " AND LOWER(COALESCE($column, '')) = 'isextra' "


/**
* FP-MTMS Version Checklist | Functions Ref. No. 37 | v1.0.0 | 2026-07-27
* Standalone `isExtra` plus merged `isExtrabatch` / `isExtrasingle` (Etra workbench).
*/
fun etraFamilySql(column: String = "dop.releaseType"): String =
" AND LOWER(COALESCE($column, '')) IN ('isextra', 'isextrabatch', 'isextrasingle') "

fun newHeaderReleaseType(isExtraRelease: Boolean, isSingleRelease: Boolean): String = when { fun newHeaderReleaseType(isExtraRelease: Boolean, isSingleRelease: Boolean): String = when {
isExtraRelease && isSingleRelease -> IS_EXTRA_SINGLE isExtraRelease && isSingleRelease -> IS_EXTRA_SINGLE
isExtraRelease -> IS_EXTRA_BATCH isExtraRelease -> IS_EXTRA_BATCH


+ 1
- 1
src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/DoDetailResponse.kt Vedi File

@@ -68,7 +68,7 @@ data class LaneBtn(
val truckDepartureTime: String? = null, val truckDepartureTime: String? = null,
) )


/** All Etra (`releaseType=isExtra`) tickets for a day, grouped by shop then truck (no 2F/4F split in UI). */
/** Etra family tickets (`isExtra` / `isExtrabatch` / `isExtrasingle`) for a day, grouped by shop then truck. */
data class WorkbenchEtraShopLaneGroup( data class WorkbenchEtraShopLaneGroup(
val shopCode: String?, val shopCode: String?,
val shopName: String?, val shopName: String?,


Caricamento…
Annulla
Salva