| @@ -18,6 +18,8 @@ import java.time.LocalDateTime | |||
| @Table(name = "do_pick_order_line_record") | |||
| open class DoPickOrderLineRecord: BaseEntity<Long>() { | |||
| @Column(name = "record_id") | |||
| open var recordId: Long? = null | |||
| @Column(name = "do_pick_order_id", length = 100) | |||
| open var doPickOrderId: Long? = null | |||
| @@ -14,7 +14,8 @@ class DoPickOrderRecord { | |||
| @Id | |||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | |||
| var id: Long? = null | |||
| @Column(name = "record_id") | |||
| var recordId: Long? = null | |||
| @Column(name = "store_id", length = 10) | |||
| var storeId: String? = null | |||
| @@ -95,6 +96,7 @@ var handlerName: String? = null | |||
| // Constructor for creating new instances | |||
| constructor( | |||
| recordId: Long?, | |||
| storeId: String, | |||
| ticketNo: String, | |||
| ticketStatus: DoPickOrderStatus, | |||
| @@ -117,6 +119,7 @@ var handlerName: String? = null | |||
| deliveryOrderCode: String? = null, | |||
| loadingSequence: Int? = null, | |||
| ) { | |||
| this.recordId= recordId | |||
| this.storeId = storeId | |||
| this.ticketNo = ticketNo | |||
| this.pickOrderId = pickOrderId | |||
| @@ -39,7 +39,7 @@ open class DoPickOrderCompletionService( | |||
| return savedDoPickOrders | |||
| } | |||
| /* | |||
| @Transactional | |||
| fun removeDoPickOrdersForPickOrder(pickOrderId: Long): Int { | |||
| val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId) | |||
| @@ -48,6 +48,7 @@ open class DoPickOrderCompletionService( | |||
| doPickOrders.forEach { doPickOrder -> | |||
| // ✅ 第一步:复制 do_pick_order 到 do_pick_order_record | |||
| val doPickOrderRecord = DoPickOrderRecord( | |||
| storeId = doPickOrder.storeId ?: "", | |||
| ticketNo = doPickOrder.ticketNo ?: "", | |||
| ticketStatus = DoPickOrderStatus.completed, | |||
| @@ -99,4 +100,5 @@ open class DoPickOrderCompletionService( | |||
| return deletedCount | |||
| } | |||
| */ | |||
| } | |||
| @@ -176,6 +176,7 @@ open class DoPickOrderService( | |||
| doPickOrders.forEach { doPickOrder -> | |||
| // ✅ 第一步:复制 do_pick_order 到 do_pick_order_record | |||
| val doPickOrderRecord = DoPickOrderRecord( | |||
| recordId = doPickOrder.id, | |||
| storeId = doPickOrder.storeId?: "", | |||
| ticketNo = doPickOrder.ticketNo?: "", | |||
| ticketStatus = DoPickOrderStatus.completed, // 设置为completed状态 | |||
| @@ -203,7 +204,8 @@ open class DoPickOrderService( | |||
| // ✅ 第二步:复制 do_pick_order_line 到 do_pick_order_line_record | |||
| val doPickOrderLines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(doPickOrder.id!!) | |||
| doPickOrderLines.forEach { line -> | |||
| val doPickOrderLineRecord = DoPickOrderLineRecord() // ✅ 使用默认构造函数 | |||
| val doPickOrderLineRecord = DoPickOrderLineRecord() | |||
| doPickOrderLineRecord.recordId = line.id // ✅ 使用默认构造函数 | |||
| doPickOrderLineRecord.doPickOrderId = savedRecord.id // ✅ 设置属性 | |||
| doPickOrderLineRecord.pickOrderId = line.pickOrderId | |||
| doPickOrderLineRecord.doOrderId = line.doOrderId | |||
| @@ -540,6 +542,7 @@ open class DoPickOrderService( | |||
| // 创建 do_pick_order_record | |||
| val doPickOrderRecord = DoPickOrderRecord( | |||
| recordId =doPickOrder.id, | |||
| storeId = storeId, | |||
| ticketNo = ticketNumber, | |||
| ticketStatus = DoPickOrderStatus.pending, | |||
| @@ -7,4 +7,6 @@ import java.util.Optional | |||
| @Repository | |||
| interface BomProcessRepository : AbstractRepository<BomProcess, Long> { | |||
| fun findBySeqNoAndBomIdAndDeletedIsFalse(seqNo: Int, bomId: Long): BomProcess? | |||
| fun findByBomIdOrderBySeqNo(bomId: Long): List<BomProcess> | |||
| fun findByBomId(bomId: Long): List<BomProcess> | |||
| } | |||
| @@ -1284,6 +1284,7 @@ open class PickOrderService( | |||
| // 2) 先复制 do_pick_order -> do_pick_order_record | |||
| val dpoRecord = DoPickOrderRecord( | |||
| recordId =dpo.id, | |||
| storeId = dpo.storeId ?: "", | |||
| ticketNo = dpo.ticketNo ?: "", | |||
| ticketStatus = DoPickOrderStatus.completed, | |||
| @@ -1310,6 +1311,7 @@ open class PickOrderService( | |||
| val lines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(dpo.id!!) | |||
| val lineRecords = lines.map { l: DoPickOrderLine -> | |||
| DoPickOrderLineRecord().apply { | |||
| this.recordId=l.id | |||
| this.doPickOrderId = savedHeader.id | |||
| this.pickOrderId = l.pickOrderId | |||
| this.doOrderId = l.doOrderId | |||
| @@ -2399,7 +2401,7 @@ open class PickOrderService( | |||
| ) | |||
| } | |||
| } | |||
| /* | |||
| @Transactional(rollbackFor = [java.lang.Exception::class]) | |||
| open fun autoAssignAndReleasePickOrderByStore(userId: Long, storeId: String): MessageResponse { | |||
| try { | |||
| @@ -2622,6 +2624,7 @@ open class PickOrderService( | |||
| println("🔍 DEBUG: Processing line - Store ID: $determinedStoreId") | |||
| val doPickOrderRecord = DoPickOrderRecord( | |||
| id=dopickorder.id, | |||
| storeId = determinedStoreId, | |||
| ticketNo = nextTicketNumber, | |||
| ticketStatus = DoPickOrderStatus.released, | |||
| @@ -2670,7 +2673,7 @@ open class PickOrderService( | |||
| ) | |||
| } | |||
| } | |||
| */ | |||
| open fun getAllPickOrderLotsWithDetailsWithAutoAssign(userId: Long): List<Map<String, Any>> { | |||
| println("=== Debug: getAllPickOrderLotsWithDetailsWithAutoAssign ===") | |||
| println("today: ${LocalDate.now()}") | |||
| @@ -234,6 +234,7 @@ class PickOrderController( | |||
| fun autoAssignAndReleasePickOrder(@PathVariable userId: Long): MessageResponse { | |||
| return pickOrderService.autoAssignAndReleasePickOrder(userId) | |||
| } | |||
| /* | |||
| @PostMapping("/auto-assign-release-by-store") | |||
| fun autoAssignReleaseByStore( | |||
| @RequestParam userId: Long, | |||
| @@ -241,6 +242,7 @@ class PickOrderController( | |||
| ): MessageResponse { | |||
| return pickOrderService.autoAssignAndReleasePickOrderByStore(userId, storeId) | |||
| } | |||
| */ | |||
| @GetMapping("/check-pick-completion/{userId}") | |||
| fun checkPickOrderCompletion(@PathVariable userId: Long): MessageResponse { | |||
| return pickOrderService.checkPickOrderCompletion(userId) | |||
| @@ -273,7 +275,7 @@ class PickOrderController( | |||
| fun getAllPickOrderLotsHierarchical(@PathVariable userId: Long): Map<String, Any?> { | |||
| return pickOrderService.getAllPickOrderLotsWithDetailsHierarchical(userId) | |||
| } | |||
| @PostMapping("/auto-assign-release-by-ticket") | |||
| /* @PostMapping("/auto-assign-release-by-ticket") | |||
| fun autoAssignAndReleasePickOrderByTicket( | |||
| @RequestParam storeId: String, | |||
| @RequestParam ticketNo: String, | |||
| @@ -281,6 +283,7 @@ fun autoAssignAndReleasePickOrderByTicket( | |||
| ): MessageResponse { | |||
| return pickOrderService.autoAssignAndReleasePickOrderByStoreAndTicket(storeId, ticketNo, userId) | |||
| } | |||
| */ | |||
| @GetMapping("/orders-by-store/{storeId}") | |||
| fun getPickOrdersByStore(@PathVariable storeId: String): Map<String, Any?> { | |||
| return pickOrderService.getPickOrdersByDateAndStore(storeId) | |||
| @@ -0,0 +1,56 @@ | |||
| package com.ffii.fpsms.modules.productProcess.entity | |||
| import com.fasterxml.jackson.annotation.JsonManagedReference | |||
| import com.ffii.core.entity.BaseEntity | |||
| import com.ffii.fpsms.modules.jobOrder.entity.JobOrder | |||
| import com.ffii.fpsms.modules.master.entity.Items | |||
| import com.ffii.fpsms.modules.productProcess.enums.ProductProcessStatus | |||
| import com.ffii.fpsms.modules.productProcess.enums.ProductProcessStatusConverter | |||
| import jakarta.persistence.* | |||
| import jakarta.validation.constraints.NotNull | |||
| import jakarta.validation.constraints.Size | |||
| import java.time.LocalDate | |||
| import java.time.LocalDateTime | |||
| import com.ffii.fpsms.modules.master.entity.Bom | |||
| @Entity | |||
| @Table(name = "productprocess") | |||
| open class ProductProcess : BaseEntity<Long>() { | |||
| @Size(max = 50) | |||
| @NotNull | |||
| @Column(name = "productprocesscode", nullable = false, unique = true, length = 50) | |||
| open var productProcessCode: String? = null | |||
| @ManyToOne(fetch = FetchType.LAZY) | |||
| @JoinColumn(name = "jobOrderId") | |||
| open var jobOrder: JobOrder? = null | |||
| @ManyToOne(fetch = FetchType.LAZY) | |||
| @JoinColumn(name = "itemsId") | |||
| open var item: Items? = null | |||
| @NotNull | |||
| @ManyToOne(fetch = FetchType.LAZY) | |||
| @JoinColumn(name = "bomId", nullable = false) | |||
| open var bom: Bom? = null | |||
| @NotNull | |||
| @Column(name = "status", nullable = false, length = 20) | |||
| @Convert(converter = ProductProcessStatusConverter::class) | |||
| open var status: ProductProcessStatus = ProductProcessStatus.PENDING | |||
| @Column(name = "startTime") | |||
| open var startTime: LocalDateTime? = null | |||
| @Column(name = "endTime") | |||
| open var endTime: LocalDateTime? = null | |||
| @Column(name = "date") | |||
| open var date: LocalDate? = null | |||
| @JsonManagedReference | |||
| @OneToMany(mappedBy = "productProcess", cascade = [CascadeType.ALL], orphanRemoval = true) | |||
| open var productionScheduleLines: MutableList<ProductProcessLine> = mutableListOf() | |||
| } | |||
| @@ -0,0 +1,106 @@ | |||
| package com.ffii.fpsms.modules.productProcess.entity | |||
| import com.fasterxml.jackson.annotation.JsonBackReference | |||
| import com.ffii.core.entity.BaseEntity | |||
| import com.ffii.fpsms.modules.master.entity.Equipment | |||
| import com.ffii.fpsms.modules.master.entity.Items | |||
| import com.ffii.fpsms.modules.user.entity.User | |||
| import jakarta.persistence.* | |||
| import jakarta.validation.constraints.Size | |||
| import java.time.LocalDateTime | |||
| import com.ffii.fpsms.modules.master.entity.BomProcess | |||
| @Entity | |||
| @Table(name = "productprocessline") | |||
| open class ProductProcessLine : BaseEntity<Long>() { | |||
| // ✅ 添加 @ManyToOne | |||
| @ManyToOne(fetch = FetchType.LAZY) | |||
| @JoinColumn(name = "operatorId") | |||
| open var operator: User? = null | |||
| // ✅ 添加 @ManyToOne | |||
| @ManyToOne(fetch = FetchType.LAZY) | |||
| @JoinColumn(name = "equipmentId") | |||
| open var equipment: Equipment? = null | |||
| @Size(max = 100) | |||
| @Column(name = "name", length = 100) | |||
| open var name: String? = null | |||
| @Column(name = "description") | |||
| open var description: String? = null | |||
| @Size(max = 100) | |||
| @Column(name = "equipment_name", length = 100) | |||
| open var equipmentType: String? = null | |||
| // ✅ 添加 @ManyToOne | |||
| @ManyToOne(fetch = FetchType.LAZY) | |||
| @JoinColumn(name = "byproductId") | |||
| open var byproduct: Items? = null | |||
| @Column(name = "status") | |||
| open var status: String? = null | |||
| @Size(max = 100) | |||
| @Column(name = "byproductName", length = 100) | |||
| open var byproductName: String? = null | |||
| @Column(name = "byproductQty", precision = 16, scale = 2) | |||
| open var byproductQty: Int? = null | |||
| @Size(max = 20) | |||
| @Column(name = "byproductUom", length = 20) | |||
| open var byproductUom: String? = null | |||
| @Column(name = "scrapQty", precision = 16, scale = 2) | |||
| open var scrapQty: Int? = null | |||
| @Size(max = 20) | |||
| @Column(name = "scrapUom", length = 20) | |||
| open var scrapUom: String? = null | |||
| @Column(name = "defectQty", precision = 16, scale = 2) | |||
| open var defectQty: Int? = null | |||
| @Size(max = 20) | |||
| @Column(name = "defectUom", length = 20) | |||
| open var defectUom: String? = null | |||
| @Column(name = "outputFromProcessQty", precision = 16, scale = 2) | |||
| open var outputFromProcessQty: Int? = null | |||
| @Size(max = 20) | |||
| @Column(name = "outputFromProcessUom", length = 20) | |||
| open var outputFromProcessUom: String? = null | |||
| @Column(name = "startTime") | |||
| open var startTime: LocalDateTime? = null | |||
| @Column(name = "endTime") | |||
| open var endTime: LocalDateTime? = null | |||
| @ManyToOne(fetch = FetchType.LAZY) | |||
| @JoinColumn(name = "bomProcessId") | |||
| open var bomProcess: BomProcess? = null | |||
| @Column(name = "seqNo") | |||
| open var seqNo: Long? = null | |||
| @ManyToOne(fetch = FetchType.LAZY) | |||
| @JoinColumn(name = "handlerId") | |||
| open var handler: User? = null | |||
| @JsonBackReference | |||
| @ManyToOne | |||
| @JoinColumn(name = "productprocessId", nullable = false) | |||
| open var productProcess: ProductProcess = ProductProcess() | |||
| data class ProductProcessLineDetail( | |||
| val id: Long?, | |||
| val name: String?, | |||
| val description: String?, | |||
| val equipmentType: String?, | |||
| val status: String?, | |||
| val byproduct: Items?, | |||
| val byproductName: String?, | |||
| val byproductQty: Int?, | |||
| val byproductUom: String?, | |||
| val scrapQty: Int?, | |||
| val scrapUom: String?, | |||
| ) | |||
| } | |||
| @@ -0,0 +1,11 @@ | |||
| package com.ffii.fpsms.modules.productProcess.entity | |||
| import org.springframework.data.jpa.repository.JpaRepository | |||
| import org.springframework.stereotype.Repository | |||
| @Repository | |||
| interface ProductProcessLineRepository : JpaRepository<ProductProcessLine, Long> { | |||
| fun findByProductProcess_Id(productProcessId: Long): List<ProductProcessLine> | |||
| fun findByProductProcess_IdAndHandler_Id(productProcessId: Long, handlerId: Long): List<ProductProcessLine> | |||
| fun findByHandler_IdAndStartTimeIsNotNullAndEndTimeIsNull(handlerId: Long): List<ProductProcessLine> | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| package com.ffii.fpsms.modules.productProcess.entity | |||
| import org.springframework.data.jpa.repository.JpaRepository | |||
| import org.springframework.data.jpa.repository.JpaSpecificationExecutor | |||
| import org.springframework.stereotype.Repository | |||
| import java.util.* | |||
| @Repository | |||
| interface ProductProcessRepository : JpaRepository<ProductProcess, Long>, JpaSpecificationExecutor<ProductProcess> { | |||
| fun findByProductProcessCode(code: String): Optional<ProductProcess> | |||
| fun findByJobOrder_Id(jobOrderId: Long): List<ProductProcess> | |||
| fun findByProductProcessCodeStartingWith(prefix: String): List<ProductProcess> | |||
| fun findByIdAndDeletedIsFalse(id: Long): Optional<ProductProcess> | |||
| } | |||
| @@ -0,0 +1,39 @@ | |||
| package com.ffii.fpsms.modules.productProcess.entity | |||
| import com.ffii.core.entity.BaseEntity | |||
| import com.ffii.fpsms.modules.user.entity.User | |||
| import jakarta.persistence.* | |||
| import jakarta.validation.constraints.NotNull | |||
| import jakarta.validation.constraints.Size | |||
| import java.time.LocalDateTime | |||
| @Entity | |||
| @Table(name = "productionprocessissue") // ✅ 修复:改为正确的表名 | |||
| open class ProductionProcessIssue : BaseEntity<Long>() { // ✅ 修复:改为正确的类名 | |||
| @ManyToOne(fetch = FetchType.LAZY) | |||
| @JoinColumn(name = "productprocessid", nullable = false) | |||
| open var productProcess: ProductProcess? = null | |||
| @ManyToOne(fetch = FetchType.LAZY) | |||
| @JoinColumn(name = "operatorId") | |||
| open var operator: User? = null | |||
| @Size(max = 100) | |||
| @Column(name = "operatorName", length = 100) | |||
| open var operatorName: String? = null | |||
| @NotNull | |||
| @Column(name = "reason", nullable = false) | |||
| open var reason: String? = null | |||
| @NotNull | |||
| @Column(name = "stopTime", nullable = false) | |||
| open var stopTime: LocalDateTime? = null | |||
| @Column(name = "resumeTime") | |||
| open var resumeTime: LocalDateTime? = null | |||
| @Column(name = "totalTime") | |||
| open var totalTime: Int? = null // 分钟 | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| package com.ffii.fpsms.modules.productProcess.entity | |||
| import org.springframework.data.jpa.repository.JpaRepository | |||
| import org.springframework.stereotype.Repository | |||
| @Repository | |||
| interface ProductionProcessIssueRepository : JpaRepository<ProductionProcessIssue, Long> { | |||
| fun findByProductProcess_Id(productProcessId: Long): List<ProductionProcessIssue> | |||
| } | |||
| @@ -0,0 +1,48 @@ | |||
| package com.ffii.fpsms.modules.productProcess.entity.projections | |||
| import java.time.LocalDateTime | |||
| import java.time.LocalDate | |||
| import com.ffii.fpsms.modules.productProcess.enums.ProductProcessStatus | |||
| import com.fasterxml.jackson.annotation.JsonFormat | |||
| data class ProductProcessInfo( | |||
| val id: Long?, | |||
| val productProcessCode: String?, | |||
| val status: ProductProcessStatus?, | |||
| @JsonFormat(pattern = "yyyy-MM-dd") | |||
| val startTime: LocalDateTime?, | |||
| @JsonFormat(pattern = "yyyy-MM-dd") | |||
| val endTime: LocalDateTime?, | |||
| @JsonFormat(pattern = "yyyy-MM-dd") | |||
| val date: LocalDate?, | |||
| val bomId: Long?, | |||
| val jobOrderId: Long?, | |||
| val productProcessLines: List<ProductProcessLineInfo>? | |||
| ) | |||
| data class ProductProcessLineInfo( | |||
| val id:Long?, | |||
| val bomprocessId: Long?, | |||
| val operatorId: Long?, | |||
| val equipmentId: Long?, | |||
| val handlerId: Long?, | |||
| val seqNo: Long?, | |||
| val name: String?, | |||
| val description: String?, | |||
| val equipment_name: String?, | |||
| val status: String?, | |||
| val byproductId: Long?, | |||
| val byproductName: String?, | |||
| val byproductQty: Int?, | |||
| val byproductUom: String?, | |||
| val scrapQty: Int?, | |||
| val defectQty: Int?, | |||
| val defectUom: String?, | |||
| val outputFromProcessQty: Int?, | |||
| val outputFromProcessUom: String?, | |||
| @JsonFormat(pattern = "yyyy-MM-dd") | |||
| val startTime: LocalDateTime?, | |||
| @JsonFormat(pattern = "yyyy-MM-dd") | |||
| val endTime: LocalDateTime? | |||
| ) | |||
| @@ -0,0 +1,19 @@ | |||
| package com.ffii.fpsms.modules.productProcess.enums | |||
| enum class ProductProcessStatus(val value: String) { | |||
| PENDING("pending"), | |||
| IN_PROGRESS("in_progress"), | |||
| STOPPED("stopped"), | |||
| COMPLETED("completed"), | |||
| CANCELLED("cancelled"); | |||
| companion object { | |||
| fun fromValue(value: String): ProductProcessStatus { | |||
| return values().find { it.value == value } | |||
| ?: throw IllegalArgumentException("Unknown status: $value") | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| package com.ffii.fpsms.modules.productProcess.enums | |||
| import jakarta.persistence.AttributeConverter | |||
| import jakarta.persistence.Converter | |||
| @Converter(autoApply = true) | |||
| class ProductProcessStatusConverter : AttributeConverter<ProductProcessStatus, String> { | |||
| override fun convertToDatabaseColumn(attribute: ProductProcessStatus?): String? { | |||
| return attribute?.value | |||
| } | |||
| override fun convertToEntityAttribute(dbData: String?): ProductProcessStatus? { | |||
| return dbData?.let { ProductProcessStatus.fromValue(it) } | |||
| } | |||
| } | |||
| @@ -0,0 +1,616 @@ | |||
| package com.ffii.fpsms.modules.productProcess.service | |||
| import com.ffii.fpsms.modules.productProcess.entity.* | |||
| import com.ffii.fpsms.modules.productProcess.entity.projections.ProductProcessInfo | |||
| import com.ffii.fpsms.modules.productProcess.entity.projections.ProductProcessLineInfo | |||
| import com.ffii.fpsms.modules.productProcess.enums.ProductProcessStatus | |||
| import com.ffii.fpsms.modules.productProcess.web.model.* | |||
| import org.springframework.data.domain.Page | |||
| import org.springframework.data.domain.Pageable | |||
| import org.springframework.stereotype.Service | |||
| import org.springframework.transaction.annotation.Transactional | |||
| import java.time.LocalDate | |||
| import java.time.LocalDateTime | |||
| import java.time.temporal.ChronoUnit | |||
| import com.ffii.fpsms.modules.user.entity.UserRepository | |||
| import com.ffii.fpsms.modules.user.entity.User | |||
| import java.time.format.DateTimeFormatter | |||
| import com.ffii.fpsms.modules.master.entity.BomRepository | |||
| import com.ffii.fpsms.modules.master.entity.BomProcessRepository | |||
| import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository | |||
| import com.ffii.fpsms.modules.master.entity.ItemsRepository | |||
| import com.ffii.fpsms.modules.master.entity.ProcessRepository | |||
| import com.ffii.fpsms.modules.master.entity.EquipmentRepository | |||
| import java.math.BigDecimal | |||
| import com.ffii.fpsms.modules.master.entity.Process | |||
| import com.ffii.fpsms.modules.master.entity.Equipment | |||
| import com.ffii.fpsms.modules.master.entity.BomProcess | |||
| import com.ffii.fpsms.modules.master.entity.Bom | |||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| @Service | |||
| @Transactional | |||
| open class ProductProcessService( | |||
| private val productProcessRepository: ProductProcessRepository, | |||
| private val productProcessLineRepository: ProductProcessLineRepository, | |||
| private val productionProcessIssueRepository: ProductionProcessIssueRepository, | |||
| private val bomRepository: BomRepository, | |||
| private val bomProcessRepository: BomProcessRepository, | |||
| private val jobOrderRepository: JobOrderRepository, | |||
| private val itemsRepository: ItemsRepository, | |||
| private val userRepository: UserRepository, | |||
| private val processRepository: ProcessRepository, | |||
| private val equipmentRepository: EquipmentRepository, | |||
| ) { | |||
| open fun findAll(pageable: Pageable): Page<ProductProcess> { | |||
| println("📋 Service: Finding all ProductProcess with page: ${pageable.pageNumber}, size: ${pageable.pageSize}") | |||
| val result = productProcessRepository.findAll(pageable) | |||
| println("✅ Service: Found ${result.totalElements} records") | |||
| return result | |||
| } | |||
| open fun findById(id: Long): ProductProcess { | |||
| println("🔍 Service: Finding ProductProcess by ID: $id") | |||
| return productProcessRepository.findById(id) | |||
| .orElseThrow { IllegalArgumentException("ProductProcess not found with id: $id") } | |||
| } | |||
| open fun findByCode(code: String): ProductProcess { | |||
| println("🔍 Service: Finding ProductProcess by code: $code") | |||
| return productProcessRepository.findByProductProcessCode(code) | |||
| .orElseThrow { IllegalArgumentException("ProductProcess not found with code: $code") } | |||
| } | |||
| open fun create(request: SaveProductProcessRequest): SaveProductProcessResponse { | |||
| println("🔧 Service: Creating ProductProcess with bomId: ${request.bomId}") | |||
| // 1. 查询 BOM | |||
| val bom = bomRepository.findById(request.bomId) | |||
| .orElseThrow { IllegalArgumentException("BOM not found with id: ${request.bomId}") } | |||
| println("📦 Service: BOM found - name: ${bom.name}, item: ${bom.item?.name ?: "N/A"}") | |||
| // 2. 生成 productProcessCode(格式:PP-20251026-001) | |||
| //val datePrefix = (request.date ?: LocalDate.now()).format(DateTimeFormatter.ofPattern("yyyyMMdd")) | |||
| val datePrefix = ( LocalDate.now()).format(DateTimeFormatter.ofPattern("yyyyMMdd")) | |||
| val productProcessCode = generateProductProcessCode(datePrefix) | |||
| println("🔢 Service: Generated code: $productProcessCode") | |||
| // 3. 创建 ProductProcess | |||
| val productProcess = ProductProcess().apply { | |||
| this.productProcessCode = productProcessCode | |||
| this.bom = bom | |||
| this.item = bom.item | |||
| this.jobOrder = request.jobOrderId?.let { jobOrderRepository.findById(it).orElse(null) } | |||
| this.status = ProductProcessStatus.PENDING | |||
| //this.date = request.date ?: LocalDate.now() | |||
| this.date = LocalDate.now() | |||
| } | |||
| val savedProcess = productProcessRepository.save(productProcess) | |||
| println("✅ Service: ProductProcess created with ID: ${savedProcess.id}") | |||
| // 4. 查询 BOM 的所有工序步骤 | |||
| val bomProcesses = bomProcessRepository.findByBomIdOrderBySeqNo(request.bomId) | |||
| println("🔍 Service: Found ${bomProcesses.size} BOM processes") | |||
| // 5. 为每个 BOM Process 创建 ProductProcessLine | |||
| bomProcesses.forEachIndexed { index, bomProcess -> // ✅ 修复 forEach | |||
| val line = ProductProcessLine().apply { | |||
| this.productProcess = savedProcess | |||
| this.bomProcess = bomProcess | |||
| this.seqNo = bomProcess.seqNo | |||
| this.name = bomProcess.process?.name | |||
| this.description = bomProcess.description | |||
| this.equipmentType = bomProcess.equipment?.name | |||
| } | |||
| productProcessLineRepository.save(line) | |||
| println("➕ Service: Created line ${index + 1} - seq: ${line.seqNo}, name: ${line.name}") | |||
| } | |||
| println("✅ Service: All ${bomProcesses.size} lines created automatically") | |||
| return SaveProductProcessResponse( | |||
| id = savedProcess.id!!, | |||
| productProcessCode = productProcessCode, | |||
| linesCreated = bomProcesses.size | |||
| ) | |||
| } | |||
| private fun generateProductProcessCode(datePrefix: String): String { | |||
| val searchPattern = "PP-$datePrefix-" | |||
| val existingCodes = productProcessRepository.findByProductProcessCodeStartingWith(searchPattern) | |||
| val nextNumber = (existingCodes.size + 1).toString().padStart(3, '0') | |||
| return "$searchPattern$nextNumber" | |||
| } | |||
| // ✅ 添加:查询工序的所有步骤 | |||
| open fun getLines(productProcessId: Long): List<ProductProcessLine> { | |||
| println("📋 Service: Getting lines for process ID: $productProcessId") | |||
| val lines = productProcessLineRepository.findByProductProcess_Id(productProcessId) | |||
| println("✅ Service: Found ${lines.size} lines") | |||
| return lines | |||
| } | |||
| open fun startProcess(id: Long, operatorId: Long): ProductProcess { | |||
| println("▶️ Service: Starting ProductProcess ID: $id with operatorId: $operatorId") | |||
| val productProcess = findById(id) | |||
| productProcess.status = ProductProcessStatus.IN_PROGRESS | |||
| productProcess.startTime = LocalDateTime.now() | |||
| val saved = productProcessRepository.save(productProcess) | |||
| println("✅ Service: Process started, status: ${saved.status}") | |||
| return saved | |||
| } | |||
| open fun stopProcess(id: Long, request: StopProcessRequest): ProductionProcessIssue { | |||
| println("⏸️ Service: Stopping ProductProcess ID: $id") | |||
| println("📦 Service: Stop reason: ${request.reason}") | |||
| val productProcess = findById(id) | |||
| productProcess.status = ProductProcessStatus.STOPPED | |||
| val issue = ProductionProcessIssue().apply { | |||
| this.productProcess = productProcess | |||
| this.operatorName = request.operatorName | |||
| this.reason = request.reason | |||
| this.stopTime = LocalDateTime.now() | |||
| } | |||
| productProcessRepository.save(productProcess) | |||
| val savedIssue = productionProcessIssueRepository.save(issue) | |||
| println("✅ Service: Process stopped, issue ID: ${savedIssue.id}") | |||
| return savedIssue | |||
| } | |||
| open fun resumeProcess(id: Long, issueId: Long): ProductionProcessIssue { | |||
| println("▶️ Service: Resuming ProductProcess ID: $id, issueId: $issueId") | |||
| val productProcess = findById(id) | |||
| productProcess.status = ProductProcessStatus.IN_PROGRESS | |||
| val issue = productionProcessIssueRepository.findById(issueId) | |||
| .orElseThrow { IllegalArgumentException("Issue not found") } | |||
| issue.resumeTime = LocalDateTime.now() | |||
| issue.totalTime = ChronoUnit.MINUTES.between(issue.stopTime, issue.resumeTime).toInt() | |||
| productProcessRepository.save(productProcess) | |||
| val savedIssue = productionProcessIssueRepository.save(issue) | |||
| println("✅ Service: Process resumed, total stop time: ${savedIssue.totalTime} minutes") | |||
| return savedIssue | |||
| } | |||
| open fun completeProcess(id: Long): ProductProcess { | |||
| println("✔️ Service: Completing ProductProcess ID: $id") | |||
| val productProcess = findById(id) | |||
| productProcess.status = ProductProcessStatus.COMPLETED | |||
| productProcess.endTime = LocalDateTime.now() | |||
| val saved = productProcessRepository.save(productProcess) | |||
| println("✅ Service: Process completed") | |||
| return saved | |||
| } | |||
| open fun addLine(productProcessId: Long, request: SaveProductProcessLineRequest): SaveProductProcessLineResponse { | |||
| println("➕ Service: Adding line to ProductProcess ID: $productProcessId") | |||
| println("📦 Service: Line data - name: ${request.name}, equipmentType: ${request.equipmentType}") | |||
| val productProcess = findById(productProcessId) | |||
| val line = ProductProcessLine().apply { | |||
| this.productProcess = productProcess | |||
| this.name = request.name | |||
| this.description = request.description | |||
| this.equipmentType = request.equipmentType | |||
| } | |||
| val saved = productProcessLineRepository.save(line) | |||
| println("✅ Service: Line added with ID: ${saved.id}") | |||
| return SaveProductProcessLineResponse(saved.id!!) | |||
| } | |||
| open fun updateLineOutput(lineId: Long, request: UpdateLineOutputRequest): ProductProcessLine { | |||
| println("📊 Service: Updating line output for line ID: $lineId") | |||
| val line = productProcessLineRepository.findById(lineId) | |||
| .orElseThrow { IllegalArgumentException("Line not found") } | |||
| line.apply { | |||
| outputFromProcessQty = request.outputQty | |||
| outputFromProcessUom = request.outputUom | |||
| defectQty = request.defectQty | |||
| defectUom = request.defectUom | |||
| scrapQty = request.scrapQty | |||
| scrapUom = request.scrapUom | |||
| byproductName = request.byproductName | |||
| byproductQty = request.byproductQty | |||
| byproductUom = request.byproductUom | |||
| endTime = LocalDateTime.now() | |||
| } | |||
| val saved = productProcessLineRepository.save(line) | |||
| println("✅ Service: Line output updated") | |||
| return saved | |||
| } | |||
| open fun getIssues(productProcessId: Long): List<ProductionProcessIssue> { | |||
| println("📋 Service: Getting issues for ProductProcess ID: $productProcessId") | |||
| val issues = productionProcessIssueRepository.findByProductProcess_Id(productProcessId) | |||
| println("✅ Service: Found ${issues.size} issues") | |||
| return issues | |||
| } | |||
| open fun findByJobOrderId(jobOrderId: Long): List<ProductProcess> { | |||
| println("🔍 Service: Finding ProductProcess by jobOrderId: $jobOrderId") | |||
| val result = productProcessRepository.findByJobOrder_Id(jobOrderId) | |||
| println("✅ Service: Found ${result.size} processes") | |||
| return result | |||
| } | |||
| open fun findByJobOrderIdWithLines(jobOrderId: Long): List<ProductProcessWithLinesResponse> { | |||
| println("🔍 Service: Finding ProductProcess with lines by jobOrderId: $jobOrderId") | |||
| val processes = productProcessRepository.findByJobOrder_Id(jobOrderId) | |||
| val result = processes.map { process -> | |||
| val lines = productProcessLineRepository.findByProductProcess_Id(process.id!!) | |||
| ProductProcessWithLinesResponse( | |||
| id = process.id!!, | |||
| productProcessCode = process.productProcessCode, | |||
| status = process.status.value, | |||
| startTime = process.startTime, | |||
| endTime = process.endTime, | |||
| date = process.date, | |||
| lines = lines.map { line -> | |||
| ProductProcessLineResponse( | |||
| id = line.id!!, | |||
| seqNo = line.seqNo, | |||
| name = line.name, | |||
| description = line.description, | |||
| equipmentType = line.equipmentType, | |||
| startTime = line.startTime, | |||
| endTime = line.endTime, | |||
| outputFromProcessQty = line.outputFromProcessQty, | |||
| outputFromProcessUom = line.outputFromProcessUom, | |||
| defectQty = line.defectQty, | |||
| scrapQty = line.scrapQty, | |||
| byproductName = line.byproductName, | |||
| byproductQty = line.byproductQty | |||
| ) | |||
| } | |||
| ) | |||
| } | |||
| println("✅ Service: Returning ${result.size} processes with ${result.sumOf { it.lines.size }} total lines") | |||
| return result | |||
| } | |||
| open fun findAllAsDto(pageable: Pageable): Page<ProductProcessSimpleResponse> { | |||
| println("📋 Service: Finding all ProductProcess as DTO with page: ${pageable.pageNumber}, size: ${pageable.pageSize}") | |||
| val entityPage = productProcessRepository.findAll(pageable) | |||
| val dtoList = entityPage.content.map { entity -> | |||
| ProductProcessSimpleResponse( | |||
| id = entity.id!!, | |||
| productProcessCode = entity.productProcessCode, | |||
| status = entity.status.value, | |||
| startTime = entity.startTime, | |||
| endTime = entity.endTime, | |||
| date = entity.date, | |||
| bomId = entity.bom?.id, | |||
| jobOrderId = entity.jobOrder?.id | |||
| ) | |||
| } | |||
| println("✅ Service: Converted ${dtoList.size} entities to DTOs") | |||
| return org.springframework.data.domain.PageImpl(dtoList, pageable, entityPage.totalElements) | |||
| } | |||
| open fun findByIdAsDto(id: Long): ProductProcessSimpleResponse { | |||
| println("🔍 Service: Finding ProductProcess by ID: $id as DTO") | |||
| val entity = productProcessRepository.findById(id) | |||
| .orElseThrow { IllegalArgumentException("ProductProcess not found with id: $id") } | |||
| return ProductProcessSimpleResponse( | |||
| id = entity.id!!, | |||
| productProcessCode = entity.productProcessCode, | |||
| status = entity.status.value, | |||
| startTime = entity.startTime, | |||
| endTime = entity.endTime, | |||
| date = entity.date, | |||
| bomId = entity.bom?.id, | |||
| jobOrderId = entity.jobOrder?.id | |||
| ) | |||
| } | |||
| open fun stopProcessAsDto(id: Long, request: StopProcessRequest): ProductionProcessIssueResponse { | |||
| val issue = stopProcess(id, request) | |||
| return ProductionProcessIssueResponse( | |||
| id = issue.id!!, | |||
| productProcessId = issue.productProcess?.id, | |||
| operatorId = issue.operator?.id, | |||
| operatorName = issue.operatorName, | |||
| reason = issue.reason!!, | |||
| stopTime = issue.stopTime!!, | |||
| resumeTime = issue.resumeTime, | |||
| totalTime = issue.totalTime | |||
| ) | |||
| } | |||
| open fun resumeProcessAsDto(id: Long, issueId: Long): ProductionProcessIssueResponse { | |||
| val issue = resumeProcess(id, issueId) | |||
| return ProductionProcessIssueResponse( | |||
| id = issue.id!!, | |||
| productProcessId = issue.productProcess?.id, | |||
| operatorId = issue.operator?.id, | |||
| operatorName = issue.operatorName, | |||
| reason = issue.reason!!, | |||
| stopTime = issue.stopTime!!, | |||
| resumeTime = issue.resumeTime, | |||
| totalTime = issue.totalTime | |||
| ) | |||
| } | |||
| open fun getIssuesAsDto(productProcessId: Long): List<ProductionProcessIssueResponse> { | |||
| val issues = getIssues(productProcessId) | |||
| return issues.map { issue -> | |||
| ProductionProcessIssueResponse( | |||
| id = issue.id!!, | |||
| productProcessId = issue.productProcess?.id, | |||
| operatorId = issue.operator?.id, | |||
| operatorName = issue.operatorName, | |||
| reason = issue.reason!!, | |||
| stopTime = issue.stopTime!!, | |||
| resumeTime = issue.resumeTime, | |||
| totalTime = issue.totalTime | |||
| ) | |||
| } | |||
| } | |||
| open fun startProcessAsDto(id: Long, operatorId: Long): ProductProcessSimpleResponse { | |||
| val entity = startProcess(id, operatorId) | |||
| return ProductProcessSimpleResponse( | |||
| id = entity.id!!, | |||
| productProcessCode = entity.productProcessCode, | |||
| status = entity.status.value, | |||
| startTime = entity.startTime, | |||
| endTime = entity.endTime, | |||
| date = entity.date, | |||
| bomId = entity.bom?.id, | |||
| jobOrderId = entity.jobOrder?.id | |||
| ) | |||
| } | |||
| open fun completeProcessAsDto(id: Long): ProductProcessSimpleResponse { | |||
| val entity = completeProcess(id) | |||
| return ProductProcessSimpleResponse( | |||
| id = entity.id!!, | |||
| productProcessCode = entity.productProcessCode, | |||
| status = entity.status.value, | |||
| startTime = entity.startTime, | |||
| endTime = entity.endTime, | |||
| date = entity.date, | |||
| bomId = entity.bom?.id, | |||
| jobOrderId = entity.jobOrder?.id | |||
| ) | |||
| } | |||
| open fun updateLineOutputAsDto(lineId: Long, request: UpdateLineOutputRequest): ProductProcessLineResponse { | |||
| val entity = updateLineOutput(lineId, request) | |||
| return ProductProcessLineResponse( | |||
| id = entity.id!!, | |||
| seqNo = entity.seqNo, | |||
| name = entity.name, | |||
| description = entity.description, | |||
| equipmentType = entity.equipmentType, | |||
| startTime = entity.startTime, | |||
| endTime = entity.endTime, | |||
| outputFromProcessQty = entity.outputFromProcessQty, | |||
| outputFromProcessUom = entity.outputFromProcessUom, | |||
| defectQty = entity.defectQty, | |||
| scrapQty = entity.scrapQty, | |||
| byproductName = entity.byproductName, | |||
| byproductQty = entity.byproductQty | |||
| ) | |||
| } | |||
| open fun getLinesAsDto(productProcessId: Long): List<ProductProcessLineResponse> { | |||
| val lines = getLines(productProcessId) | |||
| return lines.map { line -> | |||
| ProductProcessLineResponse( | |||
| id = line.id!!, | |||
| seqNo = line.seqNo, | |||
| name = line.name, | |||
| description = line.description, | |||
| equipmentType = line.equipmentType, | |||
| startTime = line.startTime, | |||
| endTime = line.endTime, | |||
| outputFromProcessQty = line.outputFromProcessQty, | |||
| outputFromProcessUom = line.outputFromProcessUom, | |||
| defectQty = line.defectQty, | |||
| scrapQty = line.scrapQty, | |||
| byproductName = line.byproductName, | |||
| byproductQty = line.byproductQty | |||
| ) | |||
| } | |||
| } | |||
| open fun startLine(lineId: Long, userId: Long): ProductProcessLine { | |||
| println("▶️ Service: Starting ProductProcessLine ID: $lineId with userId: $userId") | |||
| val line = productProcessLineRepository.findById(lineId) | |||
| .orElseThrow { IllegalArgumentException("Line not found") } | |||
| // ✅ 修复:使用 UserRepository 获取 User 对象 | |||
| val user = userRepository.findById(userId) | |||
| .orElseThrow { IllegalArgumentException("User not found with id: $userId") } | |||
| line.handler = user | |||
| line.startTime = LocalDateTime.now() | |||
| val saved = productProcessLineRepository.save(line) | |||
| println("✅ Service: Line started, handlerId: ${saved.handler?.id}") | |||
| return saved | |||
| } | |||
| open fun findPendingLinesByHandlerId(handlerId: Long): List<ProductProcessLine> { | |||
| println("🔍 Service: Finding pending lines for handlerId: $handlerId") | |||
| val lines = productProcessLineRepository.findByHandler_IdAndStartTimeIsNotNullAndEndTimeIsNull(handlerId) | |||
| println("✅ Service: Found ${lines.size} pending lines") | |||
| return lines | |||
| } | |||
| open fun productProcessDetailfindbyjoid(joid: Long): List<ProductProcessInfo> { | |||
| val productProcesses = productProcessRepository.findByJobOrder_Id(joid) | |||
| //val processId = productProcess.id ?: 0 | |||
| return productProcesses.map { process -> | |||
| ProductProcessInfo( | |||
| id = process.id?:0, | |||
| bomId = process.bom?.id?:0, | |||
| jobOrderId = process.jobOrder?.id?:0, | |||
| productProcessCode = process.productProcessCode?:"", | |||
| status = process.status?:ProductProcessStatus.PENDING, | |||
| startTime = process.startTime?:LocalDateTime.now(), | |||
| endTime = process.endTime?:LocalDateTime.now(), | |||
| date = process.date?:LocalDate.now(), | |||
| productProcessLines = productProcessLineRepository.findByProductProcess_Id(process.id?:0).map { line -> | |||
| ProductProcessLineInfo( | |||
| id = line.id?:0, | |||
| bomprocessId = line.bomProcess?.id?:0, | |||
| operatorId = line.operator?.id?:0, | |||
| equipmentId = line.equipment?.id?:0, | |||
| handlerId = line.handler?.id?:0, | |||
| seqNo = line.seqNo?:0, | |||
| name = line.name?:"", | |||
| description = line.description?:"", | |||
| equipment_name = line.equipment?.name?:"", | |||
| status = line.status?:"", | |||
| byproductId = line.byproduct?.id?:0, | |||
| byproductName = line.byproduct?.name?:"", | |||
| byproductQty = line.byproductQty?:0, | |||
| byproductUom = line.byproductUom?:"", | |||
| scrapQty = line.scrapQty?:0, | |||
| defectQty = line.defectQty?:0, | |||
| defectUom = line.defectUom?:"", | |||
| outputFromProcessQty = line.outputFromProcessQty?:0, | |||
| outputFromProcessUom = line.outputFromProcessUom?:"", | |||
| startTime = line.startTime?:LocalDateTime.now(), | |||
| endTime = line.endTime?:LocalDateTime.now() | |||
| ) | |||
| } | |||
| ) | |||
| } | |||
| } | |||
| open fun productProcessDetailfindbyidtest(id: Long): ProductProcessInfo { | |||
| val zero = BigDecimal.ZERO | |||
| val productProcess = productProcessRepository.findById(id).orElse(null) | |||
| val processId = productProcess.id ?: 0 | |||
| val productProcessLines = productProcessLineRepository.findByProductProcess_Id(processId) | |||
| return ProductProcessInfo( | |||
| id = productProcess.id?:0, | |||
| bomId = productProcess.bom?.id?:0, | |||
| jobOrderId = productProcess.jobOrder?.id?:0, | |||
| productProcessCode = productProcess.productProcessCode?:"", | |||
| status = productProcess.status?:ProductProcessStatus.PENDING, | |||
| startTime = productProcess.startTime?:LocalDateTime.now(), | |||
| endTime = productProcess.endTime?:LocalDateTime.now(), | |||
| date = productProcess.date?:LocalDate.now(), | |||
| productProcessLines = productProcessLines.map { line -> | |||
| ProductProcessLineInfo( | |||
| id = line.id?:0, | |||
| bomprocessId = line.bomProcess?.id?:0, | |||
| operatorId = line.operator?.id?:0, | |||
| equipmentId = line.equipment?.id?:0, | |||
| handlerId = line.handler?.id?:0, | |||
| seqNo = line.seqNo?:0, | |||
| name = line.name?:"", | |||
| description = line.description?:"", | |||
| equipment_name = line.equipment?.name?:"", | |||
| status = line.status?:"", | |||
| byproductId = line.byproduct?.id?:0, | |||
| byproductName = line.byproduct?.name?:"", | |||
| byproductQty = line.byproductQty?:0, | |||
| byproductUom = line.byproductUom?:"", | |||
| scrapQty = line.scrapQty?:0, | |||
| defectQty = line.defectQty?:0, | |||
| defectUom = line.defectUom?:"", | |||
| outputFromProcessQty = line.outputFromProcessQty?:0, | |||
| outputFromProcessUom = line.outputFromProcessUom?:"", | |||
| startTime = line.startTime?:LocalDateTime.now(), | |||
| endTime = line.endTime?:LocalDateTime.now() | |||
| ) | |||
| } | |||
| ) | |||
| } | |||
| open fun createProductProcessByJobOrderId(jobOrderId: Long): MessageResponse { | |||
| val jobOrder = jobOrderRepository.findById(jobOrderId).orElse(null) | |||
| val bom = bomRepository.findById(jobOrder?.bom?.id ?: 0L).orElse(null) | |||
| // val bom = jobOrder.bom.let { bomRepository.findById(it).orElse(null) } | |||
| val bomProcess = bomProcessRepository.findByBomId(jobOrder?.bom?.id?:0L) | |||
| val item = itemsRepository.findById(bom?.item?.id ?: 0L).orElse(null) | |||
| val datePrefix = ( LocalDate.now()).format(DateTimeFormatter.ofPattern("yyyyMMdd")) | |||
| val productProcessCode = generateProductProcessCode(datePrefix) | |||
| val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") | |||
| val productProcess = ProductProcess().apply { | |||
| this.jobOrder = jobOrder | |||
| this.productProcessCode =productProcessCode | |||
| this.item = item | |||
| this.status = ProductProcessStatus.PENDING | |||
| this.date = jobOrder?.planEnd?.toLocalDate() | |||
| this.bom = bom | |||
| } | |||
| productProcessRepository.save(productProcess) | |||
| bomProcess.forEach { bomProcess -> | |||
| val process = processRepository.findById(bomProcess?.process?.id?:0L).orElse(null) | |||
| val equipment = equipmentRepository.findById(bomProcess?.equipment?.id?:0L).orElse(null) | |||
| val productProcessLine = ProductProcessLine().apply { | |||
| this.productProcess = productProcess | |||
| this.bomProcess = bomProcess | |||
| // this.equipment = equipment | |||
| this.seqNo = bomProcess.seqNo?:0 | |||
| this.name = process?.name?:"" | |||
| this.description = bomProcess.description?:"" | |||
| this.equipmentType = equipment?.name?:"" | |||
| this.status = "Pending" | |||
| } | |||
| productProcessLineRepository.save(productProcessLine) | |||
| } | |||
| return MessageResponse( | |||
| id = null, | |||
| code = null, | |||
| name = null, | |||
| type = null, | |||
| message = null, | |||
| errorPosition = null, | |||
| ) | |||
| } | |||
| open fun UpdateProductProcessLineOperatorIdOrEquipmentId(request: UpdateProductProcessLineOperatorIdOrEquipmentIdRequest): MessageResponse { | |||
| val productProcessLine = productProcessLineRepository.findById(request.productProcessLineId).orElse(null) | |||
| val equipmentId = request.equipmentId | |||
| val user = userRepository.findById(request.operatorId).orElse(null) | |||
| val bomProcess= bomProcessRepository.findById(productProcessLine?.bomProcess?.id?:0L).orElse(null) | |||
| val bomProcessEquipment=bomProcess?.equipment | |||
| if(equipmentId != null &&( equipmentId==bomProcessEquipment?.id)) { | |||
| productProcessLine?.equipment = bomProcessEquipment | |||
| productProcessLineRepository.save(productProcessLine) | |||
| } | |||
| else | |||
| { | |||
| println("not match") | |||
| } | |||
| if(user != null) { | |||
| productProcessLine.operator = user | |||
| productProcessLineRepository.save(productProcessLine) | |||
| } | |||
| return MessageResponse( | |||
| id = null, | |||
| code = null, | |||
| name = null, | |||
| type = null, | |||
| message = null, | |||
| errorPosition = null, | |||
| ) | |||
| } | |||
| } | |||
| @@ -0,0 +1,170 @@ | |||
| package com.ffii.fpsms.modules.productProcess.web | |||
| import com.ffii.fpsms.modules.productProcess.entity.ProductProcess | |||
| import com.ffii.fpsms.modules.productProcess.entity.ProductProcessLine | |||
| import com.ffii.fpsms.modules.productProcess.service.ProductProcessService | |||
| import com.ffii.fpsms.modules.productProcess.web.model.* | |||
| import org.springframework.data.domain.Page | |||
| import org.springframework.data.domain.Pageable | |||
| import org.springframework.web.bind.annotation.* | |||
| import com.ffii.fpsms.modules.productProcess.entity.projections.ProductProcessInfo | |||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| @RestController | |||
| @RequestMapping("/product-process") | |||
| class ProductProcessController( | |||
| private val productProcessService: ProductProcessService | |||
| ) { | |||
| @GetMapping | |||
| fun findAll(pageable: Pageable): Page<ProductProcessSimpleResponse> { // ✅ 改为返回 DTO | |||
| println("📋 Controller: GET /product-process - Finding all") | |||
| val result = productProcessService.findAllAsDto(pageable) // ✅ 使用新方法 | |||
| println("✅ Controller: Returning ${result.totalElements} records") | |||
| return result | |||
| } | |||
| @GetMapping("/{id}") | |||
| fun findById(@PathVariable id: Long): ProductProcessSimpleResponse { // ✅ 改为返回 DTO | |||
| println("🔍 Controller: GET /product-process/$id") | |||
| val result = productProcessService.findByIdAsDto(id) // ✅ 使用新方法 | |||
| println("✅ Controller: Found: ${result.productProcessCode}") | |||
| return result | |||
| } | |||
| @GetMapping("/code/{code}") | |||
| fun findByCode(@PathVariable code: String): ProductProcess { | |||
| println("🔍 Controller: GET /product-process/code/$code") | |||
| val result = productProcessService.findByCode(code) | |||
| println("✅ Controller: Found ID: ${result.id}") | |||
| return result | |||
| } | |||
| @PostMapping | |||
| fun create(@RequestBody request: SaveProductProcessRequest): SaveProductProcessResponse { | |||
| println("💾 Controller: POST /product-process - Creating ProductProcess") | |||
| //println("📦 Controller: Request - bomId: ${request.bomId}, jobOrderId: ${request.jobOrderId}, date: ${request.date}") // ✅ 修复 | |||
| val result = productProcessService.create(request) | |||
| println("✅ Controller: Created ProductProcess - ID: ${result.id}, Code: ${result.productProcessCode}, Lines: ${result.linesCreated}") // ✅ 改进日志 | |||
| return result | |||
| } | |||
| @PostMapping("/{id}/start") | |||
| fun startProcess(@PathVariable id: Long, @RequestParam operatorId: Long): ProductProcessSimpleResponse { | |||
| println("▶️ Controller: POST /product-process/$id/start - operatorId: $operatorId") | |||
| val result = productProcessService.startProcessAsDto(id, operatorId) // ✅ 改用 AsDto 方法 | |||
| println("✅ Controller: Process started") | |||
| return result | |||
| } | |||
| @PostMapping("/{id}/stop") | |||
| fun stopProcess( | |||
| @PathVariable id: Long, | |||
| @RequestBody request: StopProcessRequest | |||
| ): ProductionProcessIssueResponse { // ✅ 改为 Issue Response | |||
| println("⏸️ Controller: POST /product-process/$id/stop") | |||
| println("📦 Controller: Reason: ${request.reason}") | |||
| val result = productProcessService.stopProcessAsDto(id, request) // ✅ 使用返回 DTO 的方法 | |||
| println("✅ Controller: Process stopped, issue ID: ${result.id}") | |||
| return result | |||
| } | |||
| @PostMapping("/{id}/resume/{issueId}") | |||
| fun resumeProcess( | |||
| @PathVariable id: Long, | |||
| @PathVariable issueId: Long | |||
| ): ProductionProcessIssueResponse { // ✅ 改为 Issue Response | |||
| println("▶️ Controller: POST /product-process/$id/resume/$issueId") | |||
| val result = productProcessService.resumeProcessAsDto(id, issueId) // ✅ 使用返回 DTO 的方法 | |||
| println("✅ Controller: Process resumed, stop duration: ${result.totalTime} min") | |||
| return result | |||
| } | |||
| @PostMapping("/{id}/complete") | |||
| fun completeProcess(@PathVariable id: Long): ProductProcessSimpleResponse { | |||
| println("✔️ Controller: POST /product-process/$id/complete") | |||
| val result = productProcessService.completeProcessAsDto(id) // ✅ 改用 AsDto 方法 | |||
| println("✅ Controller: Process completed") | |||
| return result | |||
| } | |||
| @PostMapping("/{id}/lines") | |||
| fun addLine( | |||
| @PathVariable id: Long, | |||
| @RequestBody request: SaveProductProcessLineRequest | |||
| ): SaveProductProcessLineResponse { | |||
| println("➕ Controller: POST /product-process/$id/lines") | |||
| println("📦 Controller: Line - name: ${request.name}, equipment: ${request.equipmentType}") | |||
| val result = productProcessService.addLine(id, request) | |||
| println("✅ Controller: Line added with ID: ${result.id}") | |||
| return result | |||
| } | |||
| @PutMapping("/lines/{lineId}/output") | |||
| fun updateLineOutput( | |||
| @PathVariable lineId: Long, | |||
| @RequestBody request: UpdateLineOutputRequest | |||
| ): ProductProcessLineResponse { // ✅ 改为 ProductProcessLineResponse | |||
| println("📊 Controller: PUT /lines/$lineId/output") | |||
| val result = productProcessService.updateLineOutputAsDto(lineId, request) | |||
| println("✅ Controller: Line output updated") | |||
| return result | |||
| } | |||
| @GetMapping("/{id}/lines") | |||
| fun getLines(@PathVariable id: Long): List<ProductProcessLineResponse> { | |||
| println("📋 Controller: GET /product-process/$id/lines") | |||
| val result = productProcessService.getLinesAsDto(id) | |||
| println("✅ Controller: Found ${result.size} lines") | |||
| return result | |||
| } | |||
| @GetMapping("/{id}/issues") | |||
| fun getIssues(@PathVariable id: Long): List<ProductionProcessIssueResponse> { // ✅ 改为 Issue Response List | |||
| println("📋 Controller: GET /product-process/$id/issues") | |||
| val result = productProcessService.getIssuesAsDto(id) // ✅ 使用返回 DTO 的方法 | |||
| println("✅ Controller: Found ${result.size} issues") | |||
| return result | |||
| } | |||
| @GetMapping("/by-job-order/{jobOrderId}") | |||
| fun findByJobOrderId(@PathVariable jobOrderId: Long): List<ProductProcessWithLinesResponse> { // ✅ 改为返回 DTO | |||
| println("🔍 Controller: GET /product-process/by-job-order/$jobOrderId") | |||
| val result = productProcessService.findByJobOrderIdWithLines(jobOrderId) // ✅ 直接使用 with-lines 方法 | |||
| println("✅ Controller: Found ${result.size} processes for Job Order $jobOrderId") | |||
| return result | |||
| } | |||
| // ✅ 额外添加:获取带 lines 的完整数据 | |||
| @GetMapping("/by-job-order/{jobOrderId}/with-lines") | |||
| fun findByJobOrderIdWithLines(@PathVariable jobOrderId: Long): List<ProductProcessWithLinesResponse> { | |||
| println("🔍 Controller: GET /product-process/by-job-order/$jobOrderId/with-lines") | |||
| val result = productProcessService.findByJobOrderIdWithLines(jobOrderId) | |||
| println("✅ Controller: Found ${result.size} processes with lines for Job Order $jobOrderId") | |||
| return result | |||
| } | |||
| @PostMapping("/lines/{lineId}/start") | |||
| fun startLine(@PathVariable lineId: Long, @RequestParam userId: Long): ProductProcessLineResponse { | |||
| println("▶️ Controller: POST /product-process/lines/$lineId/start - userId: $userId") | |||
| val entity = productProcessService.startLine(lineId, userId) | |||
| // ✅ 修复:返回正确的 DTO | |||
| return productProcessService.getLinesAsDto(entity.productProcess?.id!!).find { it.id == entity.id!! }!! | |||
| } | |||
| @GetMapping("/demo/{id}") | |||
| fun demo(@PathVariable id: Long): ProductProcessInfo { | |||
| return productProcessService.productProcessDetailfindbyidtest(id) | |||
| } | |||
| @GetMapping("/demo/joid/{joid}") | |||
| fun demojoid(@PathVariable joid: Long): List<ProductProcessInfo> { | |||
| return productProcessService.productProcessDetailfindbyjoid(joid) | |||
| } | |||
| @PostMapping("/demo/joid/{joid}") | |||
| fun democreate(@PathVariable joid: Long): MessageResponse { | |||
| return productProcessService.createProductProcessByJobOrderId(joid) | |||
| } | |||
| @PostMapping("/Demo/update") | |||
| fun demoupdate(@RequestBody request: UpdateProductProcessLineOperatorIdOrEquipmentIdRequest): MessageResponse { | |||
| return productProcessService.UpdateProductProcessLineOperatorIdOrEquipmentId(request) | |||
| } | |||
| } | |||
| @@ -0,0 +1,92 @@ | |||
| package com.ffii.fpsms.modules.productProcess.web.model | |||
| import java.time.LocalDate | |||
| import java.time.LocalDateTime | |||
| data class SaveProductProcessRequest( | |||
| val bomId: Long, | |||
| val jobOrderId: Long?, | |||
| ) | |||
| data class UpdateProductProcessLineOperatorIdOrEquipmentIdRequest( | |||
| val productProcessLineId: Long, | |||
| val operatorId: Long?, | |||
| val equipmentId: Long? | |||
| ) | |||
| data class ProductionProcessIssueResponse( | |||
| val id: Long, | |||
| val productProcessId: Long?, | |||
| val operatorId: Long?, | |||
| val operatorName: String?, | |||
| val reason: String, | |||
| val stopTime: LocalDateTime, | |||
| val resumeTime: LocalDateTime?, | |||
| val totalTime: Int? // 分钟 | |||
| ) | |||
| data class SaveProductProcessResponse( | |||
| val id: Long, | |||
| val productProcessCode: String? = null, // ✅ 添加 | |||
| val linesCreated: Int = 0 // ✅ 添加 | |||
| ) | |||
| data class ProductProcessWithLinesResponse( | |||
| val id: Long, | |||
| val productProcessCode: String?, | |||
| val status: String, | |||
| val startTime: LocalDateTime?, | |||
| val endTime: LocalDateTime?, | |||
| val date: LocalDate?, | |||
| val lines: List<ProductProcessLineResponse> | |||
| ) | |||
| data class ProductProcessSimpleResponse( | |||
| val id: Long, | |||
| val productProcessCode: String?, | |||
| val status: String, | |||
| val startTime: LocalDateTime?, | |||
| val endTime: LocalDateTime?, | |||
| val date: LocalDate?, | |||
| val bomId: Long?, | |||
| val jobOrderId: Long? | |||
| ) | |||
| data class ProductProcessLineResponse( | |||
| val id: Long, | |||
| val seqNo: Long?, | |||
| val name: String?, | |||
| val description: String?, | |||
| val equipmentType: String?, | |||
| val startTime: LocalDateTime?, | |||
| val endTime: LocalDateTime?, | |||
| val outputFromProcessQty: Int?, | |||
| val outputFromProcessUom: String?, | |||
| val defectQty: Int?, | |||
| val scrapQty: Int?, | |||
| val byproductName: String?, | |||
| val byproductQty: Int? | |||
| ) | |||
| data class SaveProductProcessLineRequest( | |||
| val name: String?, | |||
| val description: String?, | |||
| val equipmentType: String?, | |||
| val operatorId: Long?, | |||
| val equipmentId: Long? | |||
| ) | |||
| data class SaveProductProcessLineResponse( | |||
| val id: Long | |||
| ) | |||
| data class StopProcessRequest( | |||
| val operatorId: Long?, | |||
| val operatorName: String?, | |||
| val reason: String | |||
| ) | |||
| data class UpdateLineOutputRequest( | |||
| val outputQty: Int?, | |||
| val outputUom: String?, | |||
| val defectQty: Int?, | |||
| val defectUom: String?, | |||
| val scrapQty: Int?, | |||
| val scrapUom: String?, | |||
| val byproductName: String?, | |||
| val byproductQty: Int?, | |||
| val byproductUom: String? | |||
| ) | |||
| @@ -368,12 +368,14 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { | |||
| // 3) 全部完成 → 更新 do_pick_order 为 completed,并可复制到 record 表(可选) | |||
| val dpo = doPickOrderRepository.findById(dpoId).orElse(null) ?: return@forEach | |||
| dpo.ticketStatus = DoPickOrderStatus.completed | |||
| dpo.ticketCompleteDateTime = LocalDateTime.now() | |||
| doPickOrderRepository.save(dpo) | |||
| // 4) 可选:复制 header 到 record 表,复制 lines 到 line_record,再删除原有行/头(与你在 PickOrderService.completeStockOut 的做法保持一致) | |||
| val dpoRecord = DoPickOrderRecord( | |||
| recordId =dpoId, | |||
| storeId = dpo.storeId ?: "", | |||
| ticketNo = dpo.ticketNo ?: "", | |||
| ticketStatus = DoPickOrderStatus.completed, | |||
| @@ -398,6 +400,7 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { | |||
| val lineRecords = allLines.map { l -> | |||
| DoPickOrderLineRecord().apply { | |||
| this.recordId = l.id | |||
| this.doPickOrderId = savedHeader.id | |||
| this.pickOrderId = l.pickOrderId | |||
| this.doOrderId = l.doOrderId | |||
| @@ -0,0 +1,106 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset enson:alter_pick_execution_issue_category_20251023 | |||
| CREATE TABLE productprocess ( | |||
| id INT AUTO_INCREMENT PRIMARY KEY, | |||
| productprocesscode VARCHAR(50) UNIQUE NOT NULL COMMENT '生产工序编号', | |||
| jobOrderId INT COMMENT '工单ID', | |||
| itemsId INT COMMENT '产品ID', | |||
| -- 状态 | |||
| status VARCHAR(20) DEFAULT 'pending' , | |||
| -- 时间 | |||
| startTime DATETIME , | |||
| endTime DATETIME , | |||
| date DATE , | |||
| -- 审计字段 | |||
| created DATETIME, | |||
| createdBy VARCHAR(30), | |||
| version INT DEFAULT 0, | |||
| modified DATETIME, | |||
| modifiedBy VARCHAR(30), | |||
| deleted TINYINT(1) DEFAULT 0, | |||
| FOREIGN KEY (jobOrderId) REFERENCES job_order(id), | |||
| FOREIGN KEY (itemsId) REFERENCES items(id), | |||
| INDEX idx_status (status), | |||
| INDEX idx_date (date), | |||
| INDEX idx_job_order (jobOrderId) | |||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; | |||
| CREATE TABLE productprocessline ( | |||
| id INT AUTO_INCREMENT PRIMARY KEY, | |||
| productprocessid INT NOT NULL , | |||
| -- 操作员和设备 | |||
| operatorId INT , | |||
| equipmentId INT, | |||
| -- 步骤信息 | |||
| name VARCHAR(100) , | |||
| description varchar(255) , | |||
| equipment VARCHAR(100) , | |||
| -- 副产品信息 | |||
| byproductId INT , | |||
| byproductName VARCHAR(100) , | |||
| byproductQty DECIMAL(16,2) , | |||
| byproductUom VARCHAR(20) , | |||
| -- 废品/次品/产出 | |||
| scrapQty DECIMAL(16,2) , | |||
| scrapUom VARCHAR(20) , | |||
| defectQty DECIMAL(16,2) , | |||
| defectUom VARCHAR(20) , | |||
| outputFromProcessQty DECIMAL(16,2) , | |||
| outputFromProcessUom VARCHAR(20) , | |||
| -- 时间 | |||
| startTime DATETIME , | |||
| endTime DATETIME , | |||
| -- 审计字段 | |||
| created DATETIME, | |||
| createdBy VARCHAR(30), | |||
| version INT DEFAULT 0, | |||
| modified DATETIME, | |||
| modifiedBy VARCHAR(30), | |||
| deleted TINYINT(1) DEFAULT 0, | |||
| FOREIGN KEY (productprocessid) REFERENCES productprocess(id), | |||
| FOREIGN KEY (operatorId) REFERENCES user(id), | |||
| FOREIGN KEY (equipmentId) REFERENCES equipment(id), | |||
| FOREIGN KEY (byproductId) REFERENCES items(id), | |||
| INDEX idx_productprocess (productprocessid) | |||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; | |||
| CREATE TABLE productionprocessissue ( | |||
| id INT AUTO_INCREMENT PRIMARY KEY, | |||
| productprocessid INT NOT NULL , | |||
| operatorId INT , | |||
| operatorName VARCHAR(100) , | |||
| -- 暂停信息 | |||
| reason varchar(255) NOT NULL , | |||
| stopTime DATETIME NOT NULL , | |||
| resumeTime DATETIME , | |||
| totalTime INT , | |||
| -- 审计字段 | |||
| created DATETIME, | |||
| createdBy VARCHAR(30), | |||
| version INT DEFAULT 0, | |||
| modified DATETIME, | |||
| modifiedBy VARCHAR(30), | |||
| deleted TINYINT(1) DEFAULT 0, | |||
| FOREIGN KEY (productprocessid) REFERENCES productprocess(id), | |||
| FOREIGN KEY (operatorId) REFERENCES user(id), | |||
| INDEX idx_productprocess (productprocessid), | |||
| INDEX idx_stop_time (stopTime) | |||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; | |||
| @@ -0,0 +1,12 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset enson:alter_pick_execution_issue_category_20251023 | |||
| ALTER TABLE productprocess | |||
| ADD COLUMN bomId INT COMMENT 'BOM ID'; | |||
| ALTER TABLE productprocess | |||
| ADD CONSTRAINT fk_productprocess_bom | |||
| FOREIGN KEY (bomId) REFERENCES bom(id); | |||
| -- 添加索引 | |||
| CREATE INDEX idx_productprocess_bom ON productprocess(bomId); | |||
| @@ -0,0 +1,15 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset enson:alter_pick_execution_issue_category_20251023 | |||
| ALTER TABLE productprocessline | |||
| ADD COLUMN bomProcessId INT , | |||
| ADD COLUMN seqNo INT; | |||
| ALTER TABLE productprocessline | |||
| ADD CONSTRAINT fk_productprocessline_bomprocess | |||
| FOREIGN KEY (bomProcessId) REFERENCES bom_process(id); | |||
| -- 添加索引 | |||
| CREATE INDEX idx_productprocessline_bomprocess ON productprocessline(bomProcessId); | |||
| CREATE INDEX idx_productprocessline_seqno ON productprocessline(seqNo); | |||
| @@ -0,0 +1,5 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset Enson:alter qc category table | |||
| ALTER TABLE `productprocessline` | |||
| ADD COLUMN `handlerId` INT NULL AFTER `equipmentId`; | |||
| @@ -0,0 +1,12 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset Enson:alter qc category table | |||
| ALTER TABLE `productprocessline` | |||
| CHANGE COLUMN `equipment` `equipment_name` VARCHAR(255); | |||
| ALTER TABLE `productprocessline` | |||
| ADD COLUMN `status` VARCHAR(255) DEFAULT 'pending' AFTER `equipment_name`; | |||
| ALTER TABLE `productprocessline` | |||
| MODIFY COLUMN `bomprocessId` INT AFTER `productprocessId`, | |||
| MODIFY COLUMN `seqNo` INT AFTER `handlerId`; | |||
| @@ -0,0 +1,17 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset enson:altertable_enson | |||
| ALTER TABLE `fpsmsdb`.`pick_execution_issue` | |||
| DROP FOREIGN KEY `FK_PICK_EXECUTION_ISSUE_ON_PICK_ORDER`, | |||
| DROP FOREIGN KEY `FK_PICK_EXECUTION_ISSUE_ON_PICK_ORDER_LINE`; | |||
| ALTER TABLE `fpsmsdb`.`pick_execution_issue` | |||
| CHANGE COLUMN `pick_order_id` `pick_order_id` INT NULL , | |||
| CHANGE COLUMN `pick_order_code` `pick_order_code` VARCHAR(50) NULL , | |||
| CHANGE COLUMN `pick_order_line_id` `pick_order_line_id` INT NULL ; | |||
| ALTER TABLE `fpsmsdb`.`pick_execution_issue` | |||
| ADD CONSTRAINT `FK_PICK_EXECUTION_ISSUE_ON_PICK_ORDER` | |||
| FOREIGN KEY (`pick_order_id`) | |||
| REFERENCES `fpsmsdb`.`pick_order` (`id`), | |||
| ADD CONSTRAINT `FK_PICK_EXECUTION_ISSUE_ON_PICK_ORDER_LINE` | |||
| FOREIGN KEY (`pick_order_line_id`) | |||
| REFERENCES `fpsmsdb`.`pick_order_line` (`id`); | |||
| @@ -0,0 +1,7 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset enson:altertable_enson | |||
| ALTER TABLE `fpsmsdb`.`do_pick_order_record` | |||
| ADD COLUMN `record_id` INT NOT NULL After `id`; | |||
| ALTER TABLE `fpsmsdb`.`do_pick_order_line_record` | |||
| ADD COLUMN `record_id` INT NOT NULL After `id`; | |||