| @@ -76,4 +76,8 @@ open class JobOrder : BaseEntity<Long>() { | |||||
| @JsonManagedReference | @JsonManagedReference | ||||
| @OneToMany(mappedBy = "jobOrder", cascade = [CascadeType.ALL], orphanRemoval = true) | @OneToMany(mappedBy = "jobOrder", cascade = [CascadeType.ALL], orphanRemoval = true) | ||||
| open var stockInLines: MutableList<StockInLine> = mutableListOf() | open var stockInLines: MutableList<StockInLine> = mutableListOf() | ||||
| @Column(name = "jobTypeId") | |||||
| open var jobTypeId: Long? = null | |||||
| } | } | ||||
| @@ -0,0 +1,26 @@ | |||||
| package com.ffii.fpsms.modules.jobOrder.entity | |||||
| import com.fasterxml.jackson.annotation.JsonManagedReference | |||||
| import com.ffii.core.entity.BaseEntity | |||||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus | |||||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatusConverter | |||||
| import com.ffii.fpsms.modules.master.entity.Bom | |||||
| import com.ffii.fpsms.modules.master.entity.ProductionScheduleLine | |||||
| import com.ffii.fpsms.modules.stock.entity.StockInLine | |||||
| import com.ffii.fpsms.modules.user.entity.User | |||||
| import jakarta.persistence.* | |||||
| import jakarta.validation.constraints.NotNull | |||||
| import jakarta.validation.constraints.Size | |||||
| import java.math.BigDecimal | |||||
| import java.time.LocalDateTime | |||||
| @Entity | |||||
| @Table(name = "jobtype") | |||||
| open class JobType : BaseEntity<Long>() { | |||||
| @Size(max = 100) | |||||
| @NotNull | |||||
| @Column(name = "name", nullable = false, length = 100) | |||||
| open var code: String? = null | |||||
| } | |||||
| @@ -0,0 +1,19 @@ | |||||
| package com.ffii.fpsms.modules.jobOrder.entity | |||||
| import com.ffii.core.support.AbstractRepository | |||||
| import com.ffii.fpsms.modules.jobOrder.enums.JoPickOrderStatus | |||||
| import com.ffii.fpsms.modules.jobOrder.entity.JoPickOrder | |||||
| import com.ffii.fpsms.modules.master.entity.projections.SearchId | |||||
| import org.springframework.data.jpa.repository.JpaRepository | |||||
| import org.springframework.data.jpa.repository.Query | |||||
| import org.springframework.stereotype.Repository | |||||
| import java.io.Serializable | |||||
| import java.time.LocalDateTime | |||||
| import com.ffii.fpsms.modules.jobOrder.entity.JobType | |||||
| import java.util.Optional | |||||
| @Repository | |||||
| interface JobTypeRepository : JpaRepository<JobType, Long> { | |||||
| //fun findByName(name: String): Optional<JobType> | |||||
| //fun findByIdAndDeletedIsFalse(id: Long): Optional<JobType> | |||||
| } | |||||
| @@ -31,7 +31,7 @@ import java.time.LocalDate | |||||
| import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
| import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||
| import kotlin.jvm.optionals.getOrNull | import kotlin.jvm.optionals.getOrNull | ||||
| import com.ffii.fpsms.modules.jobOrder.entity.JobTypeRepository | |||||
| import com.ffii.fpsms.modules.jobOrder.web.model.* | import com.ffii.fpsms.modules.jobOrder.web.model.* | ||||
| import com.ffii.fpsms.modules.jobOrder.web.model.ExportPickRecordRequest | import com.ffii.fpsms.modules.jobOrder.web.model.ExportPickRecordRequest | ||||
| import com.ffii.fpsms.modules.jobOrder.web.model.PrintPickRecordRequest | import com.ffii.fpsms.modules.jobOrder.web.model.PrintPickRecordRequest | ||||
| @@ -71,12 +71,13 @@ open class JobOrderService( | |||||
| val stockOutRepository: StockOutRepository, | val stockOutRepository: StockOutRepository, | ||||
| val stockOutLineRepository: StockOutLIneRepository, | val stockOutLineRepository: StockOutLIneRepository, | ||||
| private val printerService: PrinterService, | private val printerService: PrinterService, | ||||
| val jobTypeRepository: JobTypeRepository | |||||
| ) { | ) { | ||||
| open fun allJobOrdersByPage(request: SearchJobOrderInfoRequest): RecordsRes<JobOrderInfo> { | open fun allJobOrdersByPage(request: SearchJobOrderInfoRequest): RecordsRes<JobOrderInfo> { | ||||
| val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10); | val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10); | ||||
| println("allJobOrdersByPage") | |||||
| println(request) | |||||
| val response = jobOrderRepository.findJobOrderInfoByCodeContainsAndBomNameContainsAndDeletedIsFalseOrderByIdDesc( | val response = jobOrderRepository.findJobOrderInfoByCodeContainsAndBomNameContainsAndDeletedIsFalseOrderByIdDesc( | ||||
| code = request.code ?: "", | code = request.code ?: "", | ||||
| bomName = request.itemName ?: "", | bomName = request.itemName ?: "", | ||||
| @@ -161,7 +162,9 @@ open class JobOrderService( | |||||
| val prodScheduleLine = request.prodScheduleLineId?.let { productionScheduleLineRepository.findById(it).getOrNull() } | val prodScheduleLine = request.prodScheduleLineId?.let { productionScheduleLineRepository.findById(it).getOrNull() } | ||||
| val code = assignJobNo() | val code = assignJobNo() | ||||
| val status = JobOrderStatus.entries.find { it.value == request.status } | val status = JobOrderStatus.entries.find { it.value == request.status } | ||||
| //val jobTypeId = jobTypeRepository.findByName(request.jobType).orElse(null)?.id | |||||
| val jobTypeId = request.jobTypeId | |||||
| jo.apply { | jo.apply { | ||||
| this.code = code | this.code = code | ||||
| this.bom = bom | this.bom = bom | ||||
| @@ -173,6 +176,7 @@ open class JobOrderService( | |||||
| type = request.type | type = request.type | ||||
| this.approver = approver | this.approver = approver | ||||
| this.prodScheduleLine = prodScheduleLine | this.prodScheduleLine = prodScheduleLine | ||||
| this.jobTypeId = jobTypeId | |||||
| } | } | ||||
| val savedJo = jobOrderRepository.saveAndFlush(jo); | val savedJo = jobOrderRepository.saveAndFlush(jo); | ||||
| @@ -224,7 +228,7 @@ open class JobOrderService( | |||||
| } | } | ||||
| jobOrderRepository.save(jo) | jobOrderRepository.save(jo) | ||||
| val pols = jo.jobms.filter { it.item?.type != "mat"&& it.item?.type != "item"}. | |||||
| val pols = jo.jobms.filter { it.item?.type != "CMB"&& it.item?.type != "item"}. | |||||
| map { | map { | ||||
| SavePickOrderLineRequest( | SavePickOrderLineRequest( | ||||
| itemId = it.item?.id, | itemId = it.item?.id, | ||||
| @@ -47,6 +47,8 @@ class JobOrderController( | |||||
| @GetMapping("/getRecordByPage") | @GetMapping("/getRecordByPage") | ||||
| fun allJobOrdersByPage(@ModelAttribute request: SearchJobOrderInfoRequest): RecordsRes<JobOrderInfo> { | fun allJobOrdersByPage(@ModelAttribute request: SearchJobOrderInfoRequest): RecordsRes<JobOrderInfo> { | ||||
| println("getRecordByPage") | |||||
| println(request) | |||||
| return jobOrderService.allJobOrdersByPage(request); | return jobOrderService.allJobOrdersByPage(request); | ||||
| } | } | ||||
| @@ -9,6 +9,8 @@ data class CreateJobOrderRequest ( | |||||
| val planStart: LocalDateTime? = null, | val planStart: LocalDateTime? = null, | ||||
| val planEnd: LocalDateTime? = null, | val planEnd: LocalDateTime? = null, | ||||
| val reqQty: BigDecimal?, | val reqQty: BigDecimal?, | ||||
| //val jobType: String?=null, | |||||
| val jobTypeId: Long?=null, | |||||
| val type: String? = "detailed", | val type: String? = "detailed", | ||||
| val approverId: Long? = null, | val approverId: Long? = null, | ||||
| val prodScheduleLineId: Long? = null, | val prodScheduleLineId: Long? = null, | ||||
| @@ -26,4 +26,8 @@ open class EquipmentDetail : BaseEntity<Long>() { | |||||
| open var description: String? = null | open var description: String? = null | ||||
| @Column(name = "equipmentTypeID") | |||||
| open var equipmentTypeId: Long? = null | |||||
| } | } | ||||
| @@ -6,12 +6,11 @@ import org.springframework.stereotype.Repository | |||||
| @Repository | @Repository | ||||
| interface EquipmentDetailRepository : AbstractRepository<EquipmentDetail, Long> { | interface EquipmentDetailRepository : AbstractRepository<EquipmentDetail, Long> { | ||||
| fun findAllByDeletedFalse(): List<EquipmentDetail>; | fun findAllByDeletedFalse(): List<EquipmentDetail>; | ||||
| fun findByCodeAndDeletedFalse(code: String): EquipmentDetail? | |||||
| fun findByCode(code: String): EquipmentDetail? | |||||
| fun findByIdAndDeletedFalse(id: Long): EquipmentDetail?; | fun findByIdAndDeletedFalse(id: Long): EquipmentDetail?; | ||||
| fun findByCodeAndDeletedIsFalse(code: String): EquipmentDetail?; | |||||
| fun findByNameAndDeletedIsFalse(name: String): EquipmentDetail?; | fun findByNameAndDeletedIsFalse(name: String): EquipmentDetail?; | ||||
| fun findByDescriptionAndDeletedIsFalse(description: String): EquipmentDetail?; | fun findByDescriptionAndDeletedIsFalse(description: String): EquipmentDetail?; | ||||
| } | } | ||||
| @@ -39,11 +39,11 @@ open class EquipmentDetailService( | |||||
| open fun findById(id: Long): EquipmentDetail? { | open fun findById(id: Long): EquipmentDetail? { | ||||
| return equipmentDetailRepository.findByIdAndDeletedFalse(id) | return equipmentDetailRepository.findByIdAndDeletedFalse(id) | ||||
| } | } | ||||
| /* | |||||
| open fun findByCode(code: String): EquipmentDetail? { | open fun findByCode(code: String): EquipmentDetail? { | ||||
| return equipmentDetailRepository.findByCodeAndDeletedIsFalse(code) | return equipmentDetailRepository.findByCodeAndDeletedIsFalse(code) | ||||
| } | } | ||||
| */ | |||||
| open fun findByName(name: String): EquipmentDetail? { | open fun findByName(name: String): EquipmentDetail? { | ||||
| return equipmentDetailRepository.findByNameAndDeletedIsFalse(name) | return equipmentDetailRepository.findByNameAndDeletedIsFalse(name) | ||||
| } | } | ||||
| @@ -51,7 +51,7 @@ open class EquipmentDetailService( | |||||
| open fun findByDescription(description: String): EquipmentDetail? { | open fun findByDescription(description: String): EquipmentDetail? { | ||||
| return equipmentDetailRepository.findByDescriptionAndDeletedIsFalse(description) | return equipmentDetailRepository.findByDescriptionAndDeletedIsFalse(description) | ||||
| } | } | ||||
| /* | |||||
| @Transactional | @Transactional | ||||
| open fun saveEquipmentDetail(request: NewEquipmentDetailRequest): EquipmentDetail { | open fun saveEquipmentDetail(request: NewEquipmentDetailRequest): EquipmentDetail { | ||||
| @@ -78,7 +78,7 @@ open class EquipmentDetailService( | |||||
| return equipmentDetailRepository.saveAndFlush(entity) | return equipmentDetailRepository.saveAndFlush(entity) | ||||
| } | } | ||||
| */ | |||||
| @Transactional | @Transactional | ||||
| open fun deleteEquipmentDetail(id: Long) { | open fun deleteEquipmentDetail(id: Long) { | ||||
| val equipmentDetail = equipmentDetailRepository.findByIdAndDeletedFalse(id) | val equipmentDetail = equipmentDetailRepository.findByIdAndDeletedFalse(id) | ||||
| @@ -388,7 +388,8 @@ open class ProductionScheduleService( | |||||
| bomId = bom?.id, | bomId = bom?.id, | ||||
| reqQty = request.demandQty, | reqQty = request.demandQty, | ||||
| approverId = approver?.id, | approverId = approver?.id, | ||||
| prodScheduleLineId = request.id | |||||
| prodScheduleLineId = request.id, | |||||
| //jobType = null, | |||||
| ) | ) | ||||
| val jo = jobOrderService.createJobOrder(joRequest) | val jo = jobOrderService.createJobOrder(joRequest) | ||||
| @@ -60,13 +60,13 @@ fun getAllEquipmentDetailByPage( | |||||
| fun getEquipmentDetail(@PathVariable id: Long): EquipmentDetail? { | fun getEquipmentDetail(@PathVariable id: Long): EquipmentDetail? { | ||||
| return equipmentDetailService.findById(id) | return equipmentDetailService.findById(id) | ||||
| } | } | ||||
| /* | |||||
| // 新增/编辑 | // 新增/编辑 | ||||
| @PostMapping("/save") | @PostMapping("/save") | ||||
| fun saveEquipmentDetail(@Valid @RequestBody equipmentDetail: NewEquipmentDetailRequest): EquipmentDetail { | fun saveEquipmentDetail(@Valid @RequestBody equipmentDetail: NewEquipmentDetailRequest): EquipmentDetail { | ||||
| return equipmentDetailService.saveEquipmentDetail(equipmentDetail) | return equipmentDetailService.saveEquipmentDetail(equipmentDetail) | ||||
| } | } | ||||
| */ | |||||
| // 逻辑删除 | // 逻辑删除 | ||||
| @DeleteMapping("/delete/{id}") | @DeleteMapping("/delete/{id}") | ||||
| fun deleteEquipmentDetail(@PathVariable id: Long) { | fun deleteEquipmentDetail(@PathVariable id: Long) { | ||||
| @@ -233,6 +233,7 @@ open class PickOrderService( | |||||
| suggestedList = emptyList() // Empty list since you don't need suggestions | suggestedList = emptyList() // Empty list since you don't need suggestions | ||||
| ) | ) | ||||
| } | } | ||||
| val groupName = po.id?.let { pickOrderId -> | val groupName = po.id?.let { pickOrderId -> | ||||
| groupsByPickOrderId[pickOrderId]?.firstOrNull()?.name | groupsByPickOrderId[pickOrderId]?.firstOrNull()?.name | ||||
| } ?: "No Group" | } ?: "No Group" | ||||
| @@ -732,57 +733,69 @@ open class PickOrderService( | |||||
| itemAvailableQtyMap[itemId] = totalAvailableQty | itemAvailableQtyMap[itemId] = totalAvailableQty | ||||
| } | } | ||||
| // Pick Orders | |||||
| val releasePickOrderLineInfos = pos | val releasePickOrderLineInfos = pos | ||||
| .map { po -> | |||||
| val releasePickOrderLineInfos = po.pickOrderLines.map { pol -> | |||||
| val itemId = pol.item?.id | |||||
| val availableQty = itemId?.let { itemAvailableQtyMap[it] } ?: zero | |||||
| // Move stockOutLines declaration inside the pol loop | |||||
| val stockOutLines = stockOutLinesByPickOrderLineId[pol.id] ?: emptyList() | |||||
| // Calculate total picked quantity from stock out lines | |||||
| println("=== PICKED QTY DEBUG: Line ${pol.id} ===") | |||||
| println("Stock Out Lines: ${stockOutLines.map { "${it.id}(status=${it.status}, qty=${it.qty})" }}") | |||||
| val totalPickedQty = stockOutLines | |||||
| .sumOf { it.qty ?: zero } | |||||
| println("Total Picked Qty: $totalPickedQty") | |||||
| println("=== END DEBUG ===") | |||||
| // Return | |||||
| GetPickOrderLineInfo( | |||||
| id = pol.id, | |||||
| itemId = pol.item?.id, | |||||
| itemCode = pol.item?.code, | |||||
| itemName = pol.item?.name, | |||||
| availableQty = availableQty, // Use pre-calculated value | |||||
| requiredQty = pol.qty, | |||||
| uomCode = pol.uom?.code, | |||||
| uomDesc = pol.uom?.udfudesc, | |||||
| suggestedList = suggestions.suggestedList.filter { it.pickOrderLine?.id == pol.id }, | |||||
| pickedQty = totalPickedQty | |||||
| ) | |||||
| } | |||||
| val groupName = po.id?.let { pickOrderId -> | |||||
| groupsByPickOrderId[pickOrderId]?.firstOrNull()?.name | |||||
| } ?: "No Group" | |||||
| // Return | |||||
| GetPickOrderInfo( | |||||
| id = po.id, | |||||
| code = po.code, | |||||
| consoCode = po.consoCode, | |||||
| targetDate = po.targetDate, | |||||
| type = po.type?.value, | |||||
| status = po.status?.value, | |||||
| assignTo = po.assignTo?.id, | |||||
| groupName = groupName, | |||||
| pickOrderLines = releasePickOrderLineInfos | |||||
| .map { po -> | |||||
| val releasePickOrderLineInfos = po.pickOrderLines.map { pol -> | |||||
| val itemId = pol.item?.id | |||||
| val availableQty = itemId?.let { itemAvailableQtyMap[it] } ?: zero | |||||
| // 取得這一行的所有 stock_out_line | |||||
| val stockOutLines = stockOutLinesByPickOrderLineId[pol.id] ?: emptyList() | |||||
| // Debug: 已拣数量 | |||||
| println("=== PICKED QTY DEBUG: Line ${pol.id} ===") | |||||
| println("Stock Out Lines: ${stockOutLines.map { "${it.id}(status=${it.status}, qty=${it.qty})" }}") | |||||
| val totalPickedQty = stockOutLines.sumOf { it.qty ?: zero } | |||||
| println("Total Picked Qty: $totalPickedQty") | |||||
| println("=== END DEBUG ===") | |||||
| // ✅ 無批次的 stock_out_line(inventoryLotLineId 為空) | |||||
| val noLotStockouts = stockOutLines | |||||
| .filter { it.inventoryLotLineId == null } // 注意:用 inventoryLotLineId | |||||
| .map { sol -> | |||||
| NoLotLineDto( | |||||
| stockOutLineId = sol.id!!, | |||||
| status = sol.status, | |||||
| qty = sol.qty ?: zero, | |||||
| created = null, // 若實體沒有 created/modified 欄位,就先給 null | |||||
| modified = null | |||||
| ) | |||||
| } | |||||
| // 回傳行資訊 | |||||
| GetPickOrderLineInfo( | |||||
| id = pol.id, | |||||
| itemId = pol.item?.id, | |||||
| itemCode = pol.item?.code, | |||||
| itemName = pol.item?.name, | |||||
| availableQty = availableQty, | |||||
| requiredQty = pol.qty, | |||||
| uomCode = pol.uom?.code, | |||||
| uomDesc = pol.uom?.udfudesc, | |||||
| suggestedList = suggestions.suggestedList.filter { it.pickOrderLine?.id == pol.id }, | |||||
| pickedQty = totalPickedQty, | |||||
| noLotLines = noLotStockouts // ✅ 關鍵:把無批次行帶出去 | |||||
| ) | ) | ||||
| } | } | ||||
| val groupName = po.id?.let { pickOrderId -> | |||||
| groupsByPickOrderId[pickOrderId]?.firstOrNull()?.name | |||||
| } ?: "No Group" | |||||
| GetPickOrderInfo( | |||||
| id = po.id, | |||||
| code = po.code, | |||||
| consoCode = po.consoCode, | |||||
| targetDate = po.targetDate, | |||||
| type = po.type?.value, | |||||
| status = po.status?.value, | |||||
| assignTo = po.assignTo?.id, | |||||
| groupName = groupName, | |||||
| pickOrderLines = releasePickOrderLineInfos | |||||
| ) | |||||
| } | |||||
| // Items | // Items | ||||
| val currentInventoryInfos = requiredItems.map { item -> | val currentInventoryInfos = requiredItems.map { item -> | ||||
| val inventory = item.first?.let { inventories[it] } | val inventory = item.first?.let { inventories[it] } | ||||
| @@ -829,132 +842,164 @@ open class PickOrderService( | |||||
| return getPickOrdersInfo(releasedPickOrderIds) | return getPickOrdersInfo(releasedPickOrderIds) | ||||
| } | } | ||||
| open fun getPickOrderLineLotDetails(pickOrderLineId: Long): List<Map<String, Any>> { | |||||
| open fun getPickOrderLineLotDetails(pickOrderLineId: Long): List<PickOrderLineLotDetailResponse> { | |||||
| val today = LocalDate.now() | val today = LocalDate.now() | ||||
| println("=== Debug: getPickOrderLineLotDetails ===") | |||||
| val zero = BigDecimal.ZERO | |||||
| println("=== Debug: getPickOrderLineLotDetails (Repository-based) ===") | |||||
| println("pickOrderLineId: $pickOrderLineId") | println("pickOrderLineId: $pickOrderLineId") | ||||
| println("today: $today") | println("today: $today") | ||||
| val sql = """ | |||||
| SELECT | |||||
| ill.id as lotId, | |||||
| il.lotNo, | |||||
| il.expiryDate, | |||||
| w.name as location, | |||||
| COALESCE(uc.udfudesc, 'N/A') as stockUnit, | |||||
| (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as availableQty, | |||||
| COALESCE(spl.qty, 0) as requiredQty, -- 使用COALESCE处理null值 | |||||
| COALESCE(ill.inQty, 0) as inQty, | |||||
| COALESCE(ill.outQty, 0) as outQty, | |||||
| COALESCE(ill.holdQty, 0) as holdQty, | |||||
| COALESCE(sol.qty, 0) as actualPickQty, | |||||
| COALESCE(spl.id, 0) as suggestedPickLotId, -- 使用COALESCE处理null值 | |||||
| ill.status as lotStatus, | |||||
| sol.id as stockOutLineId, | |||||
| sol.status as stockOutLineStatus, | |||||
| sol.qty as stockOutLineQty, | |||||
| COALESCE(spl.suggestedLotLineId, ill.id) as debugSuggestedLotLineId, -- 使用COALESCE处理null值 | |||||
| ill.inventoryLotId as debugInventoryLotId, | |||||
| -- Calculate total picked quantity by ALL pick orders for this lot | |||||
| COALESCE(( | |||||
| SELECT SUM(sol_all.qty) | |||||
| FROM fpsmsdb.stock_out_line sol_all | |||||
| WHERE sol_all.inventoryLotLineId = ill.id | |||||
| AND sol_all.deleted = false | |||||
| AND sol_all.status IN ('pending', 'checked', 'partially_completed', 'completed') | |||||
| ), 0) as totalPickedByAllPickOrders, | |||||
| -- FIXED: Calculate remaining available quantity correctly | |||||
| (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as remainingAfterAllPickOrders, | |||||
| -- Add detailed debug fields for lotAvailability calculation | |||||
| ill.status as debug_ill_status, | |||||
| (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) as debug_is_expired, | |||||
| sol.status as debug_sol_status, | |||||
| (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as debug_remaining_stock, | |||||
| (COALESCE(spl.qty, 0) - COALESCE(sol.qty, 0)) as debug_required_after_picked, | |||||
| CASE | |||||
| -- FIXED: Check if lot is expired | |||||
| WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired' | |||||
| -- FIXED: Check if lot has rejected stock out line for this pick order | |||||
| WHEN sol.status = 'rejected' THEN 'rejected' | |||||
| -- FIXED: Check if lot is unavailable due to insufficient stock | |||||
| WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock' | |||||
| -- FIXED: Check if lot status is unavailable | |||||
| WHEN ill.status = 'unavailable' THEN 'status_unavailable' | |||||
| -- Default to available | |||||
| ELSE 'available' | |||||
| END as lotAvailability | |||||
| FROM fpsmsdb.inventory_lot_line ill | |||||
| JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId | |||||
| LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId | |||||
| LEFT JOIN fpsmsdb.item_uom sales_iu ON sales_iu.itemId = il.itemId AND sales_iu.salesUnit = true AND sales_iu.deleted = false | |||||
| LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = sales_iu.uomId | |||||
| -- FIXED: Include both suggested lots AND lots with stock out lines for this pick order | |||||
| LEFT JOIN fpsmsdb.suggested_pick_lot spl ON spl.suggestedLotLineId = ill.id AND spl.pickOrderLineId = :pickOrderLineId | |||||
| LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = :pickOrderLineId AND sol.inventoryLotLineId = ill.id AND sol.deleted = false | |||||
| -- FIXED: Only include lots that are either suggested OR have stock out lines for this pick order | |||||
| WHERE (spl.pickOrderLineId = :pickOrderLineId OR sol.pickOrderLineId = :pickOrderLineId) | |||||
| AND(sol.status IS NULL OR sol.status != 'completed') | |||||
| AND ill.deleted = false | |||||
| AND il.deleted = false | |||||
| ORDER BY | |||||
| CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END, -- Show rejected lots first | |||||
| il.expiryDate ASC, | |||||
| il.lotNo ASC | |||||
| """.trimIndent() | |||||
| println("🔍 Executing SQL for lot details: $sql") | |||||
| println("🔍 With parameters: pickOrderLineId = $pickOrderLineId") | |||||
| val result = jdbcDao.queryForList(sql, mapOf("pickOrderLineId" to pickOrderLineId)) | |||||
| // Add detailed debug output for each lot | |||||
| println("=== DETAILED LOT AVAILABILITY DEBUG ===") | |||||
| result.forEach { row -> | |||||
| val lotId = row["lotId"] | |||||
| val lotNo = row["lotNo"] | |||||
| val illStatus = row["debug_ill_status"] | |||||
| val isExpired = row["debug_is_expired"] | |||||
| val solStatus = row["debug_sol_status"] | |||||
| val lotAvailability = row["lotAvailability"] | |||||
| println("--- Lot: $lotNo (ID: $lotId) ---") | |||||
| println(" ill.status: $illStatus") | |||||
| println(" is_expired: $isExpired") | |||||
| println(" sol.status: $solStatus") | |||||
| println(" lotAvailability: $lotAvailability") | |||||
| // Check each condition step by step | |||||
| if (isExpired == true) { | |||||
| println(" ❌ FAILED: lot is expired") | |||||
| } else if (solStatus == "rejected") { | |||||
| println(" ❌ FAILED: sol.status = 'rejected'") | |||||
| // ✅ 1. 获取 PickOrderLine 以获取 UOM 信息 | |||||
| val pickOrderLine = pickOrderLineRepository.findById(pickOrderLineId).orElse(null) | |||||
| if (pickOrderLine == null) { | |||||
| println("❌ PickOrderLine not found: $pickOrderLineId") | |||||
| return emptyList() | |||||
| } | |||||
| val uomDesc = pickOrderLine.uom?.udfudesc ?: "N/A" | |||||
| // ✅ 2. 获取所有相关的 SuggestedPickLot | |||||
| val suggestedPickLots = suggestPickLotRepository.findAllByPickOrderLineId(pickOrderLineId) | |||||
| val suggestedPickLotsByLotId = suggestedPickLots | |||||
| .mapNotNull { spl -> spl.suggestedLotLine?.id?.let { it to spl } } | |||||
| .toMap() | |||||
| // ✅ 3. 获取所有相关的 StockOutLine(包括有 lot 和没有 lot 的) | |||||
| val stockOutLines = stockOutLIneRepository.findAllByPickOrderLineIdAndDeletedFalse(pickOrderLineId) | |||||
| val stockOutLinesByLotId = stockOutLines | |||||
| .filter { it.inventoryLotLineId != null } | |||||
| .mapNotNull { sol -> sol.inventoryLotLineId?.let { it to sol } } | |||||
| .toMap() | |||||
| // ✅ 4. 获取所有 no-lot 的 StockOutLine | |||||
| val noLotStockOutLines = stockOutLines.filter { it.inventoryLotLineId == null } | |||||
| // ✅ 5. 获取所有相关的 InventoryLotLine IDs | |||||
| val inventoryLotLineIds = (suggestedPickLots.mapNotNull { it.suggestedLotLine?.id } + | |||||
| stockOutLines.mapNotNull { it.inventoryLotLineId }).distinct() | |||||
| // ✅ 6. 批量加载 InventoryLotLine 实体 | |||||
| val inventoryLotLines = if (inventoryLotLineIds.isNotEmpty()) { | |||||
| inventoryLotLineRepository.findAllById(inventoryLotLineIds) | |||||
| .associateBy { it.id!! } | |||||
| } else { | |||||
| emptyMap() | |||||
| } | |||||
| // ✅ 7. 构建有 lot 的记录 | |||||
| val lotDetails = inventoryLotLines.values.mapNotNull { ill -> | |||||
| val il = ill.inventoryLot | |||||
| val w = ill.warehouse | |||||
| val spl = ill.id?.let { suggestedPickLotsByLotId[it] } | |||||
| val sol = ill.id?.let { stockOutLinesByLotId[it] } | |||||
| // 计算可用数量 | |||||
| val inQty = ill.inQty ?: zero | |||||
| val outQty = ill.outQty ?: zero | |||||
| val holdQty = ill.holdQty ?: zero | |||||
| val availableQty = inQty.minus(outQty).minus(holdQty) | |||||
| // 计算所有 pick orders 的总已拣数量 | |||||
| val totalPickedByAllPickOrders = if (ill.id != null) { | |||||
| stockOutLIneRepository.findAllByPickOrderLineIdAndDeletedFalse(pickOrderLineId) | |||||
| .filter { it.inventoryLotLineId == ill.id } | |||||
| .sumOf { it.qty ?: zero } | |||||
| } else { | } else { | ||||
| println(" PASSED: All conditions met, should be 'available'") | |||||
| zero | |||||
| } | } | ||||
| println(" ---") | |||||
| } | |||||
| println("=== END DETAILED DEBUG ===") | |||||
| // Filter out completed lots | |||||
| val filteredResult = result.filter { row -> | |||||
| val stockOutLineStatus = row["stockOutLineStatus"] as String? | |||||
| val stockOutLineQty = row["stockOutLineQty"] as Number? | |||||
| val requiredQty = row["requiredQty"] as Number? | |||||
| // Show lot if: | |||||
| // 1. No stock out line exists, OR | |||||
| // 2. Stock out line is not completed, OR | |||||
| // 3. Stock out line qty doesn't equal required qty | |||||
| stockOutLineStatus != "completed" || | |||||
| stockOutLineQty?.toDouble() != requiredQty?.toDouble() | |||||
| // 判断是否过期 | |||||
| val isExpired = il?.expiryDate?.let { it.isBefore(today) } == true | |||||
| // 计算 lotAvailability | |||||
| val lotAvailability = when { | |||||
| isExpired -> "expired" | |||||
| sol?.status == "rejected" -> "rejected" | |||||
| availableQty <= zero -> "insufficient_stock" | |||||
| ill.status?.value == "unavailable" -> "status_unavailable" | |||||
| else -> "available" | |||||
| } | |||||
| // 过滤:只返回未完成的或需要显示的 | |||||
| val shouldInclude = sol == null || | |||||
| sol.status != "completed" || | |||||
| (sol.qty ?: zero) != (spl?.qty ?: zero) | |||||
| if (!shouldInclude) { | |||||
| return@mapNotNull null | |||||
| } | |||||
| PickOrderLineLotDetailResponse( | |||||
| lotId = ill.id, | |||||
| lotNo = il?.lotNo, | |||||
| expiryDate = il?.expiryDate, | |||||
| location = w?.name, | |||||
| stockUnit = ill.stockUom?.uom?.udfudesc ?: uomDesc, | |||||
| availableQty = availableQty, | |||||
| requiredQty = spl?.qty ?: zero, | |||||
| inQty = inQty, | |||||
| outQty = outQty, | |||||
| holdQty = holdQty, | |||||
| actualPickQty = sol?.qty ?: zero, | |||||
| suggestedPickLotId = spl?.id, | |||||
| lotStatus = ill.status?.value, | |||||
| stockOutLineId = sol?.id, | |||||
| stockOutLineStatus = sol?.status, | |||||
| stockOutLineQty = sol?.qty, | |||||
| totalPickedByAllPickOrders = totalPickedByAllPickOrders, | |||||
| remainingAfterAllPickOrders = availableQty, | |||||
| lotAvailability = lotAvailability, | |||||
| noLot = false | |||||
| ) | |||||
| } | } | ||||
| println("Final result count: ${filteredResult.size}") | |||||
| filteredResult.forEach { row -> | |||||
| println("Final Row: $row") | |||||
| // ✅ 8. 构建 no-lot 的记录 | |||||
| val noLotDetails = noLotStockOutLines.mapNotNull { sol -> | |||||
| // 过滤:只返回未完成的 | |||||
| if (sol.status == "completed") { | |||||
| return@mapNotNull null | |||||
| } | |||||
| PickOrderLineLotDetailResponse( | |||||
| lotId = null, | |||||
| lotNo = null, | |||||
| expiryDate = null, | |||||
| location = null, | |||||
| stockUnit = uomDesc, | |||||
| availableQty = null, | |||||
| requiredQty = zero, | |||||
| inQty = zero, | |||||
| outQty = zero, | |||||
| holdQty = zero, | |||||
| actualPickQty = sol.qty ?: zero, | |||||
| suggestedPickLotId = null, | |||||
| lotStatus = "unavailable", | |||||
| stockOutLineId = sol.id, | |||||
| stockOutLineStatus = sol.status, | |||||
| stockOutLineQty = sol.qty, | |||||
| totalPickedByAllPickOrders = zero, | |||||
| remainingAfterAllPickOrders = null, | |||||
| lotAvailability = "insufficient_stock", | |||||
| noLot = true | |||||
| ) | |||||
| } | } | ||||
| return filteredResult | |||||
| // ✅ 9. 合并并排序 | |||||
| val allDetails = (lotDetails + noLotDetails).sortedWith( | |||||
| compareBy<PickOrderLineLotDetailResponse>( | |||||
| { it.noLot }, // no-lot 行排在后面 | |||||
| { it.stockOutLineStatus == "rejected" }, // rejected 排在前面 | |||||
| { it.expiryDate ?: LocalDate.MAX }, // 按过期日期排序 | |||||
| { it.lotNo ?: "" } // 按 lotNo 排序 | |||||
| ) | |||||
| ) | |||||
| println("✅ Final result count: ${allDetails.size}") | |||||
| println(" - With lot: ${lotDetails.size}") | |||||
| println(" - No-lot: ${noLotDetails.size}") | |||||
| return allDetails | |||||
| } | } | ||||
| @Transactional(rollbackFor = [java.lang.Exception::class]) | @Transactional(rollbackFor = [java.lang.Exception::class]) | ||||
| @@ -158,7 +158,7 @@ class PickOrderController( | |||||
| } | } | ||||
| @GetMapping("/lot-details/{pickOrderLineId}") | @GetMapping("/lot-details/{pickOrderLineId}") | ||||
| fun getPickOrderLineLotDetails(@PathVariable pickOrderLineId: Long): List<Map<String, Any>> { | |||||
| fun getPickOrderLineLotDetails(@PathVariable pickOrderLineId: Long): List<PickOrderLineLotDetailResponse> { | |||||
| return pickOrderService.getPickOrderLineLotDetails(pickOrderLineId); | return pickOrderService.getPickOrderLineLotDetails(pickOrderLineId); | ||||
| } | } | ||||
| @GetMapping("/lot-details-by-do-pick-order-record/{doPickOrderRecordId}") | @GetMapping("/lot-details-by-do-pick-order-record/{doPickOrderRecordId}") | ||||
| @@ -17,4 +17,4 @@ data class ReleaseConsoPickOrderRequest ( | |||||
| // Start Pick Order | // Start Pick Order | ||||
| data class StartConsoPickOrderRequest ( | data class StartConsoPickOrderRequest ( | ||||
| val consoCode: String, | val consoCode: String, | ||||
| ) | |||||
| ) | |||||
| @@ -4,7 +4,7 @@ import com.ffii.fpsms.modules.stock.entity.SuggestedPickLot | |||||
| import com.ffii.fpsms.modules.stock.entity.projection.CurrentInventoryItemInfo | import com.ffii.fpsms.modules.stock.entity.projection.CurrentInventoryItemInfo | ||||
| import java.math.BigDecimal | import java.math.BigDecimal | ||||
| import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
| import java.time.LocalDate | |||||
| // Final Response - Release Conso Pick Order Page | // Final Response - Release Conso Pick Order Page | ||||
| data class ReleasePickOrderInfoResponse( | data class ReleasePickOrderInfoResponse( | ||||
| val consoCode: String, | val consoCode: String, | ||||
| @@ -63,8 +63,15 @@ data class GetPickOrderLineInfo( | |||||
| val uomDesc: String?, | val uomDesc: String?, | ||||
| val suggestedList: List<SuggestedPickLot>?, | val suggestedList: List<SuggestedPickLot>?, | ||||
| val pickedQty: BigDecimal?=BigDecimal.ZERO, | val pickedQty: BigDecimal?=BigDecimal.ZERO, | ||||
| val noLotLines: List<NoLotLineDto>?=emptyList() | |||||
| ) | |||||
| data class NoLotLineDto( | |||||
| val stockOutLineId: Long, | |||||
| val status: String?, | |||||
| val qty: BigDecimal, | |||||
| val created: LocalDateTime?, | |||||
| val modified: LocalDateTime? | |||||
| ) | ) | ||||
| // Final Response - Conso Pick Order Detail | // Final Response - Conso Pick Order Detail | ||||
| data class ConsoPickOrderResponse( | data class ConsoPickOrderResponse( | ||||
| val consoCode: String, | val consoCode: String, | ||||
| @@ -148,4 +155,27 @@ data class IdCodeDesc( | |||||
| val id: Long?, | val id: Long?, | ||||
| val code: String?, | val code: String?, | ||||
| val desc: String?, | val desc: String?, | ||||
| ) | |||||
| data class PickOrderLineLotDetailResponse( | |||||
| val lotId: Long?, | |||||
| val lotNo: String?, | |||||
| val expiryDate: LocalDate?, | |||||
| val location: String?, | |||||
| val stockUnit: String?, | |||||
| val availableQty: BigDecimal?, | |||||
| val requiredQty: BigDecimal?, | |||||
| val inQty: BigDecimal?, | |||||
| val outQty: BigDecimal?, | |||||
| val holdQty: BigDecimal?, | |||||
| val actualPickQty: BigDecimal?, | |||||
| val suggestedPickLotId: Long?, | |||||
| val lotStatus: String?, | |||||
| val stockOutLineId: Long?, | |||||
| val stockOutLineStatus: String?, | |||||
| val stockOutLineQty: BigDecimal?, | |||||
| val totalPickedByAllPickOrders: BigDecimal?, | |||||
| val remainingAfterAllPickOrders: BigDecimal?, | |||||
| val lotAvailability: String?, | |||||
| val noLot: Boolean = false | |||||
| ) | ) | ||||
| @@ -23,7 +23,9 @@ open class ProductProcessLine : BaseEntity<Long>() { | |||||
| @ManyToOne(fetch = FetchType.LAZY) | @ManyToOne(fetch = FetchType.LAZY) | ||||
| @JoinColumn(name = "equipmentId") | @JoinColumn(name = "equipmentId") | ||||
| open var equipment: Equipment? = null | open var equipment: Equipment? = null | ||||
| @Column(name = "equipmentDetailId") | |||||
| open var equipmentDetailId: Long? = null | |||||
| @Size(max = 100) | @Size(max = 100) | ||||
| @Column(name = "name", length = 100) | @Column(name = "name", length = 100) | ||||
| open var name: String? = null | open var name: String? = null | ||||
| @@ -72,8 +74,8 @@ open class ProductProcessLine : BaseEntity<Long>() { | |||||
| @Column(name = "defectQty2", precision = 16, scale = 2) | @Column(name = "defectQty2", precision = 16, scale = 2) | ||||
| open var defectQty2: Int? = null | open var defectQty2: Int? = null | ||||
| @Column(name = "defectRemark2", length = 255) | |||||
| open var defectRemark2: String? = null | |||||
| @Column(name = "defectDescription2", length = 255) | |||||
| open var defectDescription2: String? = null | |||||
| @Column(name = "defectUom2", length = 20) | @Column(name = "defectUom2", length = 20) | ||||
| open var defectUom2: String? = null | open var defectUom2: String? = null | ||||
| @@ -4,7 +4,7 @@ import java.time.LocalDateTime | |||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| import com.ffii.fpsms.modules.productProcess.enums.ProductProcessStatus | import com.ffii.fpsms.modules.productProcess.enums.ProductProcessStatus | ||||
| import com.fasterxml.jackson.annotation.JsonFormat | import com.fasterxml.jackson.annotation.JsonFormat | ||||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus | |||||
| data class ProductProcessInfo( | data class ProductProcessInfo( | ||||
| val id: Long?, | val id: Long?, | ||||
| @@ -21,6 +21,7 @@ data class ProductProcessInfo( | |||||
| val bomId: Long?, | val bomId: Long?, | ||||
| val jobOrderId: Long?, | val jobOrderId: Long?, | ||||
| val jobOrderCode: String?, | val jobOrderCode: String?, | ||||
| val jobOrderStatus: String?, | |||||
| val isDark: String?, | val isDark: String?, | ||||
| val isDense: Int?, | val isDense: Int?, | ||||
| val isFloat: String?, | val isFloat: String?, | ||||
| @@ -56,6 +57,13 @@ data class ProductProcessLineInfo( | |||||
| val scrapQty: Int?, | val scrapQty: Int?, | ||||
| val defectQty: Int?, | val defectQty: Int?, | ||||
| val defectUom: String?, | val defectUom: String?, | ||||
| val defectDescription: String?, | |||||
| val defectQty2: Int?, | |||||
| val defectUom2: String?, | |||||
| val defectDescription2: String?, | |||||
| val defectQty3: Int?, | |||||
| val defectUom3: String?, | |||||
| val defectDescription3: String?, | |||||
| val outputFromProcessQty: Int?, | val outputFromProcessQty: Int?, | ||||
| val outputFromProcessUom: String?, | val outputFromProcessUom: String?, | ||||
| val durationInMinutes: Int?, | val durationInMinutes: Int?, | ||||
| @@ -38,6 +38,7 @@ import com.ffii.fpsms.modules.stock.service.StockInLineService | |||||
| import com.ffii.fpsms.modules.stock.web.model.SaveStockInLineRequest | import com.ffii.fpsms.modules.stock.web.model.SaveStockInLineRequest | ||||
| import com.ffii.fpsms.modules.master.entity.BomProcessMaterialRepository | import com.ffii.fpsms.modules.master.entity.BomProcessMaterialRepository | ||||
| import com.ffii.fpsms.modules.master.entity.BomMaterialRepository | import com.ffii.fpsms.modules.master.entity.BomMaterialRepository | ||||
| import com.ffii.fpsms.modules.master.entity.EquipmentDetailRepository | |||||
| @Service | @Service | ||||
| @Transactional | @Transactional | ||||
| open class ProductProcessService( | open class ProductProcessService( | ||||
| @@ -57,6 +58,7 @@ open class ProductProcessService( | |||||
| private val stockInLineService: StockInLineService, | private val stockInLineService: StockInLineService, | ||||
| private val bomProcessMaterialRepository: BomProcessMaterialRepository, | private val bomProcessMaterialRepository: BomProcessMaterialRepository, | ||||
| private val bomMaterialRepository: BomMaterialRepository, | private val bomMaterialRepository: BomMaterialRepository, | ||||
| private val equipmentDetailRepository: EquipmentDetailRepository, | |||||
| ) { | ) { | ||||
| open fun findAll(pageable: Pageable): Page<ProductProcess> { | open fun findAll(pageable: Pageable): Page<ProductProcess> { | ||||
| @@ -552,6 +554,7 @@ open class ProductProcessService( | |||||
| bomId = process.bom?.id?:0, | bomId = process.bom?.id?:0, | ||||
| jobOrderId = process.jobOrder?.id?:0, | jobOrderId = process.jobOrder?.id?:0, | ||||
| jobOrderCode = jobOrder?.code?:"", | jobOrderCode = jobOrder?.code?:"", | ||||
| jobOrderStatus = jobOrder?.status?.value?:"", | |||||
| itemId = bom?.item?.id?:0, | itemId = bom?.item?.id?:0, | ||||
| itemCode = bom?.item?.code?:"", | itemCode = bom?.item?.code?:"", | ||||
| itemName = bom?.item?.name?:"", | itemName = bom?.item?.name?:"", | ||||
| @@ -592,6 +595,13 @@ open class ProductProcessService( | |||||
| scrapQty = line.scrapQty?:0, | scrapQty = line.scrapQty?:0, | ||||
| defectQty = line.defectQty?:0, | defectQty = line.defectQty?:0, | ||||
| defectUom = line.defectUom?:"", | defectUom = line.defectUom?:"", | ||||
| defectDescription = line.defectDescription?:"", | |||||
| defectQty2 = line.defectQty2?:0, | |||||
| defectUom2 = line.defectUom2?:"", | |||||
| defectDescription2 = line.defectDescription2?:"", | |||||
| defectQty3 = line.defectQty3?:0, | |||||
| defectUom3 = line.defectUom3?:"", | |||||
| defectDescription3 = line.defectDescription3?:"", | |||||
| outputFromProcessQty = line.outputFromProcessQty?:0, | outputFromProcessQty = line.outputFromProcessQty?:0, | ||||
| outputFromProcessUom = line.outputFromProcessUom?:"", | outputFromProcessUom = line.outputFromProcessUom?:"", | ||||
| startTime = line.startTime, | startTime = line.startTime, | ||||
| @@ -731,6 +741,64 @@ open class ProductProcessService( | |||||
| } | } | ||||
| return MessageResponse( | |||||
| id = null, | |||||
| code = null, | |||||
| name = null, | |||||
| type = null, | |||||
| message = null, | |||||
| errorPosition = null, | |||||
| ) | |||||
| } | |||||
| open fun NewUpdateProductProcessLineOperatorIdOrEquipmentIdAndEquipmentDetail(request: NewUpdateProductProcessLineOperatorIdOrEquipmentIdAndEquipmentDetailRequest): MessageResponse { | |||||
| val productProcessLine = productProcessLineRepository.findById(request.productProcessLineId).orElse(null) | |||||
| val equipmentDetail = equipmentDetailRepository.findByCode(request.EquipmentTypeSubTypeEquipmentNo) | |||||
| val user = userRepository.findByName(request.Name?:"") | |||||
| val bomProcess= bomProcessRepository.findById(productProcessLine?.bomProcess?.id?:0L).orElse(null) | |||||
| val bomProcessEquipment=bomProcess?.equipment | |||||
| if (equipmentDetail != null && user != null) { | |||||
| // 检查 equipmentId 是否与 bomProcessEquipment 匹配 | |||||
| if (equipmentDetail.equipmentTypeId != bomProcessEquipment?.id) { | |||||
| println("productProcessLine id${request.productProcessLineId}") | |||||
| println("operator Name${request.Name}") | |||||
| println("user ${user}") | |||||
| println("bomProcess ${bomProcess?.id}") | |||||
| println("not match equipment id${equipmentDetail.equipmentTypeId} and ${bomProcessEquipment?.id}") | |||||
| // 返回错误响应 | |||||
| return MessageResponse( | |||||
| id = request.productProcessLineId, | |||||
| code = "400", | |||||
| name = "Equipment Validation Failed", | |||||
| type = "error", | |||||
| message = "Input Equipment ID($equipmentDetail.equipmentTypeId ) and BOM Process Equipment ID(${bomProcessEquipment?.id}) not match", | |||||
| errorPosition = "equipmentId" | |||||
| ) | |||||
| } | |||||
| } | |||||
| if(equipmentDetail?.equipmentTypeId != null &&( equipmentDetail.equipmentTypeId ==bomProcessEquipment?.id)) { | |||||
| val equipment = equipmentRepository.findById(equipmentDetail.equipmentTypeId).orElse(null) | |||||
| productProcessLine?.equipment = equipment | |||||
| productProcessLine?.equipmentDetailId = equipmentDetail.id | |||||
| productProcessLineRepository.save(productProcessLine) | |||||
| } | |||||
| else | |||||
| { println("productProcessLine id${request.productProcessLineId}") | |||||
| println("operator Name${request?.Name}") | |||||
| println("user ${user}") | |||||
| println("bomProcess ${bomProcess?.id}") | |||||
| println("not match equipment id${equipmentDetail?.equipmentTypeId} and ${bomProcessEquipment?.id}") | |||||
| } | |||||
| if(user != null) { | |||||
| //productProcessLine.operator = user.name | |||||
| // productProcessLineRepository.save(productProcessLine) | |||||
| } | |||||
| return MessageResponse( | return MessageResponse( | ||||
| id = null, | id = null, | ||||
| code = null, | code = null, | ||||
| @@ -781,6 +849,13 @@ open class ProductProcessService( | |||||
| outputFromProcessUom = productProcessLine.outputFromProcessUom?:"", | outputFromProcessUom = productProcessLine.outputFromProcessUom?:"", | ||||
| defectQty = productProcessLine.defectQty?:0, | defectQty = productProcessLine.defectQty?:0, | ||||
| defectUom = productProcessLine.defectUom?:"", | defectUom = productProcessLine.defectUom?:"", | ||||
| defectDescription = productProcessLine.defectDescription?:"", | |||||
| defectQty2 = productProcessLine.defectQty2?:0, | |||||
| defectUom2 = productProcessLine.defectUom2?:"", | |||||
| defectDescription2 = productProcessLine.defectDescription2?:"", | |||||
| defectQty3 = productProcessLine.defectQty3?:0, | |||||
| defectUom3 = productProcessLine.defectUom3?:"", | |||||
| defectDescription3 = productProcessLine.defectDescription3?:"", | |||||
| scrapQty = productProcessLine.scrapQty?:0, | scrapQty = productProcessLine.scrapQty?:0, | ||||
| scrapUom = productProcessLine.scrapUom?:"", | scrapUom = productProcessLine.scrapUom?:"", | ||||
| byproductId = productProcessLine.byproduct?.id?:0, | byproductId = productProcessLine.byproduct?.id?:0, | ||||
| @@ -834,55 +909,86 @@ open class ProductProcessService( | |||||
| ) | ) | ||||
| } | } | ||||
| open fun updateProductProcessLineQty(request: UpdateProductProcessLineQtyRequest): MessageResponse { | |||||
| val outputFromProcessQty = request.outputFromProcessQty | |||||
| val outputFromProcessUom = request.outputFromProcessUom | |||||
| val defectQty = request.defectQty | |||||
| val defectUom = request.defectUom | |||||
| val scrapQty = request.scrapQty | |||||
| val scrapUom = request.scrapUom | |||||
| val byproductName = request.byproductName | |||||
| val byproductQty = request.byproductQty | |||||
| val byproductUom = request.byproductUom | |||||
| val productProcessLine = productProcessLineRepository.findById(request.productProcessLineId).orElse(null) | |||||
| if(outputFromProcessQty != null) { | |||||
| productProcessLine.outputFromProcessQty = outputFromProcessQty | |||||
| } | |||||
| if(outputFromProcessUom != null) { | |||||
| productProcessLine.outputFromProcessUom = outputFromProcessUom | |||||
| } | |||||
| if(defectQty != null) { | |||||
| productProcessLine.defectQty = defectQty | |||||
| } | |||||
| if(defectUom != null) { | |||||
| productProcessLine.defectUom = defectUom | |||||
| } | |||||
| if(scrapQty != null) { | |||||
| productProcessLine.scrapQty = scrapQty | |||||
| } | |||||
| if(scrapUom != null) { | |||||
| productProcessLine.scrapUom = scrapUom | |||||
| } | |||||
| if(byproductName != null) { | |||||
| productProcessLine.byproductName = byproductName | |||||
| } | |||||
| if(byproductQty != null) { | |||||
| productProcessLine.byproductQty = byproductQty | |||||
| } | |||||
| if(byproductUom != null) { | |||||
| productProcessLine.byproductUom = byproductUom | |||||
| open fun updateProductProcessLineQty(request: UpdateProductProcessLineQtyRequest): MessageResponse { | |||||
| val outputFromProcessQty = request.outputFromProcessQty | |||||
| val outputFromProcessUom = request.outputFromProcessUom | |||||
| val defectQty = request.defectQty | |||||
| val defectUom = request.defectUom | |||||
| val defectDescription = request.defectDescription | |||||
| val defect2Qty = request.defect2Qty | |||||
| val defect2Uom = request.defect2Uom | |||||
| val defect2Description = request.defectDescription2 | |||||
| val defect3Qty = request.defect3Qty | |||||
| val defect3Uom = request.defect3Uom | |||||
| val defect3Description = request.defectDescription3 | |||||
| val scrapQty = request.scrapQty | |||||
| val scrapUom = request.scrapUom | |||||
| val byproductName = request.byproductName | |||||
| val byproductQty = request.byproductQty | |||||
| val byproductUom = request.byproductUom | |||||
| val productProcessLine = productProcessLineRepository.findById(request.productProcessLineId).orElse(null) | |||||
| if(outputFromProcessQty != null) { | |||||
| productProcessLine.outputFromProcessQty = outputFromProcessQty | |||||
| } | |||||
| if(outputFromProcessUom != null) { | |||||
| productProcessLine.outputFromProcessUom = outputFromProcessUom | |||||
| } | |||||
| if(defectQty != null) { | |||||
| productProcessLine.defectQty = defectQty | |||||
| } | |||||
| if(defectUom != null) { | |||||
| productProcessLine.defectUom = defectUom | |||||
| } | |||||
| if(defectDescription != null) { | |||||
| productProcessLine.defectDescription = defectDescription | |||||
| } | |||||
| if(defect2Qty != null) { | |||||
| productProcessLine.defectQty2 = defect2Qty | |||||
| } | |||||
| if(defect2Uom != null) { | |||||
| productProcessLine.defectUom2 = defect2Uom | |||||
| } | |||||
| if(defect2Description != null) { | |||||
| productProcessLine.defectDescription2 = defect2Description | |||||
| } | |||||
| if(defect3Qty != null) { | |||||
| productProcessLine.defectQty3 = defect3Qty | |||||
| } | |||||
| if(defect3Uom != null) { | |||||
| productProcessLine.defectUom3 = defect3Uom | |||||
| } | |||||
| if(defect3Description != null) { | |||||
| productProcessLine.defectDescription3 = defect3Description | |||||
| } | |||||
| if(scrapQty != null) { | |||||
| productProcessLine.scrapQty = scrapQty | |||||
| } | |||||
| if(scrapUom != null) { | |||||
| productProcessLine.scrapUom = scrapUom | |||||
| } | |||||
| if(byproductName != null) { | |||||
| productProcessLine.byproductName = byproductName | |||||
| } | |||||
| if(byproductQty != null) { | |||||
| productProcessLine.byproductQty = byproductQty | |||||
| } | |||||
| if(byproductUom != null) { | |||||
| productProcessLine.byproductUom = byproductUom | |||||
| } | |||||
| productProcessLineRepository.save(productProcessLine) | |||||
| CompleteProductProcessLine(request.productProcessLineId) | |||||
| return MessageResponse( | |||||
| id = request.productProcessLineId, | |||||
| code = "200", | |||||
| name = "ProductProcessLine Qty Updated", | |||||
| type = "success", | |||||
| message = "ProductProcessLine Qty Updated", | |||||
| errorPosition = null, | |||||
| ) | |||||
| } | } | ||||
| productProcessLineRepository.save(productProcessLine) | |||||
| CompleteProductProcessLine(request.productProcessLineId) | |||||
| return MessageResponse( | |||||
| id = request.productProcessLineId, | |||||
| code = "200", | |||||
| name = "ProductProcessLine Qty Updated", | |||||
| type = "success", | |||||
| message = "ProductProcessLine Qty Updated", | |||||
| errorPosition = null, | |||||
| ) | |||||
| } | |||||
| open fun getAllJoborderProductProcessInfo(): List<AllJoborderProductProcessInfoResponse> { | open fun getAllJoborderProductProcessInfo(): List<AllJoborderProductProcessInfoResponse> { | ||||
| val productProcesses = productProcessRepository.findAllByDeletedIsFalse() | val productProcesses = productProcessRepository.findAllByDeletedIsFalse() | ||||
| @@ -1057,4 +1163,5 @@ open fun updateProductProcessLineQty(request: UpdateProductProcessLineQtyRequest | |||||
| errorPosition = null, | errorPosition = null, | ||||
| ) | ) | ||||
| } | } | ||||
| } | |||||
| } | |||||
| @@ -100,6 +100,13 @@ data class UpdateProductProcessLineQtyRequest( | |||||
| val outputFromProcessUom: String?, | val outputFromProcessUom: String?, | ||||
| val defectQty: Int?, | val defectQty: Int?, | ||||
| val defectUom: String?, | val defectUom: String?, | ||||
| val defectDescription: String?, | |||||
| val defect2Qty: Int?, | |||||
| val defect2Uom: String?, | |||||
| val defectDescription2: String?, | |||||
| val defect3Qty: Int?, | |||||
| val defect3Uom: String?, | |||||
| val defectDescription3: String?, | |||||
| val scrapQty: Int?, | val scrapQty: Int?, | ||||
| val scrapUom: String?, | val scrapUom: String?, | ||||
| val byproductName: String?, | val byproductName: String?, | ||||
| @@ -127,6 +134,13 @@ data class JobOrderProcessLineDetailResponse( | |||||
| val outputFromProcessUom: String?, | val outputFromProcessUom: String?, | ||||
| val defectQty: Int?, | val defectQty: Int?, | ||||
| val defectUom: String?, | val defectUom: String?, | ||||
| val defectDescription: String?, | |||||
| val defectQty2: Int?, | |||||
| val defectUom2: String?, | |||||
| val defectDescription2: String?, | |||||
| val defectQty3: Int?, | |||||
| val defectUom3: String?, | |||||
| val defectDescription3: String?, | |||||
| val scrapQty: Int?, | val scrapQty: Int?, | ||||
| val scrapUom: String?, | val scrapUom: String?, | ||||
| val byproductId: Long?, | val byproductId: Long?, | ||||
| @@ -166,4 +180,10 @@ data class ProductProcessInfoResponse( | |||||
| data class UpdateProductProcessLineStatusRequest( | data class UpdateProductProcessLineStatusRequest( | ||||
| val productProcessLineId: Long, | val productProcessLineId: Long, | ||||
| val status: String | val status: String | ||||
| ) | |||||
| data class NewUpdateProductProcessLineOperatorIdOrEquipmentIdAndEquipmentDetailRequest( | |||||
| val productProcessLineId: Long, | |||||
| val EquipmentTypeSubTypeEquipmentNo: String, | |||||
| val staffNo: String?, | |||||
| val Name: String?, | |||||
| ) | ) | ||||
| @@ -329,6 +329,7 @@ open class SuggestedPickLotService( | |||||
| val stockOutLine = StockOutLine().apply { | val stockOutLine = StockOutLine().apply { | ||||
| this.stockOut = stockOut | this.stockOut = stockOut | ||||
| this.pickOrderLine = pickOrderLine | this.pickOrderLine = pickOrderLine | ||||
| this.item = item | |||||
| this.inventoryLotLine = null // No lot available | this.inventoryLotLine = null // No lot available | ||||
| this.qty = (suggestion.qty ?: BigDecimal.ZERO).toDouble() | this.qty = (suggestion.qty ?: BigDecimal.ZERO).toDouble() | ||||
| this.status = StockOutLineStatus.PENDING.status | this.status = StockOutLineStatus.PENDING.status | ||||
| @@ -3,15 +3,15 @@ package com.ffii.fpsms.modules.user.entity; | |||||
| import java.util.List; | import java.util.List; | ||||
| import java.util.Optional; | import java.util.Optional; | ||||
| import com.ffii.fpsms.modules.user.entity.projections.UserCombo; | |||||
| import org.springframework.data.repository.query.Param; | import org.springframework.data.repository.query.Param; | ||||
| import com.ffii.core.support.AbstractRepository; | import com.ffii.core.support.AbstractRepository; | ||||
| import com.ffii.fpsms.modules.user.entity.projections.UserCombo; | |||||
| public interface UserRepository extends AbstractRepository<User, Long> { | public interface UserRepository extends AbstractRepository<User, Long> { | ||||
| List<User> findByName(@Param("name") String name); | List<User> findByName(@Param("name") String name); | ||||
| Optional<User> findByUsernameAndDeletedFalse(String username); | Optional<User> findByUsernameAndDeletedFalse(String username); | ||||
| List<UserCombo> findUserComboByTitleNotNullAndDepartmentNotNullAndNameNotNullAndDeletedFalse(); | List<UserCombo> findUserComboByTitleNotNullAndDepartmentNotNullAndNameNotNullAndDeletedFalse(); | ||||
| @@ -0,0 +1,6 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset enson:altertable_enson | |||||
| ALTER TABLE `fpsmsdb`.`productprocessline` | |||||
| DROP COLUMN `defectRemark2`, | |||||
| ADD COLUMN `defectDescription2` VARCHAR(255) AFTER `defectUom2`; | |||||
| @@ -0,0 +1,18 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset enson:altertable_enson | |||||
| create table jobType ( | |||||
| `id` INT NOT NULL AUTO_INCREMENT, | |||||
| `name` VARCHAR(255) NOT NULL, | |||||
| `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||||
| `createdBy` VARCHAR(30) NULL DEFAULT NULL, | |||||
| `version` INT NOT NULL DEFAULT '0', | |||||
| `modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||||
| `modifiedBy` VARCHAR(30) NULL DEFAULT NULL, | |||||
| `deleted` TINYINT(1) NOT NULL DEFAULT '0', | |||||
| PRIMARY KEY (`id`) | |||||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; | |||||
| ALTER TABLE `fpsmsdb`.`user` | |||||
| ADD COLUMN `staffNo` VARCHAR(255) After `lotusNotesUser`; | |||||
| @@ -0,0 +1,8 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset enson:altertable_enson | |||||
| ALTER TABLE `fpsmsdb`.`job_order` | |||||
| ADD COLUMN `jobTypeId` INT After `type`; | |||||