| @@ -2322,6 +2322,12 @@ ORDER BY it.code, wh.stockTakeSection, il.lotNo | |||
| } | |||
| @Transactional | |||
| /** Reject picker/approver writes when the stock_take header (or its round) is already closed. */ | |||
| private fun assertStockTakeWritable(stockTake: StockTake) { | |||
| if (stockTake.status?.isOpen() == true) return | |||
| throw IllegalArgumentException("Stock take is closed; cannot save") | |||
| } | |||
| open fun saveStockTakeRecord( | |||
| request: SaveStockTakeRecordRequest, | |||
| stockTakeId: Long, | |||
| @@ -2341,6 +2347,7 @@ ORDER BY it.code, wh.stockTakeSection, il.lotNo | |||
| ?: throw IllegalArgumentException("Warehouse not found") | |||
| val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(stockTakeId) | |||
| ?: throw IllegalArgumentException("Stock take not found: $stockTakeId") | |||
| assertStockTakeWritable(stockTake) | |||
| // 2. 计算 availableQty | |||
| val availableQty = (inventoryLotLine.inQty ?: BigDecimal.ZERO) | |||
| @@ -2356,10 +2363,7 @@ ORDER BY it.code, wh.stockTakeSection, il.lotNo | |||
| val existingRecord = stockTakeRecordRepository.findByIdAndDeletedIsFalse(request.stockTakeRecordId) | |||
| ?: throw IllegalArgumentException("Stock take record not found: ${request.stockTakeRecordId}") | |||
| val headerStatus = stockTake.status | |||
| val headerStillOpen = headerStatus == StockTakeStatus.PENDING || | |||
| headerStatus == StockTakeStatus.STOCKTAKING || | |||
| headerStatus == StockTakeStatus.APPROVING | |||
| val headerStillOpen = stockTake.status?.isOpen() == true | |||
| val revisingOpenPass = existingRecord.pickerFirstStockTakeQty != null && | |||
| existingRecord.pickerSecondStockTakeQty == null && | |||
| existingRecord.approverStockTakeQty == null && | |||
| @@ -2576,6 +2580,7 @@ ORDER BY it.code, wh.stockTakeSection, il.lotNo | |||
| // 1. 获取 stock take | |||
| val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(request.stockTakeId) | |||
| ?: throw IllegalArgumentException("Stock take not found: ${request.stockTakeId}") | |||
| assertStockTakeWritable(stockTake) | |||
| // 2. 使用 stockTakeSection 获取所有 inventory lot lines(类似 getInventoryLotDetailsByStockTakeSection) | |||
| val warehouses = warehouseRepository.findAllByStockTakeSectionAndDeletedIsFalse(request.stockTakeSection) | |||
| @@ -2869,6 +2874,11 @@ ORDER BY it.code, wh.stockTakeSection, il.lotNo | |||
| val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(stockTakeId) | |||
| ?: throw IllegalArgumentException("Stock take not found: $stockTakeId") | |||
| // Prefer the record's own header (Overall may pass a round representative id) | |||
| val writableHeader = stockTakeRecord.stockTake?.takeIf { it.id != null } | |||
| ?.let { st -> stockTakeRepository.findByIdAndDeletedIsFalse(st.id!!) ?: st } | |||
| ?: stockTake | |||
| assertStockTakeWritable(writableHeader) | |||
| val (finalQty, varianceQty) = applyApproverSaveToRecord( | |||
| stockTakeRecord, | |||
| @@ -2885,9 +2895,9 @@ ORDER BY it.code, wh.stockTakeSection, il.lotNo | |||
| // variance != 0:過帳並更新/新建 StockTakeLine;= 0:僅將開輪預建 line 標記完成(不查庫存) | |||
| if (varianceQty != BigDecimal.ZERO) { | |||
| applyVarianceAdjustment(stockTake, stockTakeRecord, finalQty!!, varianceQty, request.approverId) | |||
| applyVarianceAdjustment(writableHeader, stockTakeRecord, finalQty!!, varianceQty, request.approverId) | |||
| } else { | |||
| completeStockTakeLineForApproverNoVariance(stockTake, savedRecord, finalQty!!) | |||
| completeStockTakeLineForApproverNoVariance(writableHeader, savedRecord, finalQty!!) | |||
| } | |||
| val stockTakeSection = savedRecord.stockTakeSection | |||
| if (stockTakeSection != null) { | |||
| @@ -2908,7 +2918,7 @@ open fun batchSaveApproverStockTakeRecords( | |||
| // 1. Get stock take | |||
| val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(request.stockTakeId) | |||
| ?: throw IllegalArgumentException("Stock take not found: ${request.stockTakeId}") | |||
| assertStockTakeWritable(stockTake) | |||
| val stockTakeRecords = stockTakeRecordRepository | |||
| .findPendingApproverRecordsByStockTakeAndSection( | |||
| @@ -3029,6 +3039,7 @@ open fun batchSaveApproverStockTakeRecordsAll( | |||
| val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(request.stockTakeId) | |||
| ?: throw IllegalArgumentException("Stock take not found: ${request.stockTakeId}") | |||
| assertStockTakeWritable(stockTake) | |||
| val roundStockTakeIds: Set<Long> = resolveRoundStockTakeIds(stockTake) | |||
| @@ -3204,6 +3215,7 @@ open fun batchSaveApproverStockTakeRecordsByIds( | |||
| val user = userRepository.findById(request.approverId).orElse(null) | |||
| val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(request.stockTakeId) | |||
| ?: throw IllegalArgumentException("Stock take not found: ${request.stockTakeId}") | |||
| assertStockTakeWritable(stockTake) | |||
| val idSet = idsToLoad.toSet() | |||
| val stockTakeRecords = stockTakeRecordRepository.findAllById(idsToLoad) | |||