CANCERYS\kw093 преди 3 дни
родител
ревизия
a98cf6364d
променени са 8 файла, в които са добавени 478 реда и са изтрити 29 реда
  1. +263
    -0
      src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt
  2. +14
    -0
      src/main/java/com/ffii/fpsms/modules/master/web/BomController.kt
  3. +90
    -0
      src/main/java/com/ffii/fpsms/modules/master/web/models/EditBomRequest.kt
  4. +5
    -1
      src/main/java/com/ffii/fpsms/modules/master/web/models/ItemUomRequest.kt
  5. +11
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt
  6. +61
    -28
      src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt
  7. +26
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeRecordController.kt
  8. +8
    -0
      src/main/resources/db/changelog/changes/20260323_03_Enson/01_alter_stock_take.sql

+ 263
- 0
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<BomProcessMaterial>()
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,


+ 14
- 0
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)
}
}

+ 90
- 0
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<EditBomMaterialRequest>? = null,
val processes: List<EditBomProcessRequest>? = 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,
)


+ 5
- 1
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?


+ 11
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt Целия файл

@@ -71,6 +71,17 @@ interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long
@Query("SELECT ill FROM InventoryLotLine ill WHERE ill.warehouse.id IN :warehouseIds AND ill.deleted = false")
fun findAllByWarehouseIdInAndDeletedIsFalse(@Param("warehouseIds") warehouseIds: List<Long>): List<InventoryLotLine>

@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<Long>,
@Param("inventoryLotIds") inventoryLotIds: Collection<Long>
): List<InventoryLotLine>

@Query("""
SELECT ill FROM InventoryLotLine ill
WHERE ill.warehouse.code = :warehouseCode


+ 61
- 28
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<InventoryLotDetailResponse> {
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<Long> = 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<Pair<Long, Long>> = 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<AllPickedStockTakeListReponse> {
// Overall 卡:只取“最新一轮”,并且总数口径与


+ 26
- 0
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<InventoryLotDetailResponse> {
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<InventoryLotDetailResponse> {
return stockOutRecordService.getApproverInventoryLotDetailsAll(
stockTakeId = stockTakeId,
pageNum = pageNum,
pageSize = pageSize,
approvalView = "approved"
)
}
@GetMapping("/AllApproverStockTakeList")
fun AllApproverStockTakeList(): List<AllPickedStockTakeListReponse> {
return stockOutRecordService.AllApproverStockTakeList()


+ 8
- 0
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`;




Зареждане…
Отказ
Запис