lot expiry after create round before appove tonigh lot will expiry can not do expiry handle ,block while stock taking relatestockTakeUpdate
| @@ -582,7 +582,10 @@ open class SchedulerService( | |||||
| open fun markExpiredInventoryLotLinesAsUnavailable() { | open fun markExpiredInventoryLotLinesAsUnavailable() { | ||||
| try { | try { | ||||
| val updatedRows = inventoryLotLineService.markExpiredLotLinesAsUnavailable() | val updatedRows = inventoryLotLineService.markExpiredLotLinesAsUnavailable() | ||||
| logger.info("Scheduler - Inventory lot expiry update done, rows updated=$updatedRows") | |||||
| logger.info( | |||||
| "Scheduler - Inventory lot expiry update done, rows updated=$updatedRows " + | |||||
| "(also soft-deletes open stock-take records for those lots)" | |||||
| ) | |||||
| } catch (e: Exception) { | } catch (e: Exception) { | ||||
| logger.error("Scheduler - Inventory lot expiry update failed: ${e.message}", e) | logger.error("Scheduler - Inventory lot expiry update failed: ${e.message}", e) | ||||
| } | } | ||||
| @@ -52,6 +52,7 @@ import com.ffii.fpsms.modules.stock.entity.SuggestPickLotRepository | |||||
| import org.springframework.beans.factory.annotation.Value | import org.springframework.beans.factory.annotation.Value | ||||
| import com.ffii.fpsms.modules.stock.web.model.BatchStockOutRequest | import com.ffii.fpsms.modules.stock.web.model.BatchStockOutRequest | ||||
| import com.ffii.fpsms.modules.stock.web.model.BatchStockOutLineRequest | import com.ffii.fpsms.modules.stock.web.model.BatchStockOutLineRequest | ||||
| import com.ffii.fpsms.modules.stock.entity.StockTakeRecordRepository | |||||
| @Service | @Service | ||||
| open class PickExecutionIssueService( | open class PickExecutionIssueService( | ||||
| private val pickExecutionIssueRepository: PickExecutionIssueRepository, | private val pickExecutionIssueRepository: PickExecutionIssueRepository, | ||||
| @@ -72,7 +73,8 @@ open class PickExecutionIssueService( | |||||
| private val stockLedgerRepository: StockLedgerRepository, | private val stockLedgerRepository: StockLedgerRepository, | ||||
| private val deliveryOrderRepository: DeliveryOrderRepository, | private val deliveryOrderRepository: DeliveryOrderRepository, | ||||
| private val suggestedPickLotRepository: SuggestPickLotRepository, | private val suggestedPickLotRepository: SuggestPickLotRepository, | ||||
| private val doPickOrderRepository: DoPickOrderRepository | |||||
| private val doPickOrderRepository: DoPickOrderRepository, | |||||
| private val stockTakeRecordRepository: StockTakeRecordRepository, | |||||
| ) { | ) { | ||||
| // 配置属性:是否在批次拒绝时自动重新建议批次(默认为 false,不自动重新建议) | // 配置属性:是否在批次拒绝时自动重新建议批次(默认为 false,不自动重新建议) | ||||
| @@ -1833,11 +1835,30 @@ open fun getExpiryItemList( | |||||
| itemName = normalizedItemName, | itemName = normalizedItemName, | ||||
| lotNo = normalizedLotNo, | lotNo = normalizedLotNo, | ||||
| ) | ) | ||||
| val openStockTakePairs = openStockTakeLotWarehousePairs( | |||||
| lotLines.mapNotNull { it.inventoryLot?.id } | |||||
| ) | |||||
| return lotLines.map { lotLine -> | return lotLines.map { lotLine -> | ||||
| val lot = lotLine.inventoryLot | val lot = lotLine.inventoryLot | ||||
| val item = lot?.item // Get item from inventoryLot | val item = lot?.item // Get item from inventoryLot | ||||
| val expiry = lot?.expiryDate | val expiry = lot?.expiryDate | ||||
| val dueOrPast = expiry != null && !expiry.isAfter(today) | |||||
| val expiresToday = expiry != null && expiry.isEqual(today) | |||||
| val lotId = lot?.id | |||||
| val warehouseId = lotLine.warehouse?.id | |||||
| val stockTakeBlocked = expiresToday && | |||||
| lotId != null && | |||||
| warehouseId != null && | |||||
| (lotId to warehouseId) in openStockTakePairs | |||||
| val canHandle = dueOrPast && !stockTakeBlocked | |||||
| val blockKey = when { | |||||
| canHandle -> null | |||||
| expiry == null || expiry.isAfter(today) -> "FUTURE" | |||||
| stockTakeBlocked -> "STOCK_TAKE_OPEN" | |||||
| else -> null | |||||
| } | |||||
| ExpiryItemResponse( | ExpiryItemResponse( | ||||
| id = lotLine.id ?: 0L, | id = lotLine.id ?: 0L, | ||||
| itemId = item?.id ?: 0L, | itemId = item?.id ?: 0L, | ||||
| @@ -1849,11 +1870,36 @@ open fun getExpiryItemList( | |||||
| expiryDate = expiry, | expiryDate = expiry, | ||||
| remainingQty = (lotLine.inQty ?: BigDecimal.ZERO).subtract(lotLine.outQty ?: BigDecimal.ZERO), | remainingQty = (lotLine.inQty ?: BigDecimal.ZERO).subtract(lotLine.outQty ?: BigDecimal.ZERO), | ||||
| uomDesc = lotLine.stockUom?.uom?.udfudesc, | uomDesc = lotLine.stockUom?.uom?.udfudesc, | ||||
| canHandle = expiry != null && !expiry.isAfter(today), | |||||
| canHandle = canHandle, | |||||
| canHandleBlockKey = blockKey, | |||||
| ) | ) | ||||
| } | } | ||||
| } | } | ||||
| /** Open-round stock take records keyed by (inventoryLotId, warehouseId). */ | |||||
| private fun openStockTakeLotWarehousePairs(lotIds: Collection<Long>): Set<Pair<Long, Long>> { | |||||
| val ids = lotIds.filter { it > 0L }.distinct() | |||||
| if (ids.isEmpty()) return emptySet() | |||||
| return stockTakeRecordRepository.findAllByLotIdInAndDeletedIsFalse(ids) | |||||
| .mapNotNull { rec -> | |||||
| val header = rec.stockTake ?: return@mapNotNull null | |||||
| if (header.deleted || header.status?.isOpen() != true) return@mapNotNull null | |||||
| val lotId = rec.lotId ?: return@mapNotNull null | |||||
| val warehouseId = rec.warehouse?.id ?: return@mapNotNull null | |||||
| lotId to warehouseId | |||||
| } | |||||
| .toSet() | |||||
| } | |||||
| /** 今日到期且仍在 open 盤點中 → 不可過期處置(已過期仍可處置)。 */ | |||||
| private fun isExpiresTodayBlockedByOpenStockTake(lotLine: InventoryLotLine, today: LocalDate = LocalDate.now()): Boolean { | |||||
| val expiry = lotLine.inventoryLot?.expiryDate ?: return false | |||||
| if (!expiry.isEqual(today)) return false | |||||
| val lotId = lotLine.inventoryLot?.id ?: return false | |||||
| val warehouseId = lotLine.warehouse?.id ?: return false | |||||
| return (lotId to warehouseId) in openStockTakeLotWarehousePairs(listOf(lotId)) | |||||
| } | |||||
| @Transactional(rollbackFor = [Exception::class]) | @Transactional(rollbackFor = [Exception::class]) | ||||
| open fun submitMissItem(request: SubmitIssueRequest): MessageResponse { | open fun submitMissItem(request: SubmitIssueRequest): MessageResponse { | ||||
| try { | try { | ||||
| @@ -2030,6 +2076,17 @@ open fun submitExpiryItem(request: SubmitExpiryRequest): MessageResponse { | |||||
| errorPosition = null | errorPosition = null | ||||
| ) | ) | ||||
| } | } | ||||
| if (isExpiresTodayBlockedByOpenStockTake(lotLine, today)) { | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "STOCK_TAKE_OPEN", | |||||
| type = "stock_issue", | |||||
| message = "Expires today and is in an open stock take; cannot dispose until stock take finishes or the lot leaves the round", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| val inQty = lotLine.inQty ?: BigDecimal.ZERO | val inQty = lotLine.inQty ?: BigDecimal.ZERO | ||||
| val outQty = lotLine.outQty ?: BigDecimal.ZERO | val outQty = lotLine.outQty ?: BigDecimal.ZERO | ||||
| @@ -2214,19 +2271,21 @@ open fun batchSubmitBadItem(request: BatchSubmitIssueRequest): MessageResponse { | |||||
| @Transactional(rollbackFor = [Exception::class]) | @Transactional(rollbackFor = [Exception::class]) | ||||
| open fun batchSubmitExpiryItem(request: BatchSubmitExpiryRequest): MessageResponse { | open fun batchSubmitExpiryItem(request: BatchSubmitExpiryRequest): MessageResponse { | ||||
| try { | try { | ||||
| val today = LocalDate.now() | |||||
| val lotLines = inventoryLotLineRepository.findAllById(request.lotLineIds) | val lotLines = inventoryLotLineRepository.findAllById(request.lotLineIds) | ||||
| .filter { | .filter { | ||||
| val lot = it.inventoryLot | val lot = it.inventoryLot | ||||
| val today = LocalDate.now() | |||||
| lot?.expiryDate != null && | lot?.expiryDate != null && | ||||
| lot.expiryDate!!.let { !it.isAfter(today) } && | |||||
| (it.inQty ?: BigDecimal.ZERO) > (it.outQty ?: BigDecimal.ZERO) | |||||
| lot.expiryDate!!.let { d -> !d.isAfter(today) } && | |||||
| (it.inQty ?: BigDecimal.ZERO) > (it.outQty ?: BigDecimal.ZERO) && | |||||
| !isExpiresTodayBlockedByOpenStockTake(it, today) | |||||
| } | } | ||||
| if (lotLines.isEmpty()) { | if (lotLines.isEmpty()) { | ||||
| return MessageResponse( | return MessageResponse( | ||||
| id = null, name = "Error", code = "EMPTY", type = "stock_issue", | id = null, name = "Error", code = "EMPTY", type = "stock_issue", | ||||
| message = "No valid expiry items to submit", errorPosition = null | |||||
| message = "No valid expiry items to submit (today-expiry lots in open stock take are blocked)", | |||||
| errorPosition = null | |||||
| ) | ) | ||||
| } | } | ||||
| @@ -32,8 +32,12 @@ data class ExpiryItemResponse( | |||||
| val expiryDate: LocalDate?, | val expiryDate: LocalDate?, | ||||
| val remainingQty: BigDecimal, | val remainingQty: BigDecimal, | ||||
| val uomDesc: String?, | val uomDesc: String?, | ||||
| /** True when expiryDate is today or earlier. */ | |||||
| /** True when expiryDate is today or earlier, and not blocked by open stock take (today-expiry). */ | |||||
| val canHandle: Boolean, | val canHandle: Boolean, | ||||
| /** | |||||
| * When [canHandle] is false: `FUTURE` (not yet due) or `STOCK_TAKE_OPEN` (expires today but in open stock take). | |||||
| */ | |||||
| val canHandleBlockKey: String? = null, | |||||
| ) | ) | ||||
| data class LotIssueDetailRequest( | data class LotIssueDetailRequest( | ||||
| val lotId: Long, | val lotId: Long, | ||||
| @@ -287,6 +287,30 @@ WHERE ill.id = :id | |||||
| @Param("storeIds") storeIds: List<String> | @Param("storeIds") storeIds: List<String> | ||||
| ): List<InventoryLotLine> | ): List<InventoryLotLine> | ||||
| /** | |||||
| * Lot lines that the daily expiry job will mark unavailable | |||||
| * ([InventoryLot.expiryDate] < today and status not yet unavailable). | |||||
| */ | |||||
| @EntityGraph( | |||||
| type = EntityGraph.EntityGraphType.FETCH, | |||||
| attributePaths = ["inventoryLot", "warehouse"] | |||||
| ) | |||||
| @Query( | |||||
| """ | |||||
| SELECT ill FROM InventoryLotLine ill | |||||
| JOIN ill.inventoryLot il | |||||
| WHERE ill.deleted = false | |||||
| AND il.deleted = false | |||||
| AND il.expiryDate IS NOT NULL | |||||
| AND il.expiryDate < :today | |||||
| AND (ill.status IS NULL OR ill.status <> :unavailable) | |||||
| """ | |||||
| ) | |||||
| fun findExpiredLotLinesNotYetUnavailable( | |||||
| @Param("today") today: LocalDate, | |||||
| @Param("unavailable") unavailable: InventoryLotLineStatus, | |||||
| ): List<InventoryLotLine> | |||||
| @Modifying | @Modifying | ||||
| @Transactional | @Transactional | ||||
| @Query( | @Query( | ||||
| @@ -19,6 +19,8 @@ interface StockTakeRecordRepository : AbstractRepository<StockTakeRecord, Long> | |||||
| warehouseIds: Collection<Long> | warehouseIds: Collection<Long> | ||||
| ): List<StockTakeRecord> | ): List<StockTakeRecord> | ||||
| fun findAllByLotIdInAndDeletedIsFalse(lotIds: Collection<Long>): List<StockTakeRecord> | |||||
| @Query( | @Query( | ||||
| """ | """ | ||||
| @@ -64,10 +64,25 @@ open class InventoryLotLineService( | |||||
| @Lazy | @Lazy | ||||
| private val jobOrderService: JobOrderService, | private val jobOrderService: JobOrderService, | ||||
| private val stockTakeLocationGuard: StockTakeLocationGuard, | private val stockTakeLocationGuard: StockTakeLocationGuard, | ||||
| @Lazy | |||||
| private val stockTakeRecordService: StockTakeRecordService, | |||||
| ) { | ) { | ||||
| /** | |||||
| * Daily job: mark expired lot lines UNAVAILABLE, then drop matching open stock-take | |||||
| * placeholders so picker/approver and section status no longer see those lots. | |||||
| */ | |||||
| @Transactional | @Transactional | ||||
| open fun markExpiredLotLinesAsUnavailable(): Int { | open fun markExpiredLotLinesAsUnavailable(): Int { | ||||
| return inventoryLotLineRepository.markExpiredLotLinesAsUnavailable() | |||||
| val today = LocalDate.now() | |||||
| val candidates = inventoryLotLineRepository.findExpiredLotLinesNotYetUnavailable( | |||||
| today, | |||||
| InventoryLotLineStatus.UNAVAILABLE, | |||||
| ) | |||||
| val updatedRows = inventoryLotLineRepository.markExpiredLotLinesAsUnavailable() | |||||
| if (candidates.isNotEmpty()) { | |||||
| stockTakeRecordService.softDeleteOpenStockTakeRecordsForExpiredLotLines(candidates) | |||||
| } | |||||
| return updatedRows | |||||
| } | } | ||||
| open fun findById(id: Long): Optional<InventoryLotLine> { | open fun findById(id: Long): Optional<InventoryLotLine> { | ||||
| @@ -496,8 +496,17 @@ open class StockTakeRecordService( | |||||
| return@forEach | return@forEach | ||||
| } | } | ||||
| // 修復:曾用整輪代表 id 升錯單頭時,本區明細已全 completed 卻仍停在 approving | |||||
| val status = if (latestStockTake != null) { | val status = if (latestStockTake != null) { | ||||
| latestStockTake.status?.value ?: "pending" | |||||
| val headerId = latestStockTake.id | |||||
| if (headerId != null && latestStockTake.status == StockTakeStatus.APPROVING) { | |||||
| checkAndUpdateStockTakeStatus(headerId, stockTakeSection) | |||||
| stockTakeRepository.findByIdAndDeletedIsFalse(headerId)?.status?.value | |||||
| ?: latestStockTake.status?.value | |||||
| ?: "pending" | |||||
| } else { | |||||
| latestStockTake.status?.value ?: "pending" | |||||
| } | |||||
| } else { | } else { | ||||
| "no_cycle" | "no_cycle" | ||||
| } | } | ||||
| @@ -1186,6 +1195,61 @@ open class StockTakeRecordService( | |||||
| .sorted() | .sorted() | ||||
| } | } | ||||
| /** | |||||
| * 排程將過期 lot line 標成 UNAVAILABLE 後呼叫:對仍 open 的盤點輪次, | |||||
| * soft-delete 對應且尚未 completed 的 [StockTakeRecord]/[StockTakeLine], | |||||
| * 再重算受影響區域單頭狀態。 | |||||
| */ | |||||
| @Transactional | |||||
| open fun softDeleteOpenStockTakeRecordsForExpiredLotLines(lotLines: List<InventoryLotLine>): Int { | |||||
| if (lotLines.isEmpty()) return 0 | |||||
| val pairs = lotLines.mapNotNull { ill -> | |||||
| val lotId = ill.inventoryLot?.id ?: return@mapNotNull null | |||||
| val warehouseId = ill.warehouse?.id ?: return@mapNotNull null | |||||
| lotId to warehouseId | |||||
| }.toSet() | |||||
| if (pairs.isEmpty()) return 0 | |||||
| val lotIds = pairs.map { it.first }.distinct() | |||||
| val toRemove = stockTakeRecordRepository.findAllByLotIdInAndDeletedIsFalse(lotIds).filter { rec -> | |||||
| val lotId = rec.lotId ?: return@filter false | |||||
| val warehouseId = rec.warehouse?.id ?: return@filter false | |||||
| if ((lotId to warehouseId) !in pairs) return@filter false | |||||
| if (rec.status.equals("completed", ignoreCase = true)) return@filter false | |||||
| val header = rec.stockTake ?: return@filter false | |||||
| !header.deleted && header.status?.isOpen() == true | |||||
| } | |||||
| if (toRemove.isEmpty()) return 0 | |||||
| val recordIds = toRemove.mapNotNull { it.id } | |||||
| toRemove.forEach { it.deleted = true } | |||||
| stockTakeRecordRepository.saveAll(toRemove) | |||||
| if (recordIds.isNotEmpty()) { | |||||
| val lines = stockTakeLineRepository.findAllByStockTakeRecord_IdInAndDeletedIsFalse(recordIds) | |||||
| lines.forEach { it.deleted = true } | |||||
| if (lines.isNotEmpty()) { | |||||
| stockTakeLineRepository.saveAll(lines) | |||||
| } | |||||
| } | |||||
| val headersToRefresh = toRemove.mapNotNull { rec -> | |||||
| val stId = rec.stockTake?.id ?: return@mapNotNull null | |||||
| val section = rec.stockTakeSection?.trim()?.takeIf { it.isNotBlank() } ?: return@mapNotNull null | |||||
| stId to section | |||||
| }.distinct() | |||||
| headersToRefresh.forEach { (stId, section) -> | |||||
| checkAndUpdateStockTakeStatus(stId, section) | |||||
| } | |||||
| logger.info( | |||||
| "softDeleteOpenStockTakeRecordsForExpiredLotLines: removed {} record(s), refreshed {} section header(s)", | |||||
| toRemove.size, | |||||
| headersToRefresh.size, | |||||
| ) | |||||
| return toRemove.size | |||||
| } | |||||
| open fun listStockTakeRoundCards(yearMonth: String?): StockTakeRoundCardsRes { | open fun listStockTakeRoundCards(yearMonth: String?): StockTakeRoundCardsRes { | ||||
| val all = stockTakeRepository.findAll().filter { !it.deleted } | val all = stockTakeRepository.findAll().filter { !it.deleted } | ||||
| if (all.isEmpty()) { | if (all.isEmpty()) { | ||||
| @@ -1843,6 +1907,13 @@ open class StockTakeRecordService( | |||||
| this.stockTakerName = user?.name | this.stockTakerName = user?.name | ||||
| this.stockTakeEndTime = java.time.LocalDateTime.now() | this.stockTakeEndTime = java.time.LocalDateTime.now() | ||||
| this.varianceQty = varianceQty | this.varianceQty = varianceQty | ||||
| // 防舊資料:重新盤點後不得沿用先前核准結果 | |||||
| this.approverId = null | |||||
| this.approverName = null | |||||
| this.approverStockTakeQty = null | |||||
| this.approverBadQty = null | |||||
| this.approverTime = null | |||||
| this.lastSelect = null | |||||
| } | } | ||||
| existingRecord | existingRecord | ||||
| } | } | ||||
| @@ -2098,10 +2169,14 @@ open class StockTakeRecordService( | |||||
| /** | /** | ||||
| * FP-MTMS Version Checklist | Functions Ref. No. 70 | v1.0.0 | 2026-08-31 | * FP-MTMS Version Checklist | Functions Ref. No. 70 | v1.0.0 | 2026-08-31 | ||||
| * | |||||
| * 依 [stockTakeSection] 在同一輪次內解析「該區域」單頭再升狀態。 | |||||
| * 不可直接信任傳入的 stockTakeId:Overall 審核會傳整輪代表 id,若寫錯單頭會造成 | |||||
| * 本區已全數 completed 卻仍停在 approving(狀態寫到別區)。 | |||||
| */ | */ | ||||
| open fun checkAndUpdateStockTakeStatus(stockTakeId: Long, stockTakeSection: String): Map<String, Any> { | open fun checkAndUpdateStockTakeStatus(stockTakeId: Long, stockTakeSection: String): Map<String, Any> { | ||||
| try { | try { | ||||
| val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(stockTakeId) | |||||
| val stockTakeHint = stockTakeRepository.findByIdAndDeletedIsFalse(stockTakeId) | |||||
| ?: return mapOf("success" to false, "message" to "Stock take not found") | ?: return mapOf("success" to false, "message" to "Stock take not found") | ||||
| // 1. 获取该 section 下的所有 warehouse | // 1. 获取该 section 下的所有 warehouse | ||||
| @@ -2112,7 +2187,21 @@ open class StockTakeRecordService( | |||||
| val warehouseIds = warehouses.mapNotNull { it.id }.toSet() | val warehouseIds = warehouses.mapNotNull { it.id }.toSet() | ||||
| val roundId = stockTake.stockTakeRoundId ?: stockTake.id | |||||
| val roundId = stockTakeHint.stockTakeRoundId ?: stockTakeHint.id | |||||
| ?: return mapOf("success" to false, "message" to "Stock take id is null") | |||||
| val sectionHeaders = stockTakeRepository.findAllByStockTakeRoundIdAndDeletedIsFalse(roundId) | |||||
| .filter { !it.deleted && it.stockTakeSection == stockTakeSection } | |||||
| val stockTake = sectionHeaders.maxWithOrNull( | |||||
| compareBy<StockTake> { it.actualStart ?: it.planStart ?: LocalDateTime.MIN } | |||||
| .thenBy { it.id ?: 0L } | |||||
| ) ?: stockTakeHint.takeIf { it.stockTakeSection == stockTakeSection } | |||||
| ?: return mapOf( | |||||
| "success" to false, | |||||
| "message" to "Stock take header not found for section $stockTakeSection in round $roundId" | |||||
| ) | |||||
| val sectionHeaderId = stockTake.id | |||||
| ?: return mapOf("success" to false, "message" to "Stock take id is null") | ?: return mapOf("success" to false, "message" to "Stock take id is null") | ||||
| var stockTakeRecords = stockTakeRecordRepository.findAllByStockTakeRoundIdAndDeletedIsFalse(roundId) | var stockTakeRecords = stockTakeRecordRepository.findAllByStockTakeRoundIdAndDeletedIsFalse(roundId) | ||||
| @@ -2126,7 +2215,7 @@ open class StockTakeRecordService( | |||||
| stockTakeRecords = stockTakeRecordRepository.findAll() | stockTakeRecords = stockTakeRecordRepository.findAll() | ||||
| .filter { | .filter { | ||||
| !it.deleted && | !it.deleted && | ||||
| it.stockTake?.id == stockTakeId && | |||||
| it.stockTake?.id == sectionHeaderId && | |||||
| it.warehouse?.id in warehouseIds && | it.warehouse?.id in warehouseIds && | ||||
| it.stockTakeSection == stockTakeSection | it.stockTakeSection == stockTakeSection | ||||
| } | } | ||||
| @@ -2144,7 +2233,10 @@ open class StockTakeRecordService( | |||||
| stockTake.planEnd = now | stockTake.planEnd = now | ||||
| stockTake.actualEnd = now | stockTake.actualEnd = now | ||||
| stockTakeRepository.save(stockTake) | stockTakeRepository.save(stockTake) | ||||
| println("Stock take $stockTakeId status updated to COMPLETED - all records are completed") | |||||
| println( | |||||
| "Stock take $sectionHeaderId (section=$stockTakeSection, hintId=$stockTakeId) " + | |||||
| "status updated to COMPLETED - all records are completed" | |||||
| ) | |||||
| return mapOf( | return mapOf( | ||||
| "success" to true, | "success" to true, | ||||
| "message" to "Stock take status updated to COMPLETED", | "message" to "Stock take status updated to COMPLETED", | ||||
| @@ -2155,7 +2247,10 @@ open class StockTakeRecordService( | |||||
| stockTake.actualEnd = now | stockTake.actualEnd = now | ||||
| stockTakeRepository.save(stockTake) | stockTakeRepository.save(stockTake) | ||||
| println("Stock take $stockTakeId status updated to APPROVING - all records are pass") | |||||
| println( | |||||
| "Stock take $sectionHeaderId (section=$stockTakeSection, hintId=$stockTakeId) " + | |||||
| "status updated to APPROVING - all records are pass" | |||||
| ) | |||||
| return mapOf( | return mapOf( | ||||
| "success" to true, | "success" to true, | ||||
| "message" to "Stock take status updated to APPROVING", | "message" to "Stock take status updated to APPROVING", | ||||
| @@ -2272,7 +2367,9 @@ open class StockTakeRecordService( | |||||
| } | } | ||||
| val stockTakeSection = savedRecord.stockTakeSection | val stockTakeSection = savedRecord.stockTakeSection | ||||
| if (stockTakeSection != null) { | if (stockTakeSection != null) { | ||||
| checkAndUpdateStockTakeStatus(stockTakeId, stockTakeSection) | |||||
| // 用明細所屬區域單頭 id(Overall 審核傳入的可能是整輪代表 id) | |||||
| val statusHeaderId = savedRecord.stockTake?.id ?: stockTakeId | |||||
| checkAndUpdateStockTakeStatus(statusHeaderId, stockTakeSection) | |||||
| } | } | ||||
| return savedRecord | return savedRecord | ||||
| @@ -3248,8 +3345,30 @@ open fun updateStockTakeRecordStatusToNotMatch(stockTakeRecordId: Long): StockTa | |||||
| stockTakeRecord.apply { | stockTakeRecord.apply { | ||||
| this.status = "notMatch" | this.status = "notMatch" | ||||
| // 重新盤點後必須再審核;清掉舊核准欄位,避免 UI 仍顯示在 Approved、單頭卻升不了 completed | |||||
| this.approverId = null | |||||
| this.approverName = null | |||||
| this.approverStockTakeQty = null | |||||
| this.approverBadQty = null | |||||
| this.approverTime = null | |||||
| this.lastSelect = null | |||||
| this.pickerSecondStockTakeQty = null | |||||
| this.pickerSecondBadQty = null | |||||
| this.stockTakeEndTime = null | |||||
| } | } | ||||
| val saved = stockTakeRecordRepository.save(stockTakeRecord) | val saved = stockTakeRecordRepository.save(stockTakeRecord) | ||||
| saved.id?.let { rid -> | |||||
| stockTakeLineRepository.findByStockTakeRecord_IdAndDeletedIsFalse(rid)?.let { line -> | |||||
| line.apply { | |||||
| this.finalQty = null | |||||
| this.status = StockTakeLineStatus.PENDING | |||||
| this.completeDate = null | |||||
| } | |||||
| stockTakeLineRepository.save(line) | |||||
| } | |||||
| } | |||||
| val stockTake = saved.stockTake | val stockTake = saved.stockTake | ||||
| if (stockTake != null && !stockTake.deleted) { | if (stockTake != null && !stockTake.deleted) { | ||||
| stockTake.status = StockTakeStatus.STOCKTAKING | stockTake.status = StockTakeStatus.STOCKTAKING | ||||