Ver a proveniência

update stock take and stock take report and improved checkAndCompletePickOrderByConsoCode

production
CANCERYS\kw093 há 1 semana
ascendente
cometimento
31abe1b05a
6 ficheiros alterados com 139 adições e 14 eliminações
  1. +48
    -8
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt
  2. +38
    -2
      src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt
  3. +8
    -0
      src/main/java/com/ffii/fpsms/modules/report/web/StockTakeVarianceReportController.kt
  4. +17
    -3
      src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt
  5. +26
    -1
      src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeRecordController.kt
  6. +2
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/model/StockTakeRecordReponse.kt

+ 48
- 8
src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt Ver ficheiro

@@ -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


+ 38
- 2
src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt Ver ficheiro

@@ -305,6 +305,8 @@ ORDER BY
fun searchStockTakeVarianceReportV2(
stockTakeRoundId: Long,
itemCode: String?,
storeId: String?,
status: String?,
): List<Map<String, Any>> {
val countSql = """
SELECT COUNT(*) AS c FROM stocktakerecord s
@@ -320,6 +322,34 @@ ORDER BY

val args = mutableMapOf<String, Any>()
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), '%)')


+ 8
- 0
src/main/java/com/ffii/fpsms/modules/report/web/StockTakeVarianceReportController.kt Ver ficheiro

@@ -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<ByteArray> {
val parameters = mutableMapOf<String, Any>()

@@ -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<ByteArray> {
val cap = stockTakeVarianceReportService.getStockTakeRoundCaption(stockTakeRoundId)
val dbData = stockTakeVarianceReportService.searchStockTakeVarianceReportV2(
stockTakeRoundId = stockTakeRoundId,
itemCode = itemCode,
storeId = storeId,
status = status,
)

val excelBytes = createStockTakeVarianceExcel(


+ 17
- 3
src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt Ver ficheiro

@@ -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
)
}



+ 26
- 1
src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeRecordController.kt Ver ficheiro

@@ -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<AllPickedStockTakeListReponse> {
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)


+ 2
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/model/StockTakeRecordReponse.kt Ver ficheiro

@@ -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,


Carregando…
Cancelar
Guardar