소스 검색

update

master
CANCERYS\kw093 4 일 전
부모
커밋
f26a10348f
4개의 변경된 파일129개의 추가작업 그리고 16개의 파일을 삭제
  1. +77
    -3
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt
  2. +7
    -2
      src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt
  3. +6
    -1
      src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/CreateJobOrderRequest.kt
  4. +39
    -10
      src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt

+ 77
- 3
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt 파일 보기

@@ -1758,7 +1758,7 @@ private fun normalizeFloor(raw: String): String {
val num = cleaned.replace(Regex("[^0-9]"), "")
return if (num.isNotEmpty()) "${num}F" else cleaned
}
open fun getAllJoPickOrders(isDrink: Boolean?): List<AllJoPickOrderResponse> {
open fun getAllJoPickOrders(isDrink: Boolean?, floor: String?): List<AllJoPickOrderResponse> {
println("=== getAllJoPickOrders ===")
return try {
@@ -1771,6 +1771,8 @@ open fun getAllJoPickOrders(isDrink: Boolean?): List<AllJoPickOrderResponse> {
println("Found ${releasedPickOrders.size} released job order pick orders")
val pickOrderIds = releasedPickOrders.mapNotNull { it.id }

val normalizedFloorFilter = floor?.let { normalizeFloor(it) }?.takeIf { it.isNotBlank() }

// 2. 批量查询每个 pick order 的按楼层统计(若没有则跳过)
val floorCountsByPickOrderId: Map<Long, List<Map<String, Any?>>> = if (pickOrderIds.isNotEmpty()) {
val sql = """
@@ -1778,12 +1780,16 @@ open fun getAllJoPickOrders(isDrink: Boolean?): List<AllJoPickOrderResponse> {
po.id AS pickOrderId,
COALESCE(NULLIF(TRIM(w.store_id), ''), SUBSTRING_INDEX(w.code, '-', 1)) AS floorKey,
COUNT(DISTINCT pol.id) AS totalCount,
COUNT(DISTINCT CASE WHEN pol.status = 'COMPLETED' THEN pol.id END) AS finishedCount
COUNT(DISTINCT CASE
WHEN LOWER(sol.status) = 'completed' THEN pol.id
WHEN pol.status = 'COMPLETED' THEN pol.id
END) AS finishedCount
FROM fpsmsdb.pick_order po
JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id AND pol.deleted = false
LEFT JOIN fpsmsdb.suggested_pick_lot spl ON pol.id = spl.pickOrderLineId AND spl.deleted = false
LEFT JOIN fpsmsdb.inventory_lot_line ill ON spl.suggestedLotLineId = ill.id AND ill.deleted = false
LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.deleted = false
WHERE po.deleted = false
AND po.status = 'RELEASED'
AND po.joId IS NOT NULL
@@ -1795,6 +1801,52 @@ open fun getAllJoPickOrders(isDrink: Boolean?): List<AllJoPickOrderResponse> {
} else {
emptyMap()
}

// 2b. no-lot counts (lines with stock_out_line.inventoryLotLineId IS NULL)
val noLotCountsByPickOrderId: Map<Long, Map<String, Any?>> = if (pickOrderIds.isNotEmpty()) {
val sql = """
SELECT
po.id AS pickOrderId,
COUNT(DISTINCT pol.id) AS totalCount,
COUNT(DISTINCT CASE
WHEN LOWER(sol.status) = 'completed' THEN pol.id
WHEN pol.status = 'COMPLETED' THEN pol.id
END) AS finishedCount
FROM fpsmsdb.pick_order po
JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id AND pol.deleted = false
JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.deleted = false
WHERE po.deleted = false
AND po.status = 'RELEASED'
AND po.joId IS NOT NULL
AND po.id IN (${pickOrderIds.joinToString(",")})
AND sol.inventoryLotLineId IS NULL
GROUP BY po.id
""".trimIndent()
jdbcDao.queryForList(sql, emptyMap<String, Any?>()).associateBy { (it["pickOrderId"] as? Number)?.toLong() ?: 0L }
} else emptyMap()

// 2c. suggested fail (rejected) counts per pick order
val suggestedFailCountByPickOrderId: Map<Long, Int> = if (pickOrderIds.isNotEmpty()) {
val sql = """
SELECT
po.id AS pickOrderId,
COUNT(DISTINCT sol.id) AS rejectedCount
FROM fpsmsdb.pick_order po
JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id AND pol.deleted = false
JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.deleted = false
WHERE po.deleted = false
AND po.status = 'RELEASED'
AND po.joId IS NOT NULL
AND po.id IN (${pickOrderIds.joinToString(",")})
AND LOWER(sol.status) = 'rejected'
GROUP BY po.id
""".trimIndent()
jdbcDao.queryForList(sql, emptyMap<String, Any?>()).associate { row ->
val id = (row["pickOrderId"] as? Number)?.toLong() ?: 0L
val cnt = (row["rejectedCount"] as? Number)?.toInt() ?: 0
id to cnt
}
} else emptyMap()

val jobOrderPickOrders = releasedPickOrders.mapNotNull { pickOrder ->
@@ -1844,6 +1896,26 @@ open fun getAllJoPickOrders(isDrink: Boolean?): List<AllJoPickOrderResponse> {
totalCount = (row["totalCount"] as? Number)?.toInt() ?: 0
)
}.orEmpty()

val noLotRow = noLotCountsByPickOrderId[pickOrder.id]
val noLotPickCount = if (noLotRow != null) {
com.ffii.fpsms.modules.jobOrder.web.model.FloorPickCountDto(
floor = "NO_LOT",
finishedCount = (noLotRow["finishedCount"] as? Number)?.toInt() ?: 0,
totalCount = (noLotRow["totalCount"] as? Number)?.toInt() ?: 0
)
} else null

// Backend filtering by floor: include if there is remaining on that floor OR any no-lot remaining
if (normalizedFloorFilter != null) {
val hasRemainingOnFloor = floorPickCounts.any { c ->
c.floor == normalizedFloorFilter && (c.totalCount - c.finishedCount) > 0
}
val hasNoLotRemaining = (noLotPickCount?.let { it.totalCount - it.finishedCount } ?: 0) > 0
if (!hasRemainingOnFloor && !hasNoLotRemaining) return@mapNotNull null
}

val suggestedFailCount = suggestedFailCountByPickOrderId[pickOrder.id] ?: 0
AllJoPickOrderResponse(
id = pickOrder.id ?: 0L,
pickOrderId = pickOrder.id,
@@ -1861,7 +1933,9 @@ open fun getAllJoPickOrders(isDrink: Boolean?): List<AllJoPickOrderResponse> {
lotNo = lotNo,
jobOrderStatus = jobOrder.status?.value ?: "",
finishedPickOLineCount = finishedLines,
floorPickCounts = floorPickCounts
floorPickCounts = floorPickCounts,
noLotPickCount = noLotPickCount,
suggestedFailCount = suggestedFailCount
)
}



+ 7
- 2
src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt 파일 보기

@@ -243,8 +243,13 @@ fun recordSecondScanIssue(
return joPickOrderService.getCompletedJobOrderPickOrderLotDetailsForCompletedPick(pickOrderId)
}
@GetMapping("/AllJoPickOrder")
fun getAllJoPickOrder(@RequestParam(required = false) isDrink: Boolean?): List<AllJoPickOrderResponse> {
return joPickOrderService.getAllJoPickOrders(isDrink)
fun getAllJoPickOrder(
@RequestParam(required = false) isDrink: Boolean?,
// Single floor, e.g. "2F"/"3F"/"4F". When provided, backend returns job pick orders
// that still have unpicked lines on that floor OR any "no lot" remaining lines.
@RequestParam(required = false) floor: String?
): List<AllJoPickOrderResponse> {
return joPickOrderService.getAllJoPickOrders(isDrink, floor)
}

@GetMapping("/all-lots-hierarchical-by-pick-order/{pickOrderId}")


+ 6
- 1
src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/CreateJobOrderRequest.kt 파일 보기

@@ -55,7 +55,12 @@ data class AllJoPickOrderResponse(
val lotNo: String?,
val jobOrderStatus: String,
val finishedPickOLineCount: Int,
val floorPickCounts: List<FloorPickCountDto> = emptyList()
val floorPickCounts: List<FloorPickCountDto> = emptyList(),
// Lines which currently have no lot assigned (e.g. stockOutLine.inventoryLotLineId is null).
// For floor filtering, "no lot" should be treated as applicable to all floors.
val noLotPickCount: FloorPickCountDto? = null,
// Count of rejected stock-out lines for this pick order (proxy for "suggested fail" items).
val suggestedFailCount: Int = 0
)




+ 39
- 10
src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt 파일 보기

@@ -129,23 +129,47 @@ open class StockInLineService(
*/
fun assignLotNo(): String {
val prefix = "LT"
// 建議統一用同一種日期格式,現在 StockInLineService 是 DEFAULT_FORMATTER (LocalDate.today)
val midfix = LocalDate.now().format(CodeGenerator.DEFAULT_FORMATTER)
val fullPrefix = "$prefix-$midfix"
// 1) 今天在 inventory_lot 裡的最大 lotNo

val latestFromInventory =
inventoryLotRepository.findLatestLotNoByPrefix(fullPrefix)
// 2) 今天在 stock_in_line 裡的最大 lotNo(不分來源:JO、PO、盤點...)

val latestFromStockInLine =
stockInLineRepository.findLatestLotNoByPrefix(fullPrefix)
// 3) 兩邊取最大的一個當成 latestCode

val latestCode = listOfNotNull(latestFromInventory, latestFromStockInLine)
.maxOrNull()
// 4) 丟給你現有的 CodeGenerator 產下一號

return CodeGenerator.generateNo(
prefix = prefix,
midfix = midfix,
latestCode = latestCode,
)
}

/**
* 專門給 JO 用的批號:
* - 日期部分用 job_order.planStart (若為 null 則退回今天)
* - 年份使用兩位數(例如 2026-03-18 -> 260318)
* - 序號仍然與現有規則一致,且跨 inventory_lot / stock_in_line 同步遞增
*/
fun assignLotNoForJo(planStart: LocalDate?): String {
val prefix = "LT"
val date = planStart ?: LocalDate.now()
// 兩位數年份 + MMdd,例如 2026-03-18 -> "260318"
val midfix = date.format(DateTimeFormatter.ofPattern("yyMMdd"))
val fullPrefix = "$prefix-$midfix"

val latestFromInventory =
inventoryLotRepository.findLatestLotNoByPrefix(fullPrefix)

val latestFromStockInLine =
stockInLineRepository.findLatestLotNoByPrefix(fullPrefix)

val latestCode = listOfNotNull(latestFromInventory, latestFromStockInLine)
.maxOrNull()

return CodeGenerator.generateNo(
prefix = prefix,
midfix = midfix,
@@ -244,7 +268,8 @@ open class StockInLineService(
status = StockInLineStatus.PENDING.status
}
if (jo != null) {
stockInLine.lotNo = assignLotNo()
val planStartDate = jo.planStart?.toLocalDate()
stockInLine.lotNo = assignLotNoForJo(planStartDate)
}
val savedSIL = saveAndFlush(stockInLine)
if (pol != null) {
@@ -680,6 +705,10 @@ open class StockInLineService(
} else {
requestQty ?: this.acceptedQty
}
} else if (request.qcAccept == true && this.jobOrder != null) {
// For Job Order QC, allow updating received qty (acceptedQty) based on QC accept quantity.
// This enables stocking-in more than demand/previous received qty for JO flows only.
this.acceptedQty = request.acceptedQty
} else if (request.qcAccept == true && this.status == StockInLineStatus.ESCALATED.status) {
// Case: line was escalated (QC decision 3), handler resolves with decision 1 (accept).
// Use 揀收數量 (acceptQty) for put away instead of keeping original received qty.


불러오는 중...
취소
저장