| @@ -26,6 +26,8 @@ open class JobOrder : BaseEntity<Long>() { | |||
| @ManyToOne | |||
| @JoinColumn(name = "bomId", nullable = false) | |||
| open var bom: Bom? = null | |||
| @Column(name = "isHidden", nullable = false) | |||
| open var isHidden: Boolean? = null | |||
| @Column(name = "planStart") | |||
| open var planStart: LocalDateTime? = null | |||
| @@ -7,6 +7,7 @@ import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus | |||
| import org.springframework.data.domain.Page | |||
| import org.springframework.data.domain.Pageable | |||
| import org.springframework.data.jpa.repository.Query | |||
| import org.springframework.data.repository.query.Param | |||
| import org.springframework.stereotype.Repository | |||
| import java.time.LocalDateTime | |||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus.* | |||
| @@ -161,13 +162,91 @@ interface JobOrderRepository : AbstractRepository<JobOrder, Long> { | |||
| AND (:bomName IS NULL OR jo.bom.name LIKE CONCAT('%', :bomName, '%')) | |||
| AND (:planStartFrom IS NULL OR jo.planStart >= :planStartFrom) | |||
| AND (:planStartTo IS NULL OR jo.planStart <= :planStartTo) | |||
| AND ( | |||
| (:joSearchStatus = 'cancel' AND jo.isHidden = true) | |||
| OR | |||
| (:joSearchStatus = 'putawayed' | |||
| AND (jo.isHidden = false OR jo.isHidden IS NULL) | |||
| AND EXISTS ( | |||
| SELECT 1 FROM StockInLine sil | |||
| WHERE sil.jobOrder = jo | |||
| AND sil.deleted = false | |||
| AND LOWER(sil.status) = 'completed' | |||
| ) | |||
| ) | |||
| OR | |||
| ((:joSearchStatus = 'all' OR :joSearchStatus = '') | |||
| AND (jo.isHidden = false OR jo.isHidden IS NULL) | |||
| AND NOT EXISTS ( | |||
| SELECT 1 FROM StockInLine sil | |||
| WHERE sil.jobOrder = jo | |||
| AND sil.deleted = false | |||
| AND LOWER(sil.status) = 'completed' | |||
| ) | |||
| ) | |||
| OR | |||
| (:joSearchStatus NOT IN ('all', '', 'cancel', 'putawayed') | |||
| AND (jo.isHidden = false OR jo.isHidden IS NULL) | |||
| AND ( | |||
| ( | |||
| :joSearchStatus = 'processing' | |||
| AND ( | |||
| LOWER(jo.status) = 'processing' | |||
| OR EXISTS ( | |||
| SELECT 1 FROM ProductProcess pp | |||
| WHERE pp.jobOrder = jo | |||
| AND pp.deleted = false | |||
| AND (LOWER(pp.status) = 'in_progress' OR LOWER(pp.status) = 'processing') | |||
| ) | |||
| ) | |||
| ) | |||
| OR | |||
| ( | |||
| :joSearchStatus = 'pending' | |||
| AND LOWER(jo.status) = 'pending' | |||
| AND NOT EXISTS ( | |||
| SELECT 1 FROM ProductProcess pp | |||
| WHERE pp.jobOrder = jo | |||
| AND pp.deleted = false | |||
| AND (LOWER(pp.status) = 'in_progress' OR LOWER(pp.status) = 'processing') | |||
| ) | |||
| ) | |||
| OR | |||
| ( | |||
| :joSearchStatus = 'packaging' | |||
| AND EXISTS ( | |||
| SELECT 1 FROM PickOrder po | |||
| JOIN po.pickOrderLines pol | |||
| JOIN pol.stockOutLines sol | |||
| WHERE po.jobOrder = jo | |||
| AND po.deleted = false | |||
| AND pol.deleted = false | |||
| AND sol.deleted = false | |||
| AND LOWER(sol.status) IN ('completed', 'partially_completed', 'partly_completed') | |||
| ) | |||
| ) | |||
| OR | |||
| ( | |||
| :joSearchStatus NOT IN ('processing', 'pending') | |||
| AND LOWER(jo.status) = :joSearchStatus | |||
| ) | |||
| ) | |||
| AND NOT EXISTS ( | |||
| SELECT 1 FROM StockInLine sil | |||
| WHERE sil.jobOrder = jo | |||
| AND sil.deleted = false | |||
| AND LOWER(sil.status) = 'completed' | |||
| ) | |||
| ) | |||
| ) | |||
| ORDER BY jo.id DESC | |||
| """) | |||
| fun findJobOrderInfoWithDateFilter( | |||
| code: String?, | |||
| bomName: String?, | |||
| planStartFrom: LocalDateTime?, | |||
| planStartTo: LocalDateTime?, | |||
| @Param("code") code: String?, | |||
| @Param("bomName") bomName: String?, | |||
| @Param("planStartFrom") planStartFrom: LocalDateTime?, | |||
| @Param("planStartTo") planStartTo: LocalDateTime?, | |||
| @Param("joSearchStatus") joSearchStatus: String, | |||
| pageable: Pageable | |||
| ): Page<JobOrderInfo> | |||
| @@ -1868,6 +1868,9 @@ open fun getAllJoPickOrders(isDrink: Boolean?, floor: String?): List<AllJoPickOr | |||
| println("❌ Pick order ${pickOrder.id} has no job order") | |||
| return@mapNotNull null | |||
| } | |||
| if (jobOrder.isHidden == true) { | |||
| return@mapNotNull null | |||
| } | |||
| println("Job order found: ${jobOrder.id}, code: ${jobOrder.code}") | |||
| @@ -133,6 +133,7 @@ open class JobOrderService( | |||
| bomName = if (request.itemName.isNullOrBlank()) null else request.itemName, | |||
| planStartFrom = request.planStart, | |||
| planStartTo = request.planStartTo, | |||
| joSearchStatus = request.joSearchStatus?.trim()?.lowercase().orEmpty().ifBlank { "all" }, | |||
| pageable = pageable | |||
| ) | |||
| @@ -150,6 +151,12 @@ open class JobOrderService( | |||
| } else { | |||
| emptyMap() | |||
| } | |||
| val processByJobOrderId = if (jobOrderIds.isNotEmpty()) { | |||
| productProcessRepository.findByJobOrder_IdInAndDeletedIsFalse(jobOrderIds).groupBy { it.jobOrder?.id } | |||
| } else { | |||
| emptyMap() | |||
| } | |||
| // 获取所有涉及的 itemIds,用于批量加载 inventory | |||
| val allItemIds = jobOrdersMap.values | |||
| @@ -192,7 +199,14 @@ open class JobOrderService( | |||
| silHandlerId = info.silHandlerId, | |||
| planStart = info.planStart, | |||
| productionPriority = info.productionPriority, | |||
| status = info.status, | |||
| status = run { | |||
| val processStatuses = processByJobOrderId[info.id] | |||
| .orEmpty() | |||
| .map { it.status.value.lowercase() } | |||
| val hasStartedProcess = processStatuses.any { it == "in_progress" || it == "processing" } | |||
| val joStatus = info.status.trim().lowercase() | |||
| if (hasStartedProcess && joStatus == "pending") "processing" else info.status | |||
| }, | |||
| jobTypeId = info.jobTypeId, | |||
| jobTypeName = info.jobTypeId?.let { jobTypes[it]?.name }, | |||
| lotNo = info.lotNo | |||
| @@ -204,11 +218,7 @@ open class JobOrderService( | |||
| request.jobTypeName.isBlank() || | |||
| info.jobTypeName?.equals(request.jobTypeName, ignoreCase = true) == true || | |||
| info.jobTypeName?.contains(request.jobTypeName, ignoreCase = true) == true | |||
| val notCompletedPutaway = info.stockInLineStatus != "completed" | |||
| jobTypeNameMatch && notCompletedPutaway | |||
| jobTypeNameMatch | |||
| }.sortedByDescending { it.productionPriority } | |||
| // 修复:使用 response.totalElements,这是过滤后的总数 | |||
| @@ -445,6 +455,7 @@ open class JobOrderService( | |||
| this.approver = approver | |||
| this.prodScheduleLine = prodScheduleLine | |||
| this.jobTypeId = jobTypeId | |||
| this.isHidden = false | |||
| } | |||
| val savedJo = jobOrderRepository.saveAndFlush(jo); | |||
| @@ -502,6 +513,21 @@ open class JobOrderService( | |||
| ) | |||
| } | |||
| @Transactional(rollbackFor = [Exception::class]) | |||
| open fun setJobOrderHidden(request: SetJobOrderHiddenRequest): MessageResponse { | |||
| val jo = jobOrderRepository.findById(request.id).getOrNull() ?: throw NoSuchElementException() | |||
| jo.isHidden = request.hidden | |||
| val savedJo = jobOrderRepository.save(jo) | |||
| return MessageResponse( | |||
| id = savedJo.id, | |||
| code = savedJo.code, | |||
| name = savedJo.bom?.name, | |||
| type = null, | |||
| message = "Success", | |||
| errorPosition = null, | |||
| ) | |||
| } | |||
| @Transactional(rollbackFor = [Exception::class]) | |||
| open fun releaseJobOrder(request: JobOrderCommonActionRequest): MessageResponse { | |||
| val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() | |||
| @@ -82,6 +82,11 @@ class JobOrderController( | |||
| return jobOrderService.releaseJobOrder(request) | |||
| } | |||
| @PostMapping("/set-hidden") | |||
| fun setJobOrderHidden(@Valid @RequestBody request: SetJobOrderHiddenRequest): MessageResponse { | |||
| return jobOrderService.setJobOrderHidden(request) | |||
| } | |||
| @PostMapping("/start") | |||
| fun startJobOrder(@Valid @RequestBody request: JobOrderCommonActionRequest): MessageResponse { | |||
| val jo = jobOrderService.startJobOrder(request) | |||
| @@ -7,4 +7,9 @@ data class JobOrderCommonActionRequest( | |||
| data class JobOrderUpdateRequest( | |||
| val id: Long, | |||
| val status: String, | |||
| ) | |||
| data class SetJobOrderHiddenRequest( | |||
| val id: Long, | |||
| val hidden: Boolean, | |||
| ) | |||
| @@ -10,6 +10,7 @@ data class SearchJobOrderInfoRequest( | |||
| val pageSize: Int?, | |||
| val pageNum: Int?, | |||
| val jobTypeName: String?, | |||
| val joSearchStatus: String?, | |||
| ) | |||
| data class MaterialPickStatusItem( | |||
| val id: Long, | |||
| @@ -1522,6 +1522,7 @@ open class ProductProcessService( | |||
| } | |||
| val filteredCandidateProcesses = candidateProcesses.filter { p -> | |||
| if (p.jobOrder?.isHidden == true) return@filter false | |||
| if (isDrink != null) { | |||
| val bomIsDrink = p.bom?.isDrink | |||
| if (bomIsDrink != isDrink) return@filter false | |||
| @@ -0,0 +1,5 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset Enson:alter_job_order_is_hidden | |||
| ALTER TABLE `fpsmsdb`.`job_order` | |||
| ADD COLUMN `isHidden` TINYINT(1) NULL DEFAULT 0 AFTER `deleted`; | |||