| @@ -42,4 +42,8 @@ open class StockTake: BaseEntity<Long>() { | |||||
| @Size(max = 255) | @Size(max = 255) | ||||
| @Column(name = "stockTakeSection", length = 255) | @Column(name = "stockTakeSection", length = 255) | ||||
| open var stockTakeSection: String? = null | open var stockTakeSection: String? = null | ||||
| /** 同一輪盤點(多 section 多筆 stock_take)共用此 id,通常等於該輪第一筆 stock_take 的主鍵 */ | |||||
| @Column(name = "stockTakeRoundId") | |||||
| open var stockTakeRoundId: Long? = null | |||||
| } | } | ||||
| @@ -7,7 +7,9 @@ import java.io.Serializable | |||||
| @Repository | @Repository | ||||
| interface StockTakeRepository : AbstractRepository<StockTake, Long> { | interface StockTakeRepository : AbstractRepository<StockTake, Long> { | ||||
| fun findByIdAndDeletedIsFalse(id: Serializable): StockTake; | |||||
| fun findByIdAndDeletedIsFalse(id: Serializable): StockTake | |||||
| fun findAllByStockTakeRoundIdAndDeletedIsFalse(stockTakeRoundId: Long): List<StockTake> | |||||
| @Query(""" | @Query(""" | ||||
| select st.code from StockTake st where st.code like :prefix% order by st.code desc limit 1 | select st.code from StockTake st where st.code like :prefix% order by st.code desc limit 1 | ||||
| @@ -64,6 +64,27 @@ class StockTakeRecordService( | |||||
| ) { | ) { | ||||
| private val logger: Logger = LoggerFactory.getLogger(StockTakeRecordService::class.java) | private val logger: Logger = LoggerFactory.getLogger(StockTakeRecordService::class.java) | ||||
| /** | |||||
| * 同一輪多 section 的 stock_take:優先用 [StockTake.stockTakeRoundId];舊資料為 null 時退回以 planStart 相同辨識一輪。 | |||||
| */ | |||||
| private fun resolveRoundStockTakeIds(baseStockTake: StockTake): Set<Long> { | |||||
| val roundKey = baseStockTake.stockTakeRoundId | |||||
| if (roundKey != null) { | |||||
| return stockTakeRepository.findAllByStockTakeRoundIdAndDeletedIsFalse(roundKey) | |||||
| .mapNotNull { it.id } | |||||
| .toSet() | |||||
| } | |||||
| val planStart = baseStockTake.planStart | |||||
| return if (planStart != null) { | |||||
| stockTakeRepository.findAll() | |||||
| .filter { !it.deleted && it.planStart == planStart } | |||||
| .mapNotNull { it.id } | |||||
| .toSet() | |||||
| } else { | |||||
| listOfNotNull(baseStockTake.id).toSet() | |||||
| } | |||||
| } | |||||
| open fun AllPickedStockTakeList(): List<AllPickedStockTakeListReponse> { | open fun AllPickedStockTakeList(): List<AllPickedStockTakeListReponse> { | ||||
| // 1. 获取所有不同的 stockTakeSection(从 warehouse 表) | // 1. 获取所有不同的 stockTakeSection(从 warehouse 表) | ||||
| val allWarehouses = warehouseRepository.findAllByDeletedIsFalse() | val allWarehouses = warehouseRepository.findAllByDeletedIsFalse() | ||||
| @@ -220,21 +241,11 @@ class StockTakeRecordService( | |||||
| val inventoryLotLines = inventoryLotLineRepository.findAllByWarehouseIdInAndDeletedIsFalse(warehouseIds) | val inventoryLotLines = inventoryLotLineRepository.findAllByWarehouseIdInAndDeletedIsFalse(warehouseIds) | ||||
| println("Found ${inventoryLotLines.size} inventory lot lines for ALL sections") | println("Found ${inventoryLotLines.size} inventory lot lines for ALL sections") | ||||
| // 3. 如果传了 stockTakeId,就把「同一轮」的所有 stockTake 找出来: | |||||
| // 以该 stockTake 的 planStart 作为一轮的标识,取 planStart 相同的所有记录 | |||||
| // 3. 如果传了 stockTakeId,就把「同一轮」的所有 stockTake 找出来(stockTakeRoundId,舊資料則 planStart) | |||||
| val roundStockTakeIds: Set<Long> = if (stockTakeId != null) { | val roundStockTakeIds: Set<Long> = if (stockTakeId != null) { | ||||
| val baseStockTake = stockTakeRepository.findByIdAndDeletedIsFalse(stockTakeId) | val baseStockTake = stockTakeRepository.findByIdAndDeletedIsFalse(stockTakeId) | ||||
| ?: throw IllegalArgumentException("Stock take not found: $stockTakeId") | ?: throw IllegalArgumentException("Stock take not found: $stockTakeId") | ||||
| val planStart = baseStockTake.planStart | |||||
| val roundStockTakes = if (planStart != null) { | |||||
| stockTakeRepository.findAll() | |||||
| .filter { !it.deleted && it.planStart == planStart } | |||||
| } else { | |||||
| listOf(baseStockTake) | |||||
| } | |||||
| roundStockTakes.mapNotNull { it.id }.toSet() | |||||
| resolveRoundStockTakeIds(baseStockTake) | |||||
| } else { | } else { | ||||
| emptySet() | emptySet() | ||||
| } | } | ||||
| @@ -1242,16 +1253,7 @@ open fun batchSaveApproverStockTakeRecordsAll( | |||||
| val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(request.stockTakeId) | val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(request.stockTakeId) | ||||
| ?: throw IllegalArgumentException("Stock take not found: ${request.stockTakeId}") | ?: throw IllegalArgumentException("Stock take not found: ${request.stockTakeId}") | ||||
| // 以该 stockTake 的 planStart 作为一轮的标识,找到这一轮下所有的 stockTake(各个 section) | |||||
| val planStart = stockTake.planStart | |||||
| val roundStockTakeIds: Set<Long> = if (planStart != null) { | |||||
| stockTakeRepository.findAll() | |||||
| .filter { !it.deleted && it.planStart == planStart } | |||||
| .mapNotNull { it.id } | |||||
| .toSet() | |||||
| } else { | |||||
| listOfNotNull(stockTake.id).toSet() | |||||
| } | |||||
| val roundStockTakeIds: Set<Long> = resolveRoundStockTakeIds(stockTake) | |||||
| val stockTakeRecords = stockTakeRecordRepository.findAll() | val stockTakeRecords = stockTakeRecordRepository.findAll() | ||||
| .filter { | .filter { | ||||
| @@ -60,8 +60,9 @@ class StockTakeService( | |||||
| status?.let { stockTake.status = it } | status?.let { stockTake.status = it } | ||||
| request.remarks?.let { stockTake.remarks = it } | request.remarks?.let { stockTake.remarks = it } | ||||
| request.stockTakeSection?.let { stockTake.stockTakeSection = it } // 添加此行 | request.stockTakeSection?.let { stockTake.stockTakeSection = it } // 添加此行 | ||||
| return stockTakeRepository.save(stockTake); | |||||
| request.stockTakeRoundId?.let { stockTake.stockTakeRoundId = it } | |||||
| return stockTakeRepository.save(stockTake) | |||||
| } | } | ||||
| // ---------------------------------------------- Import Excel ---------------------------------------------- // | // ---------------------------------------------- Import Excel ---------------------------------------------- // | ||||
| @@ -290,25 +291,31 @@ class StockTakeService( | |||||
| } | } | ||||
| */ | */ | ||||
| // 移除 null section 处理逻辑,因为 warehouse 表中没有 null 的 stockTakeSection | // 移除 null section 处理逻辑,因为 warehouse 表中没有 null 的 stockTakeSection | ||||
| val batchPlanStart = LocalDateTime.now() | |||||
| val batchPlanEnd = batchPlanStart.plusDays(1) | |||||
| var roundId: Long? = null | |||||
| distinctSections.forEach { section -> | distinctSections.forEach { section -> | ||||
| try { | try { | ||||
| val now = LocalDateTime.now() | |||||
| val code = assignStockTakeNo() | val code = assignStockTakeNo() | ||||
| val saveStockTakeReq = SaveStockTakeRequest( | val saveStockTakeReq = SaveStockTakeRequest( | ||||
| code = code, | code = code, | ||||
| planStart = now, | |||||
| planEnd = now.plusDays(1), | |||||
| planStart = batchPlanStart, | |||||
| planEnd = batchPlanEnd, | |||||
| actualStart = null, | actualStart = null, | ||||
| actualEnd = null, | actualEnd = null, | ||||
| status = StockTakeStatus.PENDING.value, | status = StockTakeStatus.PENDING.value, | ||||
| remarks = null, | remarks = null, | ||||
| stockTakeSection = section | |||||
| stockTakeSection = section, | |||||
| stockTakeRoundId = roundId | |||||
| ) | ) | ||||
| val savedStockTake = saveStockTake(saveStockTakeReq) | val savedStockTake = saveStockTake(saveStockTakeReq) | ||||
| if (roundId == null) { | |||||
| roundId = savedStockTake.id | |||||
| savedStockTake.stockTakeRoundId = roundId | |||||
| stockTakeRepository.save(savedStockTake) | |||||
| } | |||||
| result[section] = "Created: ${savedStockTake.code}" | result[section] = "Created: ${savedStockTake.code}" | ||||
| logger.info("Created stock take for section $section: ${savedStockTake.code}") | |||||
| logger.info("Created stock take for section $section: ${savedStockTake.code}, roundId=$roundId") | |||||
| } catch (e: Exception) { | } catch (e: Exception) { | ||||
| result[section] = "Error: ${e.message}" | result[section] = "Error: ${e.message}" | ||||
| logger.error("Error creating stock take for section $section: ${e.message}") | logger.error("Error creating stock take for section $section: ${e.message}") | ||||
| @@ -12,4 +12,5 @@ data class SaveStockTakeRequest( | |||||
| var status: String?, | var status: String?, | ||||
| val remarks: String?, | val remarks: String?, | ||||
| val stockTakeSection: String?=null, | val stockTakeSection: String?=null, | ||||
| val stockTakeRoundId: Long? = null, | |||||
| ) | ) | ||||
| @@ -0,0 +1,5 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset Enson:alter_stock_take_stock_take_round_id | |||||
| ALTER TABLE `fpsmsdb`.`stock_take` | |||||
| ADD COLUMN `stockTakeRoundId` INT NULL AFTER `stockTakeSection`; | |||||