From 5fd679eb88dff49adf7bdc4b789f7315516129fa Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Thu, 15 Jan 2026 20:04:51 +0800 Subject: [PATCH] update --- .../jobOrder/service/JoPickOrderService.kt | 6 +- .../pickOrder/entity/PickExecutionIssue.kt | 3 +- .../entity/PickExecutionIssueRepository.kt | 46 +- .../service/PickExecutionIssueService.kt | 573 +++++++++++++++++- .../pickOrder/service/PickOrderService.kt | 14 +- .../web/PickExecutionIssueController.kt | 52 +- .../web/models/PickExecutionIssueRequest.kt | 34 ++ .../web/models/SubmitIssueRequest.kt | 34 ++ .../service/ProductProcessService.kt | 1 + .../entity/InventoryLotLineRepository.kt | 11 +- .../entity/projection/StockInLineInfo.kt | 1 + .../stock/service/StockInLineService.kt | 5 +- .../stock/service/SuggestedPickLotService.kt | 2 +- src/main/resources/application-db-local.yml | 2 +- .../20260115_01_Enson/01_alter_table.sql | 5 + 15 files changed, 769 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SubmitIssueRequest.kt create mode 100644 src/main/resources/db/changelog/changes/20260115_01_Enson/01_alter_table.sql diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt index cce1b44..f9698f2 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt @@ -474,7 +474,7 @@ open class JoPickOrderService( expiryDate = il.expiryDate?.let { "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}" }, - location = warehouse?.name, + location = warehouse?.code, availableQty = availableQty?.toDouble(), requiredQty = spl.qty?.toDouble() ?: 0.0, actualPickQty = sol?.qty ?: 0.0, @@ -701,7 +701,7 @@ open class JoPickOrderService( "expiryDate" to il.expiryDate?.let { "${it.year}-${"%02d".format(it.monthValue)}-${"%02d".format(it.dayOfMonth)}" }, - "location" to warehouse?.name, + "location" to warehouse?.code, "availableQty" to availableQty?.toDouble(), "requiredQty" to (spl.qty?.toDouble() ?: 0.0), "actualPickQty" to (sol?.qty ?: 0.0), @@ -1989,7 +1989,7 @@ open fun getJobOrderLotsHierarchicalByPickOrderId(pickOrderId: Long): JobOrderLo expiryDate = il.expiryDate?.let { "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}" }, - location = warehouse?.name, + location = warehouse?.code, availableQty = availableQty?.toDouble(), requiredQty = spl.qty?.toDouble() ?: 0.0, actualPickQty = sol?.qty ?: 0.0, diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssue.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssue.kt index f904ca7..acf8c17 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssue.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssue.kt @@ -138,8 +138,7 @@ val doPickOrderId: Long? = null, enum class HandleStatus { pending, - sort_and_repair, - dispose + completed } enum class IssueCategory { lot_issue, // 正常 Issue Button 提交 diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssueRepository.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssueRepository.kt index a2eb52f..3422587 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssueRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssueRepository.kt @@ -2,11 +2,13 @@ package com.ffii.fpsms.modules.pickOrder.entity import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Modifying import org.springframework.data.jpa.repository.Query import org.springframework.data.repository.query.Param import org.springframework.stereotype.Repository import com.ffii.fpsms.modules.pickOrder.enums.PickExecutionIssueEnum - +import java.time.LocalDate +import org.springframework.transaction.annotation.Transactional @Repository interface PickExecutionIssueRepository : JpaRepository { fun findByPickOrderIdAndDeletedFalse(pickOrderId: Long): List @@ -76,6 +78,48 @@ fun findMaterialIssues(): List fun getBadItemList(): List fun findByPickOrderId(pickOrderId: Long): List + @Query(""" + SELECT p FROM PickExecutionIssue p + WHERE p.issueCategory = :issueCategory + AND p.missQty > 0 + AND p.deleted = false + ORDER BY p.created DESC +""") +fun findMissItemList(@Param("issueCategory") issueCategory: IssueCategory): List + +@Query(""" + SELECT p FROM PickExecutionIssue p + WHERE p.issueCategory = :issueCategory + AND p.badItemQty > 0 + AND p.deleted = false + ORDER BY p.created DESC +""") +fun findBadItemListByCategory(@Param("issueCategory") issueCategory: IssueCategory): List + +@Query(""" + SELECT p FROM PickExecutionIssue p + WHERE p.issueCategory = :issueCategory + AND p.badItemQty > 0 + AND p.deleted = false + ORDER BY p.created DESC +""") +fun findBadItemOnlyList(@Param("issueCategory") issueCategory: IssueCategory): List + +@Modifying +@Transactional +@Query(""" + UPDATE PickExecutionIssue p + SET p.handleStatus = :handleStatus, + p.handleDate = :handleDate, + p.handledBy = :handledBy + WHERE p.id = :issueId +""") +fun updateHandleStatus( + @Param("issueId") issueId: Long, + @Param("handleStatus") handleStatus: HandleStatus, + @Param("handleDate") handleDate: LocalDate, + @Param("handledBy") handledBy: Long? +): Int } diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt index ec67c0f..d6f13a3 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt @@ -32,6 +32,13 @@ import com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus import com.ffii.fpsms.modules.stock.web.model.StockOutStatus import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus +import com.ffii.fpsms.modules.pickOrder.web.models.* +import com.ffii.fpsms.modules.common.SecurityUtils +import com.ffii.fpsms.modules.stock.entity.StockOut +import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLine +import com.ffii.fpsms.modules.master.entity.ItemsRepository +import com.ffii.fpsms.modules.common.CodeGenerator +import com.ffii.fpsms.modules.pickOrder.web.models.SubmitIssueRequest @Service open class PickExecutionIssueService( private val pickExecutionIssueRepository: PickExecutionIssueRepository, @@ -45,7 +52,8 @@ open class PickExecutionIssueService( private val pickOrderLineRepository: PickOrderLineRepository, private val doPickOrderService: DoPickOrderService, private val joPickOrderRepository: JoPickOrderRepository, - private val joPickOrderRecordRepository: JoPickOrderRecordRepository + private val joPickOrderRecordRepository: JoPickOrderRecordRepository, + private val itemsRepository: ItemsRepository ) { @@ -792,4 +800,567 @@ open fun createBatchReleaseIssue( return 0 } } + +open fun getMissItemList(issueCategory: String = "lot_issue"): List { + val category = try { + IssueCategory.valueOf(issueCategory) + } catch (e: Exception) { + IssueCategory.lot_issue + } + return pickExecutionIssueRepository.findMissItemList(category).filter { it.handleStatus != HandleStatus.completed } +} + +open fun getBadItemList(issueCategory: String = "lot_issue"): List { + val category = try { + IssueCategory.valueOf(issueCategory) + } catch (e: Exception) { + IssueCategory.lot_issue + } + return pickExecutionIssueRepository.findBadItemListByCategory(category).filter { it.handleStatus != HandleStatus.completed } +} +open fun getBadItemOnlyList(): List { + return pickExecutionIssueRepository.findBadItemOnlyList(IssueCategory.lot_issue) +} + +open fun getExpiryItemList(): List { + val today = LocalDate.now() + val lotLines = inventoryLotLineRepository.findExpiredItems(today) + + return lotLines.map { lotLine -> + val lot = lotLine.inventoryLot + val item = lot?.item // Get item from inventoryLot + ExpiryItemResponse( + id = lotLine.id ?: 0L, + itemId = item?.id ?: 0L, + itemCode = item?.code ?: "", + itemDescription = item?.name, + lotId = lot?.id ?: 0L, + lotNo = lot?.lotNo, + storeLocation = lotLine.warehouse?.code, // Construct from warehouse + expiryDate = lot?.expiryDate, + remainingQty = (lotLine.inQty ?: BigDecimal.ZERO).subtract(lotLine.outQty ?: BigDecimal.ZERO) + ) + } +} + +@Transactional(rollbackFor = [Exception::class]) +open fun submitMissItem(request: SubmitIssueRequest): MessageResponse { + try { + val issue = pickExecutionIssueRepository.findById(request.issueId).orElse(null) + ?: return MessageResponse( + id = null, + name = "Error", + code = "NOT_FOUND", + type = "stock_issue", + message = "Issue not found", + errorPosition = null + ) + + if (issue.missQty <= BigDecimal.ZERO || issue.deleted) { + return MessageResponse( + id = null, + name = "Error", + code = "INVALID", + type = "stock_issue", + message = "Invalid issue or no miss quantity", + errorPosition = null + ) + } + + val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L + val stockOut = createIssueStockOutHeader("MISS_ITEM", issue.issueRemark, handler) + + val pickOrderLine = issue.pickOrderLineId?.let { + pickOrderLineRepository.findById(it).orElse(null) + } + + val lotLine = issue.lotId?.let { + inventoryLotLineRepository.findById(it).orElse(null) + } + + val item = itemsRepository.findById(issue.itemId).orElse(null) + ?: return MessageResponse( + id = null, + name = "Error", + code = "ITEM_NOT_FOUND", + type = "stock_issue", + message = "Item not found", + errorPosition = null + ) + + val stockOutLine = StockOutLine().apply { + this.stockOut = stockOut + this.inventoryLotLine = lotLine + this.item = item + this.qty = issue.missQty.toDouble() + this.status = StockOutLineStatus.PENDING.status + this.pickOrderLine = pickOrderLine + } + stockOutLineRepository.save(stockOutLine) + + if (issue.lotId != null) { + updateLotLineAfterIssue(issue.lotId, issue.missQty) + } + + markIssueHandled(issue, handler) + + return MessageResponse( + id = stockOut.id, + name = "Success", + code = "SUCCESS", + type = "stock_issue", + message = "Successfully submitted miss item", + errorPosition = null + ) + } catch (e: Exception) { + return MessageResponse( + id = null, + name = "Error", + code = "ERROR", + type = "stock_issue", + message = "Failed to submit miss item: ${e.message}", + errorPosition = null + ) + } +} + +@Transactional(rollbackFor = [Exception::class]) +open fun submitBadItem(request: SubmitIssueRequest): MessageResponse { + try { + val issue = pickExecutionIssueRepository.findById(request.issueId).orElse(null) + ?: return MessageResponse( + id = null, + name = "Error", + code = "NOT_FOUND", + type = "stock_issue", + message = "Issue not found", + errorPosition = null + ) + + if (issue.badItemQty <= BigDecimal.ZERO || issue.deleted) { + return MessageResponse( + id = null, + name = "Error", + code = "INVALID", + type = "stock_issue", + message = "Invalid issue or no bad item quantity", + errorPosition = null + ) + } + + val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L + val stockOut = createIssueStockOutHeader("BAD_ITEM", issue.issueRemark, handler) + + val pickOrderLine = issue.pickOrderLineId?.let { + pickOrderLineRepository.findById(it).orElse(null) + } + + val lotLine = issue.lotId?.let { + inventoryLotLineRepository.findById(it).orElse(null) + } + + val item = itemsRepository.findById(issue.itemId).orElse(null) + ?: return MessageResponse( + id = null, + name = "Error", + code = "ITEM_NOT_FOUND", + type = "stock_issue", + message = "Item not found", + errorPosition = null + ) + + val stockOutLine = StockOutLine().apply { + this.stockOut = stockOut + this.inventoryLotLine = lotLine + this.item = item + this.qty = issue.badItemQty.toDouble() + this.status = StockOutLineStatus.PENDING.status + this.pickOrderLine = pickOrderLine + } + stockOutLineRepository.save(stockOutLine) + + if (issue.lotId != null) { + updateLotLineAfterIssue(issue.lotId, issue.badItemQty) + } + + markIssueHandled(issue, handler) + + return MessageResponse( + id = stockOut.id, + name = "Success", + code = "SUCCESS", + type = "stock_issue", + message = "Successfully submitted bad item", + errorPosition = null + ) + } catch (e: Exception) { + return MessageResponse( + id = null, + name = "Error", + code = "ERROR", + type = "stock_issue", + message = "Failed to submit bad item: ${e.message}", + errorPosition = null + ) + } +} + +@Transactional(rollbackFor = [Exception::class]) +open fun submitExpiryItem(request: SubmitExpiryRequest): MessageResponse { + try { + val lotLine = inventoryLotLineRepository.findById(request.lotLineId).orElse(null) + ?: return MessageResponse( + id = null, + name = "Error", + code = "NOT_FOUND", + type = "stock_issue", + message = "Lot line not found", + errorPosition = null + ) + + val lot = lotLine.inventoryLot + val today = LocalDate.now() + + if (lot?.expiryDate == null || !lot.expiryDate!!.isBefore(today)) { + return MessageResponse( + id = null, + name = "Error", + code = "NOT_EXPIRED", + type = "stock_issue", + message = "Item is not expired", + errorPosition = null + ) + } + + val inQty = lotLine.inQty ?: BigDecimal.ZERO + val outQty = lotLine.outQty ?: BigDecimal.ZERO + val remainingQty = inQty.subtract(outQty) + + if (remainingQty <= BigDecimal.ZERO) { + return MessageResponse( + id = null, + name = "Error", + code = "NO_REMAINING", + type = "stock_issue", + message = "No remaining quantity", + errorPosition = null + ) + } + + val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L + val stockOut = createIssueStockOutHeader("EXPIRY_ITEM", "Expired item removal", handler) + + val item = lot.item + ?: return MessageResponse( + id = null, + name = "Error", + code = "ITEM_NOT_FOUND", + type = "stock_issue", + message = "Item not found", + errorPosition = null + ) + + val stockOutLine = StockOutLine().apply { + this.stockOut = stockOut + this.inventoryLotLine = lotLine + this.item = item + this.qty = remainingQty.toDouble() + this.status = StockOutLineStatus.PENDING.status + } + stockOutLineRepository.save(stockOutLine) + + updateLotLineAfterIssue(lotLine.id ?: 0L, remainingQty) + + return MessageResponse( + id = stockOut.id, + name = "Success", + code = "SUCCESS", + type = "stock_issue", + message = "Successfully submitted expiry item", + errorPosition = null + ) + } catch (e: Exception) { + return MessageResponse( + id = null, + name = "Error", + code = "ERROR", + type = "stock_issue", + message = "Failed to submit expiry item: ${e.message}", + errorPosition = null + ) + } +} + +// Fix batchSubmitMissItem method (around line 835): +@Transactional(rollbackFor = [Exception::class]) +open fun batchSubmitMissItem(request: BatchSubmitIssueRequest): MessageResponse { + try { + val issues = pickExecutionIssueRepository.findAllById(request.issueIds) + .filter { it.missQty > BigDecimal.ZERO && !it.deleted } + + if (issues.isEmpty()) { + return MessageResponse( + id = null, + name = "Error", + code = "EMPTY", + type = "stock_issue", + message = "No valid issues to submit", + errorPosition = null + ) + } + + val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L + + // Create single StockOut header for all submissions + val stockOut = createIssueStockOutHeader("MISS_ITEM", "Batch miss item submission", handler) + + issues.forEach { issue -> + + // Get pickOrderLine if available + val pickOrderLine = issue.pickOrderLineId?.let { + pickOrderLineRepository.findById(it).orElse(null) + } + + // Get inventoryLotLine + val lotLine = issue.lotId?.let { + inventoryLotLineRepository.findById(it).orElse(null) + } + + // Get item from issue (it has itemId) + val item = itemsRepository.findById(issue.itemId).orElse(null) + + + // Create StockOutLine + val stockOutLine = StockOutLine().apply { + this.stockOut = stockOut + this.inventoryLotLine = lotLine + this.item = item + this.qty = issue.missQty.toDouble() + this.status = StockOutLineStatus.PENDING.status + this.pickOrderLine = pickOrderLine + } + stockOutLineRepository.save(stockOutLine) + + // Update InventoryLotLine + if (issue.lotId != null) { + updateLotLineAfterIssue(issue.lotId, issue.missQty) + } + + // Mark issue as handled + markIssueHandled(issue, handler) + } + + return MessageResponse( + id = null, + name = "Success", + code = "SUCCESS", + type = "stock_issue", + message = "Successfully submitted ${issues.size} miss item(s)", + errorPosition = null + ) + } catch (e: Exception) { + return MessageResponse( + id = null, + name = "Error", + code = "ERROR", + type = "stock_issue", + message = "Failed to submit miss items: ${e.message}", + errorPosition = null + ) + } +} + +// Fix batchSubmitBadItem method (around line 890) - same pattern: +@Transactional(rollbackFor = [Exception::class]) +open fun batchSubmitBadItem(request: BatchSubmitIssueRequest): MessageResponse { + try { + val issues = pickExecutionIssueRepository.findAllById(request.issueIds) + .filter { it.badItemQty > BigDecimal.ZERO && !it.deleted } + + if (issues.isEmpty()) { + return MessageResponse( + id = null, + name = "Error", + code = "EMPTY", + type = "stock_issue", + message = "No valid issues to submit", + errorPosition = null + ) + } + + val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L + + // Create single StockOut header for all submissions + val stockOut = createIssueStockOutHeader("BAD_ITEM", "Batch bad item submission", handler) + + issues.forEach { issue -> + + // Get pickOrderLine if available + val pickOrderLine = issue.pickOrderLineId?.let { + pickOrderLineRepository.findById(it).orElse(null) + } + + // Get inventoryLotLine + val lotLine = issue.lotId?.let { + inventoryLotLineRepository.findById(it).orElse(null) + } + + // Get item from issue + val item = itemsRepository.findById(issue.itemId).orElse(null) + + + // Create StockOutLine + val stockOutLine = StockOutLine().apply { + this.stockOut = stockOut + this.inventoryLotLine = lotLine + this.item = item + this.qty = issue.badItemQty.toDouble() + this.status = StockOutLineStatus.PENDING.status + this.pickOrderLine = pickOrderLine + } + stockOutLineRepository.save(stockOutLine) + + // Update InventoryLotLine + if (issue.lotId != null) { + updateLotLineAfterIssue(issue.lotId, issue.badItemQty) + } + + // Mark issue as handled + markIssueHandled(issue, handler) + } + + return MessageResponse( + id = null, + name = "Success", + code = "SUCCESS", + type = "stock_issue", + message = "Successfully submitted ${issues.size} bad item(s)", + errorPosition = null + ) + } catch (e: Exception) { + return MessageResponse( + id = null, + name = "Error", + code = "ERROR", + type = "stock_issue", + message = "Failed to submit bad items: ${e.message}", + errorPosition = null + ) + } +} + +// Fix batchSubmitExpiryItem method (around line 945): +@Transactional(rollbackFor = [Exception::class]) +open fun batchSubmitExpiryItem(request: BatchSubmitExpiryRequest): MessageResponse { + try { + val lotLines = inventoryLotLineRepository.findAllById(request.lotLineIds) + .filter { + val lot = it.inventoryLot + val today = LocalDate.now() + lot?.expiryDate != null && lot.expiryDate!!.isBefore(today) && + (it.inQty ?: BigDecimal.ZERO) != (it.outQty ?: BigDecimal.ZERO) + } + + if (lotLines.isEmpty()) { + return MessageResponse( + id = null, + name = "Error", + code = "EMPTY", + type = "stock_issue", + message = "No valid expiry items to submit", + errorPosition = null + ) + } + + val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L + + // Create single StockOut header for all submissions + val stockOut = createIssueStockOutHeader("EXPIRY_ITEM", "Batch expiry item removal", handler) + + lotLines.forEach { lotLine -> + val remainingQty = (lotLine.inQty ?: BigDecimal.ZERO).subtract(lotLine.outQty ?: BigDecimal.ZERO) + + // Get item from inventoryLot + val item = lotLine.inventoryLot?.item + + // Create StockOutLine + val stockOutLine = StockOutLine().apply { + this.stockOut = stockOut + this.inventoryLotLine = lotLine + this.item = item + this.qty = remainingQty.toDouble() + this.status = StockOutLineStatus.PENDING.status + } + stockOutLineRepository.save(stockOutLine) + + // Update InventoryLotLine + updateLotLineAfterIssue(lotLine.id ?: 0L, remainingQty) + } + + return MessageResponse( + id = null, + name = "Success", + code = "SUCCESS", + type = "stock_issue", + message = "Successfully submitted ${lotLines.size} expiry item(s)", + errorPosition = null + ) + } catch (e: Exception) { + return MessageResponse( + id = null, + name = "Error", + code = "ERROR", + type = "stock_issue", + message = "Failed to submit expiry items: ${e.message}", + errorPosition = null + ) + } +} + + +private fun markIssueHandled(issue: PickExecutionIssue, handler: Long?) { + pickExecutionIssueRepository.updateHandleStatus( + issueId = issue.id ?: 0L, + handleStatus = HandleStatus.completed, + handleDate = LocalDate.now(), + handledBy = handler + ) +} + +private fun createIssueStockOutHeader(type: String, remarks: String?, handler: Long?): StockOut { + val prefix = "ISSUE" + val midfix = CodeGenerator.DEFAULT_MIDFIX + val latestCode = stockOutRepository.findAll().mapNotNull { it.consoPickOrderCode } + .filter { it.startsWith("$prefix-$midfix") } + .maxOrNull() + val consoCode = CodeGenerator.generateNo(prefix = prefix, midfix = midfix, latestCode = latestCode) + + val stockOut = StockOut().apply { + this.type = type + this.consoPickOrderCode = consoCode + this.status = StockOutStatus.PENDING.status + this.handler = handler + this.remarks = remarks + } + return stockOutRepository.save(stockOut) +} + +private fun updateLotLineAfterIssue(lotLineId: Long, qty: BigDecimal) { + val lotLine = inventoryLotLineRepository.findById(lotLineId).orElse(null) + if (lotLine != null) { + val currentOutQty = lotLine.outQty ?: BigDecimal.ZERO + val newOutQty = currentOutQty.add(qty) + lotLine.outQty = newOutQty + + // If outQty != inQty, set status to AVAILABLE + val inQty = lotLine.inQty ?: BigDecimal.ZERO + if (newOutQty != inQty) { + lotLine.status = InventoryLotLineStatus.AVAILABLE + } + + lotLine.modified = LocalDateTime.now() + lotLine.modifiedBy = "system" + inventoryLotLineRepository.save(lotLine) + } +} } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt index b4ba1cb..44bc7d4 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt @@ -934,7 +934,7 @@ open class PickOrderService( lotId = ill.id, lotNo = il?.lotNo, expiryDate = il?.expiryDate, - location = w?.name, + location = w?.code, stockUnit = ill.stockUom?.uom?.udfudesc ?: uomDesc, availableQty = availableQty, requiredQty = spl?.qty ?: zero, @@ -3771,7 +3771,7 @@ ORDER BY "id" to lotId, "lotNo" to il?.lotNo, "expiryDate" to il?.expiryDate?.toString(), - "location" to w?.name, + "location" to w?.code, "stockUnit" to (ill.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A"), "availableQty" to availableQty, "requiredQty" to (spl.qty ?: zero), @@ -3857,7 +3857,7 @@ ORDER BY "id" to inventoryLotLineId, "lotNo" to il?.lotNo, "expiryDate" to il?.expiryDate?.toString(), - "location" to w?.name, + "location" to w?.code, "stockUnit" to (ill.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A"), "availableQty" to availableQty, "requiredQty" to (suggestion?.qty ?: zero), @@ -4957,7 +4957,7 @@ open fun getLotDetailsByDoPickOrderRecordId(doPickOrderRecordId: Long): Map { + return pickExecutionIssueService.getMissItemList(issueCategory) +} + +@GetMapping("/issues/badItem") +fun getBadItemIssues( + @RequestParam(required = false, defaultValue = "lot_issue") issueCategory: String +): List { + return pickExecutionIssueService.getBadItemList(issueCategory) +} + +@GetMapping("/issues/expiryItem") +fun getExpiryItemIssues(): List { + return pickExecutionIssueService.getExpiryItemList() +} + +@PostMapping("/submitMissItem") +fun submitMissItem(@RequestBody request: SubmitIssueRequest): MessageResponse { + return pickExecutionIssueService.submitMissItem(request) +} + +@PostMapping("/batchSubmitMissItem") +fun batchSubmitMissItem(@RequestBody request: BatchSubmitIssueRequest): MessageResponse { + return pickExecutionIssueService.batchSubmitMissItem(request) +} + +@PostMapping("/submitBadItem") +fun submitBadItem(@RequestBody request: SubmitIssueRequest): MessageResponse { + return pickExecutionIssueService.submitBadItem(request) +} + +@PostMapping("/batchSubmitBadItem") +fun batchSubmitBadItem(@RequestBody request: BatchSubmitIssueRequest): MessageResponse { + return pickExecutionIssueService.batchSubmitBadItem(request) +} + +@PostMapping("/submitExpiryItem") +fun submitExpiryItem(@RequestBody request: SubmitExpiryRequest): MessageResponse { + return pickExecutionIssueService.submitExpiryItem(request) +} + +@PostMapping("/batchSubmitExpiryItem") +fun batchSubmitExpiryItem(@RequestBody request: BatchSubmitExpiryRequest): MessageResponse { + return pickExecutionIssueService.batchSubmitExpiryItem(request) +} +} + diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/PickExecutionIssueRequest.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/PickExecutionIssueRequest.kt index 539073e..0223f6e 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/PickExecutionIssueRequest.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/PickExecutionIssueRequest.kt @@ -3,6 +3,9 @@ package com.ffii.fpsms.modules.stock.web.model import java.math.BigDecimal import java.time.LocalDate +import java.time.LocalDateTime + + data class PickExecutionIssueRequest( val pickOrderId: Long, @@ -25,4 +28,35 @@ data class PickExecutionIssueRequest( val issueRemark: String? = null, val pickerName: String? = null, val handledBy: Long? = null +) + +data class SubmitIssueRequest( + val issueId: Long, + val handler: Long? = null, +) + +data class BatchSubmitIssueRequest( + val issueIds: List, + val handler: Long? = null, +) + +data class SubmitExpiryRequest( + val lotLineId: Long, + val handler: Long? = null, +) + +data class BatchSubmitExpiryRequest( + val lotLineIds: List, + val handler: Long? = null, +) +data class ExpiryItemResponse( + val id: Long, // InventoryLotLine ID + val itemId: Long, + val itemCode: String, + val itemDescription: String?, + val lotId: Long, // InventoryLot ID + val lotNo: String?, + val storeLocation: String?, + val expiryDate: LocalDate?, + val remainingQty: BigDecimal, ) \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SubmitIssueRequest.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SubmitIssueRequest.kt new file mode 100644 index 0000000..5ba18de --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SubmitIssueRequest.kt @@ -0,0 +1,34 @@ +package com.ffii.fpsms.modules.pickOrder.web.models +import java.math.BigDecimal +import java.time.LocalDate + +data class SubmitIssueRequest( + val issueId: Long, + val handler: Long? = null, +) + +data class BatchSubmitIssueRequest( + val issueIds: List, + val handler: Long? = null, +) + +data class SubmitExpiryRequest( + val lotLineId: Long, + val handler: Long? = null, +) + +data class BatchSubmitExpiryRequest( + val lotLineIds: List, + val handler: Long? = null, +) +data class ExpiryItemResponse( + val id: Long, // InventoryLotLine ID + val itemId: Long, + val itemCode: String, + val itemDescription: String?, + val lotId: Long, // InventoryLot ID + val lotNo: String?, + val storeLocation: String?, + val expiryDate: LocalDate?, + val remainingQty: BigDecimal, +) \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt b/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt index 1a5a30b..ab76876 100644 --- a/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt +++ b/src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt @@ -1497,6 +1497,7 @@ val sufficientStockQty = bomMaterials productionDate = LocalDate.now(), jobOrderId = jobOrder.id, acceptQty =jobOrder?.reqQty?:BigDecimal.ZERO , + //acceptQty = null, expiryDate=null, status="pending", ) diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt index 2d58f7d..7790845 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt @@ -10,7 +10,7 @@ import org.springframework.stereotype.Repository import java.io.Serializable import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus import org.springframework.data.repository.query.Param - +import java.time.LocalDate @Repository interface InventoryLotLineRepository : AbstractRepository { @@ -78,4 +78,13 @@ fun countDistinctItemsByWarehouseIds(@Param("warehouseIds") warehouseIds: List): Long +@Query(""" + SELECT ill FROM InventoryLotLine ill + JOIN ill.inventoryLot il + WHERE il.expiryDate < :today + AND ill.inQty != ill.outQty + AND ill.deleted = false + ORDER BY il.expiryDate ASC +""") +fun findExpiredItems(@Param("today") today: LocalDate): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt index 78e3f8d..7218193 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt @@ -22,6 +22,7 @@ interface StockInLineInfo { val purchaseOrderId: Long? @get:Value("#{target.jobOrder?.id}") val jobOrderId: Long? + val demandQty: BigDecimal? val acceptedQty: BigDecimal @get:Value("#{target.purchaseOrderLine?.qty}") diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt index 00270f3..7f58c49 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt @@ -118,7 +118,7 @@ open class StockInLineService( // stockIn = stockInService.create(SaveStockInRequest(purchaseOrderId = request.purchaseOrderId)).entity as StockIn // var stockIn = stockInRepository.findByPurchaseOrderIdAndDeletedFalse(request.purchaseOrderId) } - createStockLedgerForStockIn(stockInLine) + val item = itemRepository.findById(request.itemId).orElseThrow() // If request contains valid POL if (pol != null) { @@ -406,7 +406,7 @@ open class StockInLineService( } // TODO: check all status to prevent reverting progress due to multiple users access to the same po? // return list of stock in line, update data grid with the list - createStockLedgerForStockIn(stockInLine) + stockInLine.apply { this.productionDate = request.productionDate?.atStartOfDay() ?: this.productionDate this.productLotNo = request.productLotNo ?: this.productLotNo @@ -458,6 +458,7 @@ open class StockInLineService( } // this.inventoryLotLine = savedInventoryLotLine } + createStockLedgerForStockIn(stockInLine) // Update JO Status if (stockInLine.jobOrder != null) { //TODO Improve val jo = stockInLine.jobOrder diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt index 53666ef..ce6f2ec 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt @@ -953,7 +953,7 @@ println("Keeping all suggestions (including rejected ones for display)") itemDescription = item.name, lotId = inventoryLotLine?.id, lotNo = inventoryLotLine?.inventoryLot?.lotNo, - storeLocation = inventoryLotLine?.warehouse?.name, + storeLocation = inventoryLotLine?.warehouse?.code, requiredQty = pickOrderLine.qty, actualPickQty = rejectedStockOutLine.qty ?: BigDecimal.ZERO, // 直接使用,不需要 toBigDecimal() missQty = (pickOrderLine.qty ?: BigDecimal.ZERO).minus(rejectedStockOutLine.qty ?: BigDecimal.ZERO), // 直接使用 diff --git a/src/main/resources/application-db-local.yml b/src/main/resources/application-db-local.yml index 4fa8584..a9c01bf 100644 --- a/src/main/resources/application-db-local.yml +++ b/src/main/resources/application-db-local.yml @@ -1,5 +1,5 @@ spring: datasource: - jdbc-url: jdbc:mysql://127.0.0.1:3306/fpsmsdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8 + jdbc-url: jdbc:mysql://127.0.0.1:3308/fpsmsdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8 username: root password: secret \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/20260115_01_Enson/01_alter_table.sql b/src/main/resources/db/changelog/changes/20260115_01_Enson/01_alter_table.sql new file mode 100644 index 0000000..59f6b82 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20260115_01_Enson/01_alter_table.sql @@ -0,0 +1,5 @@ +--liquibase formatted sql +--changeset author:add_type_and_item_id_to_stock_ledger + +ALTER TABLE `fpsmsdb`.`pick_execution_issue` +CHANGE COLUMN `handle_status` `handle_status` ENUM('pending', "completed") NULL DEFAULT 'pending' ;