From 31abe1b05a2e30c665f2fda2333c598c55ba8219 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Wed, 29 Apr 2026 22:06:51 +0800 Subject: [PATCH] update stock take and stock take report and improved checkAndCompletePickOrderByConsoCode --- .../pickOrder/service/PickOrderService.kt | 56 ++++++++++++++++--- .../service/StockTakeVarianceReportService.kt | 40 ++++++++++++- .../web/StockTakeVarianceReportController.kt | 8 +++ .../stock/service/StockTakeRecordService.kt | 20 ++++++- .../stock/web/StockTakeRecordController.kt | 27 ++++++++- .../stock/web/model/StockTakeRecordReponse.kt | 2 + 6 files changed, 139 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt index 5ecdecc..f00a937 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt @@ -1624,16 +1624,17 @@ open class PickOrderService( } throw IllegalStateException("Failed to generate unique delivery note code after $maxAttempts attempts") } - + /* @Transactional(rollbackFor = [java.lang.Exception::class]) + open fun checkAndCompletePickOrderByConsoCode(consoCode: String): MessageResponse { try { - println("=== DEBUG: checkAndCompletePickOrderByConsoCode ===") - println("consoCode: $consoCode") + // println("=== DEBUG: checkAndCompletePickOrderByConsoCode ===") + // println("consoCode: $consoCode") val stockOut = stockOutRepository.findFirstByConsoPickOrderCodeOrderByIdDesc(consoCode) if (stockOut == null) { - println("❌ No stock_out found for consoCode: $consoCode") + //println("❌ No stock_out found for consoCode: $consoCode") return MessageResponse( id = null, name = "Stock out not found", @@ -1680,13 +1681,13 @@ open class PickOrderService( !(isComplete || isRejected || isPartiallyComplete) } - println("📊 Stock out lines: ${stockOutLines.size}, Unfinished: ${unfinishedLines.size}") + // println("📊 Stock out lines: ${stockOutLines.size}, Unfinished: ${unfinishedLines.size}") if (unfinishedLines.isEmpty()) { println(" All stock out lines completed, updating pick order statuses...") return completeStockOut(consoCode) } else { - println("⏳ Still have ${unfinishedLines.size} unfinished lines") + //println("⏳ Still have ${unfinishedLines.size} unfinished lines") return MessageResponse( id = stockOut.id, name = stockOut.consoPickOrderCode ?: stockOut.deliveryOrderCode, @@ -1710,8 +1711,47 @@ open class PickOrderService( ) } } - - +*/ +@Transactional(readOnly = true) +open fun countUnfinishedLinesByConsoCode(consoCode: String): Int { + val sql = """ + SELECT COUNT(1) AS unfinished_count + FROM stock_out_line sol + JOIN pick_order_line pol ON pol.id = sol.pickOrderLineId + JOIN pick_order po ON po.id = pol.poId + WHERE po.consoCode = :consoCode + AND sol.deleted = false + AND LOWER(TRIM(COALESCE(sol.status, ''))) NOT IN ( + 'completed', + 'complete', + 'rejected', + 'partially_completed', + 'partially_complete' + ) + """.trimIndent() + val rows = jdbcDao.queryForList(sql, mapOf("consoCode" to consoCode)) + val countAny = rows.firstOrNull()?.get("unfinished_count") + return when (countAny) { + is Number -> countAny.toInt() + is String -> countAny.toIntOrNull() ?: 0 + else -> 0 + } +} +@Transactional(rollbackFor = [Exception::class]) +open fun checkAndCompletePickOrderByConsoCode(consoCode: String): MessageResponse { + val unfinished = countUnfinishedLinesByConsoCode(consoCode) + if (unfinished > 0) { + return MessageResponse( + id = null, + name = consoCode, + code = "NOT_COMPLETED", + type = "pickorder", + message = "Pick order not completed yet, $unfinished lines remaining", + errorPosition = null + ) + } + return completeStockOut(consoCode) +} open fun createGroup(name: String, targetDate: LocalDate, pickOrderId: Long?): PickOrderGroup { val group = PickOrderGroup().apply { this.name = name diff --git a/src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt b/src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt index b3b7085..510019d 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt @@ -305,6 +305,8 @@ ORDER BY fun searchStockTakeVarianceReportV2( stockTakeRoundId: Long, itemCode: String?, + storeId: String?, + status: String?, ): List> { val countSql = """ SELECT COUNT(*) AS c FROM stocktakerecord s @@ -320,6 +322,34 @@ ORDER BY val args = mutableMapOf() args["stockTakeRoundId"] = stockTakeRoundId + + val statusNormalized = status?.trim()?.lowercase().orEmpty() + // status 映射规则: + // - All/null:不过滤 + // - pending:包含 pending/pass/notMatch + // - completed:只看 completed + val statusLatestSql = when (statusNormalized) { + "pending" -> """ + AND str.status IN ('pending', 'pass', 'notMatch') + """.trimIndent() + "completed" -> """ + AND str.status = 'completed' + """.trimIndent() + else -> "" + } + + val storeIdSql = run { + val normalized = storeId?.trim() + if (normalized.isNullOrBlank() || normalized.equals("all", ignoreCase = true)) { + "" + } else { + args["storeId"] = normalized + // DB 里 store_id 可能是 "2/F" 或 "2F";用 REPLACE 去斜線做匹配 + """ + AND REPLACE(COALESCE(wh.store_id, ''), '/', '') = REPLACE(:storeId, '/', '') + """.trimIndent() + } + } val itemCodeSql = buildMultiValueLikeClause( itemCode, "it.code", @@ -345,10 +375,12 @@ latest_str AS ( str.approverStockTakeQty, str.date AS strDate, str.id, - str.approverTime + str.approverTime, + str.status AS stockTakeRecordStatus FROM stocktakerecord str WHERE str.deleted = 0 AND str.stockTakeRoundId = :stockTakeRoundId +$statusLatestSql ), in_agg AS ( SELECT @@ -443,7 +475,8 @@ data AS ( ls.approverStockTakeQty AS stkApproverQty, ls.varianceQty AS stkVarianceQty, ls.strDate AS stockTakeDateRaw, - ls.approverTime AS approvalDateTimeRaw + ls.approverTime AS approvalDateTimeRaw, + ls.stockTakeRecordStatus AS stockTakeRecordStatus FROM latest_str ls INNER JOIN inventory_lot il ON ls.lotId = il.id @@ -471,6 +504,7 @@ data AS ( WHERE 1=1 $itemCodeSql + $storeIdSql ) SELECT @@ -501,12 +535,14 @@ SELECT END AS stockTakeQty, CASE + WHEN COALESCE(stockTakeRecordStatus, '') <> 'completed' THEN '0' WHEN stkVarianceQty IS NULL THEN '0' WHEN COALESCE(stkVarianceQty, 0) < 0 THEN CONCAT('(', FORMAT(-stkVarianceQty, 0), ')') ELSE FORMAT(COALESCE(stkVarianceQty, 0), 0) END AS variance, CASE + WHEN COALESCE(stockTakeRecordStatus, '') <> 'completed' THEN '0%' WHEN stkVarianceQty IS NULL THEN '0%' WHEN COALESCE(stkBookQty, 0) = 0 THEN '0%' WHEN (COALESCE(stkVarianceQty, 0) / stkBookQty) * 100 < 0 THEN CONCAT('(', FORMAT(-(COALESCE(stkVarianceQty, 0) / stkBookQty) * 100, 0), '%)') diff --git a/src/main/java/com/ffii/fpsms/modules/report/web/StockTakeVarianceReportController.kt b/src/main/java/com/ffii/fpsms/modules/report/web/StockTakeVarianceReportController.kt index dac4db1..d78a82a 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/web/StockTakeVarianceReportController.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/web/StockTakeVarianceReportController.kt @@ -143,6 +143,8 @@ class StockTakeVarianceReportController( fun generateStockTakeVarianceReportV2( @RequestParam stockTakeRoundId: Long, @RequestParam(required = false) itemCode: String?, + @RequestParam(required = false, name = "store_id") storeId: String?, + @RequestParam(required = false) status: String?, ): ResponseEntity { val parameters = mutableMapOf() @@ -169,6 +171,8 @@ class StockTakeVarianceReportController( val dbData = stockTakeVarianceReportService.searchStockTakeVarianceReportV2( stockTakeRoundId = stockTakeRoundId, itemCode = itemCode, + storeId = storeId, + status = status, ) val stockTakeDateDisplay = dbData .mapNotNull { it["stockTakeDate"] as? String } @@ -196,11 +200,15 @@ class StockTakeVarianceReportController( fun exportStockTakeVarianceReportV2Excel( @RequestParam stockTakeRoundId: Long, @RequestParam(required = false) itemCode: String?, + @RequestParam(required = false, name = "store_id") storeId: String?, + @RequestParam(required = false) status: String?, ): ResponseEntity { val cap = stockTakeVarianceReportService.getStockTakeRoundCaption(stockTakeRoundId) val dbData = stockTakeVarianceReportService.searchStockTakeVarianceReportV2( stockTakeRoundId = stockTakeRoundId, itemCode = itemCode, + storeId = storeId, + status = status, ) val excelBytes = createStockTakeVarianceExcel( diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt index eca5189..946ba4d 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt @@ -422,6 +422,14 @@ open class StockTakeRecordService( .mapNotNull { it.stockTakeSectionDescription } // 先去掉 null .distinct() // 去重(防止误填多个不同值) .firstOrNull() + val warehouseArea = warehouses + .mapNotNull { it.area } + .distinct() + .firstOrNull() + val storeId = warehouses + .mapNotNull { it.store_id } + .distinct() + .firstOrNull() val roundIdForLatest = latestStockTake?.let { st -> st.stockTakeRoundId ?: st.id } val roundRecordsForSection = if (latestStockTake != null && roundIdForLatest != null) { @@ -483,7 +491,9 @@ open class StockTakeRecordService( endTime = latestStockTake?.actualEnd, ReStockTakeTrueFalse = reStockTakeTrueFalse, planStartDate = latestStockTake?.planStart?.toLocalDate(), - stockTakeSectionDescription = sectionDescription + stockTakeSectionDescription = sectionDescription, + warehouseArea = warehouseArea, + storeId = storeId ) ) @@ -804,7 +814,9 @@ open class StockTakeRecordService( endTime = latestBaseStockTake.actualEnd, ReStockTakeTrueFalse = anyNotMatch, planStartDate = latestBaseStockTake.planStart?.toLocalDate(), - stockTakeSectionDescription = null + stockTakeSectionDescription = null, + warehouseArea = null, + storeId = null ) ) } @@ -839,7 +851,9 @@ open class StockTakeRecordService( endTime = latestBaseStockTake.actualEnd, ReStockTakeTrueFalse = false, planStartDate = latestBaseStockTake.planStart?.toLocalDate(), - stockTakeSectionDescription = null + stockTakeSectionDescription = null, + warehouseArea = null, + storeId = null ) } diff --git a/src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeRecordController.kt b/src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeRecordController.kt index 66cd811..806d3e4 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeRecordController.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeRecordController.kt @@ -29,7 +29,10 @@ class StockTakeRecordController( @RequestParam(required = false, defaultValue = "0") pageNum: Int, @RequestParam(required = false, defaultValue = "6") pageSize: Int, @RequestParam(required = false) sectionDescription: String?, - @RequestParam(required = false) stockTakeSections: String? + @RequestParam(required = false) stockTakeSections: String?, + @RequestParam(required = false) status: String?, + @RequestParam(required = false) area: String?, + @RequestParam(required = false) storeId: String? ): RecordsRes { var all = stockOutRecordService.AllPickedStockTakeList() if (sectionDescription != null && sectionDescription != "All") { @@ -46,6 +49,28 @@ class StockTakeRecordController( } } } + if (!status.isNullOrBlank() && status != "All") { + val normalizedStatus = status.trim().lowercase() + val acceptedStatuses = when (normalizedStatus) { + "stocktaking" -> setOf("stocktaking", "processing", "in_progress") + else -> setOf(normalizedStatus) + } + all = all.filter { item -> + val itemStatus = item.status.trim().lowercase() + itemStatus in acceptedStatuses + } + } + if (!area.isNullOrBlank()) { + val areaKeyword = area.trim() + all = all.filter { it.warehouseArea?.contains(areaKeyword, ignoreCase = true) == true } + } + if (!storeId.isNullOrBlank() && storeId != "All") { + val storeIdKeyword = storeId.trim() + all = all.filter { + it.storeId?.equals(storeIdKeyword, ignoreCase = true) == true || + it.storeId?.contains(storeIdKeyword, ignoreCase = true) == true + } + } val total = all.size val fromIndex = pageNum * pageSize val toIndex = minOf(fromIndex + pageSize, total) diff --git a/src/main/java/com/ffii/fpsms/modules/stock/web/model/StockTakeRecordReponse.kt b/src/main/java/com/ffii/fpsms/modules/stock/web/model/StockTakeRecordReponse.kt index 4c25691..3496f9d 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/web/model/StockTakeRecordReponse.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/web/model/StockTakeRecordReponse.kt @@ -27,6 +27,8 @@ data class AllPickedStockTakeListReponse( @JsonFormat(pattern = "yyyy-MM-dd") val planStartDate: LocalDate?, val stockTakeSectionDescription: String?, + val warehouseArea: String?, + val storeId: String?, ) data class InventoryLotDetailResponse( val id: Long,