|
|
@@ -85,6 +85,10 @@ open class ProductProcessService( |
|
|
private val stockInLineRepository: StockInLineRepository, |
|
|
private val stockInLineRepository: StockInLineRepository, |
|
|
private val qcResultRepository: QcResultRepository, |
|
|
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> { |
|
|
open fun findAll(pageable: Pageable): Page<ProductProcess> { |
|
|
println(" Service: Finding all ProductProcess with page: ${pageable.pageNumber}, size: ${pageable.pageSize}") |
|
|
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.0 | 2026-08-09 */ |
|
|
open fun updateProductProcessLineStatus(productProcessLineId: Long, status: String): MessageResponse { |
|
|
open fun updateProductProcessLineStatus(productProcessLineId: Long, status: String): MessageResponse { |
|
|
println(" Service: Updating ProductProcessLine Status: $productProcessLineId") |
|
|
println(" Service: Updating ProductProcessLine Status: $productProcessLineId") |
|
|
val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null) |
|
|
val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null) |
|
|
@@ -1254,6 +1259,12 @@ open class ProductProcessService( |
|
|
// productProcessLine.endTime = LocalDateTime.now() |
|
|
// productProcessLine.endTime = LocalDateTime.now() |
|
|
productProcessLineRepository.save(productProcessLine) |
|
|
productProcessLineRepository.save(productProcessLine) |
|
|
println(" Service: ProductProcessLine Status Updated: ${productProcessLine.status}") |
|
|
println(" Service: ProductProcessLine Status Updated: ${productProcessLine.status}") |
|
|
|
|
|
|
|
|
|
|
|
// One packaging Complete/Pass → auto-Pass remaining packaging lines on the same JO. |
|
|
|
|
|
if (isLineDone(status) && isPackagingLine(productProcessLine)) { |
|
|
|
|
|
autoPassSiblingPackagingLines(productProcessLineId) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
syncProductProcessStatusFromLines(productProcessLine.productProcess?.id ?: 0L) |
|
|
syncProductProcessStatusFromLines(productProcessLine.productProcess?.id ?: 0L) |
|
|
|
|
|
|
|
|
return MessageResponse( |
|
|
return MessageResponse( |
|
|
@@ -1500,8 +1511,9 @@ open class ProductProcessService( |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* FP-MTMS Version Checklist | Functions Ref. No. 41 | v1.0.1 | 2026-08-06 |
|
|
|
|
|
|
|
|
* FP-MTMS Version Checklist | Functions Ref. No. 41 | v1.0.2 | 2026-08-09 |
|
|
* QC 列表:按 jobOrder 粒度分页,qcReady 由该 jobOrder 下所有 productProcessLine 是否全部 Completed/Pass 决定。 |
|
|
* QC 列表:按 jobOrder 粒度分页,qcReady 由该 jobOrder 下所有 productProcessLine 是否全部 Completed/Pass 决定。 |
|
|
|
|
|
* (包裝 Complete/Pass 時會自動 Pass 同 JO 其餘包裝 line,因此不再需要「任一包裝即可」特例。) |
|
|
* |
|
|
* |
|
|
* 注意:date/itemCode/jobOrderCode/bomIds 用来筛选“候选 jobOrder”,但 qcReady 判断会用该 jobOrder 下全部 productProcessLine。 |
|
|
* 注意:date/itemCode/jobOrderCode/bomIds 用来筛选“候选 jobOrder”,但 qcReady 判断会用该 jobOrder 下全部 productProcessLine。 |
|
|
*/ |
|
|
*/ |
|
|
@@ -1682,14 +1694,11 @@ open class ProductProcessService( |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// After packaging Complete/Pass auto-passes sibling 包裝 lines, qcReady is simply all lines done. |
|
|
val jobLines = linesByJobOrder[jobOrderId].orEmpty() |
|
|
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) } |
|
|
|
|
|
|
|
|
val allLinesDone = jobLines.isNotEmpty() && jobLines.all { done(it.status) } |
|
|
|
|
|
|
|
|
val ready = includedInList && stockInLine != null && nonPackingOk && packingOk |
|
|
|
|
|
|
|
|
val ready = includedInList && stockInLine != null && allLinesDone |
|
|
|
|
|
|
|
|
JobOrderQcKey( |
|
|
JobOrderQcKey( |
|
|
jobOrderId = jobOrderId, |
|
|
jobOrderId = jobOrderId, |
|
|
@@ -2119,6 +2128,51 @@ open class ProductProcessService( |
|
|
return n == "inprogress" || n == "paused" |
|
|
return n == "inprogress" || n == "paused" |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** FP-MTMS Version Checklist | Functions Ref. No. 58 | v1.0.0 | 2026-08-09 */ |
|
|
|
|
|
private fun isPackagingLine(line: ProductProcessLine): Boolean = |
|
|
|
|
|
(line.name ?: "").trim() == PACKAGING_PROCESS_NAME |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* FP-MTMS Version Checklist | Functions Ref. No. 58 | v1.0.0 | 2026-08-09 |
|
|
|
|
|
* When one 包裝 line is Completed/Pass, auto-Pass other unfinished 包裝 lines on the same job order, |
|
|
|
|
|
* then sync each affected ProductProcess.status (Pass 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 = "Pass" |
|
|
|
|
|
productProcessLineRepository.save(line) |
|
|
|
|
|
line.productProcess?.id?.let { affectedProcessIds.add(it) } |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for (processId in affectedProcessIds) { |
|
|
|
|
|
syncProductProcessStatusFromLines(processId) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Align parent [ProductProcess.status] with all line states. |
|
|
* Align parent [ProductProcess.status] with all line states. |
|
|
* - All Completed/Pass -> completed (via [ifAllLinesCompletedOrPassed]) |
|
|
* - All Completed/Pass -> completed (via [ifAllLinesCompletedOrPassed]) |
|
|
@@ -2631,9 +2685,6 @@ open class ProductProcessService( |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// ===== Drink Production Qty Dashboard ===== |
|
|
// ===== 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 */ |
|
|
/** FP-MTMS Version Checklist | Functions Ref. No. 27 | v1.0.0 | 2026-07-20 */ |
|
|
open fun getDrinkProductionQty( |
|
|
open fun getDrinkProductionQty( |
|
|
|