From b12b9a499f63730c0a61e40075e6eda7785f6919 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Mon, 27 Jul 2026 14:26:25 +0800 Subject: [PATCH] isextra truck X ticket fix --- .../DoWorkbenchDopoAssignmentService.kt | 10 +- .../service/DoWorkbenchMainService.kt | 135 ++++++++++++++---- .../service/WorkbenchReleaseTypeSupport.kt | 16 ++- .../web/models/DoDetailResponse.kt | 2 +- 4 files changed, 127 insertions(+), 36 deletions(-) diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchDopoAssignmentService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchDopoAssignmentService.kt index 4fa089f..375422a 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchDopoAssignmentService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchDopoAssignmentService.kt @@ -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 * [delivery_order_pick_order] (+ unassigned [pick_order]), not [do_pick_order]. */ @@ -145,7 +146,7 @@ open class DoWorkbenchDopoAssignmentService( params["loadingSequence"] = request.loadingSequence } if (isisExtraReleaseType(request.releaseType)) { - sql.append(WorkbenchReleaseTypeSupport.legacyIsExtraSql()) + sql.append(WorkbenchReleaseTypeSupport.etraFamilySql()) } else { 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 open fun assignByLaneForWorkbenchV1(request: AssignByLaneRequest): MessageResponse { val user = userRepository.findById(request.userId).orElse(null) @@ -253,7 +257,7 @@ open class DoWorkbenchDopoAssignmentService( params["loadingSequence"] = request.loadingSequence } if (isisExtraReleaseType(request.releaseType)) { - sql.append(WorkbenchReleaseTypeSupport.legacyIsExtraSql()) + sql.append(WorkbenchReleaseTypeSupport.etraFamilySql()) } else { sql.append(WorkbenchReleaseTypeSupport.assignFilterSql(request.releaseType)) } 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 9377054..fd7fdac 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 @@ -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 { val targetDate = requiredDate ?: LocalDate.now() 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 = """ 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( - DISTINCT NULLIF(TRIM(dop.handlerName), '') - ORDER BY dop.handlerName + DISTINCT NULLIF(TRIM(base.handlerName), '') + ORDER BY base.handlerName SEPARATOR ', ' ) 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() val rawRows: List> = try { - jdbcDao.queryForList(sql, mapOf("requiredDate" to targetDate)) + jdbcDao.queryForList( + sql, + mapOf( + "requiredDate" to targetDate, + "defaultTruckLane" to defaultTruckLaneCode, + ), + ) } catch (e: Exception) { println("❌ getWorkbenchEtraLaneSummary: ${e.message}") emptyList() @@ -966,8 +1037,11 @@ return MessageResponse( val aggs = rawRows.mapNotNull { row -> val lance = cellStr(row, "truckLanceCode") ?: return@mapNotNull null - if (lance == defaultTruckLaneCode) return@mapNotNull null 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 ttVal = ttKey?.let { row[it] } val sortTime = when (ttVal) { @@ -1001,13 +1075,14 @@ return MessageResponse( } return byShop.entries - .map { (key, group) -> + .map { (_, group) -> val head = group.first() val lanes = group .sortedWith( compareBy { it.sortTime } .thenBy { it.lance } .thenBy { it.loadingSequence ?: 999 } + .thenBy { it.storeId ?: "" } ) .map { 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). * 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. */ open fun findWorkbenchReleasedDeliveryOrderPickOrdersForSelectionToday( @@ -1697,6 +1772,7 @@ return MessageResponse( """.trimIndent() } + /** FP-MTMS Version Checklist | Functions Ref. No. 37 | v1.0.0 | 2026-07-27 */ private fun queryWorkbenchReleasedDopoList( shopName: String?, storeId: String?, @@ -1756,8 +1832,9 @@ return MessageResponse( params["shopPat"] = "%${shopName.trim()}%" } 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. val floorClause = truckXPreferredFloorExistsSql(floor) diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/WorkbenchReleaseTypeSupport.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/WorkbenchReleaseTypeSupport.kt index ac433ba..dc1a5ed 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/WorkbenchReleaseTypeSupport.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/WorkbenchReleaseTypeSupport.kt @@ -2,7 +2,7 @@ package com.ffii.fpsms.modules.deliveryOrder.service /** * 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 { const val BATCH = "batch" @@ -15,20 +15,23 @@ object WorkbenchReleaseTypeSupport { fun singleFamilyTypes(): List = 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 = when (releaseType.trim().lowercase()) { "batch" -> batchFamilySql(column) "single" -> singleFamilySql(column) - "isextra" -> legacyIsExtraSql(column) + // Etra UI / list: standalone + merged extra family + "isextra", "etra", "etrafamily" -> etraFamilySql(column) else -> "" } + /** FP-MTMS Version Checklist | Functions Ref. No. 37 | v1.0.0 | 2026-07-27 */ fun assignFilterSql(releaseType: String?, column: String = "dop.releaseType"): String { val n = releaseType?.trim()?.lowercase().orEmpty() return when (n) { "batch" -> batchFamilySql(column) "single" -> singleFamilySql(column) - "isextra" -> legacyIsExtraSql(column) + "isextra", "etra", "etrafamily" -> etraFamilySql(column) else -> "" } } @@ -42,6 +45,13 @@ object WorkbenchReleaseTypeSupport { fun legacyIsExtraSql(column: String = "dop.releaseType"): String = " 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 { isExtraRelease && isSingleRelease -> IS_EXTRA_SINGLE isExtraRelease -> IS_EXTRA_BATCH diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/DoDetailResponse.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/DoDetailResponse.kt index 6a82ef9..9c9260c 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/DoDetailResponse.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/DoDetailResponse.kt @@ -68,7 +68,7 @@ data class LaneBtn( 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( val shopCode: String?, val shopName: String?,