From a4ff32c62ca850e92579aaf43a2546d9360b116a Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Wed, 25 Mar 2026 15:18:26 +0800 Subject: [PATCH] update --- .../service/DeliveryOrderService.kt | 20 +++--- .../master/entity/ProcessRepository.kt | 1 + .../modules/master/web/ProcessController.kt | 17 +++++ .../modules/stock/entity/StockTakeRecord.kt | 4 +- .../stock/service/StockTakeRecordService.kt | 66 +++++++++++++++---- .../stock/web/model/StockTakeRecordReponse.kt | 4 ++ .../20260325_01_Enson/01_alter_stock_take.sql | 8 +++ 7 files changed, 98 insertions(+), 22 deletions(-) create mode 100644 src/main/java/com/ffii/fpsms/modules/master/web/ProcessController.kt create mode 100644 src/main/resources/db/changelog/changes/20260325_01_Enson/01_alter_stock_take.sql diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt index 0a879c0..a5e7efc 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt @@ -751,13 +751,15 @@ open class DeliveryOrderService( } 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 + .filter { it.deleted != true } // 只跳过 deleted=true(deleted 为空时也当作未删) + .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, @@ -1443,7 +1445,9 @@ open class DeliveryOrderService( deliveryOrderRepository.save(deliveryOrder) // 创建 pick order - val pols = deliveryOrder.deliveryOrderLines.map { + val pols = deliveryOrder.deliveryOrderLines + .filter { it.deleted != true } // 只跳过 deleted=true(deleted 为空时也当作未删) + .map { SavePickOrderLineRequest( itemId = it.item?.id, qty = it.qty ?: BigDecimal.ZERO, diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/ProcessRepository.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/ProcessRepository.kt index 9b3c912..f91789b 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/ProcessRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/ProcessRepository.kt @@ -8,4 +8,5 @@ import java.util.* interface ProcessRepository: AbstractRepository { fun findByCodeAndDeletedIsFalse(code: String): Process? fun findByNameAndDeletedIsFalse(name: String): Process? + fun findAllByDeletedFalse(): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/ProcessController.kt b/src/main/java/com/ffii/fpsms/modules/master/web/ProcessController.kt new file mode 100644 index 0000000..8502506 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/master/web/ProcessController.kt @@ -0,0 +1,17 @@ +package com.ffii.fpsms.modules.master.web + +import com.ffii.fpsms.modules.master.entity.Process +import com.ffii.fpsms.modules.master.entity.ProcessRepository +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/Process") +class ProcessController( + private val processRepository: ProcessRepository, +) { + /** All non-deleted master processes (for dropdowns, e.g. distinct codes on the client). */ + @GetMapping + fun allProcesses(): List = processRepository.findAllByDeletedFalse() +} diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeRecord.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeRecord.kt index 7e6608e..0ac40a2 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeRecord.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeRecord.kt @@ -77,7 +77,9 @@ open class StockTakeRecord : BaseEntity() { @Column(name = "stockTakeEndTime") open var stockTakeEndTime: LocalDateTime? = null - + + @Column(name = "approverTime") + open var approverTime: LocalDateTime? = null @Column(name = "date", nullable = false) open var date: LocalDate? = null diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt index f25a7d9..b583270 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt @@ -366,6 +366,8 @@ class StockTakeRecordService( stockTakeSection = stockTakeRecord?.stockTakeSection ?: warehouse?.stockTakeSection, stockTakeSectionDescription = warehouse?.stockTakeSectionDescription, stockTakerName = stockTakeRecord?.stockTakerName, + stockTakeEndTime = stockTakeRecord?.stockTakeEndTime, + approverTime = stockTakeRecord?.approverTime, ) } @@ -380,16 +382,14 @@ class StockTakeRecordService( } } + // Pending / Approved 只看「審核是否完成」,不要用 stockTakeRecord.status == "completed": + // picker 在 inQty==outQty 時也會寫 status=completed,會被誤判成已審核而只出現在 Approved 分頁。 val approvalFilteredResults = when (approvalView?.lowercase()) { "approved" -> filteredResults.filter { response -> - response.stockTakeRecordStatus == "completed" || - response.approverQty != null || - response.finalQty != null + response.approverQty != null || response.finalQty != null } "pending" -> filteredResults.filter { response -> - !(response.stockTakeRecordStatus == "completed" || - response.approverQty != null || - response.finalQty != null) + response.approverQty == null && response.finalQty == null } else -> filteredResults } @@ -683,6 +683,11 @@ class StockTakeRecordService( approverBadQty = stockTakeRecord?.approverBadQty, finalQty = stockTakeLine?.finalQty, bookQty = stockTakeRecord?.bookQty, + stockTakeSection = stockTakeRecord?.stockTakeSection ?: warehouse?.stockTakeSection, + stockTakeSectionDescription = warehouse?.stockTakeSectionDescription, + stockTakerName = stockTakeRecord?.stockTakerName, + stockTakeEndTime = stockTakeRecord?.stockTakeEndTime, + approverTime = stockTakeRecord?.approverTime, ) } @@ -1099,14 +1104,18 @@ return RecordsRes(paginatedResult, filteredResults.size) this.approverBadQty = finalBadQty this.varianceQty = varianceQty this.status = "completed" + this.approverTime = java.time.LocalDateTime.now() + // stockTakeEndTime 目前只在 saveStockTakeRecord「第二次盤點」時寫入;只做第一次盤點時會一直是 null。 + // 審核通過時若仍為空,補上時間,讓列表「審核/完成時間」有值(不覆寫已有第二次盤點結束時間)。 + if (this.stockTakeEndTime == null) { + this.stockTakeEndTime = java.time.LocalDateTime.now() + } } val savedRecord = stockTakeRecordRepository.save(stockTakeRecord) - // ------- 取当前库存行 / 库存信息 ------- - val inventoryLotLine = inventoryLotLineRepository.findByIdAndDeletedIsFalse( - stockTakeRecord.inventoryLotId ?: throw IllegalArgumentException("Inventory lot ID not found") - ) ?: throw IllegalArgumentException("Inventory lot line not found") + // stockTakeRecord.inventoryLotId 是 inventory_lot 主鍵,不是 inventory_lot_line 主鍵;必須用倉庫+批次定位庫存行 + val inventoryLotLine = resolveInventoryLotLineForStockTakeRecord(savedRecord) val inventoryLot = inventoryLotRepository.findByIdAndDeletedFalse( inventoryLotLine.inventoryLot?.id ?: throw IllegalArgumentException("Inventory lot ID not found") @@ -1209,6 +1218,10 @@ open fun batchSaveApproverStockTakeRecords( this.approverBadQty = badQty this.varianceQty = varianceQty this.status = "completed" + this.approverTime = java.time.LocalDateTime.now() + if (this.stockTakeEndTime == null) { + this.stockTakeEndTime = java.time.LocalDateTime.now() + } } stockTakeRecordRepository.save(record) @@ -1321,6 +1334,10 @@ open fun batchSaveApproverStockTakeRecordsAll( this.approverBadQty = badQty this.varianceQty = varianceQty this.status = "completed" + this.approverTime = java.time.LocalDateTime.now() + if (this.stockTakeEndTime == null) { + this.stockTakeEndTime = java.time.LocalDateTime.now() + } } stockTakeRecordRepository.save(record) @@ -1362,6 +1379,26 @@ open fun batchSaveApproverStockTakeRecordsAll( ) } +/** + * stockTakeRecord 上存的是 inventory_lot.id(批次),不是 inventory_lot_line.id;用倉庫 + 批次找唯一庫存行。 + */ +private fun resolveInventoryLotLineForStockTakeRecord(record: StockTakeRecord): InventoryLotLine { + val warehouseId = record.warehouse?.id + ?: throw IllegalArgumentException("Warehouse not found on stock take record") + val lotId = record.inventoryLotId ?: record.lotId + ?: throw IllegalArgumentException("Inventory lot ID not found on stock take record") + val lines = inventoryLotLineRepository.findAllByWarehouseIdInAndInventoryLotIdInAndDeletedIsFalse( + listOf(warehouseId), + listOf(lotId) + ) + if (lines.isEmpty()) { + throw IllegalArgumentException( + "Inventory lot line not found for warehouseId=$warehouseId inventoryLotId=$lotId" + ) + } + return lines.maxByOrNull { it.id ?: 0L }!! +} + /** * 根据 variance 调整库存并创建 Stock Ledger。 * 当 variance != 0 时:创建 StockTakeLine,并根据 variance 正负创建 StockIn/StockOut 及 Ledger。 @@ -1375,9 +1412,7 @@ private fun applyVarianceAdjustment( ) { if (varianceQty == BigDecimal.ZERO) return - val inventoryLotLine = inventoryLotLineRepository.findByIdAndDeletedIsFalse( - stockTakeRecord.inventoryLotId ?: throw IllegalArgumentException("Inventory lot ID not found") - ) ?: throw IllegalArgumentException("Inventory lot line not found") + val inventoryLotLine = resolveInventoryLotLineForStockTakeRecord(stockTakeRecord) val inventoryLot = inventoryLotRepository.findByIdAndDeletedFalse( inventoryLotLine.inventoryLot?.id ?: throw IllegalArgumentException("Inventory lot ID not found") @@ -1625,6 +1660,11 @@ open fun getInventoryLotDetailsByStockTakeSectionNotMatch( approverBadQty = stockTakeRecord.approverBadQty, finalQty = stockTakeLine?.finalQty, bookQty = stockTakeRecord.bookQty, + stockTakeSection = stockTakeRecord.stockTakeSection ?: warehouse?.stockTakeSection, + stockTakeSectionDescription = warehouse?.stockTakeSectionDescription, + stockTakerName = stockTakeRecord.stockTakerName, + stockTakeEndTime = stockTakeRecord.stockTakeEndTime, + approverTime = stockTakeRecord.approverTime, ) } diff --git a/src/main/java/com/ffii/fpsms/modules/stock/web/model/StockTakeRecordReponse.kt b/src/main/java/com/ffii/fpsms/modules/stock/web/model/StockTakeRecordReponse.kt index c549ecc..49c4664 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/web/model/StockTakeRecordReponse.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/web/model/StockTakeRecordReponse.kt @@ -64,6 +64,10 @@ data class InventoryLotDetailResponse( val stockTakeSection: String? = null, val stockTakeSectionDescription: String? = null, val stockTakerName: String? = null, + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + val stockTakeEndTime: LocalDateTime? = null, + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + val approverTime: LocalDateTime? = null, ) data class InventoryLotLineListRequest( val warehouseCode: String diff --git a/src/main/resources/db/changelog/changes/20260325_01_Enson/01_alter_stock_take.sql b/src/main/resources/db/changelog/changes/20260325_01_Enson/01_alter_stock_take.sql new file mode 100644 index 0000000..f7a3b22 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20260325_01_Enson/01_alter_stock_take.sql @@ -0,0 +1,8 @@ +-- liquibase formatted sql +-- changeset Enson:alter_stock_take_approver_time + +ALTER TABLE `fpsmsdb`.`stocktakerecord` +ADD COLUMN `approverTime` DATETIME NULL AFTER `stockTakeEndTime`; + + +