| @@ -26,6 +26,8 @@ open class JobOrder : BaseEntity<Long>() { | |||||
| @ManyToOne | @ManyToOne | ||||
| @JoinColumn(name = "bomId", nullable = false) | @JoinColumn(name = "bomId", nullable = false) | ||||
| open var bom: Bom? = null | open var bom: Bom? = null | ||||
| @Column(name = "isHidden", nullable = false) | |||||
| open var isHidden: Boolean? = null | |||||
| @Column(name = "planStart") | @Column(name = "planStart") | ||||
| open var planStart: LocalDateTime? = null | 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.Page | ||||
| import org.springframework.data.domain.Pageable | import org.springframework.data.domain.Pageable | ||||
| import org.springframework.data.jpa.repository.Query | import org.springframework.data.jpa.repository.Query | ||||
| import org.springframework.data.repository.query.Param | |||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||
| import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus.* | 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 (:bomName IS NULL OR jo.bom.name LIKE CONCAT('%', :bomName, '%')) | ||||
| AND (:planStartFrom IS NULL OR jo.planStart >= :planStartFrom) | AND (:planStartFrom IS NULL OR jo.planStart >= :planStartFrom) | ||||
| AND (:planStartTo IS NULL OR jo.planStart <= :planStartTo) | 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 | ORDER BY jo.id DESC | ||||
| """) | """) | ||||
| fun findJobOrderInfoWithDateFilter( | 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 | pageable: Pageable | ||||
| ): Page<JobOrderInfo> | ): Page<JobOrderInfo> | ||||
| @@ -1868,6 +1868,9 @@ open fun getAllJoPickOrders(isDrink: Boolean?, floor: String?): List<AllJoPickOr | |||||
| println("❌ Pick order ${pickOrder.id} has no job order") | println("❌ Pick order ${pickOrder.id} has no job order") | ||||
| return@mapNotNull null | return@mapNotNull null | ||||
| } | } | ||||
| if (jobOrder.isHidden == true) { | |||||
| return@mapNotNull null | |||||
| } | |||||
| println("Job order found: ${jobOrder.id}, code: ${jobOrder.code}") | 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, | bomName = if (request.itemName.isNullOrBlank()) null else request.itemName, | ||||
| planStartFrom = request.planStart, | planStartFrom = request.planStart, | ||||
| planStartTo = request.planStartTo, | planStartTo = request.planStartTo, | ||||
| joSearchStatus = request.joSearchStatus?.trim()?.lowercase().orEmpty().ifBlank { "all" }, | |||||
| pageable = pageable | pageable = pageable | ||||
| ) | ) | ||||
| @@ -150,6 +151,12 @@ open class JobOrderService( | |||||
| } else { | } else { | ||||
| emptyMap() | emptyMap() | ||||
| } | } | ||||
| val processByJobOrderId = if (jobOrderIds.isNotEmpty()) { | |||||
| productProcessRepository.findByJobOrder_IdInAndDeletedIsFalse(jobOrderIds).groupBy { it.jobOrder?.id } | |||||
| } else { | |||||
| emptyMap() | |||||
| } | |||||
| // 获取所有涉及的 itemIds,用于批量加载 inventory | // 获取所有涉及的 itemIds,用于批量加载 inventory | ||||
| val allItemIds = jobOrdersMap.values | val allItemIds = jobOrdersMap.values | ||||
| @@ -192,7 +199,14 @@ open class JobOrderService( | |||||
| silHandlerId = info.silHandlerId, | silHandlerId = info.silHandlerId, | ||||
| planStart = info.planStart, | planStart = info.planStart, | ||||
| productionPriority = info.productionPriority, | 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, | jobTypeId = info.jobTypeId, | ||||
| jobTypeName = info.jobTypeId?.let { jobTypes[it]?.name }, | jobTypeName = info.jobTypeId?.let { jobTypes[it]?.name }, | ||||
| lotNo = info.lotNo | lotNo = info.lotNo | ||||
| @@ -204,11 +218,7 @@ open class JobOrderService( | |||||
| request.jobTypeName.isBlank() || | request.jobTypeName.isBlank() || | ||||
| info.jobTypeName?.equals(request.jobTypeName, ignoreCase = true) == true || | info.jobTypeName?.equals(request.jobTypeName, ignoreCase = true) == true || | ||||
| info.jobTypeName?.contains(request.jobTypeName, ignoreCase = true) == true | info.jobTypeName?.contains(request.jobTypeName, ignoreCase = true) == true | ||||
| val notCompletedPutaway = info.stockInLineStatus != "completed" | |||||
| jobTypeNameMatch && notCompletedPutaway | |||||
| jobTypeNameMatch | |||||
| }.sortedByDescending { it.productionPriority } | }.sortedByDescending { it.productionPriority } | ||||
| // 修复:使用 response.totalElements,这是过滤后的总数 | // 修复:使用 response.totalElements,这是过滤后的总数 | ||||
| @@ -445,6 +455,7 @@ open class JobOrderService( | |||||
| this.approver = approver | this.approver = approver | ||||
| this.prodScheduleLine = prodScheduleLine | this.prodScheduleLine = prodScheduleLine | ||||
| this.jobTypeId = jobTypeId | this.jobTypeId = jobTypeId | ||||
| this.isHidden = false | |||||
| } | } | ||||
| val savedJo = jobOrderRepository.saveAndFlush(jo); | 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]) | @Transactional(rollbackFor = [Exception::class]) | ||||
| open fun releaseJobOrder(request: JobOrderCommonActionRequest): MessageResponse { | open fun releaseJobOrder(request: JobOrderCommonActionRequest): MessageResponse { | ||||
| val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() | val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() | ||||
| @@ -82,6 +82,11 @@ class JobOrderController( | |||||
| return jobOrderService.releaseJobOrder(request) | return jobOrderService.releaseJobOrder(request) | ||||
| } | } | ||||
| @PostMapping("/set-hidden") | |||||
| fun setJobOrderHidden(@Valid @RequestBody request: SetJobOrderHiddenRequest): MessageResponse { | |||||
| return jobOrderService.setJobOrderHidden(request) | |||||
| } | |||||
| @PostMapping("/start") | @PostMapping("/start") | ||||
| fun startJobOrder(@Valid @RequestBody request: JobOrderCommonActionRequest): MessageResponse { | fun startJobOrder(@Valid @RequestBody request: JobOrderCommonActionRequest): MessageResponse { | ||||
| val jo = jobOrderService.startJobOrder(request) | val jo = jobOrderService.startJobOrder(request) | ||||
| @@ -7,4 +7,9 @@ data class JobOrderCommonActionRequest( | |||||
| data class JobOrderUpdateRequest( | data class JobOrderUpdateRequest( | ||||
| val id: Long, | val id: Long, | ||||
| val status: String, | val status: String, | ||||
| ) | |||||
| data class SetJobOrderHiddenRequest( | |||||
| val id: Long, | |||||
| val hidden: Boolean, | |||||
| ) | ) | ||||
| @@ -10,6 +10,7 @@ data class SearchJobOrderInfoRequest( | |||||
| val pageSize: Int?, | val pageSize: Int?, | ||||
| val pageNum: Int?, | val pageNum: Int?, | ||||
| val jobTypeName: String?, | val jobTypeName: String?, | ||||
| val joSearchStatus: String?, | |||||
| ) | ) | ||||
| data class MaterialPickStatusItem( | data class MaterialPickStatusItem( | ||||
| val id: Long, | val id: Long, | ||||
| @@ -1522,6 +1522,7 @@ open class ProductProcessService( | |||||
| } | } | ||||
| val filteredCandidateProcesses = candidateProcesses.filter { p -> | val filteredCandidateProcesses = candidateProcesses.filter { p -> | ||||
| if (p.jobOrder?.isHidden == true) return@filter false | |||||
| if (isDrink != null) { | if (isDrink != null) { | ||||
| val bomIsDrink = p.bom?.isDrink | val bomIsDrink = p.bom?.isDrink | ||||
| if (bomIsDrink != isDrink) return@filter false | 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`; | |||||