| @@ -16,6 +16,8 @@ import java.time.LocalDate | |||
| @Repository | |||
| interface DoPickOrderRecordRepository : JpaRepository<DoPickOrderRecord, Long> { | |||
| fun findByPickOrderId(pickOrderId: Long): List<DoPickOrderRecord> | |||
| fun findByDoOrderIdAndDeletedFalse(doOrderId: Long): List<DoPickOrderRecord> | |||
| fun findByTicketNoStartingWith(ticketPrefix: String): List<DoPickOrderRecord> | |||
| @Query(""" | |||
| @@ -21,6 +21,8 @@ interface DoPickOrderRepository : JpaRepository<DoPickOrder, Long> { | |||
| ): List<DoPickOrder> | |||
| fun findByPickOrderId(pickOrderId: Long): List<DoPickOrder> | |||
| fun findByDoOrderIdAndDeletedFalse(doOrderId: Long): List<DoPickOrder> | |||
| fun findByTicketStatusIn(statuses: List<DoPickOrderStatus>): List<DoPickOrder> | |||
| // 在 DoPickOrderRepository 中添加这个方法 | |||
| fun findByHandledByAndTicketStatusIn(handledBy: Long, status: List<DoPickOrderStatus>): List<DoPickOrder> | |||
| @@ -473,8 +473,66 @@ open class DeliveryOrderService( | |||
| } | |||
| } | |||
| /** | |||
| * Picker display name from [DeliveryOrderPickOrder.handlerName] (workbench source of truth). | |||
| * Linked via [pick_order.doId] or [do_pick_order_line.do_order_id]; falls back to [User.name] | |||
| * from [DeliveryOrderPickOrder.handledBy] when [handlerName] was not persisted. | |||
| */ | |||
| private fun resolveHandlerNameForDeliveryOrder(doId: Long): String? { | |||
| val sql = """ | |||
| SELECT DISTINCT TRIM(handler_name) AS handler_name | |||
| FROM ( | |||
| SELECT COALESCE( | |||
| NULLIF(TRIM(dop.handlerName), ''), | |||
| NULLIF(TRIM(u.name), '') | |||
| ) AS handler_name | |||
| FROM fpsmsdb.delivery_order_pick_order dop | |||
| INNER JOIN fpsmsdb.pick_order po | |||
| ON po.deliveryOrderPickOrderId = dop.id AND po.deleted = 0 | |||
| LEFT JOIN fpsmsdb.user u | |||
| ON u.id = dop.handledBy AND u.deleted = 0 | |||
| WHERE dop.deleted = 0 | |||
| AND po.doId = :doId | |||
| UNION | |||
| SELECT COALESCE( | |||
| NULLIF(TRIM(dop.handlerName), ''), | |||
| NULLIF(TRIM(u.name), '') | |||
| ) AS handler_name | |||
| FROM fpsmsdb.delivery_order_pick_order dop | |||
| INNER JOIN fpsmsdb.pick_order po | |||
| ON po.deliveryOrderPickOrderId = dop.id AND po.deleted = 0 | |||
| INNER JOIN fpsmsdb.do_pick_order_line dpol | |||
| ON dpol.pick_order_id = po.id AND dpol.deleted = 0 | |||
| LEFT JOIN fpsmsdb.user u | |||
| ON u.id = dop.handledBy AND u.deleted = 0 | |||
| WHERE dop.deleted = 0 | |||
| AND dpol.do_order_id = :doId | |||
| ) resolved | |||
| WHERE handler_name IS NOT NULL AND TRIM(handler_name) <> '' | |||
| ORDER BY handler_name | |||
| """.trimIndent() | |||
| val names = jdbcDao.queryForList(sql, mapOf("doId" to doId)) | |||
| .mapNotNull { row -> | |||
| val key = row.keys.find { it.equals("handler_name", true) } ?: return@mapNotNull null | |||
| row[key]?.toString()?.trim()?.takeIf { it.isNotEmpty() } | |||
| } | |||
| .distinct() | |||
| .sorted() | |||
| return names.joinToString(",").ifBlank { null } | |||
| } | |||
| open fun getDetailedDo(id: Long): DoDetailResponse? { | |||
| val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(id) ?: return null | |||
| val handlerName = try { | |||
| resolveHandlerNameForDeliveryOrder(id) | |||
| } catch (ex: Exception) { | |||
| log.warn("Failed to resolve handler name for delivery order {}: {}", id, ex.message) | |||
| null | |||
| } | |||
| val itemIds = deliveryOrder.deliveryOrderLines.mapNotNull { it.item?.id }.distinct() | |||
| val stockQtyByItemId = itemIds.associateWith { itemId -> | |||
| @@ -493,6 +551,7 @@ open class DeliveryOrderService( | |||
| completeDate = deliveryOrder.completeDate, | |||
| status = deliveryOrder.status?.value, | |||
| isExtra = deliveryOrder.isExtra, | |||
| handlerName = handlerName, | |||
| deliveryOrderLines = deliveryOrder.deliveryOrderLines.map { line -> | |||
| val itemId = line.item?.id | |||
| val stockQty = itemId?.let { stockQtyByItemId[it] } ?: BigDecimal.ZERO | |||
| @@ -20,6 +20,8 @@ data class DoDetailResponse( | |||
| val status: String?, | |||
| /** 加單 DO(M18 加單專用同步) */ | |||
| val isExtra: Boolean = false, | |||
| /** 揀貨員名稱(來源:delivery_order_pick_order.handlerName) */ | |||
| val handlerName: String? = null, | |||
| val deliveryOrderLines: List<DoDetailLineResponse> | |||
| ) | |||
| @@ -0,0 +1,88 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset KelvinY:20260611_01_create_do_replenishment | |||
| -- preconditions onFail:MARK_RAN | |||
| -- precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'do_replenishment' | |||
| CREATE TABLE IF NOT EXISTS `do_replenishment` | |||
| ( | |||
| `id` BIGINT NOT NULL AUTO_INCREMENT, | |||
| `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||
| `createdBy` VARCHAR(30) NULL DEFAULT NULL, | |||
| `version` INT NOT NULL DEFAULT '0', | |||
| `modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||
| `modifiedBy` VARCHAR(30) NULL DEFAULT NULL, | |||
| `deleted` TINYINT(1) NOT NULL DEFAULT '0', | |||
| `code` VARCHAR(100) NOT NULL, | |||
| `deliveryDate` DATE NOT NULL, | |||
| `sourceDoId` BIGINT NOT NULL, | |||
| `sourceDoCode` VARCHAR(100) NULL DEFAULT NULL, | |||
| `sourceDoLineId` BIGINT NOT NULL, | |||
| `sourceM18DataLogId` BIGINT NOT NULL, | |||
| `sourceM18Id` BIGINT NOT NULL, | |||
| `itemId` BIGINT NOT NULL, | |||
| `itemNo` VARCHAR(100) NULL DEFAULT NULL, | |||
| `itemName` VARCHAR(255) NULL DEFAULT NULL, | |||
| `replenishQty` DECIMAL(14, 2) NOT NULL, | |||
| `uomId` BIGINT NULL DEFAULT NULL, | |||
| `shopId` BIGINT NULL DEFAULT NULL, | |||
| `shopCode` VARCHAR(50) NULL DEFAULT NULL, | |||
| `shopName` VARCHAR(255) NULL DEFAULT NULL, | |||
| `truckLaneCode` VARCHAR(100) NULL DEFAULT NULL, | |||
| `targetDoId` BIGINT NULL DEFAULT NULL, | |||
| `targetDoCode` VARCHAR(100) NULL DEFAULT NULL, | |||
| `pickOrderLineId` BIGINT NULL DEFAULT NULL, | |||
| `status` VARCHAR(20) NOT NULL DEFAULT 'pending', | |||
| CONSTRAINT pk_do_replenishment PRIMARY KEY (`id`) | |||
| ); | |||
| SET @idx_dr_code := ( | |||
| SELECT COUNT(*) FROM information_schema.statistics | |||
| WHERE table_schema = DATABASE() AND table_name = 'do_replenishment' AND index_name = 'uk_dr_code' | |||
| ); | |||
| SET @sql_dr_code := IF( | |||
| @idx_dr_code = 0, | |||
| 'CREATE UNIQUE INDEX uk_dr_code ON `do_replenishment` (`code`)', | |||
| 'SELECT 1' | |||
| ); | |||
| PREPARE stmt_dr_code FROM @sql_dr_code; | |||
| EXECUTE stmt_dr_code; | |||
| DEALLOCATE PREPARE stmt_dr_code; | |||
| SET @idx_dr_status_date := ( | |||
| SELECT COUNT(*) FROM information_schema.statistics | |||
| WHERE table_schema = DATABASE() AND table_name = 'do_replenishment' AND index_name = 'idx_dr_status_delivery_date' | |||
| ); | |||
| SET @sql_dr_sd := IF( | |||
| @idx_dr_status_date = 0, | |||
| 'CREATE INDEX idx_dr_status_delivery_date ON `do_replenishment` (`status`, `deliveryDate`)', | |||
| 'SELECT 1' | |||
| ); | |||
| PREPARE stmt_dr_sd FROM @sql_dr_sd; | |||
| EXECUTE stmt_dr_sd; | |||
| DEALLOCATE PREPARE stmt_dr_sd; | |||
| SET @idx_dr_lane_shop := ( | |||
| SELECT COUNT(*) FROM information_schema.statistics | |||
| WHERE table_schema = DATABASE() AND table_name = 'do_replenishment' AND index_name = 'idx_dr_lane_shop_status' | |||
| ); | |||
| SET @sql_dr_ls := IF( | |||
| @idx_dr_lane_shop = 0, | |||
| 'CREATE INDEX idx_dr_lane_shop_status ON `do_replenishment` (`truckLaneCode`, `shopId`, `status`)', | |||
| 'SELECT 1' | |||
| ); | |||
| PREPARE stmt_dr_ls FROM @sql_dr_ls; | |||
| EXECUTE stmt_dr_ls; | |||
| DEALLOCATE PREPARE stmt_dr_ls; | |||
| SET @idx_dr_source_line := ( | |||
| SELECT COUNT(*) FROM information_schema.statistics | |||
| WHERE table_schema = DATABASE() AND table_name = 'do_replenishment' AND index_name = 'idx_dr_source_line' | |||
| ); | |||
| SET @sql_dr_sl := IF( | |||
| @idx_dr_source_line = 0, | |||
| 'CREATE INDEX idx_dr_source_line ON `do_replenishment` (`sourceDoId`, `sourceDoLineId`)', | |||
| 'SELECT 1' | |||
| ); | |||
| PREPARE stmt_dr_sl FROM @sql_dr_sl; | |||
| EXECUTE stmt_dr_sl; | |||
| DEALLOCATE PREPARE stmt_dr_sl; | |||