| @@ -12,5 +12,7 @@ import java.time.LocalDateTime | |||||
| @Repository | @Repository | ||||
| interface JoBagConsumptionRepository : AbstractRepository<JoBagConsumption, Long> { | interface JoBagConsumptionRepository : AbstractRepository<JoBagConsumption, Long> { | ||||
| fun findAllByDeletedIsFalse(): List<JoBagConsumption> | |||||
| fun findAllByBagLotLineIdAndDeletedIsFalse(bagLotLineId: Long): List<JoBagConsumption> | |||||
| fun findAllByBagId(bagId: Long): List<BagLotLine> | |||||
| } | } | ||||
| @@ -14,13 +14,15 @@ import java.time.LocalTime | |||||
| import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository | import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository | ||||
| import com.ffii.fpsms.modules.productProcess.entity.ProductProcessRepository | |||||
| @Service | @Service | ||||
| open class BagService( | open class BagService( | ||||
| private val bagRepository: BagRepository, | private val bagRepository: BagRepository, | ||||
| private val bagLotLineRepository: BagLotLineRepository, | private val bagLotLineRepository: BagLotLineRepository, | ||||
| private val joBagConsumptionRepository: JoBagConsumptionRepository, | private val joBagConsumptionRepository: JoBagConsumptionRepository, | ||||
| private val inventoryLotRepository: InventoryLotRepository, | private val inventoryLotRepository: InventoryLotRepository, | ||||
| private val jobOrderRepository: JobOrderRepository | |||||
| private val jobOrderRepository: JobOrderRepository, | |||||
| private val productProcessRepository: ProductProcessRepository | |||||
| ) { | ) { | ||||
| open fun createBagLotLinesByBagId(request: CreateBagLotLineRequest): MessageResponse { | open fun createBagLotLinesByBagId(request: CreateBagLotLineRequest): MessageResponse { | ||||
| val bag = bagRepository.findById(request.bagId).orElse(null) | val bag = bagRepository.findById(request.bagId).orElse(null) | ||||
| @@ -124,6 +126,14 @@ fun createJoBagConsumption(request: CreateJoBagConsumptionRequest): MessageRespo | |||||
| this.time = LocalDateTime.now() | this.time = LocalDateTime.now() | ||||
| } | } | ||||
| joBagConsumptionRepository.save(joBagConsumption) | joBagConsumptionRepository.save(joBagConsumption) | ||||
| // 更新 ProductProcess 的 submitedBagRecord 为 true | |||||
| val productProcesses = productProcessRepository.findByJobOrder_Id(request.jobId) | |||||
| productProcesses.forEach { productProcess -> | |||||
| productProcess.submitedBagRecord = true | |||||
| productProcessRepository.save(productProcess) | |||||
| } | |||||
| return MessageResponse( | return MessageResponse( | ||||
| id = null, | id = null, | ||||
| code = null, | code = null, | ||||
| @@ -153,4 +163,81 @@ open fun getAllBagInfo(): List<BagInfo> { | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| open fun getAllBagUsageRecords(): List<BagUsageRecordResponse> { | |||||
| val allRecords = joBagConsumptionRepository.findAllByDeletedIsFalse() | |||||
| return allRecords.map { record -> | |||||
| val bag = bagRepository.findById(record.bagId ?: 0L).orElse(null) | |||||
| val bagLotLine = bagLotLineRepository.findById(record.bagLotLineId ?: 0L).orElse(null) | |||||
| BagUsageRecordResponse( | |||||
| id = record.id ?: 0L, | |||||
| bagId = record.bagId ?: 0L, | |||||
| bagLotLineId = record.bagLotLineId ?: 0L, | |||||
| jobId = record.jobId ?: 0L, | |||||
| jobOrderCode = record.jobOrderCode ?: "", | |||||
| stockOutLineId = record.stockOutLineId ?: 0L, | |||||
| startQty = record.startQty ?: 0, | |||||
| consumedQty = record.consumedQty ?: 0, | |||||
| scrapQty = record.scrapQty ?: 0, | |||||
| endQty = record.endQty ?: 0, | |||||
| date = record.date ?: LocalDate.now(), | |||||
| time = record.time ?: LocalDateTime.now(), | |||||
| bagName = bag?.itemName, | |||||
| bagCode = bag?.itemCode, | |||||
| lotNo = bagLotLine?.lotNo | |||||
| ) | |||||
| } | |||||
| } | |||||
| open fun getBagSummaries(): List<BagSummaryResponse> { | |||||
| return bagRepository.findAllByDeletedIsFalse().map { bag -> | |||||
| BagSummaryResponse( | |||||
| id = bag.id ?: 0L, | |||||
| bagName = bag.itemName, | |||||
| bagCode = bag.itemCode, | |||||
| takenBagBalance = bag.takenBagBalance, | |||||
| deleted = bag.deleted | |||||
| ) | |||||
| } | |||||
| } | |||||
| open fun getBagLotLines(bagId: Long): List<BagLotLineResponse> { | |||||
| val lines = bagLotLineRepository.findAllByBagId(bagId) | |||||
| return lines.map { line -> | |||||
| BagLotLineResponse( | |||||
| id = line.id ?: 0L, | |||||
| bagId = line.bagId ?: 0L, | |||||
| lotNo = line.lotNo, | |||||
| stockOutLineId = line.stockOutLineId, | |||||
| startQty = line.startQty, | |||||
| consumedQty = line.consumedQty, | |||||
| scrapQty = line.scrapQty, | |||||
| balanceQty = line.balanceQty, | |||||
| firstUseDate = line.firstUseDate, | |||||
| lastUseDate = line.lastUseDate | |||||
| ) | |||||
| } | |||||
| } | |||||
| open fun getBagConsumptions(bagLotLineId: Long): List<BagConsumptionResponse> { | |||||
| val records = joBagConsumptionRepository.findAllByBagLotLineIdAndDeletedIsFalse(bagLotLineId) | |||||
| return records.map { r -> | |||||
| BagConsumptionResponse( | |||||
| id = r.id ?: 0L, | |||||
| bagId = r.bagId ?: 0L, | |||||
| bagLotLineId = r.bagLotLineId ?: 0L, | |||||
| jobId = r.jobId ?: 0L, | |||||
| jobOrderCode = r.jobOrderCode, | |||||
| stockOutLineId = r.stockOutLineId, | |||||
| startQty = r.startQty, | |||||
| consumedQty = r.consumedQty, | |||||
| scrapQty = r.scrapQty, | |||||
| endQty = r.endQty, | |||||
| date = r.date, | |||||
| time = r.time | |||||
| ) | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -47,4 +47,19 @@ class BagController( | |||||
| fun createJoBagConsumption(@RequestBody request: CreateJoBagConsumptionRequest): MessageResponse { | fun createJoBagConsumption(@RequestBody request: CreateJoBagConsumptionRequest): MessageResponse { | ||||
| return bagService.createJoBagConsumption(request) | return bagService.createJoBagConsumption(request) | ||||
| } | } | ||||
| @GetMapping("/bagUsageRecords") | |||||
| fun getBagUsageRecords(): List<BagUsageRecordResponse> { | |||||
| return bagService.getAllBagUsageRecords() | |||||
| } | |||||
| @GetMapping("/bags") | |||||
| fun getBags(): List<BagSummaryResponse> = | |||||
| bagService.getBagSummaries() | |||||
| @GetMapping("/bags/{bagId}/lot-lines") | |||||
| fun getBagLotLines(@PathVariable bagId: Long): List<BagLotLineResponse> = | |||||
| bagService.getBagLotLines(bagId) | |||||
| @GetMapping("/lot-lines/{bagLotLineId}/consumptions") | |||||
| fun getBagConsumptions(@PathVariable bagLotLineId: Long): List<BagConsumptionResponse> = | |||||
| bagService.getBagConsumptions(bagLotLineId) | |||||
| } | } | ||||
| @@ -1,5 +1,6 @@ | |||||
| package com.ffii.fpsms.modules.bag.web.model | package com.ffii.fpsms.modules.bag.web.model | ||||
| import java.time.LocalDate | |||||
| import java.time.LocalDateTime | |||||
| data class GetAllBagInfoResponse( | data class GetAllBagInfoResponse( | ||||
| val bagId: Long, | val bagId: Long, | ||||
| val bagName: String, | val bagName: String, | ||||
| @@ -16,4 +17,58 @@ data class BagInfo( | |||||
| val code: String, | val code: String, | ||||
| val balanceQty: Int, | val balanceQty: Int, | ||||
| ) | |||||
| data class BagUsageRecordResponse( | |||||
| val id: Long, | |||||
| val bagId: Long, | |||||
| val bagLotLineId: Long, | |||||
| val jobId: Long, | |||||
| val jobOrderCode: String, | |||||
| val stockOutLineId: Long, | |||||
| val startQty: Int, | |||||
| val consumedQty: Int, | |||||
| val scrapQty: Int, | |||||
| val endQty: Int, | |||||
| val date: LocalDate, | |||||
| val time: LocalDateTime, | |||||
| val bagName: String?, | |||||
| val bagCode: String?, | |||||
| val lotNo: String? | |||||
| ) | |||||
| data class BagSummaryResponse( | |||||
| val id: Long, // bag.id | |||||
| val bagName: String?, | |||||
| val bagCode: String?, | |||||
| val takenBagBalance: Int?, // 如有 | |||||
| val deleted: Boolean? | |||||
| ) | |||||
| data class BagLotLineResponse( | |||||
| val id: Long, | |||||
| val bagId: Long, | |||||
| val lotNo: String?, | |||||
| val stockOutLineId: Long?, | |||||
| val startQty: Int?, | |||||
| val consumedQty: Int?, | |||||
| val scrapQty: Int?, | |||||
| val balanceQty: Int?, | |||||
| val firstUseDate: LocalDate?, | |||||
| val lastUseDate: LocalDate? | |||||
| ) | |||||
| data class BagConsumptionResponse( | |||||
| val id: Long, | |||||
| val bagId: Long, | |||||
| val bagLotLineId: Long, | |||||
| val jobId: Long, | |||||
| val jobOrderCode: String?, | |||||
| val stockOutLineId: Long?, | |||||
| val startQty: Int?, | |||||
| val consumedQty: Int?, | |||||
| val scrapQty: Int?, | |||||
| val endQty: Int?, | |||||
| val date: LocalDate?, | |||||
| val time: LocalDateTime? | |||||
| ) | ) | ||||
| @@ -28,5 +28,4 @@ interface DeliveryOrderInfo{ | |||||
| val deliveryOrderLines: List<DeliveryOrderLineInfo> | val deliveryOrderLines: List<DeliveryOrderLineInfo> | ||||
| } | } | ||||
| @@ -421,19 +421,19 @@ open class DeliveryOrderService( | |||||
| } | } | ||||
| deliveryOrderRepository.save(deliveryOrder) | deliveryOrderRepository.save(deliveryOrder) | ||||
| val pols = deliveryOrder.deliveryOrderLines.map { | |||||
| SavePickOrderLineRequest( | |||||
| itemId = it.item?.id, | |||||
| qty = it.qty ?: BigDecimal.ZERO, | |||||
| uomId = it.uom?.id, | |||||
| val pols = deliveryOrder.deliveryOrderLines.map { | |||||
| SavePickOrderLineRequest( | |||||
| itemId = it.item?.id, | |||||
| qty = it.qty ?: BigDecimal.ZERO, | |||||
| uomId = it.uom?.id, | |||||
| ) | |||||
| } | |||||
| val po = SavePickOrderRequest( | |||||
| doId = deliveryOrder.id, | |||||
| type = PickOrderType.DELIVERY_ORDER, | |||||
| targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now(), | |||||
| pickOrderLine = pols | |||||
| ) | ) | ||||
| } | |||||
| val po = SavePickOrderRequest( | |||||
| doId = deliveryOrder.id, | |||||
| type = PickOrderType.DELIVERY_ORDER, | |||||
| targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now(), | |||||
| pickOrderLine = pols | |||||
| ) | |||||
| val createdPickOrder = pickOrderService.create(po) | val createdPickOrder = pickOrderService.create(po) | ||||
| println("🔍 DEBUG: Created pick order - ID: ${createdPickOrder.id}") | println("🔍 DEBUG: Created pick order - ID: ${createdPickOrder.id}") | ||||
| @@ -608,64 +608,64 @@ open class DeliveryOrderService( | |||||
| println(" DEBUG: Truck matches preferred floor - Truck Store: $truckStoreId, Preferred: $preferredFloor") | println(" DEBUG: Truck matches preferred floor - Truck Store: $truckStoreId, Preferred: $preferredFloor") | ||||
| // storeId 基于 items 的 preferredFloor | |||||
| val storeId = "$preferredFloor/F" | |||||
| val loadingSequence = truck.loadingSequence ?: 999 | |||||
| println("🔍 DEBUG: Creating DoPickOrder - Floor: $preferredFloor, Store: $storeId, Truck: ${truck.id}") | |||||
| val doPickOrder = DoPickOrder( | |||||
| storeId = storeId, | |||||
| ticketNo = "TEMP-${System.currentTimeMillis()}", | |||||
| ticketStatus = DoPickOrderStatus.pending, | |||||
| truckId = truck.id, | |||||
| doOrderId = deliveryOrder.id, | |||||
| pickOrderId = createdPickOrder.id, | |||||
| truckDepartureTime = truck.departureTime, | |||||
| shopId = deliveryOrder.shop?.id, | |||||
| handledBy = null, | |||||
| pickOrderCode = createdPickOrder.code, | |||||
| deliveryOrderCode = deliveryOrder.code, | |||||
| loadingSequence = loadingSequence, | |||||
| ticketReleaseTime = null, | |||||
| truckLanceCode = truck.truckLanceCode, | |||||
| shopCode = deliveryOrder.shop?.code, | |||||
| shopName = deliveryOrder.shop?.name, | |||||
| requiredDeliveryDate = targetDate | |||||
| ) | |||||
| val savedDoPickOrder = doPickOrderService.save(doPickOrder) | |||||
| println("🔍 DEBUG: Saved DoPickOrder - ID: ${savedDoPickOrder.id}") | |||||
| truck | |||||
| } | |||||
| return MessageResponse( | |||||
| id = deliveryOrder.id, | |||||
| code = deliveryOrder.code, | |||||
| name = deliveryOrder.shop?.name, | |||||
| type = null, | |||||
| message = null, | |||||
| errorPosition = null, | |||||
| entity = mapOf("status" to deliveryOrder.status?.value) | |||||
| // storeId 基于 items 的 preferredFloor | |||||
| val storeId = "$preferredFloor/F" | |||||
| val loadingSequence = truck.loadingSequence ?: 999 | |||||
| println("🔍 DEBUG: Creating DoPickOrder - Floor: $preferredFloor, Store: $storeId, Truck: ${truck.id}") | |||||
| val doPickOrder = DoPickOrder( | |||||
| storeId = storeId, | |||||
| ticketNo = "TEMP-${System.currentTimeMillis()}", | |||||
| ticketStatus = DoPickOrderStatus.pending, | |||||
| truckId = truck.id, | |||||
| doOrderId = deliveryOrder.id, | |||||
| pickOrderId = createdPickOrder.id, | |||||
| truckDepartureTime = truck.departureTime, | |||||
| shopId = deliveryOrder.shop?.id, | |||||
| handledBy = null, | |||||
| pickOrderCode = createdPickOrder.code, | |||||
| deliveryOrderCode = deliveryOrder.code, | |||||
| loadingSequence = loadingSequence, | |||||
| ticketReleaseTime = null, | |||||
| truckLanceCode = truck.truckLanceCode, | |||||
| shopCode = deliveryOrder.shop?.code, | |||||
| shopName = deliveryOrder.shop?.name, | |||||
| requiredDeliveryDate = targetDate | |||||
| ) | ) | ||||
| val savedDoPickOrder = doPickOrderService.save(doPickOrder) | |||||
| println("🔍 DEBUG: Saved DoPickOrder - ID: ${savedDoPickOrder.id}") | |||||
| truck | |||||
| } | } | ||||
| return MessageResponse( | |||||
| id = deliveryOrder.id, | |||||
| code = deliveryOrder.code, | |||||
| name = deliveryOrder.shop?.name, | |||||
| type = null, | |||||
| message = null, | |||||
| errorPosition = null, | |||||
| entity = mapOf("status" to deliveryOrder.status?.value) | |||||
| ) | |||||
| } | |||||
| open fun getLotNumbersForPickOrderByItemId(itemId: Long, pickOrderId: Long): String { | |||||
| try { | |||||
| val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrderId) | |||||
| val pickOrderLineIds = pickOrderLines.mapNotNull { it.id } | |||||
| val suggestedPickLots = suggestedPickLotRepository.findAllByPickOrderLineIdIn(pickOrderLineIds) | |||||
| val lotNumbers = suggestedPickLots | |||||
| .filter { it.pickOrderLine?.item?.id == itemId } | |||||
| .mapNotNull { it.suggestedLotLine?.inventoryLot?.lotNo } | |||||
| .distinct() | |||||
| return lotNumbers.joinToString(", ") | |||||
| } catch (e: Exception) { | |||||
| println("Error getting lot numbers for item $itemId in pick order $pickOrderId: ${e.message}") | |||||
| return "" | |||||
| } | |||||
| open fun getLotNumbersForPickOrderByItemId(itemId: Long, pickOrderId: Long): String { | |||||
| try { | |||||
| val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrderId) | |||||
| val pickOrderLineIds = pickOrderLines.mapNotNull { it.id } | |||||
| val suggestedPickLots = suggestedPickLotRepository.findAllByPickOrderLineIdIn(pickOrderLineIds) | |||||
| val lotNumbers = suggestedPickLots | |||||
| .filter { it.pickOrderLine?.item?.id == itemId } | |||||
| .mapNotNull { it.suggestedLotLine?.inventoryLot?.lotNo } | |||||
| .distinct() | |||||
| return lotNumbers.joinToString(", ") | |||||
| } catch (e: Exception) { | |||||
| println("Error getting lot numbers for item $itemId in pick order $pickOrderId: ${e.message}") | |||||
| return "" | |||||
| } | } | ||||
| } | |||||
| // Delivery Note | // Delivery Note | ||||
| @@ -794,7 +794,8 @@ open class DeliveryOrderService( | |||||
| params["shopName"] = doPickOrder.shopName ?: deliveryNoteInfo[0].shopName ?: "" | params["shopName"] = doPickOrder.shopName ?: deliveryNoteInfo[0].shopName ?: "" | ||||
| params["shopAddress"] = deliveryNoteInfo[0].shopAddress ?: "" | params["shopAddress"] = deliveryNoteInfo[0].shopAddress ?: "" | ||||
| params["deliveryDate"] = deliveryNoteInfo[0].estimatedArrivalDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "" | |||||
| params["deliveryDate"] = | |||||
| deliveryNoteInfo[0].estimatedArrivalDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "" | |||||
| params["truckNo"] = truckNo | params["truckNo"] = truckNo | ||||
| params["ShopPurchaseOrderNo"] = doPickOrder.deliveryOrderCode ?: deliveryNoteInfo.joinToString(", ") { it.code } | params["ShopPurchaseOrderNo"] = doPickOrder.deliveryOrderCode ?: deliveryNoteInfo.joinToString(", ") { it.code } | ||||
| params["FGPickOrderNo"] = doPickOrder.pickOrderCode ?: selectedPickOrder?.code ?: "" | params["FGPickOrderNo"] = doPickOrder.pickOrderCode ?: selectedPickOrder?.code ?: "" | ||||
| @@ -810,7 +811,7 @@ open class DeliveryOrderService( | |||||
| deliveryNote: net.sf.jasperreports.engine.JasperReport, | deliveryNote: net.sf.jasperreports.engine.JasperReport, | ||||
| fields: MutableList<MutableMap<String, Any>>, | fields: MutableList<MutableMap<String, Any>>, | ||||
| params: MutableMap<String, Any> | params: MutableMap<String, Any> | ||||
| ) : Map<String, Any> { | |||||
| ): Map<String, Any> { | |||||
| val doPickOrderRecord = doPickOrderRecordRepository.findById(request.doPickOrderId).orElseThrow { | val doPickOrderRecord = doPickOrderRecordRepository.findById(request.doPickOrderId).orElseThrow { | ||||
| NoSuchElementException("DoPickOrderRecord not found with ID: ${request.doPickOrderId}") | NoSuchElementException("DoPickOrderRecord not found with ID: ${request.doPickOrderId}") | ||||
| @@ -819,12 +820,12 @@ open class DeliveryOrderService( | |||||
| val doPickOrderLineRecords = doPickOrderLineRecordRepository.findByDoPickOrderId(doPickOrderRecord.recordId) | val doPickOrderLineRecords = doPickOrderLineRecordRepository.findByDoPickOrderId(doPickOrderRecord.recordId) | ||||
| val pickOrderIds = doPickOrderLineRecords.mapNotNull { it.pickOrderId }.distinct() | val pickOrderIds = doPickOrderLineRecords.mapNotNull { it.pickOrderId }.distinct() | ||||
| if(pickOrderIds.isEmpty()){ | |||||
| if (pickOrderIds.isEmpty()) { | |||||
| throw IllegalStateException("DoPickOrderRecord ${request.doPickOrderId} has no associated pick orders") | throw IllegalStateException("DoPickOrderRecord ${request.doPickOrderId} has no associated pick orders") | ||||
| } | } | ||||
| val deliveryOrderIds = doPickOrderLineRecords.mapNotNull { it.doOrderId }.distinct() | val deliveryOrderIds = doPickOrderLineRecords.mapNotNull { it.doOrderId }.distinct() | ||||
| if(deliveryOrderIds.isEmpty()){ | |||||
| if (deliveryOrderIds.isEmpty()) { | |||||
| throw IllegalStateException("DoPickOrderRecord ${request.doPickOrderId} has no associated delivery orders") | throw IllegalStateException("DoPickOrderRecord ${request.doPickOrderId} has no associated delivery orders") | ||||
| } | } | ||||
| @@ -920,9 +921,11 @@ open class DeliveryOrderService( | |||||
| params["shopName"] = doPickOrderRecord.shopName ?: deliveryNoteInfo[0].shopName ?: "" | params["shopName"] = doPickOrderRecord.shopName ?: deliveryNoteInfo[0].shopName ?: "" | ||||
| params["shopAddress"] = deliveryNoteInfo[0].shopAddress ?: "" | params["shopAddress"] = deliveryNoteInfo[0].shopAddress ?: "" | ||||
| params["deliveryDate"] = deliveryNoteInfo[0].estimatedArrivalDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "" | |||||
| params["deliveryDate"] = | |||||
| deliveryNoteInfo[0].estimatedArrivalDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: "" | |||||
| params["truckNo"] = truckNo | params["truckNo"] = truckNo | ||||
| params["ShopPurchaseOrderNo"] = doPickOrderRecord.deliveryOrderCode ?: deliveryNoteInfo.joinToString(", ") { it.code } | |||||
| params["ShopPurchaseOrderNo"] = | |||||
| doPickOrderRecord.deliveryOrderCode ?: deliveryNoteInfo.joinToString(", ") { it.code } | |||||
| params["FGPickOrderNo"] = doPickOrderRecord.pickOrderCode ?: selectedPickOrder?.code ?: "" | params["FGPickOrderNo"] = doPickOrderRecord.pickOrderCode ?: selectedPickOrder?.code ?: "" | ||||
| return mapOf( | return mapOf( | ||||
| @@ -934,7 +937,8 @@ open class DeliveryOrderService( | |||||
| //Print Delivery Note | //Print Delivery Note | ||||
| @Transactional | @Transactional | ||||
| open fun printDeliveryNote(request: PrintDeliveryNoteRequest) { | open fun printDeliveryNote(request: PrintDeliveryNoteRequest) { | ||||
| val printer = printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer") | |||||
| val printer = | |||||
| printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer") | |||||
| val pdf = exportDeliveryNote( | val pdf = exportDeliveryNote( | ||||
| ExportDeliveryNoteRequest( | ExportDeliveryNoteRequest( | ||||
| @@ -993,7 +997,7 @@ open class DeliveryOrderService( | |||||
| val doPickOrderLineRecords = doPickOrderLineRecordRepository.findByDoPickOrderId(doPickOrderRecord.recordId) | val doPickOrderLineRecords = doPickOrderLineRecordRepository.findByDoPickOrderId(doPickOrderRecord.recordId) | ||||
| val deliveryOrderIds = doPickOrderLineRecords.mapNotNull { it.doOrderId }.distinct() | val deliveryOrderIds = doPickOrderLineRecords.mapNotNull { it.doOrderId }.distinct() | ||||
| if(deliveryOrderIds.isEmpty()){ | |||||
| if (deliveryOrderIds.isEmpty()) { | |||||
| throw IllegalStateException("DoPickOrderRecord ${request.doPickOrderId} has no associated delivery orders") | throw IllegalStateException("DoPickOrderRecord ${request.doPickOrderId} has no associated delivery orders") | ||||
| } | } | ||||
| @@ -1010,7 +1014,7 @@ open class DeliveryOrderService( | |||||
| val field = mutableMapOf<String, Any>() | val field = mutableMapOf<String, Any>() | ||||
| } | } | ||||
| if(cartonLabelInfo.size > 1){ | |||||
| if (cartonLabelInfo.size > 1) { | |||||
| } | } | ||||
| @@ -1036,245 +1040,375 @@ open class DeliveryOrderService( | |||||
| } | } | ||||
| //Print Carton Labels | |||||
| @Transactional | |||||
| open fun printDNLabels(request: PrintDNLabelsRequest) { | |||||
| val printer = | |||||
| printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer") | |||||
| //Print Carton Labels | |||||
| @Transactional | |||||
| open fun printDNLabels(request: PrintDNLabelsRequest) { | |||||
| val printer = | |||||
| printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer") | |||||
| val pdf = exportDNLabels( | |||||
| ExportDNLabelsRequest( | |||||
| doPickOrderId = request.doPickOrderId, | |||||
| numOfCarton = request.numOfCarton | |||||
| ) | |||||
| val pdf = exportDNLabels( | |||||
| ExportDNLabelsRequest( | |||||
| doPickOrderId = request.doPickOrderId, | |||||
| numOfCarton = request.numOfCarton | |||||
| ) | ) | ||||
| ) | |||||
| val jasperPrint = pdf["report"] as JasperPrint | |||||
| val jasperPrint = pdf["report"] as JasperPrint | |||||
| val tempPdfFile = File.createTempFile("print_job_", ".pdf") | |||||
| val tempPdfFile = File.createTempFile("print_job_", ".pdf") | |||||
| try { | |||||
| JasperExportManager.exportReportToPdfFile(jasperPrint, tempPdfFile.absolutePath) | |||||
| try { | |||||
| JasperExportManager.exportReportToPdfFile(jasperPrint, tempPdfFile.absolutePath) | |||||
| val printQty = if (request.printQty == null || request.printQty <= 0) 1 else request.printQty | |||||
| printer.ip?.let { ip -> | |||||
| printer.port?.let { port -> | |||||
| ZebraPrinterUtil.printPdfToZebra( | |||||
| tempPdfFile, | |||||
| ip, | |||||
| port, | |||||
| printQty, | |||||
| ZebraPrinterUtil.PrintDirection.ROTATED | |||||
| ) | |||||
| } | |||||
| val printQty = if (request.printQty == null || request.printQty <= 0) 1 else request.printQty | |||||
| printer.ip?.let { ip -> | |||||
| printer.port?.let { port -> | |||||
| ZebraPrinterUtil.printPdfToZebra( | |||||
| tempPdfFile, | |||||
| ip, | |||||
| port, | |||||
| printQty, | |||||
| ZebraPrinterUtil.PrintDirection.ROTATED | |||||
| ) | |||||
| } | } | ||||
| println("Test PDF saved to: ${tempPdfFile.absolutePath}") | |||||
| } finally { | |||||
| //tempPdfFile.delete() | |||||
| } | } | ||||
| } | |||||
| @Transactional(rollbackFor = [Exception::class]) | |||||
| open fun releaseDeliveryOrderWithoutTicket(request: ReleaseDoRequest): ReleaseDoResult { | |||||
| println("🔍 DEBUG: Starting releaseDeliveryOrderWithoutTicket for DO ID: ${request.id}") | |||||
| println("Test PDF saved to: ${tempPdfFile.absolutePath}") | |||||
| val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(request.id) | |||||
| ?: throw NoSuchElementException("Delivery Order not found") | |||||
| } finally { | |||||
| //tempPdfFile.delete() | |||||
| } | |||||
| // 检查状态,跳过已完成或已发布的DO | |||||
| if (deliveryOrder.status == DeliveryOrderStatus.COMPLETED || deliveryOrder.status == DeliveryOrderStatus.RECEIVING) { | |||||
| throw IllegalStateException("Delivery Order ${deliveryOrder.id} is already ${deliveryOrder.status?.value}, skipping release") | |||||
| } | } | ||||
| // 更新状态为released (使用RECEIVING表示已发布) | |||||
| deliveryOrder.apply { | |||||
| status = DeliveryOrderStatus.RECEIVING // 使用RECEIVING表示已发布状态 | |||||
| } | |||||
| deliveryOrderRepository.save(deliveryOrder) | |||||
| // 创建 pick order | |||||
| val pols = deliveryOrder.deliveryOrderLines.map { | |||||
| SavePickOrderLineRequest( | |||||
| itemId = it.item?.id, | |||||
| qty = it.qty ?: BigDecimal.ZERO, | |||||
| uomId = it.uom?.id, | |||||
| ) | |||||
| } | |||||
| val po = SavePickOrderRequest( | |||||
| doId = deliveryOrder.id, | |||||
| type = PickOrderType.DELIVERY_ORDER, | |||||
| targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now(), | |||||
| pickOrderLine = pols | |||||
| ) | |||||
| val createdPickOrder = pickOrderService.create(po) | |||||
| val consoCode = pickOrderService.assignConsoCode() | |||||
| val pickOrderEntity = pickOrderRepository.findById(createdPickOrder.id!!).orElse(null) | |||||
| if (pickOrderEntity != null) { | |||||
| pickOrderEntity.consoCode = consoCode | |||||
| pickOrderEntity.status = com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED | |||||
| pickOrderRepository.saveAndFlush(pickOrderEntity) | |||||
| // 创建 suggestions 和 hold inventory | |||||
| val lines = pickOrderLineRepository.findAllByPickOrderId(pickOrderEntity.id!!) | |||||
| val suggestions = suggestedPickLotService.suggestionForPickOrderLines( | |||||
| SuggestedPickLotForPolRequest(pickOrderLines = lines) | |||||
| ) | |||||
| val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList) | |||||
| val insufficientCount = suggestions.suggestedList.count { it.suggestedLotLine == null } | |||||
| if (insufficientCount > 0) { | |||||
| println("⚠️ WARNING: $insufficientCount items have insufficient stock (issues auto-created)") | |||||
| @Transactional(rollbackFor = [Exception::class]) | |||||
| open fun releaseDeliveryOrderWithoutTicket(request: ReleaseDoRequest): ReleaseDoResult { | |||||
| println("🔍 DEBUG: Starting releaseDeliveryOrderWithoutTicket for DO ID: ${request.id}") | |||||
| val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(request.id) | |||||
| ?: throw NoSuchElementException("Delivery Order not found") | |||||
| // 检查状态,跳过已完成或已发布的DO | |||||
| if (deliveryOrder.status == DeliveryOrderStatus.COMPLETED || deliveryOrder.status == DeliveryOrderStatus.RECEIVING) { | |||||
| throw IllegalStateException("Delivery Order ${deliveryOrder.id} is already ${deliveryOrder.status?.value}, skipping release") | |||||
| } | |||||
| // 更新状态为released (使用RECEIVING表示已发布) | |||||
| deliveryOrder.apply { | |||||
| status = DeliveryOrderStatus.RECEIVING // 使用RECEIVING表示已发布状态 | |||||
| } | |||||
| deliveryOrderRepository.save(deliveryOrder) | |||||
| // 创建 pick order | |||||
| val pols = deliveryOrder.deliveryOrderLines.map { | |||||
| SavePickOrderLineRequest( | |||||
| itemId = it.item?.id, | |||||
| qty = it.qty ?: BigDecimal.ZERO, | |||||
| uomId = it.uom?.id, | |||||
| ) | |||||
| } | } | ||||
| val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn( | |||||
| saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id } | |||||
| val po = SavePickOrderRequest( | |||||
| doId = deliveryOrder.id, | |||||
| type = PickOrderType.DELIVERY_ORDER, | |||||
| targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now(), | |||||
| pickOrderLine = pols | |||||
| ) | ) | ||||
| saveSuggestedPickLots.forEach { lot -> | |||||
| if (lot.suggestedLotLine != null && lot.suggestedLotLine?.id != null && lot.suggestedLotLine!!.id!! > 0) { | |||||
| val lineIndex = inventoryLotLines.indexOf(lot.suggestedLotLine) | |||||
| if (lineIndex >= 0) { | |||||
| inventoryLotLines[lineIndex].holdQty = | |||||
| (inventoryLotLines[lineIndex].holdQty ?: BigDecimal.ZERO).plus(lot.qty ?: BigDecimal.ZERO) | |||||
| val createdPickOrder = pickOrderService.create(po) | |||||
| val consoCode = pickOrderService.assignConsoCode() | |||||
| val pickOrderEntity = pickOrderRepository.findById(createdPickOrder.id!!).orElse(null) | |||||
| if (pickOrderEntity != null) { | |||||
| pickOrderEntity.consoCode = consoCode | |||||
| pickOrderEntity.status = com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus.RELEASED | |||||
| pickOrderRepository.saveAndFlush(pickOrderEntity) | |||||
| // 创建 suggestions 和 hold inventory | |||||
| val lines = pickOrderLineRepository.findAllByPickOrderId(pickOrderEntity.id!!) | |||||
| val suggestions = suggestedPickLotService.suggestionForPickOrderLines( | |||||
| SuggestedPickLotForPolRequest(pickOrderLines = lines) | |||||
| ) | |||||
| val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList) | |||||
| val insufficientCount = suggestions.suggestedList.count { it.suggestedLotLine == null } | |||||
| if (insufficientCount > 0) { | |||||
| println("⚠️ WARNING: $insufficientCount items have insufficient stock (issues auto-created)") | |||||
| } | |||||
| val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn( | |||||
| saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id } | |||||
| ) | |||||
| saveSuggestedPickLots.forEach { lot -> | |||||
| if (lot.suggestedLotLine != null && lot.suggestedLotLine?.id != null && lot.suggestedLotLine!!.id!! > 0) { | |||||
| val lineIndex = inventoryLotLines.indexOf(lot.suggestedLotLine) | |||||
| if (lineIndex >= 0) { | |||||
| inventoryLotLines[lineIndex].holdQty = | |||||
| (inventoryLotLines[lineIndex].holdQty ?: BigDecimal.ZERO).plus(lot.qty ?: BigDecimal.ZERO) | |||||
| } | |||||
| } | |||||
| } | |||||
| inventoryLotLineRepository.saveAll(inventoryLotLines) | |||||
| // 创建 stock out | |||||
| val stockOut = StockOut().apply { | |||||
| this.type = "do" | |||||
| this.consoPickOrderCode = consoCode | |||||
| this.status = StockOutStatus.PENDING.status | |||||
| this.handler = request.userId | |||||
| } | |||||
| val savedStockOut = stockOutRepository.saveAndFlush(stockOut) | |||||
| saveSuggestedPickLots.forEach { lot -> | |||||
| val polId = lot.pickOrderLine?.id | |||||
| val illId = lot.suggestedLotLine?.id | |||||
| if (polId != null) { | |||||
| val pickOrderLine = pickOrderLineRepository.findById(polId).orElse(null) | |||||
| val inventoryLotLine = illId?.let { inventoryLotLineRepository.findById(it).orElse(null) } | |||||
| if (pickOrderLine != null) { | |||||
| val line = StockOutLine().apply { | |||||
| this.stockOut = savedStockOut | |||||
| this.pickOrderLine = pickOrderLine | |||||
| this.inventoryLotLine = inventoryLotLine // 可能为 null | |||||
| this.item = pickOrderLine.item | |||||
| // 修复:根据是否有 inventoryLotLine 设置状态 | |||||
| this.status = if (inventoryLotLine == null) { | |||||
| StockOutLineStatus.PARTIALLY_COMPLETE.status // 没有库存批次时使用 PARTIALLY_COMPLETE | |||||
| } else { | |||||
| StockOutLineStatus.PENDING.status // 有正常库存批次时使用 PENDING | |||||
| } | |||||
| this.qty = 0.0 | |||||
| } | |||||
| stockOutLineRepository.save(line) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| // 分析楼层分布 | |||||
| val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now() | |||||
| val itemIds = deliveryOrder.deliveryOrderLines.mapNotNull { it.item?.id }.distinct() | |||||
| val itemsStoreIdQuery = """ | |||||
| SELECT | |||||
| i.store_id, | |||||
| COUNT(DISTINCT i.id) as item_count | |||||
| FROM items i | |||||
| WHERE i.id IN (${itemIds.joinToString(",")}) | |||||
| AND i.deleted = 0 | |||||
| GROUP BY i.store_id | |||||
| """.trimIndent() | |||||
| val itemsStoreIdResults = jdbcDao.queryForList(itemsStoreIdQuery) | |||||
| val storeIdItemCount = mutableMapOf<String, Int>() | |||||
| var totalItemsWithStoreId = 0 // 统计有 store_id 的商品总数 | |||||
| itemsStoreIdResults.forEach { row -> | |||||
| val rawStoreId = row["store_id"] as? String | |||||
| if (rawStoreId != null) { // 只统计非 NULL 的 store_id | |||||
| val normalizedStoreId = when (rawStoreId) { | |||||
| "3F" -> "4F" | |||||
| else -> rawStoreId | |||||
| } | } | ||||
| val count = (row["item_count"] as? Number)?.toInt() ?: 0 | |||||
| storeIdItemCount[normalizedStoreId] = | |||||
| (storeIdItemCount[normalizedStoreId] ?: 0) + count | |||||
| totalItemsWithStoreId += count | |||||
| } | } | ||||
| } | } | ||||
| inventoryLotLineRepository.saveAll(inventoryLotLines) | |||||
| // 创建 stock out | |||||
| val stockOut = StockOut().apply { | |||||
| this.type = "do" | |||||
| this.consoPickOrderCode = consoCode | |||||
| this.status = StockOutStatus.PENDING.status | |||||
| this.handler = request.userId | |||||
| val count2F = storeIdItemCount["2F"] ?: 0 | |||||
| val count4F = storeIdItemCount["4F"] ?: 0 | |||||
| val preferredFloor = when { | |||||
| totalItemsWithStoreId == 0 -> { | |||||
| println("⚠️ WARNING: All items have NULL store_id, defaulting to 2F") | |||||
| "2F" | |||||
| } | |||||
| count4F > count2F -> "4F" | |||||
| count2F > count4F -> "2F" | |||||
| count4F == totalItemsWithStoreId && count2F == 0 -> "4F" | |||||
| else -> "2F" // 默认 2F | |||||
| } | } | ||||
| val savedStockOut = stockOutRepository.saveAndFlush(stockOut) | |||||
| saveSuggestedPickLots.forEach { lot -> | |||||
| val polId = lot.pickOrderLine?.id | |||||
| val illId = lot.suggestedLotLine?.id | |||||
| if (polId != null) { | |||||
| val pickOrderLine = pickOrderLineRepository.findById(polId).orElse(null) | |||||
| val inventoryLotLine = illId?.let { inventoryLotLineRepository.findById(it).orElse(null) } | |||||
| if (pickOrderLine != null) { | |||||
| val line = StockOutLine().apply { | |||||
| this.stockOut = savedStockOut | |||||
| this.pickOrderLine = pickOrderLine | |||||
| this.inventoryLotLine = inventoryLotLine // 可能为 null | |||||
| this.item = pickOrderLine.item | |||||
| // 修复:根据是否有 inventoryLotLine 设置状态 | |||||
| this.status = if (inventoryLotLine == null) { | |||||
| StockOutLineStatus.PARTIALLY_COMPLETE.status // 没有库存批次时使用 PARTIALLY_COMPLETE | |||||
| println("🔍 DEBUG: Floor calculation for DO ${deliveryOrder.id}") | |||||
| println(" - Total items: ${itemIds.size}") | |||||
| println(" - Items with store_id: $totalItemsWithStoreId") | |||||
| println(" - Items without store_id: ${itemIds.size - totalItemsWithStoreId}") | |||||
| println(" - 2F items: $count2F") | |||||
| println(" - 4F items: $count4F") | |||||
| println(" - Preferred floor: $preferredFloor") | |||||
| val truck = deliveryOrder.shop?.id?.let { shopId -> | |||||
| val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) | |||||
| val preferredStoreId = when (preferredFloor) { | |||||
| "2F" -> "2F" | |||||
| "4F" -> "4F" | |||||
| "3F" -> "3F" | |||||
| else -> "2F" | |||||
| } | |||||
| val matchedTrucks = trucks.filter { it.storeId == preferredStoreId } | |||||
| if (matchedTrucks.isEmpty()) { | |||||
| null | |||||
| } else { | |||||
| if (preferredStoreId == "4F" && matchedTrucks.size > 1) { | |||||
| deliveryOrder.estimatedArrivalDate?.let { estimatedArrivalDate -> | |||||
| val targetDate = estimatedArrivalDate.toLocalDate() | |||||
| val dayOfWeek = targetDate.dayOfWeek | |||||
| val dayAbbr = when (dayOfWeek) { | |||||
| java.time.DayOfWeek.MONDAY -> "Mon" | |||||
| java.time.DayOfWeek.TUESDAY -> "Tue" | |||||
| java.time.DayOfWeek.WEDNESDAY -> "Wed" | |||||
| java.time.DayOfWeek.THURSDAY -> "Thu" | |||||
| java.time.DayOfWeek.FRIDAY -> "Fri" | |||||
| java.time.DayOfWeek.SATURDAY -> "Sat" | |||||
| java.time.DayOfWeek.SUNDAY -> "Sun" | |||||
| } | |||||
| println("🔍 DEBUG: DO ${deliveryOrder.id} - Target date: $targetDate ($dayAbbr), Shop: $shopId") | |||||
| println("🔍 DEBUG: Found ${matchedTrucks.size} matched 4F trucks") | |||||
| val dayMatchedTrucks = matchedTrucks.filter { | |||||
| it.truckLanceCode?.contains(dayAbbr, ignoreCase = true) == true | |||||
| } | |||||
| println("🔍 DEBUG: Found ${dayMatchedTrucks.size} trucks matching $dayAbbr") | |||||
| dayMatchedTrucks.forEach { t -> | |||||
| println(" - Truck ID=${t.id}, Code=${t.truckLanceCode}, Time=${t.departureTime}") | |||||
| } | |||||
| if (dayMatchedTrucks.isNotEmpty()) { | |||||
| val selected = dayMatchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } | |||||
| println("✅ DEBUG: Selected truck matching $dayAbbr - ID=${selected?.id}, Code=${selected?.truckLanceCode}") | |||||
| selected | |||||
| } else { | } else { | ||||
| StockOutLineStatus.PENDING.status // 有正常库存批次时使用 PENDING | |||||
| println("⚠️ WARNING: No truck matching $dayAbbr, using first available truck") | |||||
| matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } | |||||
| } | } | ||||
| this.qty = 0.0 | |||||
| } ?: run { | |||||
| matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } | |||||
| } | } | ||||
| stockOutLineRepository.save(line) | |||||
| } else { | |||||
| matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | |||||
| // 分析楼层分布 | |||||
| val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now() | |||||
| val itemIds = deliveryOrder.deliveryOrderLines.mapNotNull { it.item?.id }.distinct() | |||||
| val itemsStoreIdQuery = """ | |||||
| SELECT | |||||
| i.store_id, | |||||
| COUNT(DISTINCT i.id) as item_count | |||||
| FROM items i | |||||
| WHERE i.id IN (${itemIds.joinToString(",")}) | |||||
| AND i.deleted = 0 | |||||
| GROUP BY i.store_id | |||||
| """.trimIndent() | |||||
| val itemsStoreIdResults = jdbcDao.queryForList(itemsStoreIdQuery) | |||||
| val storeIdItemCount = mutableMapOf<String, Int>() | |||||
| itemsStoreIdResults.forEach { row -> | |||||
| val rawStoreId = row["store_id"] as? String | |||||
| if (rawStoreId != null) { | |||||
| val normalizedStoreId = when (rawStoreId) { | |||||
| "3F" -> "4F" | |||||
| else -> rawStoreId | |||||
| } | |||||
| storeIdItemCount[normalizedStoreId] = | |||||
| (storeIdItemCount[normalizedStoreId] ?: 0) + | |||||
| ((row["item_count"] as? Number)?.toInt() ?: 0) | |||||
| // 如果没有匹配的 truck,抛出异常跳过 | |||||
| if (truck == null) { | |||||
| val errorMsg = | |||||
| "No matching truck found for preferredFloor ($preferredFloor). Skipping DO ${deliveryOrder.id}." | |||||
| println("⚠️ $errorMsg") | |||||
| throw IllegalStateException(errorMsg) | |||||
| } | } | ||||
| } | |||||
| val preferredFloor = if ((storeIdItemCount["4F"] ?: 0) == itemIds.size && | |||||
| (storeIdItemCount["2F"] ?: 0) == 0) { | |||||
| "4F" | |||||
| } else { | |||||
| "2F" | |||||
| println(" DEBUG: Matched truck - ID=${truck.id}, Store=${truck.storeId}, Floor=$preferredFloor") | |||||
| return ReleaseDoResult( | |||||
| deliveryOrderId = deliveryOrder.id!!, | |||||
| deliveryOrderCode = deliveryOrder.code, | |||||
| pickOrderId = createdPickOrder.id!!, | |||||
| pickOrderCode = pickOrderEntity?.code, | |||||
| shopId = deliveryOrder.shop?.id, | |||||
| shopCode = deliveryOrder.shop?.code, | |||||
| shopName = deliveryOrder.shop?.name, | |||||
| estimatedArrivalDate = targetDate, | |||||
| preferredFloor = preferredFloor, | |||||
| truckId = truck.id, | |||||
| truckDepartureTime = truck.departureTime, | |||||
| truckLanceCode = truck.truckLanceCode, | |||||
| loadingSequence = truck.loadingSequence // 直接使用 truck 的值 | |||||
| ) | |||||
| } | } | ||||
| // 查找匹配 preferred floor 的 truck | |||||
| val truck = deliveryOrder.shop?.id?.let { shopId -> | |||||
| val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) | |||||
| val preferredStoreId = when (preferredFloor) { | |||||
| "2F" -> "2F" | |||||
| "4F" -> "4F" | |||||
| "3F" -> "3F" | |||||
| else -> "2F" | |||||
| private fun getDayOfWeekAbbr(date: LocalDate): String = | |||||
| when (date.dayOfWeek) { | |||||
| java.time.DayOfWeek.MONDAY -> "Mon" | |||||
| java.time.DayOfWeek.TUESDAY -> "Tue" | |||||
| java.time.DayOfWeek.WEDNESDAY -> "Wed" | |||||
| java.time.DayOfWeek.THURSDAY -> "Thu" | |||||
| java.time.DayOfWeek.FRIDAY -> "Fri" | |||||
| java.time.DayOfWeek.SATURDAY -> "Sat" | |||||
| java.time.DayOfWeek.SUNDAY -> "Sun" | |||||
| } | } | ||||
| // 只选择 store_id 匹配的 truck | |||||
| val matchedTrucks = trucks.filter { it.storeId == preferredStoreId } | |||||
| if (matchedTrucks.isEmpty()) { | |||||
| null // 没有匹配的 truck | |||||
| } else { | |||||
| matchedTrucks.minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } | |||||
| } | |||||
| } | |||||
| // 如果没有匹配的 truck,抛出异常跳过 | |||||
| if (truck == null) { | |||||
| val errorMsg = "No matching truck found for preferredFloor ($preferredFloor). Skipping DO ${deliveryOrder.id}." | |||||
| println("⚠️ $errorMsg") | |||||
| throw IllegalStateException(errorMsg) | |||||
| } | |||||
| open fun check4FTruckAvailabilityForDoList(doIds: List<Long>): Check4FTruckBatchResponse { | |||||
| val problems = mutableListOf<ProblemDoDto>() | |||||
| println(" DEBUG: Matched truck - ID=${truck.id}, Store=${truck.storeId}, Floor=$preferredFloor") | |||||
| return ReleaseDoResult( | |||||
| deliveryOrderId = deliveryOrder.id!!, | |||||
| deliveryOrderCode = deliveryOrder.code, | |||||
| pickOrderId = createdPickOrder.id!!, | |||||
| pickOrderCode = pickOrderEntity?.code, | |||||
| shopId = deliveryOrder.shop?.id, | |||||
| shopCode = deliveryOrder.shop?.code, | |||||
| shopName = deliveryOrder.shop?.name, | |||||
| estimatedArrivalDate = targetDate, | |||||
| preferredFloor = preferredFloor, | |||||
| truckId = truck.id, | |||||
| truckDepartureTime = truck.departureTime, | |||||
| truckLanceCode = truck.truckLanceCode, | |||||
| loadingSequence = truck.loadingSequence // 直接使用 truck 的值 | |||||
| ) | |||||
| } | |||||
| doIds.forEach { doId -> | |||||
| val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(doId) ?: return@forEach | |||||
| val shopId = deliveryOrder.shop?.id ?: return@forEach | |||||
| val eta = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: return@forEach | |||||
| @Transactional | |||||
| open fun syncDeliveryOrderStatusFromDoPickOrder(): Int { | |||||
| // 1) Check if this DO is a 4F order (your “all items on 4F, no 2F” rule) | |||||
| val sql = """ | val sql = """ | ||||
| UPDATE fpsmsdb.delivery_order do | |||||
| INNER JOIN fpsmsdb.do_pick_order dpo ON dpo.do_order_id = do.id | |||||
| SET do.status = 'completed' | |||||
| WHERE dpo.ticket_status = 'completed' | |||||
| AND do.status != 'completed' | |||||
| AND do.deleted = 0 | |||||
| AND dpo.deleted = 0 | |||||
| """.trimIndent() | |||||
| return jdbcDao.executeUpdate(sql, emptyMap<String, Any>()) | |||||
| SELECT | |||||
| COUNT(DISTINCT CASE WHEN i.store_id = '4F' THEN dol.itemId END) AS count_4f, | |||||
| COUNT(DISTINCT CASE WHEN i.store_id = '2F' THEN dol.itemId END) AS count_2f, | |||||
| COUNT(DISTINCT dol.itemId) AS total_items | |||||
| FROM fpsmsdb.delivery_order_line dol | |||||
| INNER JOIN fpsmsdb.items i ON i.id = dol.itemId AND i.deleted = 0 | |||||
| WHERE dol.deliveryOrderId = :doId AND dol.deleted = 0 | |||||
| """.trimIndent() | |||||
| val res = jdbcDao.queryForList(sql, mapOf("doId" to doId)).firstOrNull() ?: return@forEach | |||||
| val count4F = (res["count_4f"] as? Number)?.toInt() ?: 0 | |||||
| val count2F = (res["count_2f"] as? Number)?.toInt() ?: 0 | |||||
| val totalItems = (res["total_items"] as? Number)?.toInt() ?: 0 | |||||
| val is4FOrder = (totalItems > 0 && count4F == totalItems && count2F == 0) | |||||
| if (!is4FOrder) { | |||||
| // Not a 4F order → no problem, skip | |||||
| return@forEach | |||||
| } | |||||
| // 2) Find 4F trucks for that shop on that weekday | |||||
| val dayAbbr = getDayOfWeekAbbr(eta) | |||||
| val trucks = truckRepository.findByShopIdAndStoreIdAndDayOfWeek(shopId, "4F", dayAbbr) | |||||
| if (trucks.isEmpty()) { | |||||
| // Problem DO: 4F order but no matching 4F truck that day | |||||
| val availableTrucksAll4F = truckRepository.findByShopIdAndDeletedFalse(shopId) | |||||
| .filter { it.storeId == "4F" } | |||||
| val truckDtos = availableTrucksAll4F.map { t -> | |||||
| TruckInfoDto( | |||||
| id = t.id, | |||||
| truckLanceCode = t.truckLanceCode, | |||||
| departureTime = t.departureTime?.toString(), | |||||
| storeId = t.storeId, | |||||
| shopCode = t.shopCode, | |||||
| shopName = t.shopName | |||||
| ) | |||||
| } | |||||
| problems += ProblemDoDto( | |||||
| deliveryOrderId = deliveryOrder.id!!, | |||||
| deliveryOrderCode = deliveryOrder.code, | |||||
| targetDate = eta, | |||||
| availableTrucks = truckDtos | |||||
| ) | |||||
| } | |||||
| } | } | ||||
| return Check4FTruckBatchResponse( | |||||
| hasProblem = problems.isNotEmpty(), | |||||
| problems = problems | |||||
| ) | |||||
| } | } | ||||
| } | |||||
| @@ -19,6 +19,8 @@ import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository | |||||
| import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus | import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus | ||||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRepository | import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRepository | ||||
| import com.ffii.fpsms.modules.pickOrder.service.PickExecutionIssueService | import com.ffii.fpsms.modules.pickOrder.service.PickExecutionIssueService | ||||
| import java.time.LocalDate | |||||
| import java.time.DayOfWeek | |||||
| data class BatchReleaseJobStatus( | data class BatchReleaseJobStatus( | ||||
| val jobId: String, | val jobId: String, | ||||
| val total: Int, | val total: Int, | ||||
| @@ -41,9 +43,37 @@ class DoReleaseCoordinatorService( | |||||
| private val poolSize = Runtime.getRuntime().availableProcessors() | private val poolSize = Runtime.getRuntime().availableProcessors() | ||||
| private val executor = Executors.newFixedThreadPool(min(poolSize, 4)) | private val executor = Executors.newFixedThreadPool(min(poolSize, 4)) | ||||
| private val jobs = ConcurrentHashMap<String, BatchReleaseJobStatus>() | private val jobs = ConcurrentHashMap<String, BatchReleaseJobStatus>() | ||||
| private fun getDayOfWeekAbbr(date: LocalDate?): String? { | |||||
| if (date == null) return null | |||||
| return when (date.dayOfWeek) { | |||||
| DayOfWeek.MONDAY -> "Mon" | |||||
| DayOfWeek.TUESDAY -> "Tue" | |||||
| DayOfWeek.WEDNESDAY -> "Wed" | |||||
| DayOfWeek.THURSDAY -> "Thu" | |||||
| DayOfWeek.FRIDAY -> "Fri" | |||||
| DayOfWeek.SATURDAY -> "Sat" | |||||
| DayOfWeek.SUNDAY -> "Sun" | |||||
| } | |||||
| } | |||||
| // 辅助方法:在 SQL 中获取星期几的缩写(MySQL) | |||||
| private fun getDayOfWeekAbbrSql(dateColumn: String): String { | |||||
| return """ | |||||
| CASE DAYNAME($dateColumn) | |||||
| WHEN 'Monday' THEN 'Mon' | |||||
| WHEN 'Tuesday' THEN 'Tue' | |||||
| WHEN 'Wednesday' THEN 'Wed' | |||||
| WHEN 'Thursday' THEN 'Thu' | |||||
| WHEN 'Friday' THEN 'Fri' | |||||
| WHEN 'Saturday' THEN 'Sat' | |||||
| WHEN 'Sunday' THEN 'Sun' | |||||
| ELSE '' | |||||
| END | |||||
| """.trimIndent() | |||||
| } | |||||
| private fun updateBatchTicketNumbers() { | private fun updateBatchTicketNumbers() { | ||||
| try { | try { | ||||
| val dayOfWeekSql = getDayOfWeekAbbrSql("do.estimatedArrivalDate") | |||||
| val updateSql = """ | val updateSql = """ | ||||
| UPDATE fpsmsdb.do_pick_order dpo | UPDATE fpsmsdb.do_pick_order dpo | ||||
| INNER JOIN ( | INNER JOIN ( | ||||
| @@ -53,7 +83,7 @@ class DoReleaseCoordinatorService( | |||||
| CASE | CASE | ||||
| WHEN i.store_id = '3F' THEN '4F' | WHEN i.store_id = '3F' THEN '4F' | ||||
| ELSE i.store_id | ELSE i.store_id | ||||
| END AS store_id, -- 这里做 3F → 4F | |||||
| END AS store_id, | |||||
| COUNT(DISTINCT dol.itemId) AS item_count | COUNT(DISTINCT dol.itemId) AS item_count | ||||
| FROM fpsmsdb.delivery_order_line dol | FROM fpsmsdb.delivery_order_line dol | ||||
| INNER JOIN fpsmsdb.items i ON i.id = dol.itemId | INNER JOIN fpsmsdb.items i ON i.id = dol.itemId | ||||
| @@ -95,7 +125,26 @@ class DoReleaseCoordinatorService( | |||||
| do.id AS delivery_order_id, | do.id AS delivery_order_id, | ||||
| do.estimatedArrivalDate, | do.estimatedArrivalDate, | ||||
| pf.preferred_floor, | pf.preferred_floor, | ||||
| $dayOfWeekSql AS day_of_week_abbr, | |||||
| CASE | CASE | ||||
| WHEN (SELECT COUNT(*) FROM fpsmsdb.truck t WHERE t.shopId = do.shopId AND t.deleted = 0 AND t.Store_id = pf.preferred_store_id) > 1 | |||||
| AND pf.preferred_store_id = 4 | |||||
| AND do.estimatedArrivalDate IS NOT NULL THEN | |||||
| COALESCE( | |||||
| (SELECT t.DepartureTime FROM fpsmsdb.truck t | |||||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||||
| AND t.Store_id = pf.preferred_store_id | |||||
| AND t.TruckLanceCode LIKE CONCAT('%', $dayOfWeekSql, '%') | |||||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||||
| (SELECT t.DepartureTime FROM fpsmsdb.truck t | |||||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||||
| AND t.Store_id = pf.preferred_store_id | |||||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||||
| (SELECT t.DepartureTime FROM fpsmsdb.truck t | |||||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||||
| '23:59:59' | |||||
| ) | |||||
| WHEN (SELECT COUNT(*) FROM fpsmsdb.truck t WHERE t.shopId = do.shopId AND t.deleted = 0) > 1 THEN | WHEN (SELECT COUNT(*) FROM fpsmsdb.truck t WHERE t.shopId = do.shopId AND t.deleted = 0) > 1 THEN | ||||
| COALESCE( | COALESCE( | ||||
| (SELECT t.DepartureTime FROM fpsmsdb.truck t | (SELECT t.DepartureTime FROM fpsmsdb.truck t | ||||
| @@ -116,6 +165,24 @@ class DoReleaseCoordinatorService( | |||||
| ) | ) | ||||
| END AS selected_departure_time, | END AS selected_departure_time, | ||||
| CASE | CASE | ||||
| WHEN (SELECT COUNT(*) FROM fpsmsdb.truck t WHERE t.shopId = do.shopId AND t.deleted = 0 AND t.Store_id = pf.preferred_store_id) > 1 | |||||
| AND pf.preferred_store_id = 4 | |||||
| AND do.estimatedArrivalDate IS NOT NULL THEN | |||||
| COALESCE( | |||||
| (SELECT t.TruckLanceCode FROM fpsmsdb.truck t | |||||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||||
| AND t.Store_id = pf.preferred_store_id | |||||
| AND t.TruckLanceCode LIKE CONCAT('%', $dayOfWeekSql, '%') | |||||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||||
| (SELECT t.TruckLanceCode FROM fpsmsdb.truck t | |||||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||||
| AND t.Store_id = pf.preferred_store_id | |||||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||||
| (SELECT t.TruckLanceCode FROM fpsmsdb.truck t | |||||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||||
| 'ZZ' | |||||
| ) | |||||
| WHEN (SELECT COUNT(*) FROM fpsmsdb.truck t WHERE t.shopId = do.shopId AND t.deleted = 0) > 1 THEN | WHEN (SELECT COUNT(*) FROM fpsmsdb.truck t WHERE t.shopId = do.shopId AND t.deleted = 0) > 1 THEN | ||||
| COALESCE( | COALESCE( | ||||
| (SELECT t.TruckLanceCode FROM fpsmsdb.truck t | (SELECT t.TruckLanceCode FROM fpsmsdb.truck t | ||||
| @@ -136,6 +203,12 @@ class DoReleaseCoordinatorService( | |||||
| ) | ) | ||||
| END AS selected_truck_lance, | END AS selected_truck_lance, | ||||
| COALESCE( | COALESCE( | ||||
| (SELECT t.LoadingSequence FROM fpsmsdb.truck t | |||||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||||
| AND t.Store_id = pf.preferred_store_id | |||||
| AND (pf.preferred_store_id != 4 OR do.estimatedArrivalDate IS NULL OR | |||||
| t.TruckLanceCode LIKE CONCAT('%', $dayOfWeekSql, '%')) | |||||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||||
| (SELECT t.LoadingSequence FROM fpsmsdb.truck t | (SELECT t.LoadingSequence FROM fpsmsdb.truck t | ||||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | WHERE t.shopId = do.shopId AND t.deleted = 0 | ||||
| AND t.Store_id = pf.preferred_store_id | AND t.Store_id = pf.preferred_store_id | ||||
| @@ -193,6 +266,7 @@ class DoReleaseCoordinatorService( | |||||
| try { | try { | ||||
| println("🔍 DEBUG: Getting ordered IDs for ${ids.size} orders") | println("🔍 DEBUG: Getting ordered IDs for ${ids.size} orders") | ||||
| println("🔍 DEBUG: First 5 IDs: ${ids.take(5)}") | println("🔍 DEBUG: First 5 IDs: ${ids.take(5)}") | ||||
| val dayOfWeekSql = getDayOfWeekAbbrSql("do.estimatedArrivalDate") | |||||
| val sql = """ | val sql = """ | ||||
| WITH DoFloorCounts AS ( | WITH DoFloorCounts AS ( | ||||
| SELECT | SELECT | ||||
| @@ -251,13 +325,39 @@ class DoReleaseCoordinatorService( | |||||
| FROM DoFloorSummary | FROM DoFloorSummary | ||||
| ), | ), | ||||
| TruckSelection AS ( | TruckSelection AS ( | ||||
| SELECT | |||||
| SELECT | |||||
| do.id AS delivery_order_id, | do.id AS delivery_order_id, | ||||
| do.shopId, | do.shopId, | ||||
| do.estimatedArrivalDate, | do.estimatedArrivalDate, | ||||
| pf.preferred_floor, | pf.preferred_floor, | ||||
| pf.preferred_store_id, | pf.preferred_store_id, | ||||
| $dayOfWeekSql AS day_of_week_abbr, | |||||
| CASE | CASE | ||||
| WHEN (SELECT COUNT(*) FROM fpsmsdb.truck t WHERE t.shopId = do.shopId AND t.deleted = 0 AND t.Store_id = pf.preferred_store_id) > 1 | |||||
| AND pf.preferred_store_id = 4 | |||||
| AND do.estimatedArrivalDate IS NOT NULL THEN | |||||
| COALESCE( | |||||
| (SELECT t.DepartureTime | |||||
| FROM fpsmsdb.truck t | |||||
| WHERE t.shopId = do.shopId | |||||
| AND t.deleted = 0 | |||||
| AND t.Store_id = pf.preferred_store_id | |||||
| AND t.TruckLanceCode LIKE CONCAT('%', $dayOfWeekSql, '%') | |||||
| ORDER BY t.DepartureTime ASC | |||||
| LIMIT 1), | |||||
| (SELECT t.DepartureTime | |||||
| FROM fpsmsdb.truck t | |||||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||||
| AND t.Store_id = pf.preferred_store_id | |||||
| ORDER BY t.DepartureTime ASC | |||||
| LIMIT 1), | |||||
| (SELECT t.DepartureTime | |||||
| FROM fpsmsdb.truck t | |||||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||||
| ORDER BY t.DepartureTime ASC | |||||
| LIMIT 1), | |||||
| '23:59:59' | |||||
| ) | |||||
| WHEN (SELECT COUNT(*) FROM fpsmsdb.truck t WHERE t.shopId = do.shopId AND t.deleted = 0) > 1 THEN | WHEN (SELECT COUNT(*) FROM fpsmsdb.truck t WHERE t.shopId = do.shopId AND t.deleted = 0) > 1 THEN | ||||
| COALESCE( | COALESCE( | ||||
| (SELECT t.DepartureTime | (SELECT t.DepartureTime | ||||
| @@ -284,6 +384,31 @@ class DoReleaseCoordinatorService( | |||||
| ) | ) | ||||
| END AS selected_departure_time, | END AS selected_departure_time, | ||||
| CASE | CASE | ||||
| WHEN (SELECT COUNT(*) FROM fpsmsdb.truck t WHERE t.shopId = do.shopId AND t.deleted = 0 AND t.Store_id = pf.preferred_store_id) > 1 | |||||
| AND pf.preferred_store_id = 4 | |||||
| AND do.estimatedArrivalDate IS NOT NULL THEN | |||||
| COALESCE( | |||||
| (SELECT t.TruckLanceCode | |||||
| FROM fpsmsdb.truck t | |||||
| WHERE t.shopId = do.shopId | |||||
| AND t.deleted = 0 | |||||
| AND t.Store_id = pf.preferred_store_id | |||||
| AND t.TruckLanceCode LIKE CONCAT('%', $dayOfWeekSql, '%') | |||||
| ORDER BY t.DepartureTime ASC | |||||
| LIMIT 1), | |||||
| (SELECT t.TruckLanceCode | |||||
| FROM fpsmsdb.truck t | |||||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||||
| AND t.Store_id = pf.preferred_store_id | |||||
| ORDER BY t.DepartureTime ASC | |||||
| LIMIT 1), | |||||
| (SELECT t.TruckLanceCode | |||||
| FROM fpsmsdb.truck t | |||||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||||
| ORDER BY t.DepartureTime ASC | |||||
| LIMIT 1), | |||||
| 'ZZ' | |||||
| ) | |||||
| WHEN (SELECT COUNT(*) FROM fpsmsdb.truck t WHERE t.shopId = do.shopId AND t.deleted = 0) > 1 THEN | WHEN (SELECT COUNT(*) FROM fpsmsdb.truck t WHERE t.shopId = do.shopId AND t.deleted = 0) > 1 THEN | ||||
| COALESCE( | COALESCE( | ||||
| (SELECT t.TruckLanceCode | (SELECT t.TruckLanceCode | ||||
| @@ -310,17 +435,24 @@ class DoReleaseCoordinatorService( | |||||
| ) | ) | ||||
| END AS selected_truck_lance, | END AS selected_truck_lance, | ||||
| COALESCE( | COALESCE( | ||||
| (SELECT t.LoadingSequence | |||||
| FROM fpsmsdb.truck t | |||||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||||
| AND t.Store_id = pf.preferred_store_id | |||||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||||
| (SELECT t.LoadingSequence | |||||
| FROM fpsmsdb.truck t | |||||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||||
| 999 | |||||
| ) AS loading_sequence | |||||
| (SELECT t.LoadingSequence | |||||
| FROM fpsmsdb.truck t | |||||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||||
| AND t.Store_id = pf.preferred_store_id | |||||
| AND (pf.preferred_store_id != 4 OR do.estimatedArrivalDate IS NULL OR | |||||
| t.TruckLanceCode LIKE CONCAT('%', $dayOfWeekSql, '%')) | |||||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||||
| (SELECT t.LoadingSequence | |||||
| FROM fpsmsdb.truck t | |||||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||||
| AND t.Store_id = pf.preferred_store_id | |||||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||||
| (SELECT t.LoadingSequence | |||||
| FROM fpsmsdb.truck t | |||||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||||
| 999 | |||||
| ) AS loading_sequence | |||||
| FROM fpsmsdb.delivery_order do | FROM fpsmsdb.delivery_order do | ||||
| LEFT JOIN PreferredFloor pf ON pf.deliveryOrderId = do.id | LEFT JOIN PreferredFloor pf ON pf.deliveryOrderId = do.id | ||||
| WHERE do.id IN (${ids.joinToString(",")}) | WHERE do.id IN (${ids.joinToString(",")}) | ||||
| @@ -338,35 +470,34 @@ class DoReleaseCoordinatorService( | |||||
| """.trimIndent() | """.trimIndent() | ||||
| println("🔍 DEBUG: SQL length: ${sql.length} characters") // 添加这行 | |||||
| println("🔍 DEBUG: SQL first 500 chars: ${sql.take(500)}") // 添加这行 | |||||
| println("🔍 DEBUG: SQL length: ${sql.length} characters") | |||||
| println("🔍 DEBUG: SQL first 500 chars: ${sql.take(500)}") | |||||
| val results = jdbcDao.queryForList(sql) | val results = jdbcDao.queryForList(sql) | ||||
| println("🔍 DEBUG: Results type: ${results.javaClass.name}") // 添加这行 | |||||
| println("🔍 DEBUG: Results size: ${results.size}") // 添加这行 | |||||
| if (results.isNotEmpty()) { | |||||
| println("🔍 DEBUG: First result keys: ${results[0].keys}") // 添加这行 | |||||
| println("🔍 DEBUG: First result: ${results[0]}") // 添加这行 | |||||
| } | |||||
| val sortedIds = results.mapNotNull { row -> | |||||
| (row["id"] as? Number)?.toLong() | |||||
| } | |||||
| println("🔍 DEBUG: Query returned ${sortedIds.size} sorted IDs") | |||||
| println("🔍 DEBUG: First 10 sorted IDs: ${sortedIds.take(10)}") | |||||
| return if (sortedIds.isEmpty()) { | |||||
| println("⚠️ WARNING: No sorted IDs, using original order") | |||||
| ids | |||||
| } else { | |||||
| sortedIds | |||||
| } | |||||
| println("🔍 DEBUG: Results type: ${results.javaClass.name}") | |||||
| println("🔍 DEBUG: Results size: ${results.size}") | |||||
| if (results.isNotEmpty()) { | |||||
| println("🔍 DEBUG: First result keys: ${results[0].keys}") | |||||
| println("🔍 DEBUG: First result: ${results[0]}") | |||||
| } | |||||
| val sortedIds = results.mapNotNull { row -> | |||||
| (row["id"] as? Number)?.toLong() | |||||
| } | |||||
| println("🔍 DEBUG: Query returned ${sortedIds.size} sorted IDs") | |||||
| println("🔍 DEBUG: First 10 sorted IDs: ${sortedIds.take(10)}") | |||||
| return if (sortedIds.isEmpty()) { | |||||
| println("⚠️ WARNING: No sorted IDs, using original order") | |||||
| ids | |||||
| } else { | |||||
| sortedIds | |||||
| } | |||||
| } catch (e: Exception) { | } catch (e: Exception) { | ||||
| println("❌ ERROR: ${e.message}") | |||||
| println("❌ ERROR Stack Trace:") // 添加这行 | |||||
| println("❌ ERROR: ${e.message}") | |||||
| println("❌ ERROR Stack Trace:") | |||||
| e.printStackTrace() | e.printStackTrace() | ||||
| return ids | return ids | ||||
| } | } | ||||
| @@ -38,6 +38,7 @@ import com.ffii.fpsms.modules.deliveryOrder.service.DoPickOrderService | |||||
| import com.ffii.core.response.RecordsRes | import com.ffii.core.response.RecordsRes | ||||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.PrintQrCodeForDoRequest | import com.ffii.fpsms.modules.deliveryOrder.web.models.PrintQrCodeForDoRequest | ||||
| import com.ffii.fpsms.modules.stock.service.StockInLineService | import com.ffii.fpsms.modules.stock.service.StockInLineService | ||||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.Check4FTruckBatchResponse | |||||
| @RequestMapping("/do") | @RequestMapping("/do") | ||||
| @RestController | @RestController | ||||
| @@ -228,4 +229,8 @@ class DeliveryOrderController( | |||||
| fun printQrCodeForDeliveryOrder(@ModelAttribute request: PrintQrCodeForDoRequest) { | fun printQrCodeForDeliveryOrder(@ModelAttribute request: PrintQrCodeForDoRequest) { | ||||
| stockInLineService.printQrCodeForDeliveryOrder(request) | stockInLineService.printQrCodeForDeliveryOrder(request) | ||||
| } | } | ||||
| @PostMapping("/check-4f-trucks-batch") | |||||
| fun check4FTrucksBatch(@RequestBody doIds: List<Long>): Check4FTruckBatchResponse { | |||||
| return deliveryOrderService.check4FTruckAvailabilityForDoList(doIds) | |||||
| } | |||||
| } | } | ||||
| @@ -26,4 +26,24 @@ data class TicketReleaseTableResponse( | |||||
| val requiredDeliveryDate: LocalDate?, | val requiredDeliveryDate: LocalDate?, | ||||
| val handlerName: String?, | val handlerName: String?, | ||||
| val numberOfFGItems: Int = 0 | val numberOfFGItems: Int = 0 | ||||
| ) | |||||
| data class TruckInfoDto( | |||||
| val id: Long?, | |||||
| val truckLanceCode: String?, | |||||
| val departureTime: String?, // or LocalTime? | |||||
| val storeId: String?, | |||||
| val shopCode: String?, | |||||
| val shopName: String? | |||||
| ) | |||||
| data class ProblemDoDto( | |||||
| val deliveryOrderId: Long, | |||||
| val deliveryOrderCode: String?, | |||||
| val targetDate: LocalDate, | |||||
| val availableTrucks: List<TruckInfoDto> | |||||
| ) | |||||
| data class Check4FTruckBatchResponse( | |||||
| val hasProblem: Boolean, | |||||
| val problems: List<ProblemDoDto> | |||||
| ) | ) | ||||
| @@ -26,8 +26,9 @@ import com.google.gson.reflect.TypeToken | |||||
| import org.springframework.data.domain.PageRequest | import org.springframework.data.domain.PageRequest | ||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||
| import org.springframework.transaction.annotation.Transactional | import org.springframework.transaction.annotation.Transactional | ||||
| import java.math.BigDecimal | |||||
| import java.math.BigDecimal | |||||
| import com.ffii.fpsms.modules.jobOrder.entity.JobOrderBomMaterialRepository | |||||
| 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.entity.JobTypeRepository | ||||
| @@ -62,7 +63,7 @@ import kotlinx.serialization.encodeToString | |||||
| import kotlinx.serialization.json.Json | import kotlinx.serialization.json.Json | ||||
| import kotlin.math.exp | import kotlin.math.exp | ||||
| import com.ffii.fpsms.modules.productProcess.entity.ProductProcessRepository | import com.ffii.fpsms.modules.productProcess.entity.ProductProcessRepository | ||||
| import java.math.RoundingMode | |||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
| @Service | @Service | ||||
| @@ -83,7 +84,8 @@ open class JobOrderService( | |||||
| val jobTypeRepository: JobTypeRepository, | val jobTypeRepository: JobTypeRepository, | ||||
| val inventoryRepository: InventoryRepository, | val inventoryRepository: InventoryRepository, | ||||
| val stockInLineRepository: StockInLineRepository, | val stockInLineRepository: StockInLineRepository, | ||||
| val productProcessRepository: ProductProcessRepository | |||||
| val productProcessRepository: ProductProcessRepository, | |||||
| val jobOrderBomMaterialRepository: JobOrderBomMaterialRepository | |||||
| ) { | ) { | ||||
| open fun allJobOrdersByPage(request: SearchJobOrderInfoRequest): RecordsRes<JobOrderInfo> { | open fun allJobOrdersByPage(request: SearchJobOrderInfoRequest): RecordsRes<JobOrderInfo> { | ||||
| @@ -803,4 +805,40 @@ open class JobOrderService( | |||||
| ) | ) | ||||
| } | } | ||||
| */ | */ | ||||
| open fun updateJoReqQty(request: UpdateJoReqQtyRequest): MessageResponse { | |||||
| val jobOrder = jobOrderRepository.findById(request.id).orElse(null) | |||||
| ?: throw NoSuchElementException("Job Order not found with id: ${request.id}") | |||||
| val newReqQty = BigDecimal.valueOf(request.reqQty.toLong()) | |||||
| // 更新 JobOrder 的 reqQty | |||||
| jobOrder.reqQty = newReqQty | |||||
| jobOrderRepository.save(jobOrder) | |||||
| // 更新相关的 JobOrderBomMaterial 的 reqQty(根据新的比例重新计算) | |||||
| val bom = jobOrder.bom | |||||
| if (bom != null && bom.outputQty != null && bom.outputQty!! > BigDecimal.ZERO) { | |||||
| val proportion = newReqQty.divide(bom.outputQty!!, 5, RoundingMode.HALF_UP) | |||||
| val jobOrderBomMaterials = jobOrderBomMaterialRepository.findAllByJobOrderId(jobOrder.id) | |||||
| jobOrderBomMaterials.forEach { jobm -> | |||||
| // 找到对应的 BOM Material | |||||
| val bomMaterial = bom.bomMaterials?.find { it.item?.id == jobm.item?.id } | |||||
| if (bomMaterial != null && bomMaterial.qty != null) { | |||||
| jobm.reqQty = (bomMaterial.qty!!.times(proportion)).setScale(0, RoundingMode.CEILING) | |||||
| jobOrderBomMaterialRepository.save(jobm) | |||||
| } | |||||
| } | |||||
| } | |||||
| return MessageResponse( | |||||
| id = jobOrder.id, | |||||
| code = jobOrder.code, | |||||
| name = jobOrder.bom?.name, | |||||
| type = "success", | |||||
| message = "Job Order ReqQty Updated", | |||||
| errorPosition = null, | |||||
| entity = mapOf("reqQty" to request.reqQty) | |||||
| ) | |||||
| } | |||||
| } | } | ||||
| @@ -316,4 +316,8 @@ fun checkJobOrderCreated(@Valid @RequestBody request: CheckJobOrderCreatedReques | |||||
| return jobOrderService.checkJobOrderCreated(request) | return jobOrderService.checkJobOrderCreated(request) | ||||
| } | } | ||||
| */ | */ | ||||
| @PostMapping("/updateReqQty") | |||||
| fun updateJoReqQty(@Valid @RequestBody request: UpdateJoReqQtyRequest): MessageResponse { | |||||
| return jobOrderService.updateJoReqQty(request) | |||||
| } | |||||
| } | } | ||||
| @@ -149,4 +149,8 @@ data class JobOrderInfoResponse( | |||||
| val code: String, | val code: String, | ||||
| val itemName: String, | val itemName: String, | ||||
| val reqQty: BigDecimal, | val reqQty: BigDecimal, | ||||
| ) | |||||
| data class UpdateJoReqQtyRequest( | |||||
| val id: Long, | |||||
| val reqQty: Int | |||||
| ) | ) | ||||
| @@ -14,4 +14,9 @@ interface WarehouseRepository : AbstractRepository<Warehouse, Long> { | |||||
| fun findByCodeAndDeletedIsFalse(code: String): Warehouse?; | fun findByCodeAndDeletedIsFalse(code: String): Warehouse?; | ||||
| fun findAllByDeletedIsFalse(): List<Warehouse>; | |||||
| fun findAllByStockTakeSectionAndDeletedIsFalse(stockTakeSection: String): List<Warehouse>; | |||||
| fun findDistinctStockTakeSectionsByDeletedIsFalse(): List<String>; | |||||
| fun findAllByIdIn(ids: List<Long>): List<Warehouse>; | |||||
| fun findAllByCodeAndDeletedIsFalse(code: String): List<Warehouse> | |||||
| } | } | ||||
| @@ -67,10 +67,10 @@ fun findMaterialIssues(): List<PickExecutionIssue> | |||||
| fun getBadItemList_statusIn(@Param("statuses") statuses: List<PickExecutionIssueEnum>): List<PickExecutionIssue> | fun getBadItemList_statusIn(@Param("statuses") statuses: List<PickExecutionIssueEnum>): List<PickExecutionIssue> | ||||
| @Query(""" | @Query(""" | ||||
| SELECT p FROM PickExecutionIssue p | |||||
| WHERE p.badItemQty IS NOT NULL | |||||
| AND p.badItemQty > 0 | |||||
| AND p.deleted = false | |||||
| SELECT p FROM PickExecutionIssue p | |||||
| WHERE (p.badItemQty IS NOT NULL AND p.badItemQty > 0) | |||||
| OR (p.missQty IS NOT NULL AND p.missQty > 0) | |||||
| AND p.deleted = false | |||||
| ORDER BY p.created DESC | ORDER BY p.created DESC | ||||
| """) | """) | ||||
| fun getBadItemList(): List<PickExecutionIssue> | fun getBadItemList(): List<PickExecutionIssue> | ||||
| @@ -38,4 +38,17 @@ interface TruckRepository : AbstractRepository<Truck, Long> { | |||||
| fun findAllByShopId(shopId :Long):List<Truck> | fun findAllByShopId(shopId :Long):List<Truck> | ||||
| //fun findByTruckLanceCode(truckLanceCode: String):List<Truck> | //fun findByTruckLanceCode(truckLanceCode: String):List<Truck> | ||||
| //fun deleteByTruckLanceCodeAndDepartureTimeAndLoadingSequenceAndDistrictReferenceAndStoreId(truckLanceCode: String, departureTime: LocalTime, loadingSequence: Long, districtReference: Long, storeId: Long): String | //fun deleteByTruckLanceCodeAndDepartureTimeAndLoadingSequenceAndDistrictReferenceAndStoreId(truckLanceCode: String, departureTime: LocalTime, loadingSequence: Long, districtReference: Long, storeId: Long): String | ||||
| @Query(""" | |||||
| SELECT t FROM Truck t | |||||
| WHERE t.shop.id = :shopId | |||||
| AND t.storeId = :storeId | |||||
| AND t.deleted = false | |||||
| AND t.truckLanceCode LIKE CONCAT('%', :dayOfWeekAbbr, '%') | |||||
| ORDER BY t.departureTime ASC | |||||
| """) | |||||
| fun findByShopIdAndStoreIdAndDayOfWeek( | |||||
| @Param("shopId") shopId: Long, | |||||
| @Param("storeId") storeId: String, | |||||
| @Param("dayOfWeekAbbr") dayOfWeekAbbr: String | |||||
| ): List<Truck> | |||||
| } | } | ||||
| @@ -53,5 +53,8 @@ open class ProductProcess : BaseEntity<Long>() { | |||||
| @Column(name = "productionPriority") | @Column(name = "productionPriority") | ||||
| open var productionPriority: Int? = 50 | open var productionPriority: Int? = 50 | ||||
| @Column(name = "submitedBagRecord") | |||||
| open var submitedBagRecord: Boolean? = false | |||||
| } | } | ||||
| @@ -39,7 +39,12 @@ open class ProductProcessLine : BaseEntity<Long>() { | |||||
| @Column(name = "equipment_name", length = 100) | @Column(name = "equipment_name", length = 100) | ||||
| open var equipmentType: String? = null | open var equipmentType: String? = null | ||||
| // 添加 @ManyToOne | |||||
| @Column(name = "processingTime") | |||||
| open var processingTime: Int? = null | |||||
| @Column(name = "setupTime") | |||||
| open var setupTime: Int? = null | |||||
| @Column(name = "changeoverTime") | |||||
| open var changeoverTime: Int? = null | |||||
| @ManyToOne(fetch = FetchType.LAZY) | @ManyToOne(fetch = FetchType.LAZY) | ||||
| @JoinColumn(name = "byproductId") | @JoinColumn(name = "byproductId") | ||||
| open var byproduct: Items? = null | open var byproduct: Items? = null | ||||
| @@ -5,7 +5,7 @@ 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 | import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus | ||||
| import java.math.BigDecimal | |||||
| data class ProductProcessInfo( | data class ProductProcessInfo( | ||||
| val id: Long?, | val id: Long?, | ||||
| val productProcessCode: String?, | val productProcessCode: String?, | ||||
| @@ -33,11 +33,13 @@ data class ProductProcessInfo( | |||||
| val itemId: Long?, | val itemId: Long?, | ||||
| val itemCode: String?, | val itemCode: String?, | ||||
| val itemName: String?, | val itemName: String?, | ||||
| val bomBaseQty: BigDecimal?, | |||||
| val outputQtyUom: String?, | val outputQtyUom: String?, | ||||
| val outputQty: Int?, | val outputQty: Int?, | ||||
| val timeSequence: Int?, | val timeSequence: Int?, | ||||
| val complexity: Int?, | val complexity: Int?, | ||||
| val productionPriority: Int?, | val productionPriority: Int?, | ||||
| val submitedBagRecord: Boolean?, | |||||
| val productProcessLines: List<ProductProcessLineInfo>?, | val productProcessLines: List<ProductProcessLineInfo>?, | ||||
| val totalStockQty: Int?, | val totalStockQty: Int?, | ||||
| val insufficientStockQty: Int?, | val insufficientStockQty: Int?, | ||||
| @@ -76,7 +76,7 @@ open class ProductProcessService( | |||||
| ) { | ) { | ||||
| open fun findAll(pageable: Pageable): Page<ProductProcess> { | open fun findAll(pageable: Pageable): Page<ProductProcess> { | ||||
| println("📋 Service: Finding all ProductProcess with page: ${pageable.pageNumber}, size: ${pageable.pageSize}") | |||||
| println(" Service: Finding all ProductProcess with page: ${pageable.pageNumber}, size: ${pageable.pageSize}") | |||||
| val result = productProcessRepository.findAll(pageable) | val result = productProcessRepository.findAll(pageable) | ||||
| println(" Service: Found ${result.totalElements} records") | println(" Service: Found ${result.totalElements} records") | ||||
| return result | return result | ||||
| @@ -161,7 +161,7 @@ open class ProductProcessService( | |||||
| // 添加:查询工序的所有步骤 | // 添加:查询工序的所有步骤 | ||||
| open fun getLines(productProcessId: Long): List<ProductProcessLine> { | open fun getLines(productProcessId: Long): List<ProductProcessLine> { | ||||
| println("📋 Service: Getting lines for process ID: $productProcessId") | |||||
| println(" Service: Getting lines for process ID: $productProcessId") | |||||
| val lines = productProcessLineRepository.findByProductProcess_Id(productProcessId) | val lines = productProcessLineRepository.findByProductProcess_Id(productProcessId) | ||||
| println(" Service: Found ${lines.size} lines") | println(" Service: Found ${lines.size} lines") | ||||
| return lines | return lines | ||||
| @@ -268,7 +268,7 @@ open class ProductProcessService( | |||||
| } | } | ||||
| open fun getIssues(productProcessId: Long): List<ProductionProcessIssue> { | open fun getIssues(productProcessId: Long): List<ProductionProcessIssue> { | ||||
| println("📋 Service: Getting issues for ProductProcess ID: $productProcessId") | |||||
| println(" Service: Getting issues for ProductProcess ID: $productProcessId") | |||||
| val issues = productionProcessIssueRepository.findByProductProcess_Id(productProcessId) | val issues = productionProcessIssueRepository.findByProductProcess_Id(productProcessId) | ||||
| println(" Service: Found ${issues.size} issues") | println(" Service: Found ${issues.size} issues") | ||||
| return issues | return issues | ||||
| @@ -317,7 +317,7 @@ open class ProductProcessService( | |||||
| return result | return result | ||||
| } | } | ||||
| open fun findAllAsDto(pageable: Pageable): Page<ProductProcessSimpleResponse> { | open fun findAllAsDto(pageable: Pageable): Page<ProductProcessSimpleResponse> { | ||||
| println("📋 Service: Finding all ProductProcess as DTO with page: ${pageable.pageNumber}, size: ${pageable.pageSize}") | |||||
| println(" Service: Finding all ProductProcess as DTO with page: ${pageable.pageNumber}, size: ${pageable.pageSize}") | |||||
| val entityPage = productProcessRepository.findAll(pageable) | val entityPage = productProcessRepository.findAll(pageable) | ||||
| val dtoList = entityPage.content.map { entity -> | val dtoList = entityPage.content.map { entity -> | ||||
| @@ -580,6 +580,7 @@ open class ProductProcessService( | |||||
| itemName = bom?.item?.name?:"", | itemName = bom?.item?.name?:"", | ||||
| timeSequence = bom?.timeSequence?:0, | timeSequence = bom?.timeSequence?:0, | ||||
| complexity = bom?.complexity?:0, | complexity = bom?.complexity?:0, | ||||
| bomBaseQty = bom.outputQty ?: BigDecimal.ZERO, | |||||
| isDark = calculateColourScore(bom?.isDark?:0), | isDark = calculateColourScore(bom?.isDark?:0), | ||||
| isDense = bom?.isDense?:0, | isDense = bom?.isDense?:0, | ||||
| isFloat = calculateFloatScore(bom?.isFloat?:0), | isFloat = calculateFloatScore(bom?.isFloat?:0), | ||||
| @@ -594,7 +595,8 @@ open class ProductProcessService( | |||||
| startTime = process.startTime?:LocalDateTime.now(), | startTime = process.startTime?:LocalDateTime.now(), | ||||
| endTime = process.endTime?:LocalDateTime.now(), | endTime = process.endTime?:LocalDateTime.now(), | ||||
| date = process.date?:LocalDate.now(), | date = process.date?:LocalDate.now(), | ||||
| productionPriority = process.productionPriority?:50, | |||||
| productionPriority = process.productionPriority?:50, | |||||
| submitedBagRecord = process.submitedBagRecord?:false, | |||||
| totalStockQty = totalStockQty, | totalStockQty = totalStockQty, | ||||
| insufficientStockQty = insufficientStockQty, | insufficientStockQty = insufficientStockQty, | ||||
| sufficientStockQty = sufficientStockQty, | sufficientStockQty = sufficientStockQty, | ||||
| @@ -617,9 +619,9 @@ open class ProductProcessService( | |||||
| equipment_name = line.equipmentType?:"", | equipment_name = line.equipmentType?:"", | ||||
| equipmentDetailCode = equipmentDetail?.code?:"", | equipmentDetailCode = equipmentDetail?.code?:"", | ||||
| status = line.status?:"", | status = line.status?:"", | ||||
| durationInMinutes = line.bomProcess?.durationInMinute?:0, | |||||
| prepTimeInMinutes = line.bomProcess?.prepTimeInMinute?:0, | |||||
| postProdTimeInMinutes = line.bomProcess?.postProdTimeInMinute?:0, | |||||
| durationInMinutes = line.processingTime?:0, | |||||
| prepTimeInMinutes = line.setupTime?:0, | |||||
| postProdTimeInMinutes = line.changeoverTime?:0, | |||||
| byproductId = line.byproduct?.id?:0, | byproductId = line.byproduct?.id?:0, | ||||
| byproductName = line.byproduct?.name?:"", | byproductName = line.byproduct?.name?:"", | ||||
| byproductQty = line.byproductQty?:0, | byproductQty = line.byproductQty?:0, | ||||
| @@ -640,7 +642,7 @@ open class ProductProcessService( | |||||
| endTime = line.endTime | endTime = line.endTime | ||||
| ) | ) | ||||
| }, | |||||
| }.sortedBy { it.seqNo }, | |||||
| jobOrderLines = bomMaterials.map { line -> | jobOrderLines = bomMaterials.map { line -> | ||||
| val itemId = line.item?.id ?: 0L | val itemId = line.item?.id ?: 0L | ||||
| val stockQty = stockQtyMap[itemId]?.toInt() ?: 0 | val stockQty = stockQtyMap[itemId]?.toInt() ?: 0 | ||||
| @@ -719,6 +721,9 @@ open class ProductProcessService( | |||||
| this.description = bomProcess.description?:"" | this.description = bomProcess.description?:"" | ||||
| this.equipmentType = equipment?.code?:"" | this.equipmentType = equipment?.code?:"" | ||||
| this.status = "Pending" | this.status = "Pending" | ||||
| this.processingTime = bomProcess.durationInMinute | |||||
| this.setupTime = bomProcess.prepTimeInMinute | |||||
| this.changeoverTime = bomProcess.postProdTimeInMinute | |||||
| } | } | ||||
| productProcessLineRepository.save(productProcessLine) | productProcessLineRepository.save(productProcessLine) | ||||
| } | } | ||||
| @@ -921,7 +926,7 @@ open class ProductProcessService( | |||||
| } | } | ||||
| open fun getJobOrderProcessLineDetail(productProcessLineId: Long): JobOrderProcessLineDetailResponse { | open fun getJobOrderProcessLineDetail(productProcessLineId: Long): JobOrderProcessLineDetailResponse { | ||||
| val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null) | val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null) | ||||
| val productProcess = productProcessRepository.findById(productProcessLine?.productProcess?.id?:0L).orElse(null) | |||||
| val bomProcessId = productProcessLine?.bomProcess?.id | val bomProcessId = productProcessLine?.bomProcess?.id | ||||
| println("bomProcessId ${bomProcessId}") | println("bomProcessId ${bomProcessId}") | ||||
| val bomProcess: BomProcess? = bomProcessId?.let { | val bomProcess: BomProcess? = bomProcessId?.let { | ||||
| @@ -961,6 +966,7 @@ open class ProductProcessService( | |||||
| startTime = productProcessLine.startTime?:LocalDateTime.now(), | startTime = productProcessLine.startTime?:LocalDateTime.now(), | ||||
| endTime = productProcessLine.endTime?:LocalDateTime.now(), | endTime = productProcessLine.endTime?:LocalDateTime.now(), | ||||
| stopTime = productProcessIssue?.stopTime, | stopTime = productProcessIssue?.stopTime, | ||||
| submitedBagRecord = productProcess.submitedBagRecord?:false, | |||||
| // ✅ 添加总暂停时间(毫秒) | // ✅ 添加总暂停时间(毫秒) | ||||
| totalPausedTimeMs = totalPausedTimeMs.toLong(), | totalPausedTimeMs = totalPausedTimeMs.toLong(), | ||||
| status = productProcessLine.status?:"", | status = productProcessLine.status?:"", | ||||
| @@ -985,13 +991,13 @@ open class ProductProcessService( | |||||
| } | } | ||||
| open fun updateProductProcessLineStatus(productProcessLineId: Long, status: String): MessageResponse { | open fun updateProductProcessLineStatus(productProcessLineId: Long, status: String): MessageResponse { | ||||
| println("📋 Service: Updating ProductProcessLine Status: $productProcessLineId") | |||||
| println(" Service: Updating ProductProcessLine Status: $productProcessLineId") | |||||
| val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null) | val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null) | ||||
| println("📋 Service: ProductProcessLine: $productProcessLine") | |||||
| println(" Service: ProductProcessLine: $productProcessLine") | |||||
| productProcessLine.status = status | productProcessLine.status = status | ||||
| // productProcessLine.endTime = LocalDateTime.now() | // productProcessLine.endTime = LocalDateTime.now() | ||||
| productProcessLineRepository.save(productProcessLine) | productProcessLineRepository.save(productProcessLine) | ||||
| println("📋 Service: ProductProcessLine Status Updated: ${productProcessLine.status}") | |||||
| println(" Service: ProductProcessLine Status Updated: ${productProcessLine.status}") | |||||
| CompleteProductProcessStatusIfAllLinesCompleted(productProcessLine.productProcess?.id?:0) | CompleteProductProcessStatusIfAllLinesCompleted(productProcessLine.productProcess?.id?:0) | ||||
| @@ -1008,8 +1014,8 @@ open class ProductProcessService( | |||||
| val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null) | val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null) | ||||
| val productProcess = productProcessRepository.findById(productProcessLine?.productProcess?.id?:0L).orElse(null) | val productProcess = productProcessRepository.findById(productProcessLine?.productProcess?.id?:0L).orElse(null) | ||||
| // 如果 startTime 为空,才设置它(保留已存在的 startTime) | // 如果 startTime 为空,才设置它(保留已存在的 startTime) | ||||
| println("📋 Service: ProductProcessLine StartTime: ${productProcessLine.startTime}") | |||||
| println("📋 Service: ProductProcess StartTime: ${productProcess.startTime}") | |||||
| println(" Service: ProductProcessLine StartTime: ${productProcessLine.startTime}") | |||||
| println(" Service: ProductProcess StartTime: ${productProcess.startTime}") | |||||
| if (productProcessLine.startTime == null) { | if (productProcessLine.startTime == null) { | ||||
| productProcessLine.startTime = LocalDateTime.now() | productProcessLine.startTime = LocalDateTime.now() | ||||
| productProcessLineRepository.save(productProcessLine) | productProcessLineRepository.save(productProcessLine) | ||||
| @@ -1022,7 +1028,7 @@ open class ProductProcessService( | |||||
| // 总是设置 endTime(因为 Pass 意味着完成) | // 总是设置 endTime(因为 Pass 意味着完成) | ||||
| productProcessLine.endTime = LocalDateTime.now() | productProcessLine.endTime = LocalDateTime.now() | ||||
| productProcessLineRepository.save(productProcessLine) | productProcessLineRepository.save(productProcessLine) | ||||
| println("📋 Service: ProductProcessLine EndTime: ${productProcessLine.endTime}") | |||||
| println(" Service: ProductProcessLine EndTime: ${productProcessLine.endTime}") | |||||
| // 更新状态为 "Pass" | // 更新状态为 "Pass" | ||||
| updateProductProcessLineStatus(productProcessLineId, "Pass") | updateProductProcessLineStatus(productProcessLineId, "Pass") | ||||
| @@ -1042,20 +1048,20 @@ open class ProductProcessService( | |||||
| } | } | ||||
| open fun CompleteProductProcessStatusIfAllLinesCompleted(productProcessId: Long): MessageResponse { | open fun CompleteProductProcessStatusIfAllLinesCompleted(productProcessId: Long): MessageResponse { | ||||
| val productProcess = productProcessRepository.findById(productProcessId).orElse(null) | val productProcess = productProcessRepository.findById(productProcessId).orElse(null) | ||||
| println("📋 Service: ProductProcess: $productProcess") | |||||
| println(" Service: ProductProcess: $productProcess") | |||||
| val productProcessLines = productProcessLineRepository.findByProductProcess_Id(productProcessId) | val productProcessLines = productProcessLineRepository.findByProductProcess_Id(productProcessId) | ||||
| println("📋 Service: ProductProcessLines: $productProcessLines") | |||||
| println(" Service: ProductProcessLines: $productProcessLines") | |||||
| if(productProcessLines.all { it.status == "Completed" || it.status == "Pass" }) { | if(productProcessLines.all { it.status == "Completed" || it.status == "Pass" }) { | ||||
| productProcess.status = ProductProcessStatus.COMPLETED | productProcess.status = ProductProcessStatus.COMPLETED | ||||
| if (productProcess.endTime == null) { | if (productProcess.endTime == null) { | ||||
| productProcess.endTime = LocalDateTime.now() | productProcess.endTime = LocalDateTime.now() | ||||
| } | } | ||||
| productProcessRepository.save(productProcess) | productProcessRepository.save(productProcess) | ||||
| println("📋 Service: ProductProcess Status Updated: ${productProcess.status}") | |||||
| println(" Service: ProductProcess Status Updated: ${productProcess.status}") | |||||
| } | } | ||||
| else { | else { | ||||
| println("📋 Service: ProductProcess Lines are not completed") | |||||
| println(" Service: ProductProcess Lines are not completed") | |||||
| } | } | ||||
| return MessageResponse( | return MessageResponse( | ||||
| id = null, | id = null, | ||||
| @@ -1296,9 +1302,9 @@ open class ProductProcessService( | |||||
| } | } | ||||
| else{ | else{ | ||||
| updateProductProcessLineStatus(productProcessLineId, "InProgress") | updateProductProcessLineStatus(productProcessLineId, "InProgress") | ||||
| println("📋 Service: ProductProcess Lines are not Pending") | |||||
| println("📋 Service: ProductProcess Lines: ${allproductProcessLines.map { it.status }}") | |||||
| println("📋 Service: ProductProcess Line: ${productProcessLine.status}") | |||||
| println(" Service: ProductProcess Lines are not Pending") | |||||
| println(" Service: ProductProcess Lines: ${allproductProcessLines.map { it.status }}") | |||||
| println(" Service: ProductProcess Line: ${productProcessLine.status}") | |||||
| } | } | ||||
| return MessageResponse( | return MessageResponse( | ||||
| id = productProcessLine.id, | id = productProcessLine.id, | ||||
| @@ -1390,16 +1396,16 @@ open class ProductProcessService( | |||||
| } | } | ||||
| open fun SaveProductProcessIssueTime(request: SaveProductProcessIssueTimeRequest): MessageResponse { | open fun SaveProductProcessIssueTime(request: SaveProductProcessIssueTimeRequest): MessageResponse { | ||||
| println("📋 Service: Saving ProductProcess Issue Time: ${request.productProcessLineId}") | |||||
| val productProcessLine = productProcessLineRepository.findById(request.productProcessLineId).orElse(null) | val productProcessLine = productProcessLineRepository.findById(request.productProcessLineId).orElse(null) | ||||
| val productProcess = productProcessRepository.findById(productProcessLine.productProcess?.id?:0L).orElse(null) | val productProcess = productProcessRepository.findById(productProcessLine.productProcess?.id?:0L).orElse(null) | ||||
| //val productProcessLines=productProcessLineRepository.findByProductProcess_Id(productProcessId) | //val productProcessLines=productProcessLineRepository.findByProductProcess_Id(productProcessId) | ||||
| val startTime=productProcessLine?.startTime | val startTime=productProcessLine?.startTime | ||||
| println("📋 Service: Start Time: $startTime") | |||||
| val stopTime=LocalDateTime.now() | val stopTime=LocalDateTime.now() | ||||
| println("📋 Service: Stop Time: $stopTime") | |||||
| val operatorId=productProcessLine.operator?.id | val operatorId=productProcessLine.operator?.id | ||||
| val Operator=userRepository.findById(operatorId).orElse(null) | val Operator=userRepository.findById(operatorId).orElse(null) | ||||
| val reason = request.reason | val reason = request.reason | ||||
| @@ -1429,13 +1435,13 @@ open class ProductProcessService( | |||||
| open fun SaveProductProcessResumeTime(productProcessIssueId: Long): MessageResponse { | open fun SaveProductProcessResumeTime(productProcessIssueId: Long): MessageResponse { | ||||
| println("📋 Service: Saving ProductProcess Resume Time: $productProcessIssueId") | |||||
| println(" Service: Saving ProductProcess Resume Time: $productProcessIssueId") | |||||
| val productProcessLineIssue = productionProcessIssueRepository.findById(productProcessIssueId).orElse(null) | val productProcessLineIssue = productionProcessIssueRepository.findById(productProcessIssueId).orElse(null) | ||||
| val productProcessLine = productProcessLineRepository.findById(productProcessLineIssue.productProcessLineId?:0L).orElse(null) | val productProcessLine = productProcessLineRepository.findById(productProcessLineIssue.productProcessLineId?:0L).orElse(null) | ||||
| val resumeTime = LocalDateTime.now() | val resumeTime = LocalDateTime.now() | ||||
| println("📋 Service: Resume Time: $resumeTime") | |||||
| println(" Service: Resume Time: $resumeTime") | |||||
| productProcessLineIssue?.resumeTime = resumeTime | productProcessLineIssue?.resumeTime = resumeTime | ||||
| println("📋 Service: Resume Time: $resumeTime") | |||||
| println(" Service: Resume Time: $resumeTime") | |||||
| val totalTime = resumeTime.toEpochSecond(ZoneOffset.UTC) - (productProcessLineIssue.stopTime?.toEpochSecond(ZoneOffset.UTC) ?: 0L) | val totalTime = resumeTime.toEpochSecond(ZoneOffset.UTC) - (productProcessLineIssue.stopTime?.toEpochSecond(ZoneOffset.UTC) ?: 0L) | ||||
| productProcessLineIssue?.totalTime = totalTime.toInt() | productProcessLineIssue?.totalTime = totalTime.toInt() | ||||
| productProcessLineIssue?.status = "Resumed" | productProcessLineIssue?.status = "Resumed" | ||||
| @@ -1453,7 +1459,32 @@ open class ProductProcessService( | |||||
| ) | ) | ||||
| } | } | ||||
| open fun UpdateProductProcessLineProcessingTimeSetupTimeChangeoverTime(request: UpdateProductProcessLineProcessingTimeSetupTimeChangeoverTimeRequest): MessageResponse { | |||||
| val productProcessLine = productProcessLineRepository.findById(request.productProcessLineId).orElse(null) | |||||
| val processingTime = request.processingTime | |||||
| val setupTime = request.setupTime | |||||
| val changeoverTime = request.changeoverTime | |||||
| if(processingTime != null) { | |||||
| productProcessLine.processingTime = processingTime | |||||
| productProcessLineRepository.save(productProcessLine) | |||||
| } | |||||
| if(setupTime != null) { | |||||
| productProcessLine.setupTime = setupTime | |||||
| productProcessLineRepository.save(productProcessLine) | |||||
| } | |||||
| if(changeoverTime != null) { | |||||
| productProcessLine.changeoverTime = changeoverTime | |||||
| productProcessLineRepository.save(productProcessLine) | |||||
| } | |||||
| return MessageResponse( | |||||
| id = request.productProcessLineId, | |||||
| code = "200", | |||||
| name = "ProductProcess ProcessingTimeSetupTimeChangeoverTime Updated", | |||||
| type = "success", | |||||
| message = "ProductProcess ProcessingTimeSetupTimeChangeoverTime Updated", | |||||
| errorPosition = null, | |||||
| ) | |||||
| } | |||||
| open fun UpdateProductProcessPriority(productProcessId: Long, productionPriority: Int): MessageResponse { | open fun UpdateProductProcessPriority(productProcessId: Long, productionPriority: Int): MessageResponse { | ||||
| val productProcess = productProcessRepository.findById(productProcessId).orElse(null) | val productProcess = productProcessRepository.findById(productProcessId).orElse(null) | ||||
| productProcess.productionPriority = productionPriority | productProcess.productionPriority = productionPriority | ||||
| @@ -1467,5 +1498,94 @@ open class ProductProcessService( | |||||
| errorPosition = null, | errorPosition = null, | ||||
| ) | ) | ||||
| } | } | ||||
| } | |||||
| open fun createNewProductProcessLine(productProcessLineId: Long): MessageResponse { | |||||
| val sourceLine = productProcessLineRepository.findById(productProcessLineId).orElse(null) | |||||
| ?: return MessageResponse( | |||||
| id = productProcessLineId, | |||||
| code = "404", | |||||
| name = "ProductProcess Line Not Found", | |||||
| type = "error", | |||||
| message = "ProductProcess Line with ID $productProcessLineId not found", | |||||
| errorPosition = null, | |||||
| ) | |||||
| val productProcessId = sourceLine.productProcess?.id ?: return MessageResponse( | |||||
| id = productProcessLineId, | |||||
| code = "400", | |||||
| name = "Invalid ProductProcess", | |||||
| type = "error", | |||||
| message = "ProductProcess Line has no associated ProductProcess", | |||||
| errorPosition = null, | |||||
| ) | |||||
| val originalSeqNo = sourceLine.seqNo ?: return MessageResponse( | |||||
| id = productProcessLineId, | |||||
| code = "400", | |||||
| name = "Invalid SeqNo", | |||||
| type = "error", | |||||
| message = "ProductProcess Line has no seqNo", | |||||
| errorPosition = null, | |||||
| ) | |||||
| // 先获取同一 productProcess 的所有 lines(在更新前) | |||||
| val allLines = productProcessLineRepository.findByProductProcess_Id(productProcessId) | |||||
| // 更新所有其他 seqNo >= originalSeqNo + 1 的 lines(不包括原 line) | |||||
| allLines.filter { | |||||
| it.id != sourceLine.id && | |||||
| it.seqNo != null && | |||||
| it.seqNo!! >= originalSeqNo + 1 | |||||
| }.forEach { line -> | |||||
| line.seqNo = (line.seqNo ?: 0) + 1 | |||||
| productProcessLineRepository.save(line) | |||||
| } | |||||
| // 创建新的 line,复制所有字段 | |||||
| val newLine = ProductProcessLine().apply { | |||||
| // 复制基本字段 | |||||
| this.productProcess = sourceLine.productProcess | |||||
| this.bomProcess = sourceLine.bomProcess | |||||
| this.operator = sourceLine.operator | |||||
| this.equipment = sourceLine.equipment | |||||
| this.equipmentDetailId = sourceLine.equipmentDetailId | |||||
| this.handler = sourceLine.handler | |||||
| this.seqNo = originalSeqNo + 1 // 新 line 的 seqNo = originalSeqNo + 1 | |||||
| this.name = sourceLine.name | |||||
| this.description = sourceLine.description | |||||
| this.equipmentType = sourceLine.equipmentType | |||||
| this.status = "Pending" // 新创建的 line 状态总是设为 Pending | |||||
| this.byproduct = sourceLine.byproduct | |||||
| this.byproductName = sourceLine.byproductName | |||||
| this.byproductQty = sourceLine.byproductQty | |||||
| this.byproductUom = sourceLine.byproductUom | |||||
| this.scrapQty = sourceLine.scrapQty | |||||
| this.scrapUom = sourceLine.scrapUom | |||||
| this.defectQty = sourceLine.defectQty | |||||
| this.defectUom = sourceLine.defectUom | |||||
| this.defectDescription = sourceLine.defectDescription | |||||
| this.defectQty2 = sourceLine.defectQty2 | |||||
| this.defectUom2 = sourceLine.defectUom2 | |||||
| this.defectDescription2 = sourceLine.defectDescription2 | |||||
| this.defectQty3 = sourceLine.defectQty3 | |||||
| this.defectUom3 = sourceLine.defectUom3 | |||||
| this.defectDescription3 = sourceLine.defectDescription3 | |||||
| this.outputFromProcessQty = sourceLine.outputFromProcessQty | |||||
| this.outputFromProcessUom = sourceLine.outputFromProcessUom | |||||
| // 不复制时间字段,新 line 应该没有开始和结束时间 | |||||
| this.startTime = null | |||||
| this.endTime = null | |||||
| } | |||||
| // 保存新 line(原 line 的 seqNo 保持不变,不需要更新) | |||||
| val savedNewLine = productProcessLineRepository.save(newLine) | |||||
| return MessageResponse( | |||||
| id = savedNewLine.id ?: productProcessLineId, | |||||
| code = "200", | |||||
| name = "ProductProcess Line Created", | |||||
| type = "success", | |||||
| message = "ProductProcess Line Created successfully with seqNo ${originalSeqNo + 1}", | |||||
| errorPosition = null, | |||||
| ) | |||||
| }} | |||||
| @@ -217,4 +217,12 @@ class ProductProcessController( | |||||
| fun passProductProcessLine(@PathVariable lineId: Long): MessageResponse { | fun passProductProcessLine(@PathVariable lineId: Long): MessageResponse { | ||||
| return productProcessService.passProductProcessLine(lineId) | return productProcessService.passProductProcessLine(lineId) | ||||
| } | } | ||||
| @PostMapping("/Demo/ProcessLine/new/{lineId}") | |||||
| fun newProductProcessLine(@PathVariable lineId: Long): MessageResponse { | |||||
| return productProcessService.createNewProductProcessLine(lineId) | |||||
| } | |||||
| @PostMapping("/Demo/ProcessLine/update/processingTimeSetupTimeChangeoverTime/{lineId}") | |||||
| fun updateProductProcessLineProcessingTimeSetupTimeChangeoverTime(@PathVariable lineId: Long, @RequestBody request: UpdateProductProcessLineProcessingTimeSetupTimeChangeoverTimeRequest): MessageResponse { | |||||
| return productProcessService.UpdateProductProcessLineProcessingTimeSetupTimeChangeoverTime(request) | |||||
| } | |||||
| } | } | ||||
| @@ -152,7 +152,8 @@ data class JobOrderProcessLineDetailResponse( | |||||
| val byproductId: Long?, | val byproductId: Long?, | ||||
| val byproductName: String?, | val byproductName: String?, | ||||
| val byproductQty: Int?, | val byproductQty: Int?, | ||||
| val byproductUom: String? | |||||
| val byproductUom: String?, | |||||
| val submitedBagRecord: Boolean? | |||||
| ) | ) | ||||
| data class AllJoborderProductProcessInfoResponse( | data class AllJoborderProductProcessInfoResponse( | ||||
| @@ -207,4 +208,14 @@ data class SaveProductProcessIssueTimeRequest( | |||||
| data class SaveProductProcessResumeTimeRequest( | data class SaveProductProcessResumeTimeRequest( | ||||
| val productProcessLineId: Long, | val productProcessLineId: Long, | ||||
| val resumeTime: LocalDateTime? | val resumeTime: LocalDateTime? | ||||
| ) | |||||
| data class UpdateJoReqQtyRequest( | |||||
| val id: Long, | |||||
| val reqQty: Int | |||||
| ) | |||||
| data class UpdateProductProcessLineProcessingTimeSetupTimeChangeoverTimeRequest( | |||||
| val productProcessLineId: Long, | |||||
| val processingTime: Int?, | |||||
| val setupTime: Int?, | |||||
| val changeoverTime: Int? | |||||
| ) | ) | ||||
| @@ -52,4 +52,15 @@ interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long | |||||
| AND ill.deleted = false | AND ill.deleted = false | ||||
| """) | """) | ||||
| fun findByLotNoAndItemId(lotNo: String, itemId: Long): InventoryLotLine? | fun findByLotNoAndItemId(lotNo: String, itemId: Long): InventoryLotLine? | ||||
| // InventoryLotLineRepository.kt 中添加 | |||||
| @Query("SELECT ill FROM InventoryLotLine ill WHERE ill.warehouse.id IN :warehouseIds AND ill.deleted = false") | |||||
| fun findAllByWarehouseIdInAndDeletedIsFalse(@Param("warehouseIds") warehouseIds: List<Long>): List<InventoryLotLine> | |||||
| @Query(""" | |||||
| SELECT ill FROM InventoryLotLine ill | |||||
| WHERE ill.warehouse.code = :warehouseCode | |||||
| AND ill.deleted = false | |||||
| ORDER BY ill.inventoryLot.item.code, ill.inventoryLot.lotNo | |||||
| """) | |||||
| fun findAllByWarehouseCodeAndDeletedIsFalse(@Param("warehouseCode") warehouseCode: String): List<InventoryLotLine> | |||||
| } | } | ||||
| @@ -23,4 +23,5 @@ interface InventoryLotRepository: AbstractRepository<InventoryLot, Long> { | |||||
| AND il.deleted = false | AND il.deleted = false | ||||
| """) | """) | ||||
| fun findByLotNoAndItemId(lotNo: String, itemId: Long): InventoryLot? | fun findByLotNoAndItemId(lotNo: String, itemId: Long): InventoryLot? | ||||
| fun findByIdAndDeletedFalse(id: Serializable): InventoryLot? | |||||
| } | } | ||||
| @@ -39,4 +39,7 @@ open class StockTake: BaseEntity<Long>() { | |||||
| @Size(max = 500) | @Size(max = 500) | ||||
| @Column(name = "remarks", length = 500) | @Column(name = "remarks", length = 500) | ||||
| open var remarks: String? = null | open var remarks: String? = null | ||||
| @Size(max = 255) | |||||
| @Column(name = "stockTakeSection", length = 255) | |||||
| open var stockTakeSection: String? = null | |||||
| } | } | ||||
| @@ -7,4 +7,5 @@ import java.io.Serializable | |||||
| @Repository | @Repository | ||||
| interface StockTakeLineRepository : AbstractRepository<StockTakeLine, Long> { | interface StockTakeLineRepository : AbstractRepository<StockTakeLine, Long> { | ||||
| fun findByIdAndDeletedIsFalse(id: Serializable): StockTakeLine?; | fun findByIdAndDeletedIsFalse(id: Serializable): StockTakeLine?; | ||||
| fun findByInventoryLotLineIdAndStockTakeIdAndDeletedIsFalse(inventoryLotLineId: Serializable, stockTakeId: Serializable): StockTakeLine?; | |||||
| } | } | ||||
| @@ -0,0 +1,102 @@ | |||||
| // StockTakeRecord.kt | |||||
| package com.ffii.fpsms.modules.stock.entity | |||||
| import com.ffii.core.entity.BaseEntity | |||||
| import com.ffii.fpsms.modules.master.entity.Warehouse | |||||
| import jakarta.persistence.* | |||||
| import java.math.BigDecimal | |||||
| import java.time.LocalDate | |||||
| import java.time.LocalDateTime | |||||
| @Entity | |||||
| @Table(name = "stocktakerecord") | |||||
| open class StockTakeRecord : BaseEntity<Long>() { | |||||
| @Column(name = "itemId", nullable = false) | |||||
| open var itemId: Long? = null | |||||
| @Column(name = "lotId", nullable = false) | |||||
| open var lotId: Long? = null | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "warehouseId", nullable = false) | |||||
| open var warehouse: Warehouse? = null | |||||
| @Column(name = "stockTakeSection", length = 255) | |||||
| open var stockTakeSection: String? = null | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "stockTakeId", nullable = false) | |||||
| open var stockTake: StockTake? = null | |||||
| @Column(name = "approverId") | |||||
| open var approverId: Long? = null | |||||
| @Column(name = "approverName", length = 100) | |||||
| open var approverName: String? = null | |||||
| @Column(name = "stockTakerId", nullable = false) | |||||
| open var stockTakerId: Long? = null | |||||
| @Column(name = "stockTakerName", length = 100) | |||||
| open var stockTakerName: String? = null | |||||
| @Column(name = "pickerFirstStockTakeQty", precision = 14, scale = 2) | |||||
| open var pickerFirstStockTakeQty: BigDecimal? = null | |||||
| @Column(name = "pickerSecondStockTakeQty", precision = 14, scale = 2) | |||||
| open var pickerSecondStockTakeQty: BigDecimal? = null | |||||
| @Column(name = "approverStockTakeQty", precision = 14, scale = 2) | |||||
| open var approverStockTakeQty: BigDecimal? = null | |||||
| @Column(name = "approverSecondStockTakeQty", precision = 14, scale = 2) | |||||
| open var approverSecondStockTakeQty: BigDecimal? = null | |||||
| @Column(name = "bookQty", nullable = false, precision = 14, scale = 2) | |||||
| open var bookQty: BigDecimal? = null | |||||
| @Column(name = "badQty", precision = 14, scale = 2) | |||||
| open var badQty: BigDecimal? = null | |||||
| @Column(name = "pickerFirstBadQty", precision = 14, scale = 2) | |||||
| open var pickerFirstBadQty: BigDecimal? = null | |||||
| @Column(name = "pickerSecondBadQty", precision = 14, scale = 2) | |||||
| open var pickerSecondBadQty: BigDecimal? = null | |||||
| @Column(name = "approverBadQty", precision = 14, scale = 2) | |||||
| open var approverBadQty: BigDecimal? = null | |||||
| @Column(name = "varianceQty", precision = 14, scale = 2) | |||||
| open var varianceQty: BigDecimal? = null | |||||
| @Column(name = "uom", length = 30) | |||||
| open var uom: String? = null | |||||
| @Column(name = "stockTakeStartTime") | |||||
| open var stockTakeStartTime: LocalDateTime? = null | |||||
| @Column(name = "stockTakeEndTime") | |||||
| open var stockTakeEndTime: LocalDateTime? = null | |||||
| @Column(name = "date", nullable = false) | |||||
| open var date: LocalDate? = null | |||||
| @Column(name = "status", nullable = false, length = 30) | |||||
| open var status: String? = null | |||||
| @Column(name = "remarks", length = 500) | |||||
| open var remarks: String? = null | |||||
| @Column(name = "itemCode", length = 50) | |||||
| open var itemCode: String? = null | |||||
| @Column(name = "itemName", length = 200) | |||||
| open var itemName: String? = null | |||||
| @Column(name = "inventoryLotId") | |||||
| open var inventoryLotId: Long? = null | |||||
| @Column(name = "lotNo", length = 512) | |||||
| open var lotNo: String? = null | |||||
| @Column(name = "expiredDate") | |||||
| open var expiredDate: LocalDate? = null | |||||
| } | |||||
| @@ -0,0 +1,24 @@ | |||||
| // 创建 StockTakeRecordRepository.kt | |||||
| package com.ffii.fpsms.modules.stock.entity | |||||
| import com.ffii.core.support.AbstractRepository | |||||
| import org.springframework.stereotype.Repository | |||||
| import java.io.Serializable | |||||
| import org.springframework.data.jpa.repository.Query | |||||
| @Repository | |||||
| interface StockTakeRecordRepository : AbstractRepository<StockTakeRecord, Long> { | |||||
| fun findAllByStockTakeIdAndDeletedIsFalse(stockTakeId: Long): List<StockTakeRecord>; | |||||
| fun findByIdAndDeletedIsFalse(id: Serializable): StockTakeRecord?; | |||||
| @Query(""" | |||||
| SELECT w.stockTakeSection, str.stockTake.id, COUNT(str.id) as count | |||||
| FROM StockTakeRecord str | |||||
| INNER JOIN Warehouse w ON str.warehouse.id = w.id | |||||
| WHERE str.deleted = false | |||||
| AND w.deleted = false | |||||
| AND w.stockTakeSection IS NOT NULL | |||||
| AND w.stockTakeSection != '' | |||||
| AND str.stockTake.id IS NOT NULL | |||||
| GROUP BY w.stockTakeSection, str.stockTake.id | |||||
| """) | |||||
| fun countStockTakeRecordsBySectionAndStockTakeId(): List<Array<Any>> | |||||
| } | |||||
| @@ -3,5 +3,6 @@ package com.ffii.fpsms.modules.stock.enums | |||||
| enum class StockTakeStatus(val value: String) { | enum class StockTakeStatus(val value: String) { | ||||
| PENDING("pending"), | PENDING("pending"), | ||||
| STOCKTAKING("stockTaking"), | STOCKTAKING("stockTaking"), | ||||
| APPROVING("approving"), | |||||
| COMPLETED("completed"), | COMPLETED("completed"), | ||||
| } | } | ||||
| @@ -48,7 +48,7 @@ class StockTakeService( | |||||
| fun saveStockTake(request: SaveStockTakeRequest): StockTake { | fun saveStockTake(request: SaveStockTakeRequest): StockTake { | ||||
| val stockTake = request.id?.let { stockTakeRepository.findByIdAndDeletedIsFalse(it) } ?: StockTake(); | val stockTake = request.id?.let { stockTakeRepository.findByIdAndDeletedIsFalse(it) } ?: StockTake(); | ||||
| val status = request.status?.let { _status -> StockTakeStatus.entries.find { it.value == _status } } | val status = request.status?.let { _status -> StockTakeStatus.entries.find { it.value == _status } } | ||||
| request.code?.let { stockTake.code = it } | request.code?.let { stockTake.code = it } | ||||
| if (stockTake.code == null) { | if (stockTake.code == null) { | ||||
| stockTake.code = assignStockTakeNo() | stockTake.code = assignStockTakeNo() | ||||
| @@ -59,7 +59,8 @@ class StockTakeService( | |||||
| request.actualEnd?.let { stockTake.actualEnd = it } | request.actualEnd?.let { stockTake.actualEnd = it } | ||||
| status?.let { stockTake.status = it } | status?.let { stockTake.status = it } | ||||
| request.remarks?.let { stockTake.remarks = it } | request.remarks?.let { stockTake.remarks = it } | ||||
| request.stockTakeSection?.let { stockTake.stockTakeSection = it } // 添加此行 | |||||
| return stockTakeRepository.save(stockTake); | return stockTakeRepository.save(stockTake); | ||||
| } | } | ||||
| @@ -230,4 +231,67 @@ class StockTakeService( | |||||
| logger.info("--------- End - Import Stock Take Excel -------") | logger.info("--------- End - Import Stock Take Excel -------") | ||||
| return "Import Excel success"; | return "Import Excel success"; | ||||
| } | } | ||||
| fun createStockTakeForSections(): Map<String, String> { | |||||
| logger.info("--------- Start - Create Stock Take for Sections -------") | |||||
| val result = mutableMapOf<String, String>() | |||||
| // 1. 获取所有不同的 stockTakeSection(从 warehouse 表) | |||||
| val allWarehouses = warehouseRepository.findAllByDeletedIsFalse() | |||||
| val distinctSections = allWarehouses | |||||
| .mapNotNull { it.stockTakeSection } | |||||
| .distinct() | |||||
| .filter { !it.isBlank() } | |||||
| // 2. 获取所有 stock_take 记录(按 stockTakeSection 分组) | |||||
| val allStockTakes = stockTakeRepository.findAll() | |||||
| .filter { !it.deleted } | |||||
| .groupBy { it.stockTakeSection } | |||||
| // 3. 为每个 stockTakeSection 检查并创建 | |||||
| distinctSections.forEach { section -> | |||||
| val stockTakesForSection = allStockTakes[section] ?: emptyList() | |||||
| // 检查:如果该 section 的所有记录都是 COMPLETED,才创建新的 | |||||
| val allCompleted = stockTakesForSection.isEmpty() || | |||||
| stockTakesForSection.all { it.status == StockTakeStatus.COMPLETED } | |||||
| if (allCompleted) { | |||||
| try { | |||||
| val now = LocalDateTime.now() | |||||
| val code = assignStockTakeNo() | |||||
| val saveStockTakeReq = SaveStockTakeRequest( | |||||
| code = code, | |||||
| planStart = now, | |||||
| planEnd = now.plusDays(1), | |||||
| actualStart = null, | |||||
| actualEnd = null, | |||||
| status = StockTakeStatus.PENDING.value, | |||||
| remarks = null, | |||||
| stockTakeSection = section | |||||
| ) | |||||
| val savedStockTake = saveStockTake(saveStockTakeReq) | |||||
| result[section] = "Created: ${savedStockTake.code}" | |||||
| logger.info("Created stock take for section $section: ${savedStockTake.code}") | |||||
| } catch (e: Exception) { | |||||
| result[section] = "Error: ${e.message}" | |||||
| logger.error("Error creating stock take for section $section: ${e.message}") | |||||
| } | |||||
| } else { | |||||
| result[section] = "Skipped: Has non-completed records" | |||||
| logger.info("Skipped section $section: Has non-completed records") | |||||
| } | |||||
| } | |||||
| // 移除 null section 处理逻辑,因为 warehouse 表中没有 null 的 stockTakeSection | |||||
| logger.info("--------- End - Create Stock Take for Sections -------") | |||||
| return result | |||||
| } | |||||
| } | } | ||||
| @@ -40,4 +40,10 @@ class StockTakeController( | |||||
| return ResponseEntity.ok(stockTakeService.importExcel(workbook)) | return ResponseEntity.ok(stockTakeService.importExcel(workbook)) | ||||
| } | } | ||||
| @PostMapping("/createForSections") | |||||
| fun createStockTakeForSections(): ResponseEntity<Map<String, String>> { | |||||
| val result = stockTakeService.createStockTakeForSections() | |||||
| return ResponseEntity.ok(result) | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,180 @@ | |||||
| package com.ffii.fpsms.modules.stock.web | |||||
| import com.ffii.fpsms.modules.stock.service.StockTakeRecordService | |||||
| import org.springframework.web.bind.annotation.GetMapping | |||||
| import org.springframework.web.bind.annotation.PostMapping | |||||
| import org.springframework.web.bind.annotation.RequestMapping | |||||
| import org.springframework.web.bind.annotation.RestController | |||||
| import org.springframework.web.bind.annotation.RequestParam | |||||
| import org.springframework.web.bind.annotation.RequestBody | |||||
| import org.springframework.http.ResponseEntity | |||||
| import com.ffii.fpsms.modules.stock.web.model.* | |||||
| import org.slf4j.LoggerFactory | |||||
| @RestController | |||||
| @RequestMapping("/stockTakeRecord") | |||||
| class StockTakeRecordController( | |||||
| private val stockOutRecordService: StockTakeRecordService | |||||
| ) { | |||||
| private val logger = LoggerFactory.getLogger(StockTakeRecordController::class.java) | |||||
| @GetMapping("/AllPickedStockOutRecordList") | |||||
| fun AllPickedStockOutRecordList(): List<AllPickedStockTakeListReponse> { | |||||
| return stockOutRecordService.AllPickedStockTakeList() | |||||
| } | |||||
| @GetMapping("/AllApproverStockTakeList") | |||||
| fun AllApproverStockTakeList(): List<AllPickedStockTakeListReponse> { | |||||
| return stockOutRecordService.AllApproverStockTakeList() | |||||
| } | |||||
| @GetMapping("/inventoryLotDetailsBySectionNotMatch") | |||||
| fun getInventoryLotDetailsByStockTakeSectionNotMatch( | |||||
| @RequestParam stockTakeSection: String, | |||||
| @RequestParam(required = false) stockTakeId: Long? | |||||
| ): List<InventoryLotDetailResponse> { | |||||
| return stockOutRecordService.getInventoryLotDetailsByStockTakeSectionNotMatch(stockTakeSection, stockTakeId) | |||||
| } | |||||
| @GetMapping("/inventoryLotDetails") | |||||
| fun getInventoryLotDetailsByWarehouseCode( | |||||
| @RequestParam warehouseCode: String | |||||
| ): List<InventoryLotDetailResponse> { | |||||
| return stockOutRecordService.getInventoryLotDetailsByWarehouseCode(warehouseCode) | |||||
| } | |||||
| @GetMapping("/inventoryLotDetailsBySection") | |||||
| fun getInventoryLotDetailsByStockTakeSection( | |||||
| @RequestParam stockTakeSection: String, | |||||
| @RequestParam(required = false) stockTakeId: Long? | |||||
| ): List<InventoryLotDetailResponse> { | |||||
| return stockOutRecordService.getInventoryLotDetailsByStockTakeSection(stockTakeSection, stockTakeId) | |||||
| } | |||||
| @PostMapping("/saveStockTakeRecord") | |||||
| fun saveStockTakeRecord( | |||||
| @RequestBody request: SaveStockTakeRecordRequest, | |||||
| @RequestParam stockTakeId: Long, | |||||
| @RequestParam stockTakerId: Long | |||||
| ): ResponseEntity<Any> { | |||||
| return try { | |||||
| val savedRecord = stockOutRecordService.saveStockTakeRecord(request, stockTakeId, stockTakerId) | |||||
| logger.info("Successfully saved stock take record: ${savedRecord.id}") | |||||
| ResponseEntity.ok(savedRecord) | |||||
| } catch (e: IllegalArgumentException) { | |||||
| logger.warn("Validation error: ${e.message}") | |||||
| ResponseEntity.badRequest().body(mapOf( | |||||
| "error" to "VALIDATION_ERROR", | |||||
| "message" to (e.message ?: "Validation failed") | |||||
| )) | |||||
| } catch (e: Exception) { | |||||
| logger.error("Error saving stock take record", e) | |||||
| ResponseEntity.status(500).body(mapOf( | |||||
| "error" to "INTERNAL_ERROR", | |||||
| "message" to (e.message ?: "Failed to save stock take record") | |||||
| )) | |||||
| } | |||||
| } | |||||
| @PostMapping("/batchSaveStockTakeRecords") | |||||
| fun batchSaveStockTakeRecords( | |||||
| @RequestBody request: BatchSaveStockTakeRecordRequest | |||||
| ): ResponseEntity<Any> { | |||||
| return try { | |||||
| val result = stockOutRecordService.batchSaveStockTakeRecords(request) | |||||
| logger.info("Batch save completed: success=${result.successCount}, errors=${result.errorCount}") | |||||
| ResponseEntity.ok(result) | |||||
| } catch (e: IllegalArgumentException) { | |||||
| logger.warn("Validation error: ${e.message}") | |||||
| ResponseEntity.badRequest().body(mapOf( | |||||
| "error" to "VALIDATION_ERROR", | |||||
| "message" to (e.message ?: "Validation failed") | |||||
| )) | |||||
| } catch (e: Exception) { | |||||
| logger.error("Error batch saving stock take records", e) | |||||
| ResponseEntity.status(500).body(mapOf( | |||||
| "error" to "INTERNAL_ERROR", | |||||
| "message" to (e.message ?: "Failed to batch save stock take records") | |||||
| )) | |||||
| } | |||||
| } | |||||
| @GetMapping("/checkAndUpdateStockTakeStatus") | |||||
| fun checkAndUpdateStockTakeStatus( | |||||
| @RequestParam stockTakeId: Long, | |||||
| @RequestParam stockTakeSection: String | |||||
| ): ResponseEntity<Any> { | |||||
| return try { | |||||
| val result = stockOutRecordService.checkAndUpdateStockTakeStatus(stockTakeId, stockTakeSection) | |||||
| ResponseEntity.ok(result) | |||||
| } catch (e: Exception) { | |||||
| logger.error("Error checking and updating stock take status", e) | |||||
| ResponseEntity.status(500).body( | |||||
| mapOf( | |||||
| "error" to "INTERNAL_ERROR", | |||||
| "message" to (e.message ?: "Failed to check and update stock take status") | |||||
| ) | |||||
| ) | |||||
| } | |||||
| } | |||||
| @PostMapping("/saveApproverStockTakeRecord") | |||||
| fun saveApproverStockTakeRecord( | |||||
| @RequestBody request: SaveApproverStockTakeRecordRequest, | |||||
| @RequestParam stockTakeId: Long | |||||
| ): ResponseEntity<Any> { | |||||
| return try { | |||||
| val savedRecord = stockOutRecordService.saveApproverStockTakeRecord(request, stockTakeId) | |||||
| logger.info("Successfully saved approver stock take record: ${savedRecord.id}") | |||||
| ResponseEntity.ok(savedRecord) | |||||
| } catch (e: IllegalArgumentException) { | |||||
| logger.warn("Validation error: ${e.message}") | |||||
| ResponseEntity.badRequest().body(mapOf( | |||||
| "error" to "VALIDATION_ERROR", | |||||
| "message" to (e.message ?: "Validation failed") | |||||
| )) | |||||
| } catch (e: Exception) { | |||||
| logger.error("Error saving approver stock take record", e) | |||||
| ResponseEntity.status(500).body(mapOf( | |||||
| "error" to "INTERNAL_ERROR", | |||||
| "message" to (e.message ?: "Failed to save approver stock take record") | |||||
| )) | |||||
| } | |||||
| } | |||||
| @PostMapping("/batchSaveApproverStockTakeRecords") | |||||
| fun batchSaveApproverStockTakeRecords( | |||||
| @RequestBody request: BatchSaveApproverStockTakeRecordRequest | |||||
| ): ResponseEntity<Any> { | |||||
| return try { | |||||
| val result = stockOutRecordService.batchSaveApproverStockTakeRecords(request) | |||||
| logger.info("Batch approver save completed: success=${result.successCount}, errors=${result.errorCount}") | |||||
| ResponseEntity.ok(result) | |||||
| } catch (e: IllegalArgumentException) { | |||||
| logger.warn("Validation error: ${e.message}") | |||||
| ResponseEntity.badRequest().body(mapOf( | |||||
| "error" to "VALIDATION_ERROR", | |||||
| "message" to (e.message ?: "Validation failed") | |||||
| )) | |||||
| } catch (e: Exception) { | |||||
| logger.error("Error batch saving approver stock take records", e) | |||||
| ResponseEntity.status(500).body(mapOf( | |||||
| "error" to "INTERNAL_ERROR", | |||||
| "message" to (e.message ?: "Failed to batch save approver stock take records") | |||||
| )) | |||||
| } | |||||
| } | |||||
| @PostMapping("/updateStockTakeRecordStatusToNotMatch") | |||||
| fun updateStockTakeRecordStatusToNotMatch( | |||||
| @RequestParam stockTakeRecordId: Long | |||||
| ): ResponseEntity<Any> { | |||||
| return try { | |||||
| val result = stockOutRecordService.updateStockTakeRecordStatusToNotMatch(stockTakeRecordId) | |||||
| ResponseEntity.ok(result) | |||||
| } catch (e: Exception) { | |||||
| logger.error("Error updating stock take record status to not match", e) | |||||
| ResponseEntity.status(500).body(mapOf( | |||||
| "error" to "INTERNAL_ERROR", | |||||
| "message" to (e.message ?: "Failed to update stock take record status to not match") | |||||
| )) | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -11,4 +11,5 @@ data class SaveStockTakeRequest( | |||||
| var actualEnd: LocalDateTime?, | var actualEnd: LocalDateTime?, | ||||
| var status: String?, | var status: String?, | ||||
| val remarks: String?, | val remarks: String?, | ||||
| val stockTakeSection: String?=null, | |||||
| ) | ) | ||||
| @@ -0,0 +1,90 @@ | |||||
| // StockTakeRecordReponse.kt | |||||
| package com.ffii.fpsms.modules.stock.web.model | |||||
| import java.time.LocalDate | |||||
| import java.math.BigDecimal | |||||
| data class AllPickedStockTakeListReponse( | |||||
| val id: Long, | |||||
| val stockTakeSession: String, | |||||
| val lastStockTakeDate: LocalDate?, | |||||
| val status: String, | |||||
| val currentStockTakeItemNumber: Int, | |||||
| val totalInventoryLotNumber: Int, | |||||
| val stockTakeId: Long, | |||||
| ) | |||||
| data class InventoryLotDetailResponse( | |||||
| val id: Long, | |||||
| val inventoryLotId: Long, | |||||
| val itemId: Long, | |||||
| val itemCode: String?, | |||||
| val itemName: String?, | |||||
| val lotNo: String?, | |||||
| val expiryDate: LocalDate?, | |||||
| val productionDate: java.time.LocalDateTime?, | |||||
| val stockInDate: java.time.LocalDateTime?, | |||||
| val inQty: BigDecimal?, | |||||
| val outQty: BigDecimal?, | |||||
| val holdQty: BigDecimal?, | |||||
| val availableQty: BigDecimal?, | |||||
| val uom: String?, | |||||
| val warehouseCode: String?, | |||||
| val warehouse: String?, | |||||
| val warehouseSlot: String?, | |||||
| val warehouseArea: String?, | |||||
| val warehouseName: String?, | |||||
| val varianceQty: BigDecimal? = null, | |||||
| val status: String?, | |||||
| val remarks: String?, | |||||
| val stockTakeRecordStatus: String?, | |||||
| val stockTakeRecordId: Long? = null, | |||||
| val firstStockTakeQty: BigDecimal? = null, | |||||
| val secondStockTakeQty: BigDecimal? = null, | |||||
| val firstBadQty: BigDecimal? = null, | |||||
| val secondBadQty: BigDecimal? = null, | |||||
| val approverQty: BigDecimal? = null, | |||||
| val approverBadQty: BigDecimal? = null, | |||||
| val finalQty: BigDecimal? = null, | |||||
| ) | |||||
| data class InventoryLotLineListRequest( | |||||
| val warehouseCode: String | |||||
| ) | |||||
| data class SaveStockTakeRecordRequest( | |||||
| val stockTakeRecordId: Long? = null, // null = 创建,非 null = 更新 | |||||
| val inventoryLotLineId: Long, // 创建时需要,用于识别 inventory lot line | |||||
| val qty: BigDecimal, // QTY(第一次或第二次,后端判断) | |||||
| val badQty: BigDecimal, | |||||
| // val stockTakerName: String, | |||||
| val remark: String? = null | |||||
| ) | |||||
| data class SaveApproverStockTakeRecordRequest( | |||||
| val stockTakeRecordId: Long? = null, | |||||
| val qty: BigDecimal, | |||||
| val badQty: BigDecimal, | |||||
| val approverId: Long? = null, | |||||
| val approverQty: BigDecimal? = null, | |||||
| val approverBadQty: BigDecimal? = null, | |||||
| //val remark: String? = null | |||||
| ) | |||||
| data class BatchSaveStockTakeRecordRequest( | |||||
| val stockTakeId: Long, | |||||
| val stockTakeSection: String, | |||||
| val stockTakerId: Long, | |||||
| //val stockTakerName: String | |||||
| ) | |||||
| data class BatchSaveStockTakeRecordResponse( | |||||
| val successCount: Int, | |||||
| val errorCount: Int, | |||||
| val errors: List<String> | |||||
| ) | |||||
| data class BatchSaveApproverStockTakeRecordRequest( | |||||
| val stockTakeId: Long, | |||||
| val stockTakeSection: String, | |||||
| val approverId: Long, | |||||
| ) | |||||
| data class BatchSaveApproverStockTakeRecordResponse( | |||||
| val successCount: Int, | |||||
| val errorCount: Int, | |||||
| val errors: List<String> | |||||
| ) | |||||
| @@ -0,0 +1,53 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset Enson:add_column | |||||
| CREATE TABLE IF NOT EXISTS stockTakeRecord ( | |||||
| id INT AUTO_INCREMENT PRIMARY KEY, | |||||
| created DATETIME, | |||||
| createdBy VARCHAR(30), | |||||
| version INT, | |||||
| modified DATETIME, | |||||
| modifiedBy VARCHAR(30), | |||||
| deleted TINYINT(1), | |||||
| itemId INT NOT NULL, | |||||
| lotId INT NOT NULL, | |||||
| warehouseId INT NOT NULL, | |||||
| stockTakeSection varchar(255), | |||||
| stockTakeId INT NOT NULL, | |||||
| stockTakerName VARCHAR(100), | |||||
| approverId INT, | |||||
| approverName VARCHAR(100), | |||||
| stockTakerId INT NOT NULL, | |||||
| pickerFirstStockTakeQty DECIMAL(14,2), | |||||
| pickerSecondStockTakeQty DECIMAL(14,2), | |||||
| approverStockTakeQty DECIMAL(14,2), | |||||
| approverSecondStockTakeQty DECIMAL(14,2), | |||||
| bookQty DECIMAL(14,2) NOT NULL, | |||||
| badQty DECIMAL(14,2), | |||||
| pickerFirstBadQty DECIMAL(14,2), | |||||
| pickerSecondBadQty DECIMAL(14,2), | |||||
| approverBadQty DECIMAL(14,2), | |||||
| varianceQty DECIMAL(14,2), | |||||
| uom VARCHAR(30), | |||||
| stockTakeStartTime DATETIME, | |||||
| stockTakeEndTime DATETIME, | |||||
| date DATE NOT NULL, | |||||
| status VARCHAR(30) NOT NULL, | |||||
| remarks VARCHAR(500), | |||||
| itemCode VARCHAR(50), | |||||
| itemName VARCHAR(200), | |||||
| inventoryLotId INT, | |||||
| lotNo VARCHAR(512), | |||||
| expiredDate DATE | |||||
| ); | |||||
| @@ -0,0 +1,5 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset Enson:add_column | |||||
| ALTER TABLE `fpsmsdb`.`stock_take` | |||||
| ADD COLUMN `stockTakeSection` varchar(255) NULL DEFAULT '' AFTER `remarks`; | |||||
| @@ -0,0 +1,7 @@ | |||||
| --liquibase formatted sql | |||||
| --changeset author:add_time_fields_to_productprocessline | |||||
| ALTER TABLE `productprocessline` | |||||
| ADD COLUMN `processingTime` INT(11) NULL AFTER `endTime`, | |||||
| ADD COLUMN `setupTime` INT(11) NULL AFTER `processingTime`, | |||||
| ADD COLUMN `changeoverTime` INT(11) NULL AFTER `setupTime`; | |||||