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