Преглед изворни кода

update

reset-do-picking-order
CANCERYS\kw093 пре 4 дана
родитељ
комит
f88fcfda0d
3 измењених фајлова са 124 додато и 363 уклоњено
  1. +24
    -2
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt
  2. +99
    -360
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt
  3. +1
    -1
      src/main/java/com/ffii/fpsms/modules/stock/entity/SuggestPickLotRepository.kt

+ 24
- 2
src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt Прегледај датотеку

@@ -213,7 +213,8 @@ open class PickExecutionIssueService(
println("=== Final IssueQty Calculation ===")
println(" Calculated IssueQty: $issueQty")
println("================================================")
println("=== Processing Logic Selection ===")

// 5. 创建 pick execution issue 记录
val issueNo = generateIssueNo()
println("Generated issue number: $issueNo")
@@ -360,7 +361,12 @@ open class PickExecutionIssueService(
println("→ Handling: Miss Item With Partial Pick")
handleMissItemWithPartialPick(request, actualPickQtyForProcessing, missQtyForProcessing)
}
actualPickQtyForProcessing == BigDecimal.ZERO &&
missQtyForProcessing == BigDecimal.ZERO &&
badItemQtyForProcessing == BigDecimal.ZERO -> {
println("→ Handling: All zero, mark stock out line as completed")
handleAllZeroMarkCompleted(request)
}
// 情况5: 正常拣货 (actualPickQty > 0, 没有 miss 或 bad item)
actualPickQtyForProcessing > BigDecimal.ZERO -> {
println("→ Handling: Normal Pick")
@@ -419,6 +425,22 @@ open class PickExecutionIssueService(
)
}
}
private fun handleAllZeroMarkCompleted(request: PickExecutionIssueRequest) {
val stockOutLines = stockOutLineRepository
.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(
request.pickOrderLineId,
request.lotId ?: 0L
)
stockOutLines.forEach { sol ->
sol.status = "completed"
sol.modified = LocalDateTime.now()
sol.modifiedBy = "system"
stockOutLineRepository.save(sol)
println("All-zero case: mark stock out line ${sol.id} as completed (qty kept as ${sol.qty})")
}
stockOutLineRepository.flush()
}
private fun generateIssueNo(): String {
val now = LocalDateTime.now()
val yearMonth = now.format(java.time.format.DateTimeFormatter.ofPattern("yyMM"))


+ 99
- 360
src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt Прегледај датотеку

@@ -21,6 +21,7 @@ import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderGroup
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderGroupRepository
import com.ffii.fpsms.modules.pickOrder.web.models.*
import com.ffii.fpsms.modules.stock.entity.InventoryLotLine
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository
import com.ffii.fpsms.modules.stock.entity.StockOut
import com.ffii.fpsms.modules.stock.entity.StockOutLIneRepository
@@ -3357,286 +3358,7 @@ ORDER BY
val enrichedResults = filteredResults
return enrichedResults
}
// 修改后的逻辑
/*
open fun getAllPickOrderLotsWithDetailsHierarchicalold(userId: Long): Map<String, Any?> {
println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical (Repository-based) ===")
println("userId filter: $userId")

val user = userService.find(userId).orElse(null)
if (user == null) {
println("❌ User not found: $userId")
return emptyMap()
}

// Step 1:直接按 handledBy 查当前用户的活动 do_pick_order(一个 ticket)
val activeTicketStatuses = listOf("released", "picking") // 如果你用的是 DoPickOrderStatus 枚举,也可以改成 List<DoPickOrderStatus>
val doPickOrder = doPickOrderRepository
.findFirstByHandledByAndDeletedFalseAndTicketStatusIn(user.id!!, activeTicketStatuses)

if (doPickOrder == null) {
println("❌ No active do_pick_order found for handledBy user $userId")
return mapOf(
"fgInfo" to null,
"pickOrders" to emptyList<Any>()
)
}

val doPickOrderId = doPickOrder.id!!
println(" Using do_pick_order ID (by handledBy): $doPickOrderId")

// Step 2:用这个 do_pick_orderId 查对应的 do_pick_order_line / pick_order
val allDoPickOrderLines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(doPickOrderId)
val allPickOrderIdsForThisTicket = allDoPickOrderLines.mapNotNull { it.pickOrderId }.distinct()

println(" Found ${allPickOrderIdsForThisTicket.size} pick orders in this do_pick_order (including completed)")

// Step 3:加载这些 pick orders(包括 COMPLETED)
val pickOrders = pickOrderRepository.findAllById(allPickOrderIdsForThisTicket)
.filter {
it.deleted == false &&
it.assignTo?.id == userId &&
it.type?.value == "do"
}

println(" Loaded ${pickOrders.size} pick orders (including completed)")

// Step 4:原来你从 3413 行开始的收集所有 line / lots 的逻辑,全部保留
val allPickOrderLineIds = pickOrders.flatMap { it.pickOrderLines }.mapNotNull { it.id }

val allSuggestions = suggestPickLotRepository.findAllByPickOrderLineIdIn(allPickOrderLineIds)
val allStockOutLines = allPickOrderLineIds.flatMap { lineId ->
stockOutLIneRepository.findAllByPickOrderLineIdAndDeletedFalse(lineId)
}

val suggestionsByLineId = allSuggestions.groupBy { spl: SuggestedPickLot ->
spl.pickOrderLine?.id
}
val stockOutLinesByLineId = allStockOutLines.groupBy { sol: StockOutLineInfo ->
sol.pickOrderLineId
}

val allPickOrderLines = mutableListOf<Map<String, Any?>>()
val lineCountsPerPickOrder = mutableListOf<Int>()
val pickOrderIdsList = mutableListOf<Long>()
val pickOrderCodesList = mutableListOf<String>()
val doOrderIdsList = mutableListOf<Long>()
val deliveryOrderCodesList = mutableListOf<String>()

pickOrders.forEach { po ->
pickOrderIdsList.add(po.id!!)
pickOrderCodesList.add(po.code ?: "")

val doOrderId = po.deliveryOrder?.id
if (doOrderId != null) doOrderIdsList.add(doOrderId)
deliveryOrderCodesList.add(po.deliveryOrder?.code ?: "")

val lines = po.pickOrderLines.filter { !it.deleted }

val lineDtos = po.pickOrderLines
.filter { !it.deleted }
.map { pol ->
val lineId = pol.id
val item = pol.item
val uom = pol.uom
// 获取该 line 的 suggestions 和 stock out lines
val suggestions = lineId?.let { suggestionsByLineId[it] } ?: emptyList()
val stockOutLines = lineId?.let { stockOutLinesByLineId[it] } ?: emptyList()
// 构建 lots(合并相同 lot 的多个 suggestions)
val lotMap = mutableMapOf<Long?, LotDetailResponse>()
suggestions.forEach { spl ->
val ill = spl.suggestedLotLine
if (ill != null && ill.id != null) {
val illId = ill.id!!
val illEntity = inventoryLotLinesMap[illId] ?: ill
val il = illEntity.inventoryLot
val w = illEntity.warehouse
val isExpired = il?.expiryDate?.let { exp -> exp.isBefore(today) } == true
val availableQty = (illEntity.inQty ?: zero)
.minus(illEntity.outQty ?: zero)
.minus(illEntity.holdQty ?: zero)
// 查找对应的 stock out line
val stockOutLine = stockOutLines.find { sol ->
sol.inventoryLotLineId == illId
}
// 计算 actualPickQty
val actualPickQty = stockOutLine?.qty?.let { numToBigDecimal(it as? Number) }
if (lotMap.containsKey(illId)) {
// 合并 requiredQty
val existing = lotMap[illId]!!
val newRequiredQty = (existing.requiredQty ?: zero).plus(spl.qty ?: zero)
lotMap[illId] = existing.copy(requiredQty = newRequiredQty)
} else {
lotMap[illId] = LotDetailResponse(
id = illId,
lotNo = il?.lotNo,
expiryDate = il?.expiryDate,
location = w?.code,
stockUnit = illEntity.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A",
availableQty = availableQty,
requiredQty = spl.qty,
actualPickQty = actualPickQty,
inQty = illEntity.inQty,
outQty = illEntity.outQty,
holdQty = illEntity.holdQty,
lotStatus = illEntity.status?.value,
lotAvailability = when {
isExpired -> "expired"
stockOutLine?.status == "rejected" -> "rejected"
availableQty <= zero -> "insufficient_stock"
illEntity.status?.value == "unavailable" -> "status_unavailable"
else -> "available"
},
processingStatus = when {
stockOutLine?.status == "completed" -> "completed"
stockOutLine?.status == "rejected" -> "rejected"
else -> "pending"
},
suggestedPickLotId = spl.id,
stockOutLineId = stockOutLine?.id,
stockOutLineStatus = stockOutLine?.status,
stockOutLineQty = stockOutLine?.qty?.let { numToBigDecimal(it as? Number) },
router = RouterInfoResponse(
id = null,
index = w?.order.toString(),
route = w?.code,
area = w?.code
)
)
}
}
}
val lots = lotMap.values.toList()
// 构建 stockouts(包括没有 lot 的)
val stockouts = stockOutLines.map { sol ->
val illId = sol.inventoryLotLineId
val ill = illId?.let { inventoryLotLinesMap[it] }
val il = ill?.inventoryLot
val w = ill?.warehouse
val available = if (ill == null) null else
(ill.inQty ?: zero)
.minus(ill.outQty ?: zero)
.minus(ill.holdQty ?: zero)
StockOutDetailResponse(
id = sol.id,
status = sol.status,
qty = sol.qty?.let { numToBigDecimal(it as? Number) },
lotId = ill?.id,
lotNo = il?.lotNo ?: "",
location = w?.code ?: "",
availableQty = available,
noLot = (ill == null)
)
}
PickOrderLineDetailResponse(
id = lineId,
requiredQty = pol.qty,
status = pol.status?.value,
item = ItemInfoResponse(
id = item?.id,
code = item?.code,
name = item?.name,
uomCode = uom?.code,
uomDesc = uom?.udfudesc,
uomShortDesc = uom?.udfShortDesc
),
lots = lots,
stockouts = stockouts
)
}


lineCountsPerPickOrder.add(lineDtos.size)
allPickOrderLines.addAll(lineDtos)
}

// 排序、fgInfo、mergedPickOrder 这些也全部沿用你当前代码,只要用上面定义好的 doPickOrder/doPickOrderId 即可:
allPickOrderLines.sortWith(compareBy(
{ line ->
val lots = line["lots"] as? List<Map<String, Any?>>
val firstLot = lots?.firstOrNull()
val router = firstLot?.get("router") as? Map<String, Any?>
val indexValue = router?.get("index")
val floorSortValue = when (indexValue) {
is String -> {
val parts = indexValue.split("-")
if (parts.isNotEmpty()) {
val floorPart = parts[0].uppercase()
when (floorPart) {
"1F" -> 1
"2F", "4F" -> 2
else -> 3
}
} else 3
}
else -> 3
}
floorSortValue
},
{ line ->
val lots = line["lots"] as? List<Map<String, Any?>>
val firstLot = lots?.firstOrNull()
val router = firstLot?.get("router") as? Map<String, Any?>
val indexValue = router?.get("index")
when (indexValue) {
is Number -> indexValue.toInt()
is String -> {
val parts = indexValue.split("-")
if (parts.size > 1) {
parts.last().toIntOrNull() ?: 999999
} else {
indexValue.toIntOrNull() ?: 999999
}
}
else -> 999999
}
}
))

val fgInfo = mapOf(
"doPickOrderId" to doPickOrderId,
"ticketNo" to doPickOrder.ticketNo,
"storeId" to doPickOrder.storeId,
"shopCode" to doPickOrder.shopCode,
"shopName" to doPickOrder.shopName,
"truckLanceCode" to doPickOrder.truckLanceCode,
"departureTime" to doPickOrder.truckDepartureTime?.toString()
)

val mergedPickOrder = if (pickOrders.isNotEmpty()) {
val firstPickOrder = pickOrders.first()
mapOf(
"pickOrderIds" to pickOrderIdsList,
"pickOrderCodes" to pickOrderCodesList,
"doOrderIds" to doOrderIdsList,
"deliveryOrderCodes" to deliveryOrderCodesList,
"lineCountsPerPickOrder" to lineCountsPerPickOrder,
"consoCodes" to pickOrders.mapNotNull { it.consoCode }.distinct(),
"status" to doPickOrder.ticketStatus?.value,
"targetDate" to firstPickOrder.targetDate?.toLocalDate()?.toString(),
"pickOrderLines" to allPickOrderLines
)
} else {
null
}

return mapOf(
"fgInfo" to fgInfo,
"pickOrders" to listOfNotNull(mergedPickOrder)
)
}
*/
open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, Any?> {
println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical (NEW STRUCTURE) ===")
println("userId filter: $userId")
@@ -4159,112 +3881,105 @@ println("DEBUG sol polIds in linesResults: " + linesResults.mapNotNull { it["sto
)
}
}

@Transactional(rollbackFor = [java.lang.Exception::class])
open fun confirmLotSubstitution(req: LotSubstitutionConfirmRequest): MessageResponse {
val zero = BigDecimal.ZERO
// Validate pick order line
val pol = req.pickOrderLineId.let { pickOrderLineRepository.findById(it).orElse(null) }
val pol = pickOrderLineRepository.findById(req.pickOrderLineId).orElse(null)
?: return MessageResponse(
id = null, name = "Pick order line not found", code = "ERROR", type = "pickorder",
message = "Pick order line ${req.pickOrderLineId} not found", errorPosition = null
)
val polItemId = pol.item?.id
if (polItemId == null) {
return MessageResponse(
?: return MessageResponse(
id = null, name = "Item not found", code = "ERROR", type = "pickorder",
message = "Pick order line item is null", errorPosition = null
)
}
// ✅ 根据 lotNo 和 itemId 查找新的 InventoryLotLine
val newIll = when {
// 优先使用 stockInLineId(更可靠)
req.newStockInLineId != null && req.newStockInLineId > 0 -> {
// 通过 stockInLineId 查找 InventoryLot
val inventoryLot = inventoryLotRepository.findByStockInLineIdAndDeletedFalse(req.newStockInLineId)
?: return MessageResponse(
id = null, name = "Inventory lot not found", code = "ERROR", type = "pickorder",
message = "Inventory lot with stockInLineId ${req.newStockInLineId} not found",
errorPosition = null
)
// 通过 InventoryLot 和 itemId 查找 InventoryLotLine
val lotLines = inventoryLotLineRepository.findAllByInventoryLotId(inventoryLot.id!!)
.filter { it.inventoryLot?.item?.id == polItemId && !it.deleted }
if (lotLines.isEmpty()) {
return MessageResponse(
id = null, name = "Lot line not found", code = "ERROR", type = "pickorder",
message = "Inventory lot line with stockInLineId ${req.newStockInLineId} and itemId ${polItemId} not found",
errorPosition = null
)
}
// 如果有多个,取第一个(通常应该只有一个)
lotLines.first()
}
// 兼容旧方式:使用 lotNo
req.newInventoryLotNo != null && req.newInventoryLotNo.isNotBlank() -> {
inventoryLotLineRepository.findByLotNoAndItemId(req.newInventoryLotNo, polItemId)
?: return MessageResponse(
id = null, name = "New lot line not found", code = "ERROR", type = "pickorder",
message = "Inventory lot line with lotNo '${req.newInventoryLotNo}' and itemId ${polItemId} not found",
errorPosition = null
)
}
else -> {
return MessageResponse(
id = null, name = "Invalid request", code = "ERROR", type = "pickorder",
message = "Either newStockInLineId or newInventoryLotNo must be provided",
errorPosition = null
)
}
}
// Find new InventoryLotLine (from stockInLineId first, fallback lotNo)
val newIll = resolveNewInventoryLotLine(req, polItemId)
?: return MessageResponse(
id = null, name = "New lot line not found", code = "ERROR", type = "pickorder",
message = "Cannot resolve new inventory lot line", errorPosition = null
)
// Item consistency check (应该已经通过上面的查询保证了,但再次确认)
// Item consistency check
val newItemId = newIll.inventoryLot?.item?.id
if (newItemId == null || polItemId != newItemId) {
if (newItemId == null || newItemId != polItemId) {
return MessageResponse(
id = null, name = "Item mismatch", code = "ERROR", type = "pickorder",
message = "New lot line item does not match pick order line item", errorPosition = null
)
}
val newIllId = newIll.id ?: return MessageResponse(
id = null, name = "Invalid lot line", code = "ERROR", type = "pickorder",
message = "New inventory lot line has no ID", errorPosition = null
)
val newLotNo = newIll.inventoryLot?.lotNo ?: req.newInventoryLotNo ?: "unknown"
// 1) Update suggested pick lot (if provided): move holdQty from old ILL to new ILL and re-point the suggestion
if (req.originalSuggestedPickLotId != null && req.originalSuggestedPickLotId > 0) {
// ✅ 使用 repository 而不是 SQL
val originalSpl = suggestPickLotRepository.findById(req.originalSuggestedPickLotId).orElse(null)
if (originalSpl != null) {
val oldIll = originalSpl.suggestedLotLine
val qty = originalSpl.qty ?: zero
// Resolve SuggestedPickLot:
// - If originalSuggestedPickLotId provided: use it
// - Else (1:1 assumption): find by pickOrderLineId (optionally also by stockOutLineId if you add repository method)
val spl = if (req.originalSuggestedPickLotId != null && req.originalSuggestedPickLotId > 0) {
suggestPickLotRepository.findById(req.originalSuggestedPickLotId).orElse(null)
} else {
// 1:1 assumption fallback (you need a repository method; replace with your actual one)
// e.g. suggestPickLotRepository.findFirstByPickOrderLineIdAndDeletedFalseOrderByIdDesc(req.pickOrderLineId)
suggestPickLotRepository.findFirstByPickOrderLineId(req.pickOrderLineId)
}
if (oldIll != null && oldIll.id != newIllId) {
// Decrease hold on old, increase on new
oldIll.holdQty = (oldIll.holdQty ?: zero).minus(qty).max(zero)
inventoryLotLineRepository.save(oldIll)
newIll.holdQty = (newIll.holdQty ?: zero).plus(qty)
inventoryLotLineRepository.save(newIll)
}
if (spl == null) {
return MessageResponse(
id = null, name = "Suggested pick lot not found", code = "ERROR", type = "pickorder",
message = "SuggestedPickLot not found for pickOrderLineId=${req.pickOrderLineId}", errorPosition = null
)
}
// ✅ 使用 repository 更新 suggestion
originalSpl.suggestedLotLine = newIll
suggestPickLotRepository.save(originalSpl)
}
val qtyToHold = spl.qty ?: zero
if (qtyToHold.compareTo(zero) <= 0) {
return MessageResponse(
id = null, name = "Invalid qty", code = "ERROR", type = "pickorder",
message = "SuggestedPickLot qty is invalid: $qtyToHold", errorPosition = null
)
}
// Availability check on newIll BEFORE updates
val inQty = newIll.inQty ?: zero
val outQty = newIll.outQty ?: zero
val holdQty = newIll.holdQty ?: zero
val issueQty = newIll.issueQty ?: zero
val available = inQty.subtract(outQty).subtract(holdQty).subtract(issueQty)
if (available.compareTo(qtyToHold) < 0) {
return MessageResponse(
id = null, name = "Insufficient lot qty", code = "REJECT", type = "pickorder",
message = "Reject switch lot: available=$available < required=$qtyToHold", errorPosition = null
)
}
// 2) Update stock out line (if provided): re-point to new ILL; keep qty and status unchanged
val oldIll = spl.suggestedLotLine
// If oldIll exists and different: move hold old -> new
if (oldIll != null && oldIll.id != null && oldIll.id != newIll.id) {
val oldHold = oldIll.holdQty ?: zero
val newOldHold = oldHold.subtract(qtyToHold)
oldIll.holdQty = if (newOldHold.compareTo(zero) < 0) zero else newOldHold
inventoryLotLineRepository.save(oldIll)
val newHold = (newIll.holdQty ?: zero).add(qtyToHold)
newIll.holdQty = newHold
inventoryLotLineRepository.save(newIll)
}
// If first bind (oldIll == null): just hold on new
if (oldIll == null) {
val newHold = (newIll.holdQty ?: zero).add(qtyToHold)
newIll.holdQty = newHold
inventoryLotLineRepository.save(newIll)
}
// Point suggestion to new lot line
spl.suggestedLotLine = newIll
suggestPickLotRepository.save(spl)
// Update stock out line if provided
if (req.stockOutLineId != null && req.stockOutLineId > 0) {
val sol = stockOutLIneRepository.findById(req.stockOutLineId).orElse(null)
if (sol != null) {
@@ -4274,15 +3989,39 @@ println("DEBUG sol polIds in linesResults: " + linesResults.mapNotNull { it["sto
}
}
val newLotNo = newIll.inventoryLot?.lotNo ?: req.newInventoryLotNo
return MessageResponse(
id = null,
name = "Lot substitution confirmed",
code = "SUCCESS",
type = "pickorder",
message = "Updated suggestion and stock out line to new lot line with lotNo '${newLotNo}'",
errorPosition = null
message = "Updated suggestion and stock out line to new lot line with lotNo '$newLotNo'",
errorPosition = null
)
}
private fun resolveNewInventoryLotLine(
req: LotSubstitutionConfirmRequest,
itemId: Long
): InventoryLotLine? {
// Prefer stockInLineId
if (req.newStockInLineId != null && req.newStockInLineId > 0) {
val inventoryLot = inventoryLotRepository.findByStockInLineIdAndDeletedFalse(req.newStockInLineId)
?: return null
val lotLines = inventoryLotLineRepository.findAllByInventoryLotId(inventoryLot.id!!)
.filter { it.inventoryLot?.item?.id == itemId && !it.deleted }
return lotLines.firstOrNull()
}
// Fallback lotNo (req.newInventoryLotNo is non-null String in your model)
if (req.newInventoryLotNo.isNotBlank()) {
return inventoryLotLineRepository.findByLotNoAndItemId(req.newInventoryLotNo, itemId)
}
return null
}

open fun getCompletedDoPickOrders(
userId: Long,


+ 1
- 1
src/main/java/com/ffii/fpsms/modules/stock/entity/SuggestPickLotRepository.kt Прегледај датотеку

@@ -8,7 +8,7 @@ import org.springframework.stereotype.Repository
interface SuggestPickLotRepository : AbstractRepository<SuggestedPickLot, Long> {
fun findAllByPickOrderLineIn(lines: List<PickOrderLine>): List<SuggestedPickLot>
fun findAllByPickOrderLineIdIn(pickOrderLineIds: List<Long>): List<SuggestedPickLot>
fun findFirstByPickOrderLineId(pickOrderLineId: Long): SuggestedPickLot?
fun findAllByPickOrderLineId(pickOrderLineId: Long): List<SuggestedPickLot>
}

Loading…
Откажи
Сачувај