소스 검색

Merge remote-tracking branch 'origin/production' into production

fix負數倉
CANCERYS\kw093 2 주 전
부모
커밋
55afdb05d8
2개의 변경된 파일78개의 추가작업 그리고 33개의 파일을 삭제
  1. +77
    -32
      src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt
  2. +1
    -1
      src/main/java/com/ffii/fpsms/modules/productProcess/web/model/SaveProductProcessRequest.kt

+ 77
- 32
src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt 파일 보기

@@ -85,6 +85,10 @@ open class ProductProcessService(
private val stockInLineRepository: StockInLineRepository,
private val qcResultRepository: QcResultRepository,
) {
companion object {
private const val PACKAGING_PROCESS_NAME = "包裝"
private const val DRINK_PLANNED_LOOKBACK_DAYS = 30L
}

open fun findAll(pageable: Pageable): Page<ProductProcess> {
println(" Service: Finding all ProductProcess with page: ${pageable.pageNumber}, size: ${pageable.pageSize}")
@@ -1246,6 +1250,7 @@ open class ProductProcessService(
)
}

/** FP-MTMS Version Checklist | Functions Ref. No. 58 | v1.0.1 | 2026-08-10 */
open fun updateProductProcessLineStatus(productProcessLineId: Long, status: String): MessageResponse {
println(" Service: Updating ProductProcessLine Status: $productProcessLineId")
val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null)
@@ -1254,6 +1259,12 @@ open class ProductProcessService(
// productProcessLine.endTime = LocalDateTime.now()
productProcessLineRepository.save(productProcessLine)
println(" Service: ProductProcessLine Status Updated: ${productProcessLine.status}")

// One packaging Complete/Pass → autoPass remaining packaging lines on the same JO.
if (isManualLineDone(status) && isPackagingLine(productProcessLine)) {
autoPassSiblingPackagingLines(productProcessLineId)
}

syncProductProcessStatusFromLines(productProcessLine.productProcess?.id ?: 0L)

return MessageResponse(
@@ -1308,7 +1319,7 @@ open class ProductProcessService(
val productProcessLines = productProcessLineRepository.findByProductProcess_Id(productProcessId)

println(" Service: ProductProcessLines: $productProcessLines")
if (productProcessLines.all { it.status == "Completed" || it.status == "Pass" }) {
if (productProcessLines.all { isLineDone(it.status) }) {
productProcess.status = ProductProcessStatus.COMPLETED
if (productProcess.endTime == null) {
productProcess.endTime = LocalDateTime.now()
@@ -1500,8 +1511,9 @@ open class ProductProcessService(
}

/**
* FP-MTMS Version Checklist | Functions Ref. No. 41 | v1.0.1 | 2026-08-06
* QC 列表:按 jobOrder 粒度分页,qcReady 由该 jobOrder 下所有 productProcessLine 是否全部 Completed/Pass 决定。
* FP-MTMS Version Checklist | Functions Ref. No. 41 | v1.0.3 | 2026-08-10
* QC 列表:按 jobOrder 粒度分页,qcReady 由该 jobOrder 下所有 productProcessLine 是否全部 Completed/Pass/autoPass 决定。
* (包裝 Complete/Pass 時會自動 autoPass 同 JO 其餘包裝 line,因此不再需要「任一包裝即可」特例。)
*
* 注意:date/itemCode/jobOrderCode/bomIds 用来筛选“候选 jobOrder”,但 qcReady 判断会用该 jobOrder 下全部 productProcessLine。
*/
@@ -1651,10 +1663,6 @@ open class ProductProcessService(
val linesByJobOrder = candidateLines.groupBy { it.productProcess.jobOrder?.id ?: 0L }
val done = { s: String? ->
val x = s?.trim()?.lowercase()
x == "completed" || x == "pass"
}
val qcKeys = candidateAggregates.mapNotNull { agg ->
val jobOrderId = agg.jobOrderId
val stockInLine = stockInLineByJobOrderId[jobOrderId]
@@ -1676,20 +1684,12 @@ open class ProductProcessService(

val lineAggregate = lineStatusByJobOrderId[jobOrderId]
val totalLines = lineAggregate?.totalLines ?: 0L
val done = { s: String? ->
val x = s?.trim()?.lowercase()
x == "completed" || x == "pass"
}
val jobLines = linesByJobOrder[jobOrderId].orEmpty()
val packing = jobLines.filter { (it.name ?: "").trim() == "包裝" }
val nonPacking = jobLines.filter { (it.name ?: "").trim() != "包裝" }

val nonPackingOk = nonPacking.isEmpty() || nonPacking.all { done(it.status) }
val packingOk = packing.isEmpty() || packing.any { done(it.status) }
// After packaging Complete/Pass autoPasses sibling 包裝 lines, qcReady is simply all lines done.
val jobLines = linesByJobOrder[jobOrderId].orEmpty()
val allLinesDone = jobLines.isNotEmpty() && jobLines.all { isLineDone(it.status) }

val ready = includedInList && stockInLine != null && nonPackingOk && packingOk
val ready = includedInList && stockInLine != null && allLinesDone
JobOrderQcKey(
jobOrderId = jobOrderId,
@@ -1877,10 +1877,7 @@ open class ProductProcessService(
val joPickOrdersList = if (pickOrderId != null) joPickOrdersByPickOrderId[pickOrderId].orEmpty() else emptyList()

val productProcessLines = linesByProcessId[productProcess.id ?: 0L].orEmpty()
val finishedCount = productProcessLines.count {
val s = it.status?.trim()?.lowercase()
s == "completed" || s == "pass"
}
val finishedCount = productProcessLines.count { isLineDone(it.status) }

val bomIsDrink = productProcess.bom?.isDrink
val matchStatus = if (joPickOrdersList.isNotEmpty() &&
@@ -1954,7 +1951,7 @@ open class ProductProcessService(
}

/**
* FG QC / 上架 reminders: same eligibility as 完成QC工單 (qcReady): all lines Completed/Pass, stock-in exists and not completed/rejected;
* FG QC / 上架 reminders: same eligibility as 完成QC工單 (qcReady): all lines Completed/Pass/autoPass, stock-in exists and not completed/rejected;
* only job orders that have a product process dated **today or yesterday** (server local date).
*/
open fun findJobOrderFgQcAndPutAwayAlertsForTodayYesterday(): JobOrderFgAlertsResponse {
@@ -2008,7 +2005,7 @@ open class ProductProcessService(

val jobOrderLines = processes.flatMap { p -> linesByProcessId[p.id ?: 0L].orEmpty() }
val allLinesDone = jobOrderLines.isNotEmpty() &&
jobOrderLines.all { it.status == "Completed" || it.status == "Pass" }
jobOrderLines.all { isLineDone(it.status) }
if (!allLinesDone) continue

val maxDate = processes.mapNotNull { it.date }.maxOrNull()
@@ -2110,6 +2107,12 @@ open class ProductProcessService(
status?.trim()?.lowercase()?.replace(" ", "") ?: ""

private fun isLineDone(status: String?): Boolean {
val n = normalizeLineStatus(status)
return n == "completed" || n == "pass" || n == "autopass"
}

/** Manual Complete/Pass only — does not include autoPass (avoids cascade loops). */
private fun isManualLineDone(status: String?): Boolean {
val n = normalizeLineStatus(status)
return n == "completed" || n == "pass"
}
@@ -2119,9 +2122,54 @@ open class ProductProcessService(
return n == "inprogress" || n == "paused"
}

/** FP-MTMS Version Checklist | Functions Ref. No. 58 | v1.0.1 | 2026-08-10 */
private fun isPackagingLine(line: ProductProcessLine): Boolean =
(line.name ?: "").trim() == PACKAGING_PROCESS_NAME

/**
* FP-MTMS Version Checklist | Functions Ref. No. 58 | v1.0.1 | 2026-08-10
* When one 包裝 line is Completed/Pass, set other unfinished 包裝 lines on the same job order to autoPass,
* then sync each affected ProductProcess.status (autoPass counts as done → COMPLETED).
*/
private fun autoPassSiblingPackagingLines(triggerLineId: Long) {
val trigger = productProcessLineRepository.findById(triggerLineId).orElse(null) ?: return
if (!isPackagingLine(trigger)) return

val jobOrderId = trigger.productProcess?.jobOrder?.id ?: return
val processes = productProcessRepository.findByJobOrder_Id(jobOrderId)
.filter { it.deleted != true }
val processIds = processes.mapNotNull { it.id }
if (processIds.isEmpty()) return

val allLines = productProcessLineRepository.findByProductProcess_IdIn(processIds)
val siblings = allLines.filter { line ->
line.id != null &&
line.id != triggerLineId &&
isPackagingLine(line) &&
!isLineDone(line.status)
}
if (siblings.isEmpty()) return

val now = LocalDateTime.now()
val affectedProcessIds = linkedSetOf<Long>()
for (line in siblings) {
if (line.startTime == null) {
line.startTime = now
}
line.endTime = now
line.status = "autoPass"
productProcessLineRepository.save(line)
line.productProcess?.id?.let { affectedProcessIds.add(it) }
}

for (processId in affectedProcessIds) {
syncProductProcessStatusFromLines(processId)
}
}

/**
* Align parent [ProductProcess.status] with all line states.
* - All Completed/Pass -> completed (via [ifAllLinesCompletedOrPassed])
* - All Completed/Pass/autoPass -> completed (via [ifAllLinesCompletedOrPassed])
* - Any line started but not all done -> in_progress
* - Does not override STOPPED or CANCELLED on the parent.
*/
@@ -2201,8 +2249,8 @@ open class ProductProcessService(
// 获取所有 product process lines
val allproductProcessLines = productProcessLineRepository.findByProductProcess_Id(productProcessId)

// 检查是否所有 lines 都是 "Completed" 或 "Pass"
if (allproductProcessLines.all { it.status == "Completed" || it.status == "Pass" }) {
// 检查是否所有 lines 都是 Completed / Pass / autoPass
if (allproductProcessLines.all { isLineDone(it.status) }) {
// 更新 product process 的 endTime 和状态
updateProductProcessEndTime(productProcessId)
updateProductProcessStatus(productProcessId, ProductProcessStatus.COMPLETED)
@@ -2631,9 +2679,6 @@ open class ProductProcessService(
}

// ===== Drink Production Qty Dashboard =====
private companion object {
const val DRINK_PLANNED_LOOKBACK_DAYS = 30L
}

/** FP-MTMS Version Checklist | Functions Ref. No. 27 | v1.0.0 | 2026-07-20 */
open fun getDrinkProductionQty(
@@ -3047,7 +3092,7 @@ open class ProductProcessService(
}

val currentProcesses = operatorLines
.filter { it.endTime == null || (it.status != null && it.status != "Completed" && it.status != "Pass") }
.filter { it.endTime == null || (it.status != null && !isLineDone(it.status)) }
.map { line ->
val productProcess = line.productProcess
val jobOrder = productProcess.jobOrder


+ 1
- 1
src/main/java/com/ffii/fpsms/modules/productProcess/web/model/SaveProductProcessRequest.kt 파일 보기

@@ -218,7 +218,7 @@ data class JobOrderProductProcessPageResponse(
)

/**
* Nav alerts aligned with 完成QC工單 list: all product process lines Completed/Pass, stock-in not completed/rejected;
* Nav alerts aligned with 完成QC工單 list: all product process lines Completed/Pass/autoPass, stock-in not completed/rejected;
* job order has at least one process dated today or yesterday. [qc] = before received; [putAway] = received / partially_completed.
*/
data class JobOrderFgAlertRowResponse(


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