| @@ -654,6 +654,7 @@ open class PickOrderService( | |||
| println(pos) | |||
| val groupsByPickOrderId = pickOrderGroupRepository.findByPickOrderIdInAndDeletedIsFalse(ids) | |||
| .groupBy { it.pickOrderId } | |||
| // Get Inventory Data | |||
| val requiredItems = pos | |||
| .flatMap { it.pickOrderLines } | |||
| @@ -666,13 +667,10 @@ open class PickOrderService( | |||
| override val uomDesc: String? = value[0].uom?.udfudesc | |||
| override var availableQty: BigDecimal? = zero | |||
| override val requiredQty: BigDecimal = value.sumOf { it.qty ?: zero } | |||
| } | |||
| } // itemId - requiredQty | |||
| val itemIds = requiredItems.mapNotNull { it.first } | |||
| // val inventories = inventoryLotLineRepository.findCurrentInventoryByItems(itemIds) | |||
| // val inventories = inventoryService.allInventoriesByItemIds(itemIds) | |||
| val inventories = inventoryLotLineService | |||
| .allInventoryLotLinesByItemIdIn(itemIds) | |||
| .filter { it.status == InventoryLotLineStatus.AVAILABLE.value } | |||
| @@ -680,37 +678,71 @@ open class PickOrderService( | |||
| .filter { it.expiryDate.isAfter(today) || it.expiryDate.isEqual(today) } | |||
| .sortedBy { it.expiryDate } | |||
| .groupBy { it.item?.id } | |||
| val suggestions = | |||
| suggestedPickLotService.suggestionForPickOrders(SuggestedPickLotForPoRequest(pickOrders = pos)) | |||
| // ✅ Get stock out line data for each pick order line | |||
| val pickOrderLineIds = pos.flatMap { it.pickOrderLines }.mapNotNull { it.id } | |||
| val stockOutLinesByPickOrderLineId = if (pickOrderLineIds.isNotEmpty()) { | |||
| pickOrderLineIds.associateWith { pickOrderLineId -> | |||
| stockOutLIneRepository.findAllByPickOrderLineIdAndDeletedFalse(pickOrderLineId) | |||
| } | |||
| } else { | |||
| emptyMap() | |||
| } | |||
| // Pick Orders | |||
| val releasePickOrderInfos = pos | |||
| .map { po -> | |||
| val releasePickOrderLineInfos = po.pickOrderLines.map { pol -> | |||
| // if (pol.item?.id != null && pol.item!!.id!! > 0) { | |||
| val inventory = pol.item?.id?.let { inventories[it] } | |||
| val itemUom = pol.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } | |||
| // val inventory = inventories.find { it.itemId == pol.item?.id } | |||
| // ✅ Fix: Convert availableQty from base units to sales units | |||
| val convertedAvailableQty = inventory?.sumOf { i -> | |||
| val inQty = i.inQty ?: zero | |||
| val outQty = i.outQty ?: zero | |||
| val holdQty = i.holdQty ?: zero | |||
| val baseQty = inQty.minus(outQty).minus(holdQty) | |||
| println("Item ID: ${i.item?.id}, InQty: $inQty, OutQty: $outQty, HoldQty: $holdQty, BaseQty: $baseQty") | |||
| // Apply unit conversion if needed | |||
| val itemUom = pol.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } | |||
| val ratioN = itemUom?.ratioN | |||
| val ratioD = itemUom?.ratioD | |||
| println("Item ID: ${pol.item?.id}, RatioN: $ratioN, RatioD: $ratioD") | |||
| val convertedQty: BigDecimal = if (ratioN != null && ratioD != null && ratioD != zero) { | |||
| baseQty.divide(ratioN.divide(ratioD, 10, java.math.RoundingMode.HALF_UP), 2, java.math.RoundingMode.HALF_UP) | |||
| } else { | |||
| baseQty | |||
| } | |||
| // Ensure the lambda returns BigDecimal | |||
| convertedQty | |||
| } | |||
| // ✅ Calculate total picked quantity from stock out lines | |||
| val stockOutLines = stockOutLinesByPickOrderLineId[pol.id] ?: emptyList() | |||
| val totalPickedQty = stockOutLines.sumOf { it.qty ?: zero } | |||
| // Return | |||
| GetPickOrderLineInfo( | |||
| id = pol.id, | |||
| itemId = pol.item?.id, | |||
| itemCode = pol.item?.code, | |||
| itemName = pol.item?.name, | |||
| // availableQty = inventory?.availableQty, | |||
| availableQty = inventory?.sumOf { i -> (i.availableQty ?: zero) }, | |||
| // availableQty = inventory?.sumOf { i -> (i.availableQty ?: zero) * (itemUom?.ratioN ?: one) * (itemUom?.ratioD ?: one) }, | |||
| availableQty = convertedAvailableQty, // ✅ Use converted quantity | |||
| requiredQty = pol.qty, | |||
| uomCode = pol.uom?.code, | |||
| uomDesc = pol.uom?.udfudesc, | |||
| suggestedList = suggestions.suggestedList.filter { it.pickOrderLine?.id == pol.id } | |||
| suggestedList = suggestions.suggestedList.filter { it.pickOrderLine?.id == pol.id }, | |||
| // ✅ Add picked quantity data | |||
| pickedQty = totalPickedQty | |||
| ) | |||
| // } | |||
| } | |||
| val groupName = po.id?.let { pickOrderId -> | |||
| groupsByPickOrderId[pickOrderId]?.firstOrNull()?.name | |||
| } ?: "No Group" | |||
| groupsByPickOrderId[pickOrderId]?.firstOrNull()?.name | |||
| } ?: "No Group" | |||
| // Return | |||
| GetPickOrderInfo( | |||
| id = po.id, | |||
| @@ -723,34 +755,37 @@ open class PickOrderService( | |||
| pickOrderLines = releasePickOrderLineInfos | |||
| ) | |||
| } | |||
| // Items | |||
| val currentInventoryInfos = requiredItems.map { item -> | |||
| // val inventory = inventories | |||
| // .find { it.itemId == item.first } | |||
| val inventory = item.first?.let { inventories[it] } | |||
| val itemUom = item.first?.let { itemUomService.findSalesUnitByItemId(it) } | |||
| item.second.let { | |||
| // it.availableQty = inventory?.availableQty | |||
| it.availableQty = inventory?.sumOf { i -> i.availableQty ?: zero } | |||
| // it.availableQty = inventory?.sumOf { i -> (i.availableQty ?: zero) * (itemUom?.ratioN ?: one) * (itemUom?.ratioD ?: one) } | |||
| // return | |||
| val convertedAvailableQty = inventory?.sumOf { i -> | |||
| val baseQty = (i.availableQty ?: zero) | |||
| val ratioN = itemUom?.ratioN | |||
| val ratioD = itemUom?.ratioD | |||
| if (ratioN != null && ratioD != null && ratioD != zero) { | |||
| baseQty.divide(ratioN.divide(ratioD, 10, java.math.RoundingMode.HALF_UP), 2, java.math.RoundingMode.HALF_UP) | |||
| } else { | |||
| baseQty | |||
| } | |||
| } | |||
| it.availableQty = convertedAvailableQty | |||
| it | |||
| } | |||
| } | |||
| val consoCode = pos.firstOrNull()?.consoCode | |||
| return GetPickOrderInfoResponse( | |||
| // consoCode = consoCode, | |||
| consoCode = consoCode, | |||
| pickOrders = releasePickOrderInfos, | |||
| items = currentInventoryInfos, | |||
| ) | |||
| } | |||
| open fun getAllPickOrdersInfo(): GetPickOrderInfoResponse { | |||
| // 使用现有的查询方法获取所有 Pick Orders,然后在内存中过滤 | |||
| val allPickOrders = pickOrderRepository.findAll() | |||
| @@ -763,6 +798,7 @@ open class PickOrderService( | |||
| // 如果没有任何已发布的 Pick Orders,返回空结果 | |||
| if (releasedPickOrderIds.isEmpty()) { | |||
| return GetPickOrderInfoResponse( | |||
| consoCode = null, | |||
| pickOrders = emptyList(), | |||
| items = emptyList() | |||
| ) | |||
| @@ -779,150 +815,72 @@ open class PickOrderService( | |||
| println("pickOrderLineId: $pickOrderLineId") | |||
| println("today: $today") | |||
| // 检查具体的数量字段值 | |||
| val quantityCheckSql = """ | |||
| SELECT | |||
| spl.id as suggestedPickLotId, | |||
| ill.id as lotLineId, | |||
| ill.inQty, | |||
| ill.outQty, | |||
| ill.holdQty, | |||
| COALESCE(ill.inQty, 0) as inQtyCoalesced, | |||
| COALESCE(ill.outQty, 0) as outQtyCoalesced, | |||
| COALESCE(ill.holdQty, 0) as holdQtyCoalesced, | |||
| (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as availableQtyCalculated | |||
| FROM fpsmsdb.suggested_pick_lot spl | |||
| JOIN fpsmsdb.inventory_lot_line ill ON ill.id = spl.suggestedLotLineId | |||
| WHERE spl.pickOrderLineId = :pickOrderLineId | |||
| AND ill.status = 'available' | |||
| """.trimIndent() | |||
| val quantityCheckResult = jdbcDao.queryForList(quantityCheckSql, mapOf("pickOrderLineId" to pickOrderLineId)) | |||
| println("Quantity check result:") | |||
| quantityCheckResult.forEach { row -> | |||
| println("Row: $row") | |||
| // ✅ 重新添加:首先检查是否需要 resuggest | |||
| val needsResuggest = checkIfNeedsResuggest(pickOrderLineId) | |||
| if (needsResuggest) { | |||
| println("⚠️ NEEDS RESUGGEST: Auto-triggering resuggest for pick order line ID: $pickOrderLineId") | |||
| try { | |||
| // 获取 pick order ID | |||
| val pickOrderId = getPickOrderIdFromPickOrderLineId(pickOrderLineId) | |||
| if (pickOrderId != null) { | |||
| suggestedPickLotService.resuggestPickOrder(pickOrderId) | |||
| println("✅ Resuggest completed successfully for pick order ID: $pickOrderId") | |||
| } else { | |||
| println("❌ Could not find pick order ID for pick order line ID: $pickOrderLineId") | |||
| } | |||
| } catch (e: Exception) { | |||
| println("❌ Error during auto-resuggest: ${e.message}") | |||
| } | |||
| } else { | |||
| println("✅ No resuggest needed for pick order line ID: $pickOrderLineId") | |||
| } | |||
| // 检查日期条件 | |||
| val dateCheckSql = """ | |||
| val sql = """ | |||
| SELECT | |||
| spl.id as suggestedPickLotId, | |||
| ill.id as lotLineId, | |||
| ill.id as lotId, | |||
| il.lotNo, | |||
| il.expiryDate, | |||
| :today as today, | |||
| il.expiryDate >= :today as isNotExpired, | |||
| il.expiryDate IS NULL as isNullExpiry | |||
| FROM fpsmsdb.suggested_pick_lot spl | |||
| JOIN fpsmsdb.inventory_lot_line ill ON ill.id = spl.suggestedLotLineId | |||
| JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId | |||
| WHERE spl.pickOrderLineId = :pickOrderLineId | |||
| AND ill.status = 'available' | |||
| AND (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) > 0 | |||
| """.trimIndent() | |||
| val dateCheckResult = | |||
| jdbcDao.queryForList(dateCheckSql, mapOf("pickOrderLineId" to pickOrderLineId, "today" to today)) | |||
| println("Date check result:") | |||
| dateCheckResult.forEach { row -> | |||
| println("Row: $row") | |||
| } | |||
| // 检查 warehouse JOIN | |||
| val warehouseCheckSql = """ | |||
| SELECT | |||
| spl.id as suggestedPickLotId, | |||
| ill.id as lotLineId, | |||
| ill.warehouseId, | |||
| w.id as warehouseId, | |||
| w.name as warehouseName | |||
| FROM fpsmsdb.suggested_pick_lot spl | |||
| JOIN fpsmsdb.inventory_lot_line ill ON ill.id = spl.suggestedLotLineId | |||
| JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId | |||
| JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId | |||
| WHERE spl.pickOrderLineId = :pickOrderLineId | |||
| AND ill.status = 'available' | |||
| AND (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) > 0 | |||
| AND (il.expiryDate IS NULL OR il.expiryDate >= :today) | |||
| """.trimIndent() | |||
| val warehouseCheckResult = | |||
| jdbcDao.queryForList(warehouseCheckSql, mapOf("pickOrderLineId" to pickOrderLineId, "today" to today)) | |||
| println("Warehouse check result count: ${warehouseCheckResult.size}") | |||
| warehouseCheckResult.forEach { row -> | |||
| println("Warehouse Row: $row") | |||
| } | |||
| // 检查 uom_conversion JOIN - 使用 LEFT JOIN 并检查 stockItemUomId | |||
| val uomCheckSql = """ | |||
| SELECT | |||
| w.name as location, | |||
| COALESCE(uc.udfudesc, 'N/A') as stockUnit, | |||
| -- ✅ 修复:availableQty 应该显示实际可用数量(保持小数) | |||
| CASE | |||
| WHEN sales_iu.ratioN IS NOT NULL AND sales_iu.ratioD IS NOT NULL AND sales_iu.ratioD != 0 THEN | |||
| (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) / (sales_iu.ratioN / sales_iu.ratioD) | |||
| ELSE | |||
| (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) | |||
| END as availableQty, | |||
| -- ✅ 修复:requiredQty 直接使用 spl.qty,因为它已经是销售单位 | |||
| spl.qty as requiredQty, | |||
| COALESCE(sol.qty, 0) as actualPickQty, | |||
| spl.id as suggestedPickLotId, | |||
| ill.id as lotLineId, | |||
| ill.stockItemUomId, | |||
| uc.id as uomId, | |||
| uc.code as uomCode | |||
| ill.status as lotStatus, | |||
| sol.id as stockOutLineId, | |||
| sol.status as stockOutLineStatus, | |||
| sol.qty as stockOutLineQty, | |||
| spl.suggestedLotLineId as debugSuggestedLotLineId, | |||
| ill.inventoryLotId as debugInventoryLotId, | |||
| CASE | |||
| WHEN ill.status != 'available' THEN 'unavailable' | |||
| WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired' | |||
| WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) < (spl.qty * (sales_iu.ratioN / sales_iu.ratioD)) THEN 'insufficient_stock' | |||
| ELSE 'available' | |||
| END as lotAvailability | |||
| FROM fpsmsdb.suggested_pick_lot spl | |||
| JOIN fpsmsdb.inventory_lot_line ill ON ill.id = spl.suggestedLotLineId | |||
| JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId | |||
| JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId | |||
| LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = ill.stockItemUomId | |||
| LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId | |||
| LEFT JOIN fpsmsdb.item_uom sales_iu ON sales_iu.itemId = il.itemId AND sales_iu.salesUnit = true AND sales_iu.deleted = false | |||
| LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = sales_iu.uomId | |||
| LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = spl.pickOrderLineId AND sol.inventoryLotLineId = spl.suggestedLotLineId | |||
| WHERE spl.pickOrderLineId = :pickOrderLineId | |||
| AND ill.status = 'available' | |||
| AND (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) > 0 | |||
| AND (il.expiryDate IS NULL OR il.expiryDate >= :today) | |||
| ORDER BY il.expiryDate ASC, il.lotNo ASC | |||
| """.trimIndent() | |||
| val uomCheckResult = | |||
| jdbcDao.queryForList(uomCheckSql, mapOf("pickOrderLineId" to pickOrderLineId, "today" to today)) | |||
| println("UOM check result count: ${uomCheckResult.size}") | |||
| uomCheckResult.forEach { row -> | |||
| println("UOM Row: $row") | |||
| } | |||
| println("🔍 Executing SQL for lot details: $sql") | |||
| println("🔍 With parameters: pickOrderLineId = $pickOrderLineId") | |||
| // 修改查询,通过 item_uom 表获取 UOM 信息 | |||
| val sql = """ | |||
| SELECT | |||
| ill.id as lotId, | |||
| il.lotNo, | |||
| il.expiryDate, | |||
| w.name as location, | |||
| COALESCE(uc.udfudesc, 'N/A') as stockUnit, | |||
| (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as availableQty, | |||
| spl.qty as requiredQty, | |||
| COALESCE(sol.qty, 0) as actualPickQty, | |||
| spl.id as suggestedPickLotId, | |||
| ill.status as lotStatus, | |||
| CASE | |||
| WHEN ill.status != 'available' THEN 'status_unavailable' | |||
| WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < :today) THEN 'expired' | |||
| WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) < spl.qty THEN 'insufficient_stock' | |||
| ELSE 'available' | |||
| END as lotAvailability | |||
| FROM fpsmsdb.suggested_pick_lot spl | |||
| JOIN fpsmsdb.inventory_lot_line ill ON ill.id = spl.suggestedLotLineId | |||
| JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId | |||
| JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId | |||
| LEFT JOIN fpsmsdb.item_uom iu ON iu.id = ill.stockItemUomId | |||
| LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = iu.uomId | |||
| LEFT JOIN fpsmsdb.stock_out_line sol ON sol.inventoryLotLineId = ill.id | |||
| AND sol.pickOrderLineId = spl.pickOrderLineId | |||
| WHERE spl.pickOrderLineId = :pickOrderLineId | |||
| ORDER BY | |||
| CASE lotAvailability | |||
| WHEN 'available' THEN 1 | |||
| WHEN 'insufficient_stock' THEN 2 | |||
| WHEN 'expired' THEN 3 | |||
| WHEN 'status_unavailable' THEN 4 | |||
| END, | |||
| il.expiryDate ASC | |||
| """.trimIndent() | |||
| val params = mapOf( | |||
| "pickOrderLineId" to pickOrderLineId, | |||
| "today" to today | |||
| ) | |||
| val result = jdbcDao.queryForList(sql, mapOf("pickOrderLineId" to pickOrderLineId)) | |||
| val result = jdbcDao.queryForList(sql, params) | |||
| println("Final result count: ${result.size}") | |||
| result.forEach { row -> | |||
| println("Final Row: $row") | |||
| @@ -931,7 +889,6 @@ open class PickOrderService( | |||
| return result | |||
| } | |||
| @Transactional(rollbackFor = [java.lang.Exception::class]) | |||
| open fun releaseConsoPickOrderAction(request: ReleaseConsoPickOrderRequest): ReleasePickOrderInfoResponse { | |||
| val zero = BigDecimal.ZERO | |||
| @@ -993,7 +950,6 @@ open class PickOrderService( | |||
| val releasedBy = SecurityUtils.getUser().getOrNull() | |||
| val user = userService.find(assignTo).orElse(null) | |||
| val pickOrders = pickOrderRepository.findAllByIdInAndStatus(pickOrderIds, PickOrderStatus.ASSIGNED) | |||
| if (pickOrders.isEmpty()) { | |||
| @@ -1007,36 +963,37 @@ open class PickOrderService( | |||
| ) | |||
| } | |||
| // ← GENERATE CONSOCODE EARLY AND SAVE IMMEDIATELY | |||
| val newConsoCode = assignConsoCode() | |||
| val currUser = SecurityUtils.getUser().orElseThrow() | |||
| // Create and save StockOut immediately to prevent duplicate consoCodes | |||
| val stockOut = StockOut().apply { | |||
| this.type = "job" | |||
| this.consoPickOrderCode = newConsoCode | |||
| this.status = StockOutStatus.PENDING.status | |||
| this.handler = currUser.id | |||
| } | |||
| val savedStockOut = stockOutRepository.saveAndFlush(stockOut) // ← FLUSH to commit immediately | |||
| // Update pick orders | |||
| pickOrders.forEach { pickOrder -> | |||
| pickOrder.apply { | |||
| this.releasedBy = releasedBy | |||
| status = PickOrderStatus.RELEASED | |||
| this.assignTo = user | |||
| this.consoCode = newConsoCode // ← Also assign consoCode to pick orders | |||
| } | |||
| } | |||
| val suggestions = suggestedPickLotService.suggestionForPickOrders( | |||
| SuggestedPickLotForPoRequest(pickOrders = pickOrders) | |||
| ) | |||
| val currUser = SecurityUtils.getUser().orElseThrow() | |||
| val stockOut = StockOut().apply { | |||
| this.type = "job" | |||
| this.consoPickOrderCode = null // 单个 pick orders 没有 consoCode | |||
| this.status = StockOutStatus.PENDING.status | |||
| this.handler = currUser.id | |||
| } | |||
| stockOutRepository.save(stockOut) | |||
| val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList) | |||
| pickOrderRepository.saveAll(pickOrders) | |||
| val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn( | |||
| saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id } | |||
| ) | |||
| @@ -1059,7 +1016,8 @@ open class PickOrderService( | |||
| code = "SUCCESS", | |||
| type = "pickorder", | |||
| message = "Pick orders released successfully with inventory management", | |||
| errorPosition = null | |||
| errorPosition = null, | |||
| entity = mapOf("consoCode" to newConsoCode) // ← Return the generated consoCode | |||
| ) | |||
| } catch (e: Exception) { | |||
| @@ -1228,4 +1186,139 @@ open class PickOrderService( | |||
| errorPosition = "", | |||
| ) | |||
| } | |||
| // ✅ 添加缺失的方法:从 pick order line ID 获取 pick order ID | |||
| private fun getPickOrderIdFromPickOrderLineId(pickOrderLineId: Long): Long? { | |||
| val sql = """ | |||
| SELECT po.id as pickOrderId | |||
| FROM fpsmsdb.pick_order_line pol | |||
| JOIN fpsmsdb.pick_order po ON po.id = pol.poId | |||
| WHERE pol.id = :pickOrderLineId | |||
| """.trimIndent() | |||
| val result = jdbcDao.queryForList(sql, mapOf("pickOrderLineId" to pickOrderLineId)) | |||
| return if (result.isNotEmpty()) { | |||
| (result[0]["pickOrderId"] as Number).toLong() | |||
| } else { | |||
| null | |||
| } | |||
| } | |||
| // ✅ 添加检查是否需要 resuggest 的方法 | |||
| private fun checkIfNeedsResuggest(pickOrderLineId: Long): Boolean { | |||
| println("🔍 checkIfNeedsResuggest called with pickOrderLineId: $pickOrderLineId") | |||
| // ✅ 首先执行一个调试查询来查看实际的数值 | |||
| val debugSql = """ | |||
| SELECT | |||
| spl.id as suggestedPickLotId, | |||
| spl.qty as suggestedQty, | |||
| ill.inQty, | |||
| ill.outQty, | |||
| ill.holdQty, | |||
| (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as baseAvailableQty, | |||
| sales_iu.ratioN, | |||
| sales_iu.ratioD, | |||
| CASE | |||
| WHEN sales_iu.ratioN IS NOT NULL AND sales_iu.ratioD IS NOT NULL AND sales_iu.ratioD != 0 | |||
| THEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) / (sales_iu.ratioN / sales_iu.ratioD) | |||
| ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) | |||
| END as convertedAvailableQty | |||
| FROM fpsmsdb.suggested_pick_lot spl | |||
| JOIN fpsmsdb.inventory_lot_line ill ON ill.id = spl.suggestedLotLineId | |||
| JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId | |||
| LEFT JOIN fpsmsdb.item_uom sales_iu ON sales_iu.itemId = il.itemId AND sales_iu.salesUnit = true AND sales_iu.deleted = false | |||
| WHERE spl.pickOrderLineId = :pickOrderLineId | |||
| """.trimIndent() | |||
| println("🔍 Debug SQL: $debugSql") | |||
| val debugResult = jdbcDao.queryForList(debugSql, mapOf("pickOrderLineId" to pickOrderLineId)) | |||
| println("🔍 Debug result:") | |||
| debugResult.forEach { row -> | |||
| println(" SuggestedPickLotId: ${row["suggestedPickLotId"]}") | |||
| println(" SuggestedQty: ${row["suggestedQty"]}") | |||
| println(" InQty: ${row["inQty"]}") | |||
| println(" OutQty: ${row["outQty"]}") | |||
| println(" HoldQty: ${row["holdQty"]}") | |||
| println(" BaseAvailableQty: ${row["baseAvailableQty"]}") | |||
| println(" RatioN: ${row["ratioN"]}") | |||
| println(" RatioD: ${row["ratioD"]}") | |||
| println(" ConvertedAvailableQty: ${row["convertedAvailableQty"]}") | |||
| // ✅ 修复:正确处理 Number 类型转换 | |||
| val convertedAvailableQty = (row["convertedAvailableQty"] as Number).toDouble() | |||
| val suggestedQty = (row["suggestedQty"] as Number).toDouble() | |||
| println(" Is Insufficient: ${convertedAvailableQty < suggestedQty}") | |||
| println(" ---") | |||
| } | |||
| val checkSql = """ | |||
| SELECT | |||
| COUNT(*) as totalSuggestions, | |||
| COUNT(CASE WHEN ill.status != 'available' THEN 1 END) as unavailableLots, | |||
| COUNT(CASE WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 1 END) as expiredLots, | |||
| -- ✅ 修复:使用销售单位进行比较 | |||
| COUNT(CASE WHEN | |||
| CASE | |||
| WHEN sales_iu.ratioN IS NOT NULL AND sales_iu.ratioD IS NOT NULL AND sales_iu.ratioD != 0 | |||
| THEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) / (sales_iu.ratioN / sales_iu.ratioD) | |||
| ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) | |||
| END < spl.qty | |||
| THEN 1 END) as insufficientStockLots, | |||
| COUNT(CASE WHEN sol.status IN ('rejected', 'lot-change', 'determine1') THEN 1 END) as problematicStockOutLines, | |||
| -- ✅ 检查单位不一致问题 | |||
| COUNT(CASE WHEN spl.qty > pol.qty * 100 THEN 1 END) as unitMismatchLots | |||
| FROM fpsmsdb.suggested_pick_lot spl | |||
| JOIN fpsmsdb.inventory_lot_line ill ON ill.id = spl.suggestedLotLineId | |||
| JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId | |||
| JOIN fpsmsdb.pick_order_line pol ON pol.id = spl.pickOrderLineId | |||
| -- ✅ 添加销售单位转换 | |||
| LEFT JOIN fpsmsdb.item_uom sales_iu ON sales_iu.itemId = il.itemId AND sales_iu.salesUnit = true AND sales_iu.deleted = false | |||
| LEFT JOIN fpsmsdb.stock_out_line sol ON sol.inventoryLotLineId = ill.id | |||
| AND sol.pickOrderLineId = spl.pickOrderLineId | |||
| WHERE spl.pickOrderLineId = :pickOrderLineId | |||
| """.trimIndent() | |||
| println("🔍 Executing SQL: $checkSql") | |||
| println("🔍 With parameters: pickOrderLineId = $pickOrderLineId") | |||
| val result = jdbcDao.queryForList(checkSql, mapOf("pickOrderLineId" to pickOrderLineId)) | |||
| println("🔍 SQL result size: ${result.size}") | |||
| if (result.isNotEmpty()) { | |||
| val row = result[0] | |||
| val totalSuggestions = (row["totalSuggestions"] as Number).toInt() | |||
| val unavailableLots = (row["unavailableLots"] as Number).toInt() | |||
| val expiredLots = (row["expiredLots"] as Number).toInt() | |||
| val insufficientStockLots = (row["insufficientStockLots"] as Number).toInt() | |||
| val problematicStockOutLines = (row["problematicStockOutLines"] as Number).toInt() | |||
| val unitMismatchLots = (row["unitMismatchLots"] as Number).toInt() | |||
| println("=== Resuggest Check ===") | |||
| println("Total suggestions: $totalSuggestions") | |||
| println("Unavailable lots: $unavailableLots") | |||
| println("Expired lots: $expiredLots") | |||
| println("Insufficient stock lots: $insufficientStockLots") | |||
| println("Problematic stock out lines: $problematicStockOutLines") | |||
| println("Unit mismatch lots: $unitMismatchLots") | |||
| // 检查是否需要 resuggest 的条件 | |||
| val needsResuggest = ( | |||
| unavailableLots > 0 || | |||
| expiredLots > 0 || | |||
| insufficientStockLots > 0 || | |||
| problematicStockOutLines > 0 || | |||
| unitMismatchLots > 0 || // ✅ 单位不一致检查 | |||
| totalSuggestions == 0 // 没有建议也需要 resuggest | |||
| ) | |||
| println("Needs resuggest: $needsResuggest") | |||
| return needsResuggest | |||
| } else { | |||
| println("🔍 No results returned from SQL query") | |||
| } | |||
| return false | |||
| } | |||
| } | |||
| @@ -13,7 +13,8 @@ data class ReleasePickOrderInfoResponse( | |||
| ) | |||
| data class GetPickOrderInfoResponse( | |||
| // val consoCode: String, | |||
| val consoCode: String?, | |||
| val pickOrders: List<GetPickOrderInfo>, | |||
| val items: List<CurrentInventoryItemInfo> | |||
| ) | |||
| @@ -60,6 +61,7 @@ data class GetPickOrderLineInfo( | |||
| val uomCode: String?, | |||
| val uomDesc: String?, | |||
| val suggestedList: List<SuggestedPickLot>?, | |||
| val pickedQty: BigDecimal?=BigDecimal.ZERO, | |||
| ) | |||
| // Final Response - Conso Pick Order Detail | |||
| @@ -16,4 +16,8 @@ interface StockOutLIneRepository: AbstractRepository<StockOutLine, Long> { | |||
| fun findStockOutLineInfoById(id: Long): StockOutLineInfo | |||
| fun findAllByStockOutId(stockOutId: Long): List<StockOutLine> | |||
| fun findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( | |||
| pickOrderLineId: Long, | |||
| inventoryLotLineId: Long | |||
| ): List<StockOutLine> | |||
| } | |||
| @@ -7,4 +7,8 @@ import org.springframework.stereotype.Repository | |||
| @Repository | |||
| interface SuggestPickLotRepository : AbstractRepository<SuggestedPickLot, Long> { | |||
| fun findAllByPickOrderLineIn(lines: List<PickOrderLine>): List<SuggestedPickLot> | |||
| fun findAllByPickOrderLineIdIn(pickOrderLineIds: List<Long>): List<SuggestedPickLot> | |||
| fun findAllByPickOrderLineId(pickOrderLineId: Long): List<SuggestedPickLot> | |||
| } | |||
| @@ -13,6 +13,8 @@ import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus | |||
| import com.ffii.fpsms.modules.stock.entity.projection.InventoryLotLineInfo | |||
| import com.ffii.fpsms.modules.stock.entity.projection.LotLineToQrcode | |||
| import com.ffii.fpsms.modules.stock.web.InventoryLotLineController | |||
| import com.ffii.fpsms.modules.stock.web.model.UpdateInventoryLotLineStatusRequest | |||
| import com.ffii.fpsms.modules.stock.web.model.ExportQrCodeRequest | |||
| import com.ffii.fpsms.modules.stock.web.model.SaveInventoryLotLineRequest | |||
| import com.ffii.fpsms.modules.stock.web.model.SearchInventoryLotLineInfoRequest | |||
| @@ -29,7 +31,7 @@ import java.math.BigDecimal | |||
| import java.time.format.DateTimeFormatter | |||
| import java.util.Optional | |||
| import kotlin.jvm.optionals.getOrNull | |||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| @Service | |||
| open class InventoryLotLineService( | |||
| private val inventoryLotLineRepository: InventoryLotLineRepository, | |||
| @@ -87,7 +89,45 @@ open class InventoryLotLineService( | |||
| return inventoryLotLineRepository.save(inventoryLotLine) | |||
| } | |||
| @Transactional | |||
| open fun updateInventoryLotLineStatus(request: UpdateInventoryLotLineStatusRequest): MessageResponse { | |||
| try { | |||
| val inventoryLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow() | |||
| // 使用现有的 saveInventoryLotLine 逻辑 | |||
| val updateRequest = SaveInventoryLotLineRequest( | |||
| id = inventoryLotLine.id, | |||
| inventoryLotId = inventoryLotLine.inventoryLot?.id, | |||
| warehouseId = inventoryLotLine.warehouse?.id, | |||
| stockUomId = inventoryLotLine.stockUom?.id, | |||
| inQty = inventoryLotLine.inQty, | |||
| outQty = inventoryLotLine.outQty, | |||
| holdQty = inventoryLotLine.holdQty, | |||
| status = request.status, // 新的状态 | |||
| remarks = inventoryLotLine.remarks | |||
| ) | |||
| val updatedInventoryLotLine = saveInventoryLotLine(updateRequest) | |||
| return MessageResponse( | |||
| id = updatedInventoryLotLine.id, | |||
| name = "Inventory lot line status updated", | |||
| code = "SUCCESS", | |||
| type = "inventory_lot_line", | |||
| message = "Status updated to ${request.status}", | |||
| errorPosition = null | |||
| ) | |||
| } catch (e: Exception) { | |||
| return MessageResponse( | |||
| id = null, | |||
| name = "Failed to update inventory lot line status", | |||
| code = "ERROR", | |||
| type = "inventory_lot_line", | |||
| message = "Error: ${e.message}", | |||
| errorPosition = null | |||
| ) | |||
| } | |||
| } | |||
| @Throws(IOException::class) | |||
| @Transactional | |||
| open fun exportStockInLineQrcode(request: LotLineToQrcode): Map<String, Any> { | |||
| @@ -66,6 +66,23 @@ open class StockOutLineService( | |||
| open fun create(request: CreateStockOutLineRequest): MessageResponse { | |||
| // pick flow step 1 | |||
| // println(request.pickOrderLineId) | |||
| val existingStockOutLine = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( | |||
| request.pickOrderLineId, | |||
| request.inventoryLotLineId | |||
| ) | |||
| if (existingStockOutLine.isNotEmpty()) { | |||
| // ✅ 如果已存在,返回 null 表示不需要创建 | |||
| return MessageResponse( | |||
| id = null, | |||
| name = "Stock out line already exists", | |||
| code = "EXISTS", | |||
| type = "stock_out_line", | |||
| message = "Stock out line already exists for this pick order line and inventory lot line", | |||
| errorPosition = null, | |||
| entity = null, | |||
| ) | |||
| } | |||
| val stockOut = stockOutRepository.findByConsoPickOrderCode(request.consoCode).orElseThrow() | |||
| val pickOrderLine = pickOrderLineRepository.saveAndFlush( | |||
| pickOrderLineRepository.findById(request.pickOrderLineId).orElseThrow() | |||
| @@ -97,48 +114,146 @@ open class StockOutLineService( | |||
| entity = mappedSavedStockOutLine, | |||
| ) | |||
| } | |||
| @Transactional | |||
| fun handleQc(stockOutLine: StockOutLine, request: UpdateStockOutLineRequest): List<StockOutLine?> { | |||
| var newStockOutLine: StockOutLine? = null | |||
| if (request.qty < stockOutLine.qty!!) { | |||
| newStockOutLine = StockOutLine().apply { | |||
| this.pickOrderLine = stockOutLine.pickOrderLine | |||
| this.stockOut = stockOutLine.stockOut | |||
| this.item = stockOutLine.item | |||
| this.qty = stockOutLine.qty!! - request.qty | |||
| this.status = StockOutLineStatus.DETERMINE1.status // escalated | |||
| } | |||
| } | |||
| val inventoryLotLine = if (request.inventoryLotLineId != null) | |||
| inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow() | |||
| else null | |||
| stockOutLine.apply { | |||
| this.inventoryLotLine = inventoryLotLine ?: stockOutLine.inventoryLotLine | |||
| this.qty = request.qty | |||
| this.status = StockOutLineStatus.COMPLETE.status // complete | |||
| } | |||
| // update inventory lot line | |||
| val zero = BigDecimal.ZERO | |||
| val one = BigDecimal.ONE | |||
| val targetLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId!!).orElseThrow() | |||
| val salesUnit = inventoryLotLine?.inventoryLot?.item?.id?.let {_itemId -> itemUomRespository.findByItemIdAndSalesUnitIsTrueAndDeletedIsFalse(_itemId) } | |||
| val ratio = (salesUnit?.ratioN ?: zero).divide(salesUnit?.ratioD ?: one).toDouble() | |||
| val targetLotLineEntry = targetLotLine.apply { | |||
| this.outQty = (this.outQty?: BigDecimal.ZERO) + (request.qty.div(ratio)).toBigDecimal() | |||
| this.holdQty = this.holdQty!!.minus((request.qty.div(ratio)).toBigDecimal()) | |||
| } | |||
| inventoryLotLineRepository.save(targetLotLineEntry) | |||
| // update inventory | |||
| val inventory = inventoryRepository.findByItemId(request.itemId).orElseThrow() | |||
| val inventoryEntry = inventory.apply { | |||
| this.onHandQty = this.onHandQty!!.minus((request.qty.div(ratio)).toBigDecimal()) | |||
| this.onHoldQty = this.onHoldQty!!.minus((request.qty.div(ratio)).toBigDecimal()) | |||
| } | |||
| inventoryRepository.save(inventoryEntry) | |||
| return listOf(stockOutLine, newStockOutLine) | |||
| } | |||
| @Transactional | |||
| fun handleQc(stockOutLine: StockOutLine, request: UpdateStockOutLineRequest): List<StockOutLine?> { | |||
| var newStockOutLine: StockOutLine? = null | |||
| if (request.qty < stockOutLine.qty!!) { | |||
| newStockOutLine = StockOutLine().apply { | |||
| this.pickOrderLine = stockOutLine.pickOrderLine | |||
| this.stockOut = stockOutLine.stockOut | |||
| this.item = stockOutLine.item | |||
| this.qty = stockOutLine.qty!! - request.qty | |||
| this.status = StockOutLineStatus.DETERMINE1.status // escalated | |||
| } | |||
| open fun createWithoutConso(request: CreateStockOutLineWithoutConsoRequest): MessageResponse { | |||
| try { | |||
| // ✅ Get stockOutId from pickOrderLineId with detailed error | |||
| val stockOutId = getStockOutIdFromPickOrderLine(request.pickOrderLineId) | |||
| println("Found stockOutId: $stockOutId for pickOrderLineId: ${request.pickOrderLineId}") | |||
| val stockOut = stockOutRepository.findById(stockOutId).orElseThrow { | |||
| IllegalArgumentException("StockOut not found with ID: $stockOutId") | |||
| } | |||
| val inventoryLotLine = if (request.inventoryLotLineId != null) | |||
| inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow() | |||
| else null | |||
| stockOutLine.apply { | |||
| this.inventoryLotLine = inventoryLotLine ?: stockOutLine.inventoryLotLine | |||
| this.qty = request.qty | |||
| this.status = StockOutLineStatus.COMPLETE.status // complete | |||
| println("Found stockOut: ${stockOut.id} with consoCode: ${stockOut.consoPickOrderCode}") | |||
| val pickOrderLine = pickOrderLineRepository.findById(request.pickOrderLineId).orElseThrow { | |||
| IllegalArgumentException("PickOrderLine not found with ID: ${request.pickOrderLineId}") | |||
| } | |||
| // update inventory lot line | |||
| val zero = BigDecimal.ZERO | |||
| val one = BigDecimal.ONE | |||
| val targetLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId!!).orElseThrow() | |||
| val salesUnit = inventoryLotLine?.inventoryLot?.item?.id?.let {_itemId -> itemUomRespository.findByItemIdAndSalesUnitIsTrueAndDeletedIsFalse(_itemId) } | |||
| val ratio = (salesUnit?.ratioN ?: zero).divide(salesUnit?.ratioD ?: one).toDouble() | |||
| val targetLotLineEntry = targetLotLine.apply { | |||
| this.outQty = (this.outQty?: BigDecimal.ZERO) + (request.qty.div(ratio)).toBigDecimal() | |||
| this.holdQty = this.holdQty!!.minus((request.qty.div(ratio)).toBigDecimal()) | |||
| println("Found pickOrderLine: ${pickOrderLine.id}") | |||
| val updatedPickOrderLine = pickOrderLineRepository.saveAndFlush( | |||
| pickOrderLine.apply { | |||
| this.status = PickOrderLineStatus.PICKING | |||
| } | |||
| ) | |||
| println("Updated pickOrderLine status to: ${updatedPickOrderLine.status}") | |||
| val item = itemRepository.findById(updatedPickOrderLine.item!!.id!!).orElseThrow { | |||
| IllegalArgumentException("Item not found with ID: ${updatedPickOrderLine.item!!.id}") | |||
| } | |||
| inventoryLotLineRepository.save(targetLotLineEntry) | |||
| // update inventory | |||
| val inventory = inventoryRepository.findByItemId(request.itemId).orElseThrow() | |||
| val inventoryEntry = inventory.apply { | |||
| this.onHandQty = this.onHandQty!!.minus((request.qty.div(ratio)).toBigDecimal()) | |||
| this.onHoldQty = this.onHoldQty!!.minus((request.qty.div(ratio)).toBigDecimal()) | |||
| println("Found item: ${item.id} - ${item.code}") | |||
| val inventoryLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow { | |||
| IllegalArgumentException("InventoryLotLine not found with ID: ${request.inventoryLotLineId}") | |||
| } | |||
| inventoryRepository.save(inventoryEntry) | |||
| return listOf(stockOutLine, newStockOutLine) | |||
| println("Found inventoryLotLine: ${inventoryLotLine.id}") | |||
| val stockOutLine = StockOutLine() | |||
| .apply { | |||
| this.item = item | |||
| this.qty = request.qty | |||
| this.stockOut = stockOut | |||
| this.inventoryLotLine = inventoryLotLine | |||
| this.pickOrderLine = updatedPickOrderLine | |||
| this.status = StockOutLineStatus.PENDING.status | |||
| } | |||
| println("Created stockOutLine with qty: ${request.qty}") | |||
| val savedStockOutLine = saveAndFlush(stockOutLine) | |||
| println("Saved stockOutLine with ID: ${savedStockOutLine.id}") | |||
| val mappedSavedStockOutLine = stockOutLineRepository.findStockOutLineInfoById(savedStockOutLine.id!!) | |||
| println("Mapped stockOutLine info: $mappedSavedStockOutLine") | |||
| return MessageResponse( | |||
| id = savedStockOutLine.id, | |||
| name = savedStockOutLine.inventoryLotLine!!.inventoryLot!!.lotNo, | |||
| code = savedStockOutLine.stockOut!!.consoPickOrderCode, | |||
| type = savedStockOutLine.status, | |||
| message = "success", | |||
| errorPosition = null, | |||
| entity = mappedSavedStockOutLine, | |||
| ) | |||
| } catch (e: Exception) { | |||
| println("Error in createWithoutConso: ${e.message}") | |||
| e.printStackTrace() | |||
| throw e | |||
| } | |||
| } | |||
| // ✅ Update helper method with detailed error messages | |||
| private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { | |||
| println("Getting stockOutId for pickOrderLineId: $pickOrderLineId") | |||
| // ✅ Fixed: Use poId instead of pick_order_id | |||
| val sql = """ | |||
| SELECT so.id as stockOutId, so.consoPickOrderCode, po.consoCode | |||
| FROM stock_out so | |||
| JOIN pick_order po ON po.consoCode = so.consoPickOrderCode | |||
| JOIN pick_order_line pol ON pol.poId = po.id -- ✅ Fixed: Use poId | |||
| WHERE pol.id = :pickOrderLineId | |||
| """.trimIndent() | |||
| val result = jdbcDao.queryForList(sql, mapOf("pickOrderLineId" to pickOrderLineId)) | |||
| println("SQL result: $result") | |||
| if (result.isEmpty()) { | |||
| throw IllegalArgumentException("No StockOut found for pickOrderLineId: $pickOrderLineId. Check if pick order line exists and has associated stock out.") | |||
| } | |||
| val stockOutId = result[0]["stockOutId"] as? Long | |||
| val consoPickOrderCode = result[0]["consoPickOrderCode"] as? String | |||
| val consoCode = result[0]["consoCode"] as? String | |||
| println("Found stockOutId: $stockOutId, consoPickOrderCode: $consoPickOrderCode, consoCode: $consoCode") | |||
| if (stockOutId == null) { | |||
| throw IllegalArgumentException("StockOut ID is null for pickOrderLineId: $pickOrderLineId. ConsoCode: $consoCode, ConsoPickOrderCode: $consoPickOrderCode") | |||
| } | |||
| return stockOutId | |||
| } | |||
| @Transactional | |||
| fun handleLotChangeApprovalOrReject(stockOutLine: StockOutLine, request: UpdateStockOutLineRequest): List<StockOutLine?> { | |||
| /** | |||
| @@ -244,4 +359,44 @@ open class StockOutLineService( | |||
| entity = lineInfoList, | |||
| ) | |||
| } | |||
| @Transactional | |||
| open fun updateStatus(request: UpdateStockOutLineStatusRequest): MessageResponse { | |||
| try { | |||
| val stockOutLine = stockOutLineRepository.findById(request.id).orElseThrow { | |||
| IllegalArgumentException("StockOutLine not found with ID: ${request.id}") | |||
| } | |||
| println("Updating StockOutLine ID: ${request.id}") | |||
| println("Current status: ${stockOutLine.status}") | |||
| println("New status: ${request.status}") | |||
| // Update status | |||
| stockOutLine.status = request.status | |||
| // Update quantity if provided | |||
| if (request.qty != null) { | |||
| stockOutLine.qty = request.qty | |||
| } | |||
| val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine) | |||
| println("Updated StockOutLine: ${savedStockOutLine.id} with status: ${savedStockOutLine.status}") | |||
| val mappedSavedStockOutLine = stockOutLineRepository.findStockOutLineInfoById(savedStockOutLine.id!!) | |||
| return MessageResponse( | |||
| id = savedStockOutLine.id, | |||
| name = savedStockOutLine.inventoryLotLine!!.inventoryLot!!.lotNo, | |||
| code = savedStockOutLine.stockOut!!.consoPickOrderCode, | |||
| type = savedStockOutLine.status, | |||
| message = "Stock out line status updated successfully", | |||
| errorPosition = null, | |||
| entity = mappedSavedStockOutLine, | |||
| ) | |||
| } catch (e: Exception) { | |||
| println("Error updating stock out line status: ${e.message}") | |||
| e.printStackTrace() | |||
| throw e | |||
| } | |||
| } | |||
| } | |||
| @@ -17,7 +17,10 @@ import java.math.BigDecimal | |||
| import java.time.LocalDate | |||
| import kotlin.jvm.optionals.getOrDefault | |||
| import kotlin.jvm.optionals.getOrNull | |||
| import org.springframework.transaction.annotation.Transactional | |||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository | |||
| import java.math.RoundingMode | |||
| @Service | |||
| open class SuggestedPickLotService( | |||
| val suggestedPickLotRepository: SuggestPickLotRepository, | |||
| @@ -26,6 +29,7 @@ open class SuggestedPickLotService( | |||
| val pickOrderLineRepository: PickOrderLineRepository, | |||
| val inventoryLotLineService: InventoryLotLineService, | |||
| val itemUomService: ItemUomService, | |||
| val pickOrderRepository: PickOrderRepository | |||
| ) { | |||
| // Calculation Available Qty / Remaining Qty | |||
| open fun calculateRemainingQtyForInfo(inventoryLotLine: InventoryLotLineInfo?): BigDecimal { | |||
| @@ -84,22 +88,28 @@ open class SuggestedPickLotService( | |||
| pols.forEach { line -> | |||
| val salesUnit = line.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } | |||
| val lotLines = availableInventoryLotLines[line.item?.id].orEmpty() | |||
| val ratio = (salesUnit?.ratioN ?: zero).divide(salesUnit?.ratioD ?: one) | |||
| var remainingQty = (line.qty ?: zero).multiply(ratio) | |||
| println("remaining1 $remainingQty") | |||
| val ratio = (salesUnit?.ratioN ?: one).divide(salesUnit?.ratioD ?: one, 10, RoundingMode.HALF_UP) | |||
| // ✅ 修复:remainingQty 应该是销售单位,不需要乘以 ratio | |||
| var remainingQty = line.qty ?: zero | |||
| println("remaining1 $remainingQty (sales units)") | |||
| val updatedLotLines = mutableListOf<InventoryLotLineInfo>() | |||
| lotLines.forEachIndexed { index, lotLine -> | |||
| if (remainingQty <= zero) return@forEachIndexed | |||
| println("calculateRemainingQtyForInfo(lotLine) ${calculateRemainingQtyForInfo(lotLine)}") | |||
| val availableQty = calculateRemainingQtyForInfo(lotLine) | |||
| .multiply(ratio) | |||
| .minus(holdQtyMap[lotLine.id] ?: zero) | |||
| // ✅ 修复:计算可用数量,转换为销售单位 | |||
| val availableQtyInBaseUnits = calculateRemainingQtyForInfo(lotLine) | |||
| val holdQtyInBaseUnits = holdQtyMap[lotLine.id] ?: zero | |||
| val availableQtyInSalesUnits = availableQtyInBaseUnits | |||
| .minus(holdQtyInBaseUnits) | |||
| .divide(ratio, 2, RoundingMode.HALF_UP) | |||
| println("holdQtyMap[lotLine.id] ?: zero ${holdQtyMap[lotLine.id] ?: zero}") | |||
| if (availableQty <= zero) { | |||
| if (availableQtyInSalesUnits <= zero) { | |||
| updatedLotLines += lotLine | |||
| return@forEachIndexed | |||
| } | |||
| @@ -107,27 +117,30 @@ open class SuggestedPickLotService( | |||
| val inventoryLotLine = lotLine.id?.let { inventoryLotLineService.findById(it).getOrNull() } | |||
| val originalHoldQty = inventoryLotLine?.holdQty | |||
| // Update Qty | |||
| val assignQty = minOf(availableQty, remainingQty) | |||
| remainingQty = remainingQty.minus(assignQty) | |||
| holdQtyMap[lotLine.id] = (holdQtyMap[lotLine.id] ?: zero).plus(assignQty) | |||
| // lotLine.holdQty = lotLine.holdQty?.plus(assignQty) | |||
| // ✅ 修复:在销售单位中计算分配数量 | |||
| val assignQtyInSalesUnits = minOf(availableQtyInSalesUnits, remainingQty) | |||
| remainingQty = remainingQty.minus(assignQtyInSalesUnits) | |||
| // ✅ 修复:将销售单位转换为基础单位来更新 holdQty | |||
| val assignQtyInBaseUnits = assignQtyInSalesUnits.multiply(ratio) | |||
| holdQtyMap[lotLine.id] = (holdQtyMap[lotLine.id] ?: zero).plus(assignQtyInBaseUnits) | |||
| suggestedList += SuggestedPickLot().apply { | |||
| type = SuggestedPickLotType.PICK_ORDER | |||
| suggestedLotLine = inventoryLotLine | |||
| pickOrderLine = line | |||
| qty = assignQty | |||
| qty = assignQtyInSalesUnits // ✅ 保存销售单位 | |||
| } | |||
| } | |||
| // if still have remainingQty | |||
| println("remaining2 $remainingQty") | |||
| println("remaining2 $remainingQty (sales units)") | |||
| if (remainingQty > zero) { | |||
| suggestedList += SuggestedPickLot().apply { | |||
| type = SuggestedPickLotType.PICK_ORDER | |||
| suggestedLotLine = null | |||
| pickOrderLine = line | |||
| qty = remainingQty | |||
| qty = remainingQty // ✅ 保存销售单位 | |||
| } | |||
| } | |||
| } | |||
| @@ -184,4 +197,305 @@ open class SuggestedPickLotService( | |||
| open fun saveAll(request: List<SuggestedPickLot>): List<SuggestedPickLot> { | |||
| return suggestedPickLotRepository.saveAllAndFlush(request) | |||
| } | |||
| } | |||
| @Transactional(rollbackFor = [Exception::class]) | |||
| open fun resuggestPickOrder(pickOrderId: Long): MessageResponse { | |||
| try { | |||
| val pickOrder = pickOrderRepository.findById(pickOrderId).orElseThrow() | |||
| // Step 1: Find ALL suggestions for this pick order (not just problematic ones) | |||
| val allSuggestions = findAllSuggestionsForPickOrder(pickOrderId) | |||
| if (allSuggestions.isEmpty()) { | |||
| return MessageResponse( | |||
| id = pickOrderId, | |||
| name = "No suggestions found", | |||
| code = "SUCCESS", | |||
| type = "resuggest", | |||
| message = "No suggestions to resuggest", | |||
| errorPosition = null | |||
| ) | |||
| } | |||
| // Step 2: Calculate total excess quantity from problematic suggestions | |||
| val problematicSuggestions = findProblematicSuggestions(pickOrderId) | |||
| val totalExcessQty = problematicSuggestions.sumOf { suggestion -> | |||
| val pickOrderLine = pickOrder.pickOrderLines.find { it.id == suggestion.pickOrderLine?.id } | |||
| val inventoryLotLine = suggestion.suggestedLotLine | |||
| if (pickOrderLine == null || inventoryLotLine == null) { | |||
| BigDecimal.ZERO | |||
| } else { | |||
| val salesUnit = pickOrderLine.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } | |||
| val ratio = (salesUnit?.ratioN ?: BigDecimal.ONE).divide(salesUnit?.ratioD ?: BigDecimal.ONE, 10, RoundingMode.HALF_UP) | |||
| val availableQtyInBaseUnits = (inventoryLotLine.inQty ?: BigDecimal.ZERO) | |||
| .minus(inventoryLotLine.outQty ?: BigDecimal.ZERO) | |||
| .minus(inventoryLotLine.holdQty ?: BigDecimal.ZERO) | |||
| val suggestionQtyInBaseUnits = (suggestion.qty ?: BigDecimal.ZERO).multiply(ratio) | |||
| val excessQtyInBaseUnits = suggestionQtyInBaseUnits.minus(availableQtyInBaseUnits) | |||
| if (excessQtyInBaseUnits > BigDecimal.ZERO) { | |||
| excessQtyInBaseUnits | |||
| } else { | |||
| BigDecimal.ZERO | |||
| } | |||
| } | |||
| } | |||
| // Step 3: Remove ALL existing suggestions and reset holdQty | |||
| val affectedLotLineIds = allSuggestions.mapNotNull { it.suggestedLotLine?.id }.distinct() | |||
| val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn(affectedLotLineIds) | |||
| inventoryLotLines.forEach { lotLine -> | |||
| // ✅ FIX: Reset holdQty to 0 for lots being resuggested | |||
| lotLine.holdQty = BigDecimal.ZERO | |||
| } | |||
| inventoryLotLineRepository.saveAll(inventoryLotLines) | |||
| // Delete ALL suggestions for this pick order | |||
| suggestedPickLotRepository.deleteAllById(allSuggestions.mapNotNull { it.id }) | |||
| // Step 4: Generate completely new suggestions following FEFO | |||
| val newSuggestions = generateCorrectSuggestionsWithExistingHolds(pickOrder) | |||
| // Step 5: Save new suggestions and update holdQty | |||
| val savedSuggestions = suggestedPickLotRepository.saveAll(newSuggestions) | |||
| val newInventoryLotLines = inventoryLotLineRepository.findAllByIdIn( | |||
| savedSuggestions.mapNotNull { it.suggestedLotLine?.id } | |||
| ) | |||
| /* | |||
| savedSuggestions.forEach { suggestion -> | |||
| val lotLine = newInventoryLotLines.find { it.id == suggestion.suggestedLotLine?.id } | |||
| lotLine?.let { | |||
| it.holdQty = (it.holdQty ?: BigDecimal.ZERO).plus(suggestion.qty ?: BigDecimal.ZERO) | |||
| } | |||
| } | |||
| inventoryLotLineRepository.saveAll(newInventoryLotLines) | |||
| */ | |||
| return MessageResponse( | |||
| id = pickOrderId, | |||
| name = "Pick order resuggested successfully", | |||
| code = "SUCCESS", | |||
| type = "resuggest", | |||
| message = "Redistributed ${allSuggestions.size} suggestions following FEFO order", | |||
| errorPosition = null | |||
| ) | |||
| } catch (e: Exception) { | |||
| return MessageResponse( | |||
| id = pickOrderId, | |||
| name = "Failed to resuggest pick order", | |||
| code = "ERROR", | |||
| type = "resuggest", | |||
| message = "Error: ${e.message}", | |||
| errorPosition = null | |||
| ) | |||
| } | |||
| } | |||
| private fun findAllSuggestionsForPickOrder(pickOrderId: Long): List<SuggestedPickLot> { | |||
| val pickOrderLines = pickOrderRepository.findById(pickOrderId) | |||
| .orElseThrow() | |||
| .pickOrderLines | |||
| val pickOrderLineIds = pickOrderLines.mapNotNull { it.id } | |||
| return suggestedPickLotRepository.findAllByPickOrderLineIdIn(pickOrderLineIds) | |||
| } | |||
| private fun generateSuggestionsForExcessQuantity(pickOrder: PickOrder, excessQtyInBaseUnits: BigDecimal): List<SuggestedPickLot> { | |||
| val suggestions = mutableListOf<SuggestedPickLot>() | |||
| val zero = BigDecimal.ZERO | |||
| val one = BigDecimal.ONE | |||
| pickOrder.pickOrderLines.forEach { orderLine -> | |||
| val itemId = orderLine.item?.id ?: return@forEach | |||
| // Get sales unit conversion ratio | |||
| val salesUnit = itemUomService.findSalesUnitByItemId(itemId) | |||
| val ratio = (salesUnit?.ratioN ?: one).divide(salesUnit?.ratioD ?: one, 10, RoundingMode.HALF_UP) | |||
| // Convert excess quantity to sales units | |||
| val excessQtyInSalesUnits = excessQtyInBaseUnits.divide(ratio, 0, RoundingMode.HALF_UP) | |||
| if (excessQtyInSalesUnits <= zero) return@forEach | |||
| // Get available inventory lots for this item (FEFO order) | |||
| val availableLots = inventoryLotLineService | |||
| .allInventoryLotLinesByItemIdIn(listOf(itemId)) | |||
| .filter { it.status == InventoryLotLineStatus.AVAILABLE.value } | |||
| .sortedBy { it.expiryDate } | |||
| var remainingQtyInBaseUnits = excessQtyInBaseUnits | |||
| availableLots.forEach { lotInfo -> | |||
| if (remainingQtyInBaseUnits <= zero) return@forEach | |||
| val lot = lotInfo.id?.let { inventoryLotLineRepository.findById(it).orElse(null) } | |||
| ?: return@forEach | |||
| val totalQty = lot.inQty ?: zero | |||
| val outQty = lot.outQty ?: zero | |||
| val holdQty = lot.holdQty ?: zero | |||
| val availableQtyInBaseUnits = totalQty.minus(outQty).minus(holdQty) | |||
| if (availableQtyInBaseUnits <= zero) return@forEach | |||
| val assignQtyInBaseUnits = minOf(availableQtyInBaseUnits, remainingQtyInBaseUnits) | |||
| remainingQtyInBaseUnits = remainingQtyInBaseUnits.minus(assignQtyInBaseUnits) | |||
| val assignQtyInSalesUnits = assignQtyInBaseUnits.divide(ratio, 0, RoundingMode.DOWN) | |||
| if (assignQtyInSalesUnits > zero) { | |||
| suggestions.add(SuggestedPickLot().apply { | |||
| type = SuggestedPickLotType.PICK_ORDER | |||
| suggestedLotLine = lot | |||
| pickOrderLine = orderLine | |||
| qty = assignQtyInSalesUnits | |||
| }) | |||
| } | |||
| } | |||
| } | |||
| return suggestions | |||
| } | |||
| private fun findProblematicSuggestions(pickOrderId: Long): List<SuggestedPickLot> { | |||
| val pickOrderLines = pickOrderRepository.findById(pickOrderId) | |||
| .orElseThrow() | |||
| .pickOrderLines | |||
| val pickOrderLineIds = pickOrderLines.mapNotNull { it.id } | |||
| println("=== DEBUG: findProblematicSuggestions ===") | |||
| println("Pick Order ID: $pickOrderId") | |||
| println("Pick Order Line IDs: $pickOrderLineIds") | |||
| val allSuggestions = suggestedPickLotRepository.findAllByPickOrderLineIdIn(pickOrderLineIds) | |||
| println("Total suggestions found: ${allSuggestions.size}") | |||
| val problematicSuggestions = allSuggestions.filter { suggestion -> | |||
| val pickOrderLine = pickOrderLines.find { it.id == suggestion.pickOrderLine?.id } | |||
| val inventoryLotLine = suggestion.suggestedLotLine | |||
| if (pickOrderLine == null || inventoryLotLine == null) { | |||
| println("Suggestion ${suggestion.id}: Missing pickOrderLine or inventoryLotLine") | |||
| return@filter true | |||
| } | |||
| // Get sales unit conversion ratio | |||
| val salesUnit = pickOrderLine.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } | |||
| val ratio = (salesUnit?.ratioN ?: BigDecimal.ONE).divide(salesUnit?.ratioD ?: BigDecimal.ONE, 10, RoundingMode.HALF_UP) | |||
| val ratioCheck = (suggestion.qty ?: BigDecimal.ZERO).divide(pickOrderLine.qty ?: BigDecimal.ONE, 2, RoundingMode.HALF_UP) | |||
| // Calculate available qty in base units | |||
| val availableQtyInBaseUnits = (inventoryLotLine.inQty ?: BigDecimal.ZERO) | |||
| .minus(inventoryLotLine.outQty ?: BigDecimal.ZERO) | |||
| .minus(inventoryLotLine.holdQty ?: BigDecimal.ZERO) | |||
| // Convert available qty to sales units for comparison | |||
| val availableQtyInSalesUnits = availableQtyInBaseUnits.divide(ratio, 2, RoundingMode.HALF_UP) | |||
| // Convert suggestion qty to base units for comparison | |||
| val suggestionQtyInBaseUnits = (suggestion.qty ?: BigDecimal.ZERO).multiply(ratio) | |||
| println("Suggestion ${suggestion.id}: qty=${suggestion.qty} (sales), requiredQty=${pickOrderLine.qty} (sales), ratio=$ratioCheck") | |||
| println(" - availableQtyInBaseUnits=$availableQtyInBaseUnits, availableQtyInSalesUnits=$availableQtyInSalesUnits") | |||
| println(" - suggestionQtyInBaseUnits=$suggestionQtyInBaseUnits") | |||
| // Flag as problematic if: | |||
| val exceedsRatio = ratioCheck > BigDecimal("10") | |||
| val exceedsAvailable = suggestionQtyInBaseUnits > availableQtyInBaseUnits | |||
| println(" - exceedsRatio=$exceedsRatio, exceedsAvailable=$exceedsAvailable") | |||
| val isProblematic = exceedsRatio || exceedsAvailable | |||
| if (isProblematic) { | |||
| println("Suggestion ${suggestion.id}: PROBLEMATIC - ratio=$ratioCheck, exceedsAvailable=$exceedsAvailable") | |||
| } | |||
| isProblematic | |||
| } | |||
| println("Problematic suggestions found: ${problematicSuggestions.size}") | |||
| return problematicSuggestions | |||
| } | |||
| private fun generateCorrectSuggestionsWithExistingHolds(pickOrder: PickOrder): List<SuggestedPickLot> { | |||
| val suggestions = mutableListOf<SuggestedPickLot>() | |||
| val zero = BigDecimal.ZERO | |||
| val one = BigDecimal.ONE | |||
| pickOrder.pickOrderLines.forEach { orderLine -> | |||
| val itemId = orderLine.item?.id ?: return@forEach | |||
| val requiredQty = orderLine.qty ?: zero // This is in sales units | |||
| println("=== DEBUG: generateCorrectSuggestionsWithExistingHolds ===") | |||
| println("Item ID: $itemId, Required Qty: $requiredQty") | |||
| // Get sales unit conversion ratio | |||
| val salesUnit = itemUomService.findSalesUnitByItemId(itemId) | |||
| val ratio = (salesUnit?.ratioN ?: one).divide(salesUnit?.ratioD ?: one, 10, RoundingMode.HALF_UP) | |||
| println("Ratio: $ratio") | |||
| // Get available inventory lots for this item (FEFO order) | |||
| val availableLots = inventoryLotLineService | |||
| .allInventoryLotLinesByItemIdIn(listOf(itemId)) | |||
| .filter { it.status == InventoryLotLineStatus.AVAILABLE.value } | |||
| .sortedBy { it.expiryDate } | |||
| var remainingQtyInSalesUnits = requiredQty | |||
| availableLots.forEach { lotInfo -> | |||
| if (remainingQtyInSalesUnits <= zero) return@forEach | |||
| // Get the actual InventoryLotLine entity | |||
| val lot = lotInfo.id?.let { inventoryLotLineRepository.findById(it).orElse(null) } | |||
| ?: return@forEach | |||
| // ✅ FIX: Calculate available qty directly in sales units | |||
| val totalQty = lot.inQty ?: zero | |||
| val outQty = lot.outQty ?: zero | |||
| val holdQty = lot.holdQty ?: zero | |||
| val availableQtyInBaseUnits = totalQty.minus(outQty).minus(holdQty) | |||
| // ✅ FIX: Convert to sales units with proper rounding | |||
| val availableQtyInSalesUnits = availableQtyInBaseUnits.divide(ratio, 2, RoundingMode.HALF_UP) | |||
| println("Lot ${lot.id}: availableQtyInBaseUnits=$availableQtyInBaseUnits, availableQtyInSalesUnits=$availableQtyInSalesUnits") | |||
| if (availableQtyInSalesUnits <= zero) return@forEach | |||
| // ✅ FIX: Take what we can from this lot (constrained by available AND remaining) | |||
| val assignQtyInSalesUnits = minOf(availableQtyInSalesUnits, remainingQtyInSalesUnits) | |||
| remainingQtyInSalesUnits = remainingQtyInSalesUnits.minus(assignQtyInSalesUnits) | |||
| println("Lot ${lot.id}: assignQtyInSalesUnits=$assignQtyInSalesUnits, remainingQtyInSalesUnits=$remainingQtyInSalesUnits") | |||
| if (assignQtyInSalesUnits > zero) { | |||
| suggestions.add(SuggestedPickLot().apply { | |||
| type = SuggestedPickLotType.PICK_ORDER | |||
| suggestedLotLine = lot | |||
| pickOrderLine = orderLine | |||
| qty = assignQtyInSalesUnits // Store in sales units | |||
| }) | |||
| } | |||
| } | |||
| // If we still have remaining qty, create a suggestion with null lot (insufficient stock) | |||
| if (remainingQtyInSalesUnits > zero) { | |||
| println("Remaining Qty in Sales Units: $remainingQtyInSalesUnits") | |||
| suggestions.add(SuggestedPickLot().apply { | |||
| type = SuggestedPickLotType.PICK_ORDER | |||
| suggestedLotLine = null // No lot available | |||
| pickOrderLine = orderLine | |||
| qty = remainingQtyInSalesUnits | |||
| }) | |||
| } | |||
| } | |||
| return suggestions | |||
| } | |||
| } | |||
| @@ -21,6 +21,8 @@ import java.io.OutputStream | |||
| import java.io.UnsupportedEncodingException | |||
| import java.math.BigDecimal | |||
| import java.text.ParseException | |||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| import com.ffii.fpsms.modules.stock.web.model.UpdateInventoryLotLineStatusRequest | |||
| @RequestMapping("/inventoryLotLine") | |||
| @RestController | |||
| @@ -75,4 +77,8 @@ class InventoryLotLineController ( | |||
| response.addHeader("filename", "${pdf["fileName"]}.pdf") | |||
| out.write(JasperExportManager.exportReportToPdf(jasperPrint)); | |||
| } | |||
| @PostMapping("/updateStatus") | |||
| fun updateInventoryLotLineStatus(@RequestBody request: UpdateInventoryLotLineStatusRequest): MessageResponse { | |||
| return inventoryLotLineService.updateInventoryLotLineStatus(request) | |||
| } | |||
| } | |||
| @@ -8,8 +8,10 @@ import com.ffii.fpsms.modules.stock.service.StockInLineService | |||
| import com.ffii.fpsms.modules.stock.service.StockOutLineService | |||
| import com.ffii.fpsms.modules.stock.web.model.CreateStockOutLineRequest | |||
| import com.ffii.fpsms.modules.stock.web.model.UpdateStockOutLineRequest | |||
| import com.ffii.fpsms.modules.stock.web.model.UpdateStockOutLineStatusRequest | |||
| import jakarta.validation.Valid | |||
| import org.springframework.web.bind.annotation.* | |||
| import com.ffii.fpsms.modules.stock.web.model.CreateStockOutLineWithoutConsoRequest | |||
| @RestController | |||
| @RequestMapping("/stockOutLine") | |||
| @@ -25,9 +27,18 @@ class StockOutLineController( | |||
| fun create(@Valid @RequestBody request: CreateStockOutLineRequest): MessageResponse { | |||
| return stockOutLineService.create(request) | |||
| } | |||
| @PostMapping("/createWithoutConso") | |||
| fun createWithoutConso(@Valid @RequestBody request: CreateStockOutLineWithoutConsoRequest): MessageResponse { | |||
| return stockOutLineService.createWithoutConso(request) | |||
| } | |||
| @PostMapping("/update") | |||
| fun update(@Valid @RequestBody request: UpdateStockOutLineRequest): MessageResponse { | |||
| println("triggering") | |||
| return stockOutLineService.update(request) | |||
| } | |||
| @PostMapping("/updateStatus") | |||
| fun updateStatus(@Valid @RequestBody request: UpdateStockOutLineStatusRequest): MessageResponse { | |||
| return stockOutLineService.updateStatus(request) | |||
| } | |||
| } | |||
| @@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.PathVariable | |||
| import org.springframework.web.bind.annotation.PostMapping | |||
| import org.springframework.web.bind.annotation.RequestMapping | |||
| import org.springframework.web.bind.annotation.RestController | |||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| @RequestMapping("/suggestedPickLot") | |||
| @RestController | |||
| @@ -23,4 +24,9 @@ class SuggestedPickLotController( | |||
| // val test1 = pickOrderRepository.findAllByConsoCode(conso).flatMap { it.pickOrderLines } | |||
| // return suggestedPickLotService.suggestionForPickOrderLines(test1) | |||
| // } | |||
| @PostMapping("/resuggest/{pickOrderId}") | |||
| fun resuggestPickOrder(@PathVariable pickOrderId: Long): MessageResponse { | |||
| return suggestedPickLotService.resuggestPickOrder(pickOrderId) | |||
| } | |||
| } | |||
| @@ -11,3 +11,7 @@ data class LotLineInfo( | |||
| val remainingQty: BigDecimal, | |||
| val uom: String | |||
| ) | |||
| data class UpdateInventoryLotLineStatusRequest( | |||
| val inventoryLotLineId: Long, | |||
| val status: String | |||
| ) | |||
| @@ -46,3 +46,15 @@ data class UpdateStockOutLineRequest( | |||
| val pickTime: LocalDateTime?, | |||
| // val pickerId: Long? | |||
| ) | |||
| data class CreateStockOutLineWithoutConsoRequest( | |||
| val pickOrderLineId: Long, | |||
| val inventoryLotLineId: Long, | |||
| val stockOutId: Long, | |||
| val qty: Double, | |||
| ) | |||
| data class UpdateStockOutLineStatusRequest( | |||
| val id: Long, | |||
| val status: String, | |||
| val qty: Double? = null, | |||
| val remarks: String? = null | |||
| ) | |||