| @@ -80,4 +80,6 @@ open class JobOrder : BaseEntity<Long>() { | |||
| @Column(name = "jobTypeId") | |||
| open var jobTypeId: Long? = null | |||
| } | |||
| @@ -16,4 +16,5 @@ import java.util.Optional | |||
| interface JobTypeRepository : JpaRepository<JobType, Long> { | |||
| //fun findByName(name: String): Optional<JobType> | |||
| //fun findByIdAndDeletedIsFalse(id: Long): Optional<JobType> | |||
| } | |||
| @@ -38,8 +38,32 @@ interface JobOrderInfo { | |||
| @get:Value("#{target.status.value}") | |||
| val status: String; | |||
| } | |||
| @get:Value("#{target.jobTypeId}") | |||
| val jobTypeId: Long?; | |||
| } | |||
| data class JobTypeResponse( | |||
| val id: Long?, | |||
| val name: String? | |||
| ) | |||
| data class JobOrderInfoWithTypeName( | |||
| val id: Long, | |||
| val code: String, | |||
| val itemCode: String, | |||
| val itemName: String, | |||
| val name: String, | |||
| val reqQty: BigDecimal, | |||
| val item: JobOrderItemInfo, | |||
| val stockInLineId: Long?, | |||
| val stockInLineStatus: String?, | |||
| val silHandlerId: Long?, | |||
| val planStart: LocalDateTime?, | |||
| val status: String, | |||
| val jobTypeId: Long?, | |||
| val jobTypeName: String? | |||
| ) | |||
| // Job Order | |||
| interface JobOrderDetailWithJsonString { | |||
| val id: Long?; | |||
| @@ -14,6 +14,7 @@ import org.springframework.stereotype.Service | |||
| import org.springframework.transaction.annotation.Transactional | |||
| import java.time.LocalDate | |||
| import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus | |||
| import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus | |||
| import java.time.LocalDateTime | |||
| import com.ffii.core.support.JdbcDao | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.SecondScanSubmitRequest | |||
| @@ -29,6 +30,10 @@ import com.ffii.fpsms.modules.stock.entity.SuggestPickLotRepository | |||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository | |||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository | |||
| import com.ffii.fpsms.modules.stock.entity.StockOutLIneRepository | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.AllJoPickOrderResponse | |||
| import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository | |||
| import com.ffii.fpsms.modules.jobOrder.entity.JobTypeRepository | |||
| import com.ffii.fpsms.modules.master.entity.ItemsRepository | |||
| @Service | |||
| open class JoPickOrderService( | |||
| private val joPickOrderRepository: JoPickOrderRepository, | |||
| @@ -42,7 +47,11 @@ open class JoPickOrderService( | |||
| private val suggestPickLotRepository: SuggestPickLotRepository, | |||
| private val inventoryLotLineRepository: InventoryLotLineRepository, | |||
| private val inventoryLotRepository: InventoryLotRepository, | |||
| private val stockOutLineRepository: StockOutLIneRepository | |||
| private val stockOutLineRepository: StockOutLIneRepository, | |||
| private val jobOrderRepository: JobOrderRepository, | |||
| private val jobTypeRepository: JobTypeRepository, | |||
| private val itemsRepository: ItemsRepository | |||
| ) { | |||
| open fun save(record: JoPickOrder): JoPickOrder { | |||
| @@ -1629,4 +1638,318 @@ open fun getCompletedJobOrderPickOrderLotDetailsForCompletedPick(pickOrderId: Lo | |||
| emptyList() | |||
| } | |||
| } | |||
| open fun getAllJoPickOrders(): List<AllJoPickOrderResponse> { | |||
| println("=== getAllJoPickOrders ===") | |||
| return try { | |||
| val releasedPickOrders = pickOrderRepository.findAllByStatusAndDeletedFalse( | |||
| PickOrderStatus.RELEASED | |||
| ).filter { pickOrder -> | |||
| pickOrder.jobOrder != null | |||
| } | |||
| println("Found ${releasedPickOrders.size} released job order pick orders") | |||
| val jobOrderPickOrders = releasedPickOrders.mapNotNull { pickOrder -> | |||
| println("Processing pick order: ${pickOrder.id}, code: ${pickOrder.code}") | |||
| val jobOrder = pickOrder.jobOrder | |||
| if (jobOrder == null) { | |||
| println("❌ Pick order ${pickOrder.id} has no job order") | |||
| return@mapNotNull null | |||
| } | |||
| println("Job order found: ${jobOrder.id}, code: ${jobOrder.code}") | |||
| val bom = jobOrder.bom | |||
| println("BOM found: ${bom?.id}") | |||
| val item = bom?.item | |||
| if (item == null) { | |||
| println("❌ BOM ${bom?.id} has no item") | |||
| return@mapNotNull null | |||
| } | |||
| println("Item found: ${item.id}, name: ${item.name}") | |||
| val uom = bom.outputQtyUom | |||
| if (uom == null) { | |||
| println("❌ BOM ${bom.id} has no uom") | |||
| return@mapNotNull null | |||
| } | |||
| // println("UOM found: ${uom.id}, code: ${uom.code}") | |||
| val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrder.id ?: return@mapNotNull null) | |||
| val finishedLines = pickOrderLines.count { it.status == PickOrderLineStatus.COMPLETED } | |||
| val jobOrderType = jobOrder.jobTypeId?.let { jobTypeRepository.findById(it).orElse(null) } | |||
| println("✅ Building response for pick order ${pickOrder.id}") | |||
| AllJoPickOrderResponse( | |||
| id = pickOrder.id ?: 0L, | |||
| pickOrderId = pickOrder.id, | |||
| pickOrderCode = pickOrder.code, | |||
| jobOrderId = jobOrder.id, | |||
| jobOrderCode = jobOrder.code, | |||
| jobOrderTypeId = jobOrder.jobTypeId, | |||
| jobOrderType = jobOrderType?.name, | |||
| itemId = item.id ?: 0L, | |||
| itemName = item.name ?: "", | |||
| reqQty = jobOrder.reqQty ?: BigDecimal.ZERO, | |||
| //uomId = bom.outputQtyUom?.id : 0L, | |||
| uomId = 0, | |||
| uomName = bom?.outputQtyUom?: "", | |||
| jobOrderStatus = jobOrder.status?.value ?: "", | |||
| finishedPickOLineCount = finishedLines | |||
| ) | |||
| } | |||
| println("Returning ${jobOrderPickOrders.size} released job order pick orders") | |||
| jobOrderPickOrders | |||
| } catch (e: Exception) { | |||
| println("❌ Error in getAllJoPickOrders: ${e.message}") | |||
| e.printStackTrace() | |||
| emptyList() | |||
| } | |||
| } | |||
| open fun getJobOrderLotsHierarchicalByPickOrderId(pickOrderId: Long): Map<String, Any?> { | |||
| println("=== getJobOrderLotsHierarchicalByPickOrderId ===") | |||
| println("pickOrderId: $pickOrderId") | |||
| return try { | |||
| val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) | |||
| if (pickOrder == null || pickOrder.deleted == true) { | |||
| println("❌ Pick order $pickOrderId not found or deleted") | |||
| return mapOf( | |||
| "pickOrder" to null as Any?, | |||
| "pickOrderLines" to emptyList<Map<String, Any>>() as Any? | |||
| ) | |||
| } | |||
| val jobOrder = pickOrder.jobOrder ?: return mapOf( | |||
| "pickOrder" to null as Any?, | |||
| "pickOrderLines" to emptyList<Map<String, Any>>() as Any? | |||
| ) | |||
| // 获取 pick order lines | |||
| val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrder.id!!) | |||
| .filter { it.deleted == false } | |||
| // 获取所有 pick order line IDs | |||
| val pickOrderLineIds = pickOrderLines.map { it.id!! } | |||
| // 获取 suggested pick lots | |||
| val suggestedPickLots = if (pickOrderLineIds.isNotEmpty()) { | |||
| suggestPickLotRepository.findAllByPickOrderLineIdIn(pickOrderLineIds) | |||
| .filter { it.deleted == false } | |||
| } else { | |||
| emptyList() | |||
| } | |||
| // 获取所有 inventory lot line IDs | |||
| val inventoryLotLineIds = suggestedPickLots.mapNotNull { it.suggestedLotLine?.id } | |||
| // 获取 inventory lot lines | |||
| val inventoryLotLines = if (inventoryLotLineIds.isNotEmpty()) { | |||
| inventoryLotLineRepository.findAllByIdIn(inventoryLotLineIds) | |||
| .filter { it.deleted == false } | |||
| } else { | |||
| emptyList() | |||
| } | |||
| // 获取 inventory lots | |||
| val inventoryLotIds = inventoryLotLines.mapNotNull { it.inventoryLot?.id }.distinct() | |||
| val inventoryLots = if (inventoryLotIds.isNotEmpty()) { | |||
| inventoryLotRepository.findAllByIdIn(inventoryLotIds) | |||
| .filter { it.deleted == false } | |||
| } else { | |||
| emptyList() | |||
| } | |||
| // 获取 stock out lines | |||
| val stockOutLines = if (pickOrderLineIds.isNotEmpty() && inventoryLotLineIds.isNotEmpty()) { | |||
| pickOrderLineIds.flatMap { polId -> | |||
| inventoryLotLineIds.flatMap { illId -> | |||
| stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId) | |||
| } | |||
| } | |||
| } else { | |||
| emptyList() | |||
| } | |||
| // 取得所有 stock out line(含無 lot 情況) | |||
| val stockOutLinesByPickOrderLine = pickOrderLineIds.associateWith { polId -> | |||
| stockOutLineRepository.findAllByPickOrderLineIdAndDeletedFalse(polId) | |||
| } | |||
| // 获取 jo_pick_order 记录 | |||
| val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrder.id!!) | |||
| // 构建 pick order info | |||
| val pickOrderInfo = mapOf( | |||
| "id" to pickOrder.id, | |||
| "code" to pickOrder.code, | |||
| "consoCode" to pickOrder.consoCode, | |||
| "targetDate" to pickOrder.targetDate?.let { | |||
| "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}" | |||
| }, | |||
| "type" to pickOrder.type?.value, | |||
| "status" to pickOrder.status?.value, | |||
| "assignTo" to pickOrder.assignTo?.id, | |||
| "jobOrder" to mapOf( | |||
| "id" to jobOrder.id, | |||
| "code" to jobOrder.code, | |||
| "name" to "Job Order ${jobOrder.code}" | |||
| ) | |||
| ) | |||
| // 构建 pick order lines with lots | |||
| val pickOrderLinesResult = pickOrderLines.map { pol -> | |||
| val item = pol.item | |||
| val uom = pol.uom | |||
| val lineId = pol.id!! | |||
| val suggestions = suggestedPickLots.filter { it.pickOrderLine?.id == lineId } | |||
| val stockoutsForLine = stockOutLinesByPickOrderLine[lineId].orEmpty() | |||
| // 获取该 line 的 suggested pick lots | |||
| val lineSuggestedLots = suggestedPickLots.filter { it.pickOrderLine?.id == pol.id } | |||
| // 构建 lots 数据 | |||
| val lots = lineSuggestedLots.mapNotNull { spl -> | |||
| val ill = spl.suggestedLotLine | |||
| if (ill == null || ill.deleted == true) return@mapNotNull null | |||
| val il = ill.inventoryLot | |||
| if (il == null || il.deleted == true) return@mapNotNull null | |||
| val warehouse = ill.warehouse | |||
| // 获取对应的 stock out line | |||
| val sol = stockOutLines.firstOrNull { | |||
| it.pickOrderLine?.id == pol.id && it.inventoryLotLine?.id == ill.id | |||
| } | |||
| // 获取对应的 jo_pick_order | |||
| val jpo = joPickOrders.firstOrNull { it.itemId == item?.id } | |||
| // 计算 available quantity | |||
| val availableQty = if (sol?.status == "rejected") { | |||
| null | |||
| } else { | |||
| (ill.inQty ?: BigDecimal.ZERO) - (ill.outQty ?: BigDecimal.ZERO) - (ill.holdQty ?: BigDecimal.ZERO) | |||
| } | |||
| // 计算 total picked by all pick orders | |||
| val totalPickedByAllPickOrders = stockOutLines | |||
| .filter { it.inventoryLotLine?.id == ill.id && it.deleted == false } | |||
| .filter { it.status in listOf("pending", "checked", "partially_completed", "completed") } | |||
| .sumOf { it.qty?.toBigDecimal() ?: BigDecimal.ZERO } | |||
| // 计算 lot availability | |||
| val lotAvailability = when { | |||
| il.expiryDate != null && il.expiryDate!!.isBefore(LocalDate.now()) -> "expired" | |||
| sol?.status == "rejected" -> "rejected" | |||
| availableQty != null && availableQty <= BigDecimal.ZERO -> "insufficient_stock" | |||
| ill.status == InventoryLotLineStatus.UNAVAILABLE -> "status_unavailable" | |||
| else -> "available" | |||
| } | |||
| // 计算 processing status | |||
| val processingStatus = when (sol?.status) { | |||
| "completed" -> "completed" | |||
| "rejected" -> "rejected" | |||
| "created" -> "pending" | |||
| else -> "pending" | |||
| } | |||
| mapOf( | |||
| "lotId" to ill.id, | |||
| "lotNo" to il.lotNo, | |||
| "expiryDate" to il.expiryDate?.let { | |||
| "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}" | |||
| }, | |||
| "location" to warehouse?.name, | |||
| "availableQty" to availableQty?.toDouble(), | |||
| "requiredQty" to (spl.qty?.toDouble() ?: 0.0), | |||
| "actualPickQty" to (sol?.qty ?: 0.0), | |||
| "processingStatus" to processingStatus, | |||
| "lotAvailability" to lotAvailability, | |||
| "pickOrderId" to pickOrder.id, | |||
| "pickOrderCode" to pickOrder.code, | |||
| "pickOrderConsoCode" to pickOrder.consoCode, | |||
| "pickOrderLineId" to pol.id, | |||
| "stockOutLineId" to sol?.id, | |||
| "suggestedPickLotId" to spl.id, | |||
| "stockOutLineQty" to (sol?.qty ?: 0.0), | |||
| "stockOutLineStatus" to sol?.status, | |||
| "routerIndex" to warehouse?.order, | |||
| "routerArea" to warehouse?.code, | |||
| "routerRoute" to warehouse?.code, | |||
| "uomShortDesc" to uom?.udfShortDesc, | |||
| "matchStatus" to jpo?.matchStatus?.value, | |||
| "matchBy" to jpo?.matchBy, | |||
| "matchQty" to jpo?.matchQty | |||
| ) | |||
| } | |||
| mapOf( | |||
| "id" to pol.id, | |||
| "itemId" to item?.id, | |||
| "itemCode" to item?.code, | |||
| "itemName" to item?.name, | |||
| "requiredQty" to pol.qty?.toDouble(), | |||
| "uomCode" to uom?.code, | |||
| "uomDesc" to uom?.udfudesc, | |||
| "lots" to lots | |||
| ) | |||
| } | |||
| return mapOf( | |||
| "pickOrder" to pickOrderInfo as Any?, | |||
| "pickOrderLines" to pickOrderLinesResult as Any? | |||
| ) | |||
| } catch (e: Exception) { | |||
| println("❌ Error in getJobOrderLotsHierarchicalByPickOrderId: ${e.message}") | |||
| e.printStackTrace() | |||
| return mapOf( | |||
| "pickOrder" to null as Any?, | |||
| "pickOrderLines" to emptyList<Map<String, Any>>() as Any? | |||
| ) | |||
| } | |||
| } | |||
| open fun updateHandledByForItem(pickOrderId: Long, itemId: Long, userId: Long): JoPickOrder? { | |||
| val joPickOrderOpt = joPickOrderRepository.findByPickOrderIdAndItemId(pickOrderId, itemId) | |||
| if (joPickOrderOpt.isEmpty) { | |||
| println("⚠️ JoPickOrder not found for pickOrderId: $pickOrderId, itemId: $itemId") | |||
| return null | |||
| } | |||
| val joPickOrder = joPickOrderOpt.get() | |||
| joPickOrder.handledBy = userId | |||
| // Don't update other fields - only handledBy | |||
| return joPickOrderRepository.save(joPickOrder) | |||
| } | |||
| open fun updateRecordHandledByForItem(pickOrderId: Long, itemId: Long, userId: Long): JoPickOrderRecord? { | |||
| val joPickOrderRecordOpt = joPickOrderRecordRepository.findByPickOrderIdAndItemId(pickOrderId, itemId) | |||
| if (joPickOrderRecordOpt.isEmpty) { | |||
| println("⚠️ JoPickOrderRecord not found for pickOrderId: $pickOrderId, itemId: $itemId") | |||
| return null | |||
| } | |||
| val joPickOrderRecord = joPickOrderRecordOpt.get() | |||
| joPickOrderRecord.handledBy = userId | |||
| // Don't update other fields - only handledBy | |||
| return joPickOrderRecordRepository.save(joPickOrderRecord) | |||
| } | |||
| } | |||
| @@ -54,6 +54,8 @@ import org.springframework.core.io.ClassPathResource | |||
| import java.io.File | |||
| import java.io.FileNotFoundException | |||
| import java.io.IOException | |||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfoWithTypeName | |||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobTypeResponse | |||
| @Service | |||
| @@ -82,7 +84,7 @@ open class JobOrderService( | |||
| code = request.code ?: "", | |||
| bomName = request.itemName ?: "", | |||
| pageable = pageable | |||
| ) | |||
| ) | |||
| val planStartFrom = request.planStart | |||
| val planStartTo = request.planStartTo | |||
| @@ -97,7 +99,59 @@ open class JobOrderService( | |||
| val total = response.totalElements | |||
| return RecordsRes<JobOrderInfo>(records, total.toInt()); | |||
| } | |||
| open fun allJobOrdersByPageWithTypeName(request: SearchJobOrderInfoRequest): RecordsRes<JobOrderInfoWithTypeName> { | |||
| val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10); | |||
| println("allJobOrdersByPage") | |||
| println(request) | |||
| val response = jobOrderRepository.findJobOrderInfoByCodeContainsAndBomNameContainsAndDeletedIsFalseOrderByIdDesc( | |||
| code = request.code ?: "", | |||
| bomName = request.itemName ?: "", | |||
| pageable = pageable | |||
| ) | |||
| val jobTypeIds = response.content.mapNotNull { it.jobTypeId }.distinct() | |||
| val jobTypes = if (jobTypeIds.isNotEmpty()) { | |||
| jobTypeRepository.findAllById(jobTypeIds).associateBy { it.id } | |||
| } else { | |||
| emptyMap() | |||
| } | |||
| val planStartFrom = request.planStart | |||
| val planStartTo = request.planStartTo | |||
| val records = response.content | |||
| .filter { | |||
| (planStartFrom == null || (it.planStart != null && (planStartFrom.isEqual(it.planStart) || planStartFrom.isBefore(it.planStart)))) && | |||
| (planStartTo == null || (it.planStart != null && (planStartTo.isEqual(it.planStart) || planStartTo.isAfter(it.planStart)))) | |||
| } | |||
| .map { info -> | |||
| JobOrderInfoWithTypeName( | |||
| id = info.id, | |||
| code = info.code, | |||
| itemCode = info.itemCode, | |||
| itemName = info.itemName, | |||
| name = info.name, | |||
| reqQty = info.reqQty, | |||
| item = info.item, | |||
| stockInLineId = info.stockInLineId, | |||
| stockInLineStatus = info.stockInLineStatus, | |||
| silHandlerId = info.silHandlerId, | |||
| planStart = info.planStart, | |||
| status = info.status, | |||
| jobTypeId = info.jobTypeId, | |||
| jobTypeName = info.jobTypeId?.let { jobTypes[it]?.name } | |||
| ) | |||
| } | |||
| .filter { info -> | |||
| // Filter by jobTypeName if provided | |||
| request.jobTypeName == null || | |||
| request.jobTypeName.isBlank() || | |||
| info.jobTypeName?.equals(request.jobTypeName, ignoreCase = true) == true || | |||
| info.jobTypeName?.contains(request.jobTypeName, ignoreCase = true) == true | |||
| } | |||
| val total = response.totalElements | |||
| return RecordsRes<JobOrderInfoWithTypeName>(records, total.toInt()); | |||
| } | |||
| open fun jobOrderDetail(id: Long): JobOrderDetail { | |||
| val sqlResult = jobOrderRepository.findJobOrderDetailById(id) ?: throw NoSuchElementException(); | |||
| @@ -594,6 +648,8 @@ open class JobOrderService( | |||
| //tempPdfFile.delete | |||
| } | |||
| } | |||
| open fun getAllJobTypes(): List<JobTypeResponse> { | |||
| return jobTypeRepository.findAll().map { JobTypeResponse(it.id, it.name) } | |||
| } | |||
| } | |||
| @@ -2,7 +2,7 @@ package com.ffii.fpsms.modules.jobOrder.web | |||
| import com.ffii.core.response.RecordsRes | |||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetail | |||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfo | |||
| import com.ffii.fpsms.modules.jobOrder.service.JobOrderBomMaterialService | |||
| import com.ffii.fpsms.modules.jobOrder.service.JobOrderProcessService | |||
| import com.ffii.fpsms.modules.jobOrder.service.JobOrderService | |||
| @@ -10,6 +10,7 @@ import com.ffii.fpsms.modules.jobOrder.web.model.CreateJobOrderRequest | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.JobOrderCommonActionRequest | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.SearchJobOrderInfoRequest | |||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobTypeResponse | |||
| import jakarta.validation.Valid | |||
| import org.springframework.web.bind.annotation.GetMapping | |||
| import org.springframework.web.bind.annotation.ModelAttribute | |||
| @@ -35,6 +36,9 @@ import java.io.UnsupportedEncodingException | |||
| import java.text.ParseException | |||
| import org.springframework.web.bind.annotation.* | |||
| import org.springframework.web.bind.annotation.RequestParam | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.UpdateJoPickOrderHandledByRequest | |||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfo | |||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfoWithTypeName | |||
| @RestController | |||
| @RequestMapping("/jo") | |||
| class JobOrderController( | |||
| @@ -46,10 +50,11 @@ class JobOrderController( | |||
| ) { | |||
| @GetMapping("/getRecordByPage") | |||
| fun allJobOrdersByPage(@ModelAttribute request: SearchJobOrderInfoRequest): RecordsRes<JobOrderInfo> { | |||
| fun allJobOrdersByPage(@ModelAttribute request: SearchJobOrderInfoRequest): RecordsRes<JobOrderInfoWithTypeName> { | |||
| println("getRecordByPage") | |||
| println(request) | |||
| return jobOrderService.allJobOrdersByPage(request); | |||
| //return jobOrderService.allJobOrdersByPage(request); | |||
| return jobOrderService.allJobOrdersByPageWithTypeName(request); | |||
| } | |||
| @GetMapping("/detail/{id}") | |||
| @@ -224,4 +229,64 @@ fun recordSecondScanIssue( | |||
| fun getCompletedJobOrderPickOrderLotDetailsForCompletedPick(@PathVariable pickOrderId: Long): List<Map<String, Any?>> { | |||
| return joPickOrderService.getCompletedJobOrderPickOrderLotDetailsForCompletedPick(pickOrderId) | |||
| } | |||
| @GetMapping("/AllJoPickOrder") | |||
| fun getAllJoPickOrder(): List<AllJoPickOrderResponse> { | |||
| return joPickOrderService.getAllJoPickOrders() | |||
| } | |||
| @GetMapping("/all-lots-hierarchical-by-pick-order/{pickOrderId}") | |||
| fun getJobOrderLotsHierarchicalByPickOrderId(@PathVariable pickOrderId: Long): Map<String, Any?> { | |||
| return joPickOrderService.getJobOrderLotsHierarchicalByPickOrderId(pickOrderId) | |||
| } | |||
| @PostMapping("/update-jo-pick-order-handled-by") | |||
| fun updateJoPickOrderHandledBy(@Valid @RequestBody request: UpdateJoPickOrderHandledByRequest): MessageResponse { | |||
| try { | |||
| val joPickOrder = joPickOrderService.updateHandledByForItem( | |||
| request.pickOrderId, | |||
| request.itemId, | |||
| request.userId | |||
| ) | |||
| val joPickOrderRecord = joPickOrderService.updateRecordHandledByForItem( | |||
| request.pickOrderId, | |||
| request.itemId, | |||
| request.userId | |||
| ) | |||
| if (joPickOrder == null) { | |||
| return MessageResponse( | |||
| id = null, | |||
| code = "NOT_FOUND", | |||
| name = null, | |||
| type = null, | |||
| message = "JoPickOrder not found for pickOrderId: ${request.pickOrderId}, itemId: ${request.itemId}", | |||
| errorPosition = null | |||
| ) | |||
| } | |||
| return MessageResponse( | |||
| id = joPickOrder.id, | |||
| code = "SUCCESS", | |||
| name = null, | |||
| type = null, | |||
| message = "JoPickOrder handledBy updated successfully for item ${request.itemId}", | |||
| errorPosition = null | |||
| ) | |||
| } catch (e: Exception) { | |||
| println("Error updating JoPickOrder handledBy: ${e.message}") | |||
| e.printStackTrace() | |||
| return MessageResponse( | |||
| id = null, | |||
| code = "ERROR", | |||
| name = null, | |||
| type = null, | |||
| message = "Failed to update JoPickOrder handledBy: ${e.message}", | |||
| errorPosition = null | |||
| ) | |||
| } | |||
| } | |||
| @GetMapping("/jobTypes") | |||
| fun getAllJobTypes(): List<JobTypeResponse> { | |||
| return jobOrderService.getAllJobTypes() | |||
| } | |||
| } | |||
| @@ -1,24 +0,0 @@ | |||
| package com.ffii.fpsms.modules.jobOrder.web | |||
| import OperatorRequest | |||
| import com.ffii.core.response.RecordsRes | |||
| import com.ffii.core.utils.CriteriaArgsBuilder | |||
| import com.ffii.fpsms.modules.jobOrder.service.JobOrderProcessService | |||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| import com.ffii.fpsms.modules.purchaseOrder.entity.projections.PurchaseOrderDataClass | |||
| import com.ffii.fpsms.modules.purchaseOrder.service.PurchaseOrderService | |||
| import com.ffii.fpsms.modules.purchaseOrder.web.model.PagingRequest | |||
| import jakarta.servlet.http.HttpServletRequest | |||
| import org.springframework.web.bind.annotation.* | |||
| @RestController | |||
| @RequestMapping("/jop") | |||
| class JobOrderProcessController( | |||
| private val jobOrderProcessService: JobOrderProcessService | |||
| ) { | |||
| @PostMapping("/isOperatorExist") | |||
| fun checkPolAndCompletePo(@RequestBody request: OperatorRequest): MessageResponse { | |||
| println(request) | |||
| return jobOrderProcessService.isOperatorExist(request) | |||
| } | |||
| } | |||
| @@ -31,4 +31,38 @@ data class CreateJobOrderProcessRequest ( | |||
| val seqNo: Long?, | |||
| val remarks: String? = null, | |||
| val status: String = "pending", | |||
| ) | |||
| data class AllJoPickOrderResponse( | |||
| val id: Long, | |||
| val pickOrderId: Long?, | |||
| val pickOrderCode: String?, | |||
| val jobOrderId: Long?, | |||
| val jobOrderCode: String?, | |||
| val jobOrderTypeId: Long?, | |||
| val jobOrderType: String?, | |||
| val itemId: Long, | |||
| val itemName: String, | |||
| val reqQty: BigDecimal, | |||
| val uomId: Long, | |||
| val uomName: String, | |||
| val jobOrderStatus: String, | |||
| val finishedPickOLineCount: Int, | |||
| ) | |||
| data class JobOrderLotsHierarchicalResponse( | |||
| val id: Long, | |||
| val lotId: Long?, | |||
| val lotCode: String?, | |||
| val lotName: String?, | |||
| val lotQty: BigDecimal?, | |||
| val lotUomId: Long?, | |||
| val lotUomName: String?, | |||
| ) | |||
| data class UpdateJoPickOrderHandledByRequest( | |||
| val pickOrderId: Long, | |||
| val itemId: Long, | |||
| val userId: Long | |||
| ) | |||
| @@ -9,4 +9,5 @@ data class SearchJobOrderInfoRequest( | |||
| val planStartTo: LocalDateTime?, | |||
| val pageSize: Int?, | |||
| val pageNum: Int?, | |||
| val jobTypeName: String?, | |||
| ) | |||
| @@ -11,6 +11,6 @@ interface EquipmentDetailRepository : AbstractRepository<EquipmentDetail, Long> | |||
| fun findByNameAndDeletedIsFalse(name: String): EquipmentDetail?; | |||
| fun findByDescriptionAndDeletedIsFalse(description: String): EquipmentDetail?; | |||
| fun findByEquipmentTypeIdAndDeletedFalse(equipmentTypeId: Long): List<EquipmentDetail>; | |||
| } | |||
| @@ -78,4 +78,7 @@ interface PickOrderRepository : AbstractRepository<PickOrder, Long> { | |||
| // 在 PickOrderRepository 中添加: | |||
| fun findAllByStatusAndAssignToIsNullAndDeletedFalse(status: PickOrderStatus): List<PickOrder> | |||
| //fun findAllByJoid(jobOrderId: Long): List<PickOrder> | |||
| fun findAllByJobOrder_Id(jobOrderId: Long): List<PickOrder> | |||
| fun findTopByJobOrder_IdOrderByCreatedDesc(jobOrderId: Long): PickOrder? | |||
| } | |||
| @@ -26,6 +26,8 @@ open class ProductProcessLine : BaseEntity<Long>() { | |||
| @Column(name = "equipmentDetailId") | |||
| open var equipmentDetailId: Long? = null | |||
| @Size(max = 100) | |||
| @Column(name = "name", length = 100) | |||
| open var name: String? = null | |||
| @@ -52,6 +52,7 @@ data class ProductProcessLineInfo( | |||
| val name: String?, | |||
| val description: String?, | |||
| val equipment_name: String?, | |||
| val equipmentDetailCode: String?, | |||
| val status: String?, | |||
| val byproductId: Long?, | |||
| val byproductName: String?, | |||
| @@ -7,6 +7,7 @@ import com.ffii.fpsms.modules.productProcess.enums.ProductProcessStatus | |||
| import com.ffii.fpsms.modules.productProcess.web.model.* | |||
| import org.springframework.data.domain.Page | |||
| import org.springframework.data.domain.Pageable | |||
| import com.ffii.fpsms.modules.master.entity.EquipmentDetailRepository | |||
| import com.ffii.fpsms.modules.productProcess.entity.projections.jobOrderLineInfo | |||
| import org.springframework.stereotype.Service | |||
| import org.springframework.transaction.annotation.Transactional | |||
| @@ -38,7 +39,7 @@ import com.ffii.fpsms.modules.stock.service.StockInLineService | |||
| import com.ffii.fpsms.modules.stock.web.model.SaveStockInLineRequest | |||
| import com.ffii.fpsms.modules.master.entity.BomProcessMaterialRepository | |||
| import com.ffii.fpsms.modules.master.entity.BomMaterialRepository | |||
| import com.ffii.fpsms.modules.master.entity.EquipmentDetailRepository | |||
| import com.ffii.fpsms.modules.jobOrder.entity.JobTypeRepository | |||
| @Service | |||
| @Transactional | |||
| @@ -482,6 +483,7 @@ open class ProductProcessService( | |||
| // get bom materials | |||
| val bomMaterials = jobOrderBomMaterialRepository.findAllByJobOrderId(jobOrder?.id?:0) | |||
| val bomProcessIds = bomProcess.mapNotNull { it.id } | |||
| val itemIds = bomMaterials.mapNotNull { it.item?.id } | |||
| // calculate each item's available stock | |||
| @@ -559,6 +561,8 @@ open class ProductProcessService( | |||
| return productProcesses.map { process -> | |||
| val jobType = jobTypeRepository.findById(process.jobOrder?.jobTypeId?:0L).orElse(null) | |||
| println("jobType id ${process.jobOrder?.jobTypeId}") | |||
| ProductProcessInfo( | |||
| id = process.id?:0, | |||
| bomId = process.bom?.id?:0, | |||
| @@ -586,6 +590,10 @@ open class ProductProcessService( | |||
| insufficientStockQty = insufficientStockQty, | |||
| sufficientStockQty = sufficientStockQty, | |||
| productProcessLines = productProcessLineRepository.findByProductProcess_Id(process.id?:0).map { line -> | |||
| val equipmentDetail =equipmentDetailRepository.findById(line.equipmentDetailId?:0L).orElse(null) | |||
| println("equipmentDetail ${equipmentDetail?.code}") | |||
| println("equipmentDetail id${line.equipmentDetailId}") | |||
| println("line id${line.id}") | |||
| ProductProcessLineInfo( | |||
| id = line.id?:0, | |||
| bomprocessId = line.bomProcess?.id?:0, | |||
| @@ -597,6 +605,7 @@ open class ProductProcessService( | |||
| name = line.name?:"", | |||
| description = line.description?:"", | |||
| equipment_name = line.equipmentType?:"", | |||
| equipmentDetailCode = equipmentDetail?.code?:"", | |||
| status = line.status?:"", | |||
| durationInMinutes = line.bomProcess?.durationInMinute?:0, | |||
| prepTimeInMinutes = line.bomProcess?.prepTimeInMinute?:0, | |||
| @@ -693,7 +702,7 @@ open class ProductProcessService( | |||
| this.seqNo = bomProcess.seqNo?:0 | |||
| this.name = process?.name?:"" | |||
| this.description = bomProcess.description?:"" | |||
| this.equipmentType = equipment?.name?:"" | |||
| this.equipmentType = equipment?.code?:"" | |||
| this.status = "Pending" | |||
| } | |||
| productProcessLineRepository.save(productProcessLine) | |||
| @@ -710,6 +719,7 @@ open class ProductProcessService( | |||
| } | |||
| open fun UpdateProductProcessLineOperatorIdOrEquipmentId(request: UpdateProductProcessLineOperatorIdOrEquipmentIdRequest): MessageResponse { | |||
| val productProcessLine = productProcessLineRepository.findById(request.productProcessLineId).orElse(null) | |||
| /* | |||
| val equipmentId = request.equipmentId | |||
| val user = userRepository.findById(request.operatorId?:0L).orElse(null) | |||
| val bomProcess= bomProcessRepository.findById(productProcessLine?.bomProcess?.id?:0L).orElse(null) | |||
| @@ -753,6 +763,52 @@ open class ProductProcessService( | |||
| productProcessLineRepository.save(productProcessLine) | |||
| } | |||
| */ | |||
| val equipmentTypeSubTypeEquipmentNo = request.equipmentTypeSubTypeEquipmentNo | |||
| println("equipmentTypeSubTypeEquipmentNo ${equipmentTypeSubTypeEquipmentNo}") | |||
| val staffNo = request.staffNo | |||
| println("staffNo ${staffNo}") | |||
| val user = userRepository.findByStaffNo(staffNo?:"").orElse(null) | |||
| println("user ${user?.id}") | |||
| val equipmentDetail = equipmentDetailRepository.findByCode(equipmentTypeSubTypeEquipmentNo?:"") | |||
| println("equipmentDetail ${equipmentDetail?.id}") | |||
| val equipmentId = equipmentDetail?.equipmentTypeId | |||
| println("equipmentId ${equipmentId}") | |||
| val bomProcess= bomProcessRepository.findById(productProcessLine?.bomProcess?.id?:0L).orElse(null) | |||
| val bomProcessEquipment=bomProcess?.equipment | |||
| println("bomProcessEquipment ${bomProcessEquipment?.id}") | |||
| if (equipmentId != null && user != null) { | |||
| // 检查 equipmentId 是否与 bomProcessEquipment 匹配 | |||
| if (equipmentId != bomProcessEquipment?.id) { | |||
| println("productProcessLine id${request.productProcessLineId}") | |||
| println("user ${user?.id}") | |||
| println("bomProcess ${bomProcess?.id}") | |||
| println("not match equipment id${equipmentId} and ${bomProcessEquipment?.id}") | |||
| // 返回错误响应 | |||
| return MessageResponse( | |||
| id = request.productProcessLineId, | |||
| code = "400", | |||
| name = "Equipment Validation Failed", | |||
| type = "error", | |||
| message = "Input Equipment ID($equipmentId) and BOM Process Equipment ID(${bomProcessEquipment?.id}) not match", | |||
| errorPosition = "equipmentId" | |||
| ) | |||
| } | |||
| } | |||
| if(equipmentId != null &&( equipmentId==bomProcessEquipment?.id)) { | |||
| productProcessLine?.equipment = bomProcessEquipment | |||
| productProcessLine?.equipmentDetailId = equipmentDetail?.id | |||
| productProcessLineRepository.save(productProcessLine) | |||
| } | |||
| if(user != null) { | |||
| productProcessLine.operator = user | |||
| productProcessLineRepository.save(productProcessLine) | |||
| } | |||
| return MessageResponse( | |||
| id = null, | |||
| @@ -2,14 +2,18 @@ package com.ffii.fpsms.modules.productProcess.web.model | |||
| import java.time.LocalDate | |||
| import java.time.LocalDateTime | |||
| import com.fasterxml.jackson.annotation.JsonProperty | |||
| data class SaveProductProcessRequest( | |||
| val bomId: Long, | |||
| val jobOrderId: Long?, | |||
| ) | |||
| data class UpdateProductProcessLineOperatorIdOrEquipmentIdRequest( | |||
| val productProcessLineId: Long, | |||
| val operatorId: Long?, | |||
| val equipmentId: Long? | |||
| //val operatorId: Long?, | |||
| //val equipmentId: Long?, | |||
| @JsonProperty("EquipmentType-SubType-EquipmentNo") | |||
| val equipmentTypeSubTypeEquipmentNo: String?, | |||
| val staffNo: String?, | |||
| ) | |||
| data class UpdateProductProcessLineHandlerIdRequest( | |||
| val productProcessLineId: Long, | |||
| @@ -3,18 +3,18 @@ package com.ffii.fpsms.modules.user.entity; | |||
| import java.time.LocalDate; | |||
| import java.util.Collection; | |||
| import jakarta.persistence.Column; | |||
| import jakarta.persistence.Entity; | |||
| import jakarta.persistence.Table; | |||
| import jakarta.persistence.Transient; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| import org.springframework.security.core.GrantedAuthority; | |||
| import org.springframework.security.core.userdetails.UserDetails; | |||
| import com.fasterxml.jackson.annotation.JsonIgnore; | |||
| import com.ffii.core.entity.BaseEntity; | |||
| import jakarta.persistence.Column; | |||
| import jakarta.persistence.Entity; | |||
| import jakarta.persistence.Table; | |||
| import jakarta.persistence.Transient; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| /** @author Terence */ | |||
| @Entity | |||
| @Table(name = "user") | |||
| @@ -77,6 +77,17 @@ public class User extends BaseEntity<Long> implements UserDetails { | |||
| @Column | |||
| private boolean lotusNotesUser = false; | |||
| @Column | |||
| private String staffNo; | |||
| public String getStaffNo() { | |||
| return staffNo; | |||
| } | |||
| public void setStaffNo(String staffNo) { | |||
| this.staffNo = staffNo; | |||
| } | |||
| public boolean isLocked() { | |||
| return this.locked == null ? false : this.locked; | |||
| } | |||
| @@ -15,4 +15,6 @@ public interface UserRepository extends AbstractRepository<User, Long> { | |||
| Optional<User> findByUsernameAndDeletedFalse(String username); | |||
| List<UserCombo> findUserComboByTitleNotNullAndDepartmentNotNullAndNameNotNullAndDeletedFalse(); | |||
| Optional<User> findByStaffNo(@Param("staffNo") String staffNo); | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset enson:altertable_enson | |||
| ALTER TABLE `fpsmsdb`.`equipment_detail` | |||
| MODIFY COLUMN `equipmentTypeID` INT NULL; | |||