diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt index ab20b40..e111e58 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt @@ -33,6 +33,7 @@ import java.util.UUID import java.util.Comparator import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository import com.ffii.fpsms.modules.productProcess.entity.ProductProcessRepository +import org.springframework.transaction.annotation.Transactional @Service open class BomService( @@ -158,6 +159,265 @@ open class BomService( return response } + /** + * Edit BOM (basic fields + BOM materials + BOM process lines). + * + * Key rules: + * 1) baseScore is server-calculated and cannot be edited by client. + * 2) Input existence will be validated (BOM/Item/Process/Equipment/UOM settings). + * 3) When `materials` or `processes` is provided, we rebuild BomProcessMaterial mappings + * using current BOM materials & process lines (default: link every material to every process line). + */ + @Transactional + open fun editBom(id: Long, request: EditBomRequest): BomDetailResponse { + val bom = bomRepository.findByIdAndDeletedIsFalse(id) + ?: throw NotFoundException() + + // ---- update basic/score fields (only when provided) ---- + request.description?.let { bom.description = it } + request.outputQty?.let { bom.outputQty = it } + request.outputQtyUom?.let { bom.outputQtyUom = it } + request.yield?.let { bom.yield = it } + + request.isDark?.let { bom.isDark = it } + request.isFloat?.let { bom.isFloat = it } + request.isDense?.let { bom.isDense = it } + request.scrapRate?.let { bom.scrapRate = it } + request.allergicSubstances?.let { bom.allergicSubstances = it } + request.timeSequence?.let { bom.timeSequence = it } + request.complexity?.let { bom.complexity = it } + request.isDrink?.let { bom.isDrink = it } + + val replaceMaterials = request.materials != null + val replaceProcesses = request.processes != null + + if (!replaceMaterials && !replaceProcesses) { + // Only basic/score fields changed: just recalc. + bom.baseScore = calculateBaseScore(bom) + bomRepository.saveAndFlush(bom) + return getBomDetail(bom.id!!) + } + + // ---- pre-clean BomProcessMaterial to avoid FK issues when clearing/recreating children ---- + fun deleteAllBomProcessMaterialsForCurrentProcesses() { + val processIds = bom.bomProcesses.mapNotNull { it.id } + if (processIds.isNotEmpty()) { + val existing = bomProcessMaterialRepository.findByBomProcess_IdIn(processIds) + if (existing.isNotEmpty()) { + bomProcessMaterialRepository.deleteAll(existing) + } + } + } + + if (replaceMaterials) { + deleteAllBomProcessMaterialsForCurrentProcesses() + bom.bomMaterials.clear() + request.materials?.forEach { mReq -> + val item = when { + mReq.itemId != null -> itemsRepository.findByIdAndDeletedFalse(mReq.itemId) ?: throw IllegalArgumentException("Item not found: id=${mReq.itemId}") + !mReq.itemCode.isNullOrBlank() -> itemsRepository.findByCodeAndDeletedFalse(mReq.itemCode.trim()) ?: throw IllegalArgumentException("Item not found: code=${mReq.itemCode}") + else -> throw IllegalArgumentException("Material requires itemId or itemCode") + } + + val baseItemUom = itemUomService.findBaseUnitByItemId(item.id!!) + ?: throw IllegalArgumentException("Item base unit not configured: itemId=${item.id}") + val stockItemUom = itemUomService.findStockUnitByItemId(item.id!!) + ?: throw IllegalArgumentException("Item stock unit not configured: itemId=${item.id}") + val salesItemUom = itemUomService.findSalesUnitByItemId(item.id!!) + ?: throw IllegalArgumentException("Item sales unit not configured: itemId=${item.id}") + + val baseUom = baseItemUom.uom ?: throw IllegalArgumentException("Base UOM conversion not configured: itemId=${item.id}") + val stockUom = stockItemUom.uom ?: throw IllegalArgumentException("Stock UOM conversion not configured: itemId=${item.id}") + val salesUom = salesItemUom.uom ?: throw IllegalArgumentException("Sales UOM conversion not configured: itemId=${item.id}") + + val baseQty = mReq.qty + + val stockQty = itemUomService.convertUomByItem( + ConvertUomByItemRequest( + itemId = item.id!!, + qty = baseQty, + uomId = baseUom.id, + targetUnit = "stockUnit" + ) + ).newQty + + val salesConverted = itemUomService.convertUomByItem( + ConvertUomByItemRequest( + itemId = item.id!!, + qty = baseQty, + uomId = baseUom.id, + targetUnit = "salesUnit" + ) + ) + val salesQty = salesConverted.newQty + val stockUnitName = stockItemUom.uom?.udfudesc + val baseUnitName = baseItemUom.uom?.udfudesc + + val salesUnitCode = salesConverted.udfudesc + + val bomMaterial = BomMaterial().apply { + this.item = item + this.itemName = item.name + this.isConsumable = mReq.isConsumable ?: false + + // BOM unit fields: derive from item's base unit. + this.qty = baseQty + this.uom = baseUom + this.uomName = baseUnitName + + // Derived UOMs for display/logic. + this.saleQty = salesQty + this.salesUnit = salesUom + this.salesUnitCode = salesUnitCode + + this.baseQty = baseQty + this.baseUnit = baseUom.id!!.toInt() + .let { Integer.valueOf(it) } as? Integer + this.baseUnitName = baseUnitName + + this.stockQty = stockQty + this.stockUnit = stockUom.id!!.toInt() + .let { Integer.valueOf(it) } as? Integer + this.stockUnitName = stockUnitName + + this.bom = bom + } + bom.bomMaterials.add(bomMaterial) + } + } + + if (replaceProcesses) { + deleteAllBomProcessMaterialsForCurrentProcesses() + bom.bomProcesses.clear() + + val maxSeqNoFromProvided = + request.processes?.mapNotNull { it.seqNo }?.maxOrNull() ?: 0L + var nextSeqNo = maxSeqNoFromProvided + 1 + + // ---- seqNo uniqueness validation (when provided) ---- + val seqCounts = request.processes + ?.mapNotNull { it.seqNo } + ?.groupingBy { it } + ?.eachCount() + ?: emptyMap() + val duplicates = seqCounts.filter { it.value > 1 }.keys + if (duplicates.isNotEmpty()) { + throw IllegalArgumentException("Duplicate process seqNo: ${duplicates.joinToString(",")}") + } + + request.processes?.forEach { pReq -> + val process = when { + pReq.processId != null -> processRepository.findById(pReq.processId).orElse(null) + !pReq.processCode.isNullOrBlank() -> processRepository.findByCodeAndDeletedIsFalse(pReq.processCode.trim()) + else -> null + } ?: throw IllegalArgumentException("Process not found: processId=${pReq.processId}, processCode=${pReq.processCode}") + + if (process.deleted == true) { + throw IllegalArgumentException("Process is deleted: id=${process.id}") + } + + val equipment = resolveEquipmentForBomProcess(pReq) + + val seqNo = pReq.seqNo ?: nextSeqNo++ + val description = pReq.description ?: "" + + val bomProcess = BomProcess().apply { + this.process = process + this.equipment = equipment + this.description = description + this.seqNo = seqNo + this.durationInMinute = pReq.durationInMinute ?: 0 + this.prepTimeInMinute = pReq.prepTimeInMinute ?: 0 + this.postProdTimeInMinute = pReq.postProdTimeInMinute ?: 0 + this.bom = bom + } + bom.bomProcesses.add(bomProcess) + } + } + + // ---- persist children first (so ids exist) ---- + bomRepository.saveAndFlush(bom) + + // ---- rebuild BomProcessMaterial mappings when children changed ---- + if (replaceMaterials || replaceProcesses) { + val processIds = bom.bomProcesses.mapNotNull { it.id } + if (processIds.isNotEmpty()) { + val existing = bomProcessMaterialRepository.findByBomProcess_IdIn(processIds) + if (existing.isNotEmpty()) { + bomProcessMaterialRepository.deleteAll(existing) + } + } + + val mappings = mutableListOf() + bom.bomProcesses.forEach { proc -> + bom.bomMaterials.forEach { mat -> + mappings.add( + BomProcessMaterial().apply { + this.bomProcess = proc + this.bomMaterial = mat + } + ) + } + } + if (mappings.isNotEmpty()) { + bomProcessMaterialRepository.saveAll(mappings) + } + } + + // ---- baseScore recalculation (server controlled) ---- + bom.baseScore = calculateBaseScore(bom) + bomRepository.saveAndFlush(bom) + + return getBomDetail(bom.id!!) + } + + private fun resolveEquipmentForBomProcess(pReq: EditBomProcessRequest): Equipment { + val equipmentId = pReq.equipmentId + val equipmentCode = pReq.equipmentCode?.trim().orEmpty() + + val found = when { + equipmentId != null -> equipmentRepository.findByIdAndDeletedFalse(equipmentId) + equipmentCode.isNotBlank() -> equipmentRepository.findByCodeAndDeletedFalse(equipmentCode) + ?: equipmentRepository.findByNameAndDeletedIsFalse(equipmentCode) + else -> null + } + + if (found != null) return found + + // Create new equipment on-demand (optional) + if (pReq.newEquipment != null) { + val req = pReq.newEquipment + val duplicated = equipmentRepository.findByCodeAndDeletedIsFalse(req.code.trim()) + if (duplicated != null) { + throw IllegalArgumentException("Equipment code already exists: code=${req.code}") + } + + val equipmentDetail = req.equipmentTypeId?.let { equipmentDetailRepository.findByIdAndDeletedFalse(it) } + + val entity = Equipment().apply { + code = req.code.trim() + name = req.name.trim() + description = req.description?.trim().orEmpty() + this.EquipmentDetail = equipmentDetail + deleted = false + } + return equipmentRepository.saveAndFlush(entity) + } + + // Fallback: create a default placeholder equipment to satisfy non-null FK. + val placeholderCode = if (equipmentCode.isNotBlank()) equipmentCode else "不適用" + return equipmentRepository.findByCodeAndDeletedIsFalse(placeholderCode) + ?: equipmentRepository.findByNameAndDeletedIsFalse(placeholderCode) + ?: equipmentRepository.saveAndFlush( + Equipment().apply { + code = placeholderCode + name = placeholderCode + description = placeholderCode + deleted = false + } + ) + } + //////// -------------------------------- for excel import ------------------------------- ///////// private fun saveBomEntity(req: ImportBomRequest): Bom { val item = itemsRepository.findByCodeAndDeletedFalse(req.code) ?: itemsRepository.findByNameAndDeletedFalse(req.name) @@ -2544,6 +2804,7 @@ println("=====================================") BomMaterialDto( itemCode = m.item?.code, itemName = m.item?.name ?: m.itemName, + isConsumable = m.isConsumable, baseQty = m.baseQty, baseUom = m.baseUnitName, stockQty = m.stockQty, @@ -2558,8 +2819,10 @@ println("=====================================") .map { p -> BomProcessDto( seqNo = p.seqNo, + processCode = p.process?.code, processName = p.process?.name, processDescription = p.description, + equipmentCode = p.equipment?.code, equipmentName = p.equipment?.name, durationInMinute = p.durationInMinute, prepTimeInMinute = p.prepTimeInMinute, diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/BomController.kt b/src/main/java/com/ffii/fpsms/modules/master/web/BomController.kt index f5fdd68..79be196 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/web/BomController.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/web/BomController.kt @@ -11,6 +11,7 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam @@ -26,6 +27,7 @@ import com.ffii.fpsms.modules.master.web.models.BomUploadResponse import com.ffii.fpsms.modules.master.web.models.BomFormatCheckRequest import com.ffii.fpsms.modules.master.web.models.ImportBomRequestPayload import com.ffii.fpsms.modules.master.web.models.BomDetailResponse +import com.ffii.fpsms.modules.master.web.models.EditBomRequest import com.ffii.fpsms.modules.master.web.models.BomExcelCheckProgress import java.util.logging.Logger import java.nio.file.Files @@ -122,4 +124,16 @@ fun downloadBomFormatIssueLog( fun getBomDetail(@PathVariable id: Long): BomDetailResponse { return bomService.getBomDetail(id) } + + /** + * Edit BOM (basic fields + materials + process lines). + * baseScore is recalculated on server. + */ + @PutMapping("/{id}") + fun editBom( + @PathVariable id: Long, + @RequestBody request: EditBomRequest, + ): BomDetailResponse { + return bomService.editBom(id, request) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/models/EditBomRequest.kt b/src/main/java/com/ffii/fpsms/modules/master/web/models/EditBomRequest.kt new file mode 100644 index 0000000..30ddd64 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/master/web/models/EditBomRequest.kt @@ -0,0 +1,90 @@ +package com.ffii.fpsms.modules.master.web.models + +import com.ffii.fpsms.modules.master.entity.Equipment +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull +import java.math.BigDecimal + +/** + * BOM edit request. + * + * Notes: + * - baseScore is NOT editable by client; server recalculates it after update. + * - When `materials` or `processes` is provided, we will rebuild BOMProcessMaterial mappings + * using current BOM materials & process lines (default: link every material to every process line). + */ +data class EditBomRequest( + // basic fields + val description: String? = null, + val outputQty: BigDecimal? = null, + val outputQtyUom: String? = null, + val yield: BigDecimal? = null, + + // score-related fields (baseScore inputs) + val isDark: Int? = null, + val isFloat: Int? = null, + val isDense: Int? = null, + val scrapRate: Int? = null, + val allergicSubstances: Int? = null, + val timeSequence: Int? = null, + val complexity: Int? = null, + val isDrink: Boolean? = null, + + // children + val materials: List? = null, + val processes: List? = null, +) + +data class EditBomMaterialRequest( + val id: Long? = null, + + // At least one of itemId/itemCode is required. + val itemId: Long? = null, + val itemCode: String? = null, + + /** + * BOM qty input. + * We treat it as "base unit qty" for the item (auto derive base/stock/sales units by item UOM settings). + */ + @field:NotNull + val qty: BigDecimal, + + val isConsumable: Boolean? = null, +) + +data class EditBomProcessRequest( + val id: Long? = null, + + // If seqNo is null, we will assign next seqNo. + val seqNo: Long? = null, + + // At least one of processId/processCode is required. + val processId: Long? = null, + val processCode: String? = null, + + // Select existing equipment by id/code. + val equipmentId: Long? = null, + val equipmentCode: String? = null, + + // If you need to create equipment on the fly, provide this. + val newEquipment: NewEquipmentForBomProcessRequest? = null, + + val description: String? = null, + val durationInMinute: Int? = null, + val prepTimeInMinute: Int? = null, + val postProdTimeInMinute: Int? = null, +) + +data class NewEquipmentForBomProcessRequest( + @field:NotBlank + val code: String, + @field:NotBlank + val name: String, + val description: String? = null, + /** + * Optional EquipmentDetail.id used as Equipment.EquipmentDetail. + * If null, equipmentType is left empty. + */ + val equipmentTypeId: Long? = null, +) + diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/models/ItemUomRequest.kt b/src/main/java/com/ffii/fpsms/modules/master/web/models/ItemUomRequest.kt index 3e939bf..c1f9b17 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/web/models/ItemUomRequest.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/web/models/ItemUomRequest.kt @@ -73,6 +73,7 @@ data class ImportBomRequestPayload( data class BomMaterialDto( val itemCode: String?, val itemName: String?, + val isConsumable: Boolean?, val baseQty: BigDecimal?, val baseUom: String?, val stockQty: BigDecimal?, @@ -84,9 +85,12 @@ data class BomMaterialDto( data class BomProcessDto( val seqNo: Long?, + val processCode: String?, val processName: String?, val processDescription: String?, - val equipmentName: String?, + // For display we prefer equipment.code (stable identifier). Keep equipmentName for backward compatibility. + val equipmentCode: String? = null, + val equipmentName: String? = null, val durationInMinute: Int?, val prepTimeInMinute: Int?, val postProdTimeInMinute: Int? 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 91f8479..ee2e815 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 @@ -71,6 +71,17 @@ interface InventoryLotLineRepository : AbstractRepository): List + @Query(""" + SELECT ill FROM InventoryLotLine ill + WHERE ill.deleted = false + AND ill.warehouse.id IN :warehouseIds + AND ill.inventoryLot.id IN :inventoryLotIds + """) + fun findAllByWarehouseIdInAndInventoryLotIdInAndDeletedIsFalse( + @Param("warehouseIds") warehouseIds: Collection, + @Param("inventoryLotIds") inventoryLotIds: Collection + ): List + @Query(""" SELECT ill FROM InventoryLotLine ill WHERE ill.warehouse.code = :warehouseCode 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 5e45ca8..e0efb04 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 @@ -223,24 +223,11 @@ class StockTakeRecordService( open fun getApproverInventoryLotDetailsAll( stockTakeId: Long? = null, pageNum: Int = 0, - pageSize: Int = 100 + pageSize: Int = 100, + approvalView: String? = null ): RecordsRes { println("getApproverInventoryLotDetailsAll called with stockTakeId: $stockTakeId, pageNum: $pageNum, pageSize: $pageSize") - - // 1. 不分 section,直接拿所有未删除的 warehouse - val warehouses = warehouseRepository.findAllByDeletedIsFalse() - if (warehouses.isEmpty()) { - logger.warn("No warehouses found for approverInventoryLotDetailsAll") - return RecordsRes(emptyList(), 0) - } - - val warehouseIds = warehouses.mapNotNull { it.id } - println("Found ${warehouses.size} warehouses for ALL sections") - - // 2. 拿所有这些仓库下面的 lot line - val inventoryLotLines = inventoryLotLineRepository.findAllByWarehouseIdInAndDeletedIsFalse(warehouseIds) - println("Found ${inventoryLotLines.size} inventory lot lines for ALL sections") // 3. 如果传了 stockTakeId,就把「同一轮」的所有 stockTake 找出来(stockTakeRoundId,舊資料則 planStart) val roundStockTakeIds: Set = if (stockTakeId != null) { @@ -254,7 +241,6 @@ class StockTakeRecordService( // 4. 如果有 stockTakeId,则预先把这一轮相关的 stockTakeRecord 查出来建 map(避免全表扫描 + N^2) val roundStockTakeRecords = if (stockTakeId != null && roundStockTakeIds.isNotEmpty()) { stockTakeRecordRepository.findAllByStockTakeIdInAndDeletedIsFalse(roundStockTakeIds) - .filter { it.warehouse?.id in warehouseIds } } else { emptyList() } @@ -262,7 +248,42 @@ class StockTakeRecordService( .groupBy { Pair(it.lotId ?: 0L, it.warehouse?.id ?: 0L) } .mapValues { (_, records) -> records.maxByOrNull { it.id ?: 0L }!! } - // 4.1 预先拉取本轮 stockTakeLine,避免每行调用 repository 形成 N+1 + // A:Approver 只处理“已有 picker record 的行” + // stockTakeId != null 时,allowedPairs 里一定对应 stockTakeRecordId != null + val allowedPairs: Set> = if (stockTakeId != null) { + stockTakeRecordsMap.keys + } else { + emptySet() + } + + // 4.1 只抓本轮 record 涉及到的 lot/warehouse 对应库存行(避免扫描全仓库 inventory_lot_line) + val inventoryLotLines = if (stockTakeId != null) { + val lotIds = roundStockTakeRecords.mapNotNull { it.lotId }.toSet() + val warehouseIds = roundStockTakeRecords.mapNotNull { it.warehouse?.id }.toSet() + if (lotIds.isEmpty() || warehouseIds.isEmpty()) { + emptyList() + } else { + inventoryLotLineRepository.findAllByWarehouseIdInAndInventoryLotIdInAndDeletedIsFalse( + warehouseIds = warehouseIds, + inventoryLotIds = lotIds + ).filter { ill -> + val lotId = ill.inventoryLot?.id + val warehouseId = ill.warehouse?.id + lotId != null && warehouseId != null && allowedPairs.contains(Pair(lotId, warehouseId)) + } + } + } else { + val warehouses = warehouseRepository.findAllByDeletedIsFalse() + if (warehouses.isEmpty()) { + logger.warn("No warehouses found for approverInventoryLotDetailsAll") + return RecordsRes(emptyList(), 0) + } + val warehouseIds = warehouses.mapNotNull { it.id } + println("Found ${warehouses.size} warehouses for ALL sections") + inventoryLotLineRepository.findAllByWarehouseIdInAndDeletedIsFalse(warehouseIds) + } + + // 4.2 预先拉取本轮 stockTakeLine,避免每行调用 repository 形成 N+1 val stockTakeLineMap = if (stockTakeId != null && roundStockTakeRecords.isNotEmpty()) { val lineIds = inventoryLotLines.mapNotNull { it.id }.toSet() val roundIds = roundStockTakeIds @@ -277,8 +298,10 @@ class StockTakeRecordService( emptyMap() } + val targetInventoryLotLines = inventoryLotLines + // 5. 组装 InventoryLotDetailResponse(基本复制 section 版里的 map 那段) - val allResults = inventoryLotLines.map { ill -> + val allResults = targetInventoryLotLines.map { ill -> val inventoryLot = ill.inventoryLot val item = inventoryLot?.item val warehouse = ill.warehouse @@ -336,32 +359,42 @@ class StockTakeRecordService( } // 6. 过滤结果: - // 如果带了 stockTakeId,表示只看这一轮盘点的记录,此时只保留「已有 stockTakeRecord 的行」(即 picker 已经盘点过的行) - // 如果没有带 stockTakeId,则沿用原逻辑:availableQty > 0 或已有盘点记录 + // stockTakeId != null 时已在 map 前过滤 allowedPairs => 只保留 stockTakeRecordId != null 的行 val filteredResults = if (stockTakeId != null) { - allResults.filter { response -> - val av = response.availableQty ?: BigDecimal.ZERO - av.compareTo(BigDecimal.ZERO) > 0 || response.stockTakeRecordId != null - } + allResults } else { allResults.filter { response -> val av = response.availableQty ?: BigDecimal.ZERO av.compareTo(BigDecimal.ZERO) > 0 || response.stockTakeRecordId != null } } + + val approvalFilteredResults = when (approvalView?.lowercase()) { + "approved" -> filteredResults.filter { response -> + response.stockTakeRecordStatus == "completed" || + response.approverQty != null || + response.finalQty != null + } + "pending" -> filteredResults.filter { response -> + !(response.stockTakeRecordStatus == "completed" || + response.approverQty != null || + response.finalQty != null) + } + else -> filteredResults + } // 7. 分页(和 section 版一模一样) val pageable = PageRequest.of(pageNum, pageSize) val startIndex = pageable.offset.toInt() - val endIndex = minOf(startIndex + pageSize, filteredResults.size) + val endIndex = minOf(startIndex + pageSize, approvalFilteredResults.size) - val paginatedResult = if (startIndex < filteredResults.size) { - filteredResults.subList(startIndex, endIndex) + val paginatedResult = if (startIndex < approvalFilteredResults.size) { + approvalFilteredResults.subList(startIndex, endIndex) } else { emptyList() } - return RecordsRes(paginatedResult, filteredResults.size) + return RecordsRes(paginatedResult, approvalFilteredResults.size) } open fun AllApproverStockTakeList(): List { // Overall 卡:只取“最新一轮”,并且总数口径与 diff --git a/src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeRecordController.kt b/src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeRecordController.kt index fc809ae..d7545d3 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeRecordController.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeRecordController.kt @@ -64,6 +64,32 @@ class StockTakeRecordController( pageSize = pageSize ) } + @GetMapping("/approverInventoryLotDetailsAllPending") + fun getApproverInventoryLotDetailsAllPending( + @RequestParam(required = false) stockTakeId: Long?, + @RequestParam(required = false, defaultValue = "0") pageNum: Int, + @RequestParam(required = false, defaultValue = "2147483647") pageSize: Int + ): RecordsRes { + return stockOutRecordService.getApproverInventoryLotDetailsAll( + stockTakeId = stockTakeId, + pageNum = pageNum, + pageSize = pageSize, + approvalView = "pending" + ) + } + @GetMapping("/approverInventoryLotDetailsAllApproved") + fun getApproverInventoryLotDetailsAllApproved( + @RequestParam(required = false) stockTakeId: Long?, + @RequestParam(required = false, defaultValue = "0") pageNum: Int, + @RequestParam(required = false, defaultValue = "50") pageSize: Int + ): RecordsRes { + return stockOutRecordService.getApproverInventoryLotDetailsAll( + stockTakeId = stockTakeId, + pageNum = pageNum, + pageSize = pageSize, + approvalView = "approved" + ) + } @GetMapping("/AllApproverStockTakeList") fun AllApproverStockTakeList(): List { return stockOutRecordService.AllApproverStockTakeList() diff --git a/src/main/resources/db/changelog/changes/20260323_03_Enson/01_alter_stock_take.sql b/src/main/resources/db/changelog/changes/20260323_03_Enson/01_alter_stock_take.sql new file mode 100644 index 0000000..84b02fe --- /dev/null +++ b/src/main/resources/db/changelog/changes/20260323_03_Enson/01_alter_stock_take.sql @@ -0,0 +1,8 @@ +-- liquibase formatted sql +-- changeset Enson:alter_do_pick_order_record_box_number + +ALTER TABLE `fpsmsdb`.`do_pick_order_record` +CHANGE COLUMN `BoxNumber` `cartonQty` INT NULL AFTER `deleted`; + + +