浏览代码

update

master
CANCERYS\kw093 2 个月前
父节点
当前提交
04f3c54339
共有 4 个文件被更改,包括 736 次插入155 次删除
  1. +50
    -45
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt
  2. +7
    -5
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt
  3. +663
    -105
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt
  4. +16
    -0
      src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt

+ 50
- 45
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt 查看文件

@@ -444,13 +444,8 @@ open class DeliveryOrderService(
println("�� DEBUG: Target date: $targetDate, Date prefix: $datePrefix")
// Get next ticket number for this date
val nextTicketNumber = doPickOrderService.getNextTicketNumber(datePrefix)
println("🔍 DEBUG: Next ticket number: $nextTicketNumber")
// ✅ Find truck by shop ID with earliest departure time
val truck = deliveryOrder.shop?.id?.let { shopId ->
println("�� DEBUG: Looking for truck with shop ID: $shopId")
println("🔍 DEBUG: Looking for truck with shop ID: $shopId")
val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId)
println("🔍 DEBUG: Found ${trucks.size} trucks for shop $shopId")
trucks.forEach { t ->
@@ -463,48 +458,58 @@ open class DeliveryOrderService(
println("🔍 DEBUG: Processing ${deliveryOrder.deliveryOrderLines.size} delivery order lines")
deliveryOrder.deliveryOrderLines.forEach { line ->
val storeId = if (deliveryOrder.supplier?.code == "P06B") "4/F" else "2/F"
println(" DEBUG: Processing line - Item ID: ${line.item?.id}, Store ID: $storeId")
val doPickOrderRecord = DoPickOrderRecord( // ✅ Use DoPickOrderRecord instead of DoPickOrder
storeId = storeId,
ticketNo = nextTicketNumber,
ticketStatus = DoPickOrderStatus.pending,
truckId = truck?.id,
pickOrderId = createdPickOrder.id,
truckDepartureTime = truck?.departureTime,
itemId = line.item?.id,
shopId = deliveryOrder.shop?.id,
shopPoSupplierId = deliveryOrder.shop?.id,
handledBy = null
)
println(" DEBUG: Creating DoPickOrderRecord - Store: $storeId, Ticket: $nextTicketNumber, Truck: ${truck?.id}")
val savedDoPickOrderRecord = doPickOrderRecordRepository.save(doPickOrderRecord) // ✅ Now types match
println("🔍 DEBUG: Saved DoPickOrderRecord - ID: ${savedDoPickOrderRecord.id}")
// ✅ Group lines by store ID to get unique ticket numbers per store
val linesByStore = deliveryOrder.deliveryOrderLines.groupBy { line ->
if (deliveryOrder.supplier?.code == "P06B") "4/F" else "2/F"
}
deliveryOrder.deliveryOrderLines.forEach { line ->
val storeId = if (deliveryOrder.supplier?.code == "P06B") "4/F" else "2/F"
println("�� DEBUG: Creating DoPickOrder - Store: $storeId, Ticket: $nextTicketNumber, Truck: ${truck?.id}")
linesByStore.forEach { (storeId, lines) ->
// ✅ Get ticket number for this specific store
val nextTicketNumber = doPickOrderService.getNextTicketNumber(datePrefix, storeId)
println("🔍 DEBUG: Next ticket number for store $storeId: $nextTicketNumber")
val doPickOrder = DoPickOrder( // ✅ Create DoPickOrder for assignment
storeId = storeId,
ticketNo = nextTicketNumber,
ticketStatus = DoPickOrderStatus.pending,
truckId = truck?.id,
pickOrderId = createdPickOrder.id,
truckDepartureTime = truck?.departureTime,
itemId = line.item?.id,
shopId = deliveryOrder.shop?.id,
shopPoSupplierId = deliveryOrder.shop?.id,
handledBy = null
)
lines.forEach { line ->
println("�� DEBUG: Processing line - Item ID: ${line.item?.id}, Store ID: $storeId")
val doPickOrderRecord = DoPickOrderRecord(
storeId = storeId,
ticketNo = nextTicketNumber,
ticketStatus = DoPickOrderStatus.pending,
truckId = truck?.id,
pickOrderId = createdPickOrder.id,
truckDepartureTime = truck?.departureTime,
itemId = line.item?.id,
shopId = deliveryOrder.shop?.id,
shopPoSupplierId = deliveryOrder.shop?.id,
handledBy = null
)
println("�� DEBUG: Creating DoPickOrderRecord - Store: $storeId, Ticket: $nextTicketNumber, Truck: ${truck?.id}")
val savedDoPickOrderRecord = doPickOrderRecordRepository.save(doPickOrderRecord)
println("🔍 DEBUG: Saved DoPickOrderRecord - ID: ${savedDoPickOrderRecord.id}")
}
val savedDoPickOrder = doPickOrderService.save(doPickOrder)
println("🔍 DEBUG: Saved DoPickOrder - ID: ${savedDoPickOrder.id}")
// ✅ Also create DoPickOrder records for this store
lines.forEach { line ->
println("�� DEBUG: Creating DoPickOrder - Store: $storeId, Ticket: $nextTicketNumber, Truck: ${truck?.id}")
val doPickOrder = DoPickOrder(
storeId = storeId,
ticketNo = nextTicketNumber,
ticketStatus = DoPickOrderStatus.pending,
truckId = truck?.id,
pickOrderId = createdPickOrder.id,
truckDepartureTime = truck?.departureTime,
itemId = line.item?.id,
shopId = deliveryOrder.shop?.id,
shopPoSupplierId = deliveryOrder.shop?.id,
handledBy = null
)
val savedDoPickOrder = doPickOrderService.save(doPickOrder)
println("🔍 DEBUG: Saved DoPickOrder - ID: ${savedDoPickOrder.id}")
}
}
return MessageResponse(
id = deliveryOrder.id,


+ 7
- 5
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt 查看文件

@@ -41,16 +41,18 @@ class DoPickOrderService(
private val doPickOrderRecordRepository: DoPickOrderRecordRepository
) {
fun getNextTicketNumber(datePrefix: String): String {
println("🔍 DEBUG: Getting next ticket number for date prefix: $datePrefix")
fun getNextTicketNumber(datePrefix: String, storeId: String): String {
println("🔍 DEBUG: Getting next ticket number for date prefix: $datePrefix, store: $storeId")
try {
val todayTickets = doPickOrderRepository.findByTicketNoStartingWith("${datePrefix}_")
println("🔍 DEBUG: Found ${todayTickets.size} existing tickets with prefix ${datePrefix}_")
// ✅ Include store ID in the search pattern
val searchPattern = "${datePrefix}_${storeId}_"
val todayTickets = doPickOrderRepository.findByTicketNoStartingWith(searchPattern)
println("🔍 DEBUG: Found ${todayTickets.size} existing tickets with prefix $searchPattern")
todayTickets.forEach { ticket ->
println("�� DEBUG: Existing ticket: ${ticket.ticketNo}, Status: ${ticket.ticketStatus}")
}
val nextNumber = (todayTickets.size + 1).toString().padStart(3, '0')
val ticketNumber = "${datePrefix}_${nextNumber}"
val ticketNumber = "${datePrefix}_${storeId}_${nextNumber}"
println("🔍 DEBUG: Generated ticket number: $ticketNumber")
return ticketNumber
} catch (e: Exception) {


+ 663
- 105
src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt 查看文件

@@ -56,6 +56,7 @@ import com.ffii.fpsms.modules.deliveryOrder.service.DoPickOrderService
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrder
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecord
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecordRepository
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRepository
@Service
open class PickOrderService(
private val jdbcDao: JdbcDao,
@@ -77,7 +78,9 @@ open class PickOrderService(
private val truckRepository: TruckRepository,
private val doPickOrderService: DoPickOrderService,
private val routerRepository: RouterRepository,
private val doPickOrderRecordRepository: DoPickOrderRecordRepository
private val doPickOrderRecordRepository: DoPickOrderRecordRepository,
private val doPickOrderRepository: DoPickOrderRepository

) : AbstractBaseEntityService<PickOrder, Long, PickOrderRepository>(jdbcDao, pickOrderRepository) {
open fun create(request: SavePickOrderRequest): MessageResponse {
@@ -1584,113 +1587,224 @@ logger.info("Precreated $precreated stock out lines for suggested lots on releas
}


fun getAllPickOrderLotsWithDetails(pickOrderIds: List<Long>): List<Map<String, Any>> {
val today = LocalDate.now()
// Add this new method to PickOrderService.kt
@Transactional(rollbackFor = [java.lang.Exception::class])
open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo: String, userId: Long): MessageResponse {
try {
println("=== DEBUG: autoAssignAndReleasePickOrderByStoreAndTicket ===")
println("storeId: $storeId, ticketNo: $ticketNo, userId: $userId")

val zero = BigDecimal.ZERO
println("=== Debug: getAllPickOrderLotsWithDetails ===")
println("today: $today")
println("pickOrderIds: $pickOrderIds")
val releasedBy = SecurityUtils.getUser().getOrNull()
val user = userService.find(userId).orElse(null)
if (pickOrderIds.isEmpty()) {
return emptyList()
if (user == null) {
return MessageResponse(
id = null,
name = "User not found",
code = "ERROR",
type = "pickorder",
message = "User with ID $userId not found",
errorPosition = null
)
}

// Find the do_pick_order by store_id and ticket_no
val sql = """
SELECT
-- Pick Order Information
po.id as pickOrderId,
po.code as pickOrderCode,
po.targetDate as pickOrderTargetDate,
po.consoCode as pickOrderConsoCode,
po.type as pickOrderType,
po.status as pickOrderStatus,
po.assignTo as pickOrderAssignTo,
pog.name as groupName,
-- Pick Order Line Information
pol.id as pickOrderLineId,
pol.qty as pickOrderLineRequiredQty,
pol.status as pickOrderLineStatus,
-- Item Information
i.id as itemId,
i.code as itemCode,
i.name as itemName,
uc.code as uomCode,
uc.udfudesc as uomDesc,
-- Lot Information
ill.id as lotId,
il.lotNo,
il.expiryDate,
w.name as location,
COALESCE(uc.udfudesc, 'N/A') as stockUnit,
-- ✅ FIXED: Set quantities to NULL for rejected lots
CASE
WHEN sol.status = 'rejected' THEN NULL
ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0))
END as availableQty,
-- Required quantity for this lot
COALESCE(spl.qty, 0) as requiredQty,
-- Actual picked quantity
COALESCE(sol.qty, 0) as actualPickQty,
-- Suggested pick lot information
spl.id as suggestedPickLotId,
sol.status as lotStatus,
-- Stock out line information
sol.id as stockOutLineId,
sol.status as stockOutLineStatus,
COALESCE(sol.qty, 0) as stockOutLineQty,
-- Lot availability status
CASE
WHEN sol.status = 'rejected' THEN 'rejected'
WHEN sol.status = 'completed' THEN 'completed'
WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'unavailable'
ELSE 'available'
END as lotAvailability,
-- Processing status
CASE
WHEN sol.status = 'completed' THEN 'completed'
WHEN sol.status = 'rejected' THEN 'rejected'
WHEN sol.status = 'created' THEN 'pending'
ELSE 'pending'
END as processingStatus
FROM pick_order po
LEFT JOIN pick_order_group pog ON po.groupId = pog.id
LEFT JOIN pick_order_line pol ON po.id = pol.pickOrderId
LEFT JOIN items i ON pol.itemId = i.id
LEFT JOIN uom_conversion uc ON i.uomId = uc.id
LEFT JOIN suggested_pick_lot spl ON pol.id = spl.pickOrderLineId
LEFT JOIN inventory_lot_line ill ON spl.suggestedLotLineId = ill.id
LEFT JOIN inventory_lot il ON ill.lotId = il.id
LEFT JOIN warehouse w ON ill.warehouseId = w.id
LEFT JOIN stock_out_line sol ON spl.id = sol.suggestedPickLotId
WHERE po.id IN (${pickOrderIds.joinToString(",")})
AND po.status IN ('assigned', 'released', 'picking')
AND pol.status IN ('assigned', 'released', 'picking')
AND (sol.status IS NULL OR sol.status != 'completed')
ORDER BY po.id, pol.id, ill.id
SELECT DISTINCT dpo.pick_order_id AS pickOrderId
FROM do_pick_order dpo
WHERE dpo.deleted = false
AND dpo.store_id = :storeId
AND dpo.ticket_no = :ticketNo
AND dpo.pick_order_id IS NOT NULL
""".trimIndent()
println("🔍 Executing SQL for all pick order lots: $sql")
val result = jdbcDao.queryForList(sql, emptyMap<String, Any>())
val idRows = jdbcDao.queryForList(sql, mapOf("storeId" to storeId, "ticketNo" to ticketNo))
val pickOrderIdsByTicket = idRows.mapNotNull { row ->
when (val id = row["pickOrderId"]) {
is Number -> id.toLong()
is String -> id.toLongOrNull()
else -> null
}
}.toSet()
println("Total result count (including completed and rejected): ${result.size}")
result.forEach { row ->
println("Row: $row")
println("Candidate pickOrderIds by ticket $ticketNo: $pickOrderIdsByTicket")

if (pickOrderIdsByTicket.isEmpty()) {
return MessageResponse(
id = null,
name = "No pick orders found",
code = "NO_ORDERS",
type = "pickorder",
message = "No pick orders found for store $storeId with ticket $ticketNo",
errorPosition = null
)
}
return result
// Check if user already has pick orders in progress
val userExistingOrders = pickOrderRepository.findAllByAssignToAndStatusIn(
user,
listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED)
).filter { it.deliveryOrder != null }

if (userExistingOrders.isNotEmpty()) {
println("🔍 DEBUG: User $userId already has ${userExistingOrders.size} pick orders in progress")
return MessageResponse(
id = null,
name = "User already has pick orders",
code = "USER_BUSY",
type = "pickorder",
message = "User $userId already has ${userExistingOrders.size} pick orders in progress. Cannot assign new orders.",
errorPosition = null,
entity = mapOf(
"existingOrders" to userExistingOrders.map { mapOf(
"id" to it.id,
"code" to it.code,
"status" to it.status?.value
)}
)
)
}

// Find available pick orders for the specific ticket
val availablePickOrders = pickOrderRepository
.findAll()
.filter { it.id != null && pickOrderIdsByTicket.contains(it.id!!) }
.filter { it.deliveryOrder != null }
.filter { it.assignTo == null }
.filter { it.status == PickOrderStatus.PENDING || it.status == PickOrderStatus.RELEASED }
.sortedBy { it.targetDate }
.take(1) // Take only one pick order

if (availablePickOrders.isEmpty()) {
return MessageResponse(
id = null,
name = "No available pick orders",
code = "NO_ORDERS",
type = "pickorder",
message = "No unassigned pick orders available for store $storeId with ticket $ticketNo",
errorPosition = null
)
}

val selected = availablePickOrders.first()
val currUser = SecurityUtils.getUser().orElseThrow()

// If still PENDING, perform full release flow
if (selected.status == PickOrderStatus.PENDING) {
val newConsoCode = assignConsoCode()

val stockOut = StockOut().apply {
this.type = "job"
this.consoPickOrderCode = newConsoCode
this.status = StockOutStatus.PENDING.status
this.handler = currUser.id
}
val savedStockOut = stockOutRepository.saveAndFlush(stockOut)

selected.apply {
this.releasedBy = releasedBy
status = PickOrderStatus.RELEASED
this.consoCode = newConsoCode
}

val suggestions = suggestedPickLotService.suggestionForPickOrders(
SuggestedPickLotForPoRequest(pickOrders = listOf(selected))
)
val saveSuggestedPickLots = suggestedPickLotService.saveAll(suggestions.suggestedList)
pickOrderRepository.saveAndFlush(selected)

val inventoryLotLines = inventoryLotLineRepository.findAllByIdIn(
saveSuggestedPickLots.mapNotNull { it.suggestedLotLine?.id }
)
saveSuggestedPickLots.forEach { lot ->
val lotLineId = lot.suggestedLotLine?.id
if (lotLineId != null) {
val idx = inventoryLotLines.indexOf(lot.suggestedLotLine)
if (idx >= 0) {
val currentHold = inventoryLotLines[idx].holdQty ?: zero
val addHold = lot.qty ?: zero
inventoryLotLines[idx].holdQty = currentHold.plus(addHold)
}
}
}
inventoryLotLineRepository.saveAll(inventoryLotLines)

// Pre-create stock out lines
var precreated = 0
saveSuggestedPickLots.forEach { lot ->
val polId = lot.pickOrderLine?.id
val illId = lot.suggestedLotLine?.id
if (polId != null && illId != null) {
val existing = stockOutLIneRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId)
if (existing.isEmpty()) {
val pol = pickOrderLineRepository.findById(polId).orElse(null)
val ill = inventoryLotLineRepository.findById(illId).orElse(null)
if (pol != null && ill != null) {
val line = com.ffii.fpsms.modules.stock.entity.StockOutLine().apply {
this.stockOut = savedStockOut
this.pickOrderLine = pol
this.inventoryLotLine = ill
this.item = pol.item
this.status = com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status
this.qty = 0.0
}
stockOutLIneRepository.save(line)
precreated++
}
}
}
}
println("Precreated $precreated stock out lines for ticket $ticketNo")
}

// Assign user (both pending->released and already released)
selected.assignTo = user
pickOrderRepository.saveAndFlush(selected)
// Update do_pick_order_record entries
val existingRecords = doPickOrderRecordRepository.findByPickOrderId(selected.id!!)
println("🔍 DEBUG: Found ${existingRecords.size} existing DoPickOrderRecord entries for pick order ${selected.id}")

if (existingRecords.isNotEmpty()) {
existingRecords.forEach { record ->
record.handledBy = user.id
record.ticketStatus = DoPickOrderStatus.released
println("🔍 DEBUG: Updating existing DoPickOrderRecord ID: ${record.id} - handledBy: ${user.id}, status: released")
}
doPickOrderRecordRepository.saveAll(existingRecords)
println("✅ Updated ${existingRecords.size} existing DoPickOrderRecord entries")
}
doPickOrderService.updateHandledByForPickOrder(selected.id!!, user.id!!)
println("✅ Updated DoPickOrder handledBy to user $userId for pick order ${selected.id}")
return MessageResponse(
id = null,
name = "Pick order assigned by ticket",
code = "SUCCESS",
type = "pickorder",
message = "Assigned to user $userId for store $storeId with ticket $ticketNo",
errorPosition = null,
entity = mapOf(
"pickOrderIds" to listOf(selected.id!!),
"storeId" to storeId,
"ticketNo" to ticketNo,
"status" to selected.status?.value
)
)
} catch (e: Exception) {
e.printStackTrace()
return MessageResponse(
id = null,
name = "Failed to auto-assign by ticket",
code = "ERROR",
type = "pickorder",
message = "Failed to auto-assign by ticket: ${e.message}",
errorPosition = null
)
}
}

open fun getPickOrderDetailsOptimized(pickOrderIds: List<Long>): GetPickOrderInfoResponse {
@@ -2263,10 +2377,13 @@ open fun autoAssignAndReleasePickOrderByStore(userId: Long, storeId: String): Me
val datePrefix = targetDate.format(DateTimeFormatter.ofPattern("ddMMyy"))
println("�� DEBUG: Target date: $targetDate, Date prefix: $datePrefix")
// Get next ticket number for this date
val nextTicketNumber = doPickOrderService.getNextTicketNumber(datePrefix)
println("�� DEBUG: Next ticket number: $nextTicketNumber")

val storeId = if (deliveryOrder.supplier?.code == "P06B") "4/F" else "2/F"
println("🔍 DEBUG: Determined store ID: $storeId")

// Get next ticket number for this date and store
val nextTicketNumber = doPickOrderService.getNextTicketNumber(datePrefix, storeId)
println("🔍 DEBUG: Next ticket number: $nextTicketNumber")
// ✅ Find truck by shop ID with earliest departure time
val truck = deliveryOrder.shop?.id?.let { shopId ->
@@ -2698,7 +2815,15 @@ if (existingRecords.isNotEmpty()) {
println("⚠️ Shop ID is null")
null
}
val ticketNo = try {
val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId)
val ticketNo = doPickOrders.firstOrNull()?.ticketNo ?: ""
println("🔍 Found ticket number: $ticketNo for pick order $pickOrderId")
ticketNo
} catch (e: Exception) {
println("⚠️ Error getting ticket number for pick order $pickOrderId: ${e.message}")
""
}
val result = mapOf(
"pickOrderId" to (pickOrder.id ?: 0L),
"pickOrderCode" to (pickOrder.code ?: ""),
@@ -2716,6 +2841,7 @@ if (existingRecords.isNotEmpty()) {
"numberOfCartons" to (pickOrder.pickOrderLines.size),
"truckNo" to (truck?.truckNo ?: ""), // ✅ Use entity property
"DepartureTime" to (truck?.departureTime?.toString() ?: ""),
"ticketNo" to ticketNo,
"qrCodeData" to (pickOrder.id ?: 0L)
)
@@ -2829,6 +2955,7 @@ open fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId: Long): List<Map
i.name as itemName,
uc.code as uomCode,
uc.udfudesc as uomDesc,
uc.udfShortDesc as uomShortDesc,
-- Lot Information (similar to lot-details endpoint)
ill.id as lotId,
@@ -2841,6 +2968,7 @@ open fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId: Long): List<Map
r.id as routerId,
r.index as routerIndex,
r.route as routerRoute,
-- ✅ FIXED: Set quantities to NULL for rejected lots
CASE
WHEN sol.status = 'rejected' THEN NULL
@@ -3013,4 +3141,434 @@ open fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId: Long): List<Map
return enrichedResults
}
// ... existing code ...

// Fix the method signature and return types
open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, Any?> {
println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical ===")
println("today: ${LocalDate.now()}")
println("userId filter: $userId")
// Get all pick order IDs assigned to the user (both RELEASED and PENDING with doId)
val user = userService.find(userId).orElse(null)
if (user == null) {
println("❌ User not found: $userId")
return emptyMap()
}
// Get all pick orders assigned to user with PENDING or RELEASED status that have doId
val allAssignedPickOrders = pickOrderRepository.findAllByAssignToAndStatusIn(
user,
listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED, PickOrderStatus.COMPLETED)
).filter { it.deliveryOrder != null } // Only pick orders with doId
println("🔍 DEBUG: Found ${allAssignedPickOrders.size} pick orders assigned to user $userId")
// ✅ NEW LOGIC: Filter based on assignment and status
val filteredPickOrders = if (allAssignedPickOrders.isNotEmpty()) {
// Check if there are any RELEASED orders assigned to this user (active work)
val assignedReleasedOrders = allAssignedPickOrders.filter {
it.status == PickOrderStatus.RELEASED && it.assignTo?.id == userId
}
if (assignedReleasedOrders.isNotEmpty()) {
// ✅ If there are assigned RELEASED orders, show only those
println("🔍 DEBUG: Found ${assignedReleasedOrders.size} assigned RELEASED orders, showing only those")
assignedReleasedOrders
} else {
// ✅ If no assigned RELEASED orders, show only the latest COMPLETED order
val completedOrders = allAssignedPickOrders.filter { it.status == PickOrderStatus.COMPLETED }
if (completedOrders.isNotEmpty()) {
val latestCompleted = completedOrders.maxByOrNull { it.completeDate ?: it.modified ?: LocalDateTime.MIN }
println(" DEBUG: No assigned RELEASED orders, showing latest completed order: ${latestCompleted?.code}")
listOfNotNull(latestCompleted)
} else {
println("🔍 DEBUG: No orders found")
emptyList()
}
}
} else {
emptyList()
}
val pickOrderIds = filteredPickOrders.map { it.id!! }
println("🎯 Pick order IDs to fetch: $pickOrderIds")
if (pickOrderIds.isEmpty()) {
return mapOf(
"pickOrder" to null as Any?,
"pickOrderLines" to emptyList<Map<String, Any>>() as Any?
)
}
// Use the same SQL query but transform the results into hierarchical structure
val pickOrderIdsStr = pickOrderIds.joinToString(",")
val sql = """
SELECT
-- Pick Order Information
po.id as pickOrderId,
po.code as pickOrderCode,
po.consoCode as pickOrderConsoCode,
DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate,
po.type as pickOrderType,
po.status as pickOrderStatus,
po.assignTo as pickOrderAssignTo,
-- Pick Order Line Information
pol.id as pickOrderLineId,
pol.qty as pickOrderLineRequiredQty,
pol.status as pickOrderLineStatus,
-- Item Information
i.id as itemId,
i.code as itemCode,
i.name as itemName,
uc.code as uomCode,
uc.udfudesc as uomDesc,
uc.udfShortDesc as uomShortDesc,
-- Lot Information
ill.id as lotId,
il.lotNo,
DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate,
w.name as location,
COALESCE(uc.udfudesc, 'N/A') as stockUnit,
-- Router Information
r.id as routerId,
r.index as routerIndex,
r.route as routerRoute,
r.area as routerArea,
-- ✅ FIXED: Set quantities to NULL for rejected lots
CASE
WHEN sol.status = 'rejected' THEN NULL
ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0))
END as availableQty,
-- Required quantity for this lot
COALESCE(spl.qty, 0) as requiredQty,
-- Actual picked quantity
COALESCE(sol.qty, 0) as actualPickQty,
-- Suggested pick lot information
spl.id as suggestedPickLotId,
ill.status as lotStatus,
-- Stock out line information
sol.id as stockOutLineId,
sol.status as stockOutLineStatus,
COALESCE(sol.qty, 0) as stockOutLineQty,
-- Additional detailed fields
COALESCE(ill.inQty, 0) as inQty,
COALESCE(ill.outQty, 0) as outQty,
COALESCE(ill.holdQty, 0) as holdQty,
COALESCE(spl.suggestedLotLineId, ill.id) as debugSuggestedLotLineId,
ill.inventoryLotId as debugInventoryLotId,
-- Calculate total picked quantity by ALL pick orders for this lot
COALESCE((
SELECT SUM(sol_all.qty)
FROM fpsmsdb.stock_out_line sol_all
WHERE sol_all.inventoryLotLineId = ill.id
AND sol_all.deleted = false
AND sol_all.status IN ('pending', 'checked', 'partially_completed', 'completed')
), 0) as totalPickedByAllPickOrders,
-- Calculate remaining available quantity correctly
(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as remainingAfterAllPickOrders,
-- Lot availability status
CASE
WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired'
WHEN sol.status = 'rejected' THEN 'rejected'
WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock'
WHEN ill.status = 'unavailable' THEN 'status_unavailable'
ELSE 'available'
END as lotAvailability,
-- Processing status
CASE
WHEN sol.status = 'completed' THEN 'completed'
WHEN sol.status = 'rejected' THEN 'rejected'
WHEN sol.status = 'created' THEN 'pending'
ELSE 'pending'
END as processingStatus
FROM fpsmsdb.pick_order po
JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id
JOIN fpsmsdb.items i ON i.id = pol.itemId
LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId
LEFT JOIN fpsmsdb.suggested_pick_lot spl ON pol.id = spl.pickOrderLineId
LEFT JOIN fpsmsdb.inventory_lot_line ill ON spl.suggestedLotLineId = ill.id
LEFT JOIN fpsmsdb.router r ON r.inventoryLotId = ill.id AND r.deleted = false
LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId
LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.inventoryLotLineId = ill.id AND sol.deleted = false
WHERE po.deleted = false
AND po.id IN ($pickOrderIdsStr)
AND pol.deleted = false
AND po.status IN ('PENDING', 'RELEASED', 'COMPLETED')
AND po.assignTo = :userId
AND ill.deleted = false
AND il.deleted = false
AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL)
ORDER BY
CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END,
COALESCE(r.index, 0) ASC,
po.code ASC,
i.code ASC,
il.expiryDate ASC,
il.lotNo ASC
""".trimIndent()
println("�� Executing SQL for hierarchical structure: $sql")
println("�� With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr")
val results = jdbcDao.queryForList(sql, mapOf("userId" to userId))
println("✅ Total result count: ${results.size}")
// Filter out lots with null availableQty (rejected lots)
val filteredResults = results.filter { row ->
val availableQty = row["availableQty"]
availableQty != null
}
println("✅ Filtered result count: ${filteredResults.size}")
// ✅ Transform flat results into hierarchical structure
if (filteredResults.isEmpty()) {
return mapOf(
"pickOrder" to null as Any?,
"pickOrderLines" to emptyList<Map<String, Any>>() as Any?
)
}
// Get pick order info from first row (all rows have same pick order info)
val firstRow = filteredResults.first()
val pickOrderInfo = mapOf(
"id" to firstRow["pickOrderId"],
"code" to firstRow["pickOrderCode"],
"consoCode" to firstRow["pickOrderConsoCode"],
"targetDate" to firstRow["pickOrderTargetDate"],
"type" to firstRow["pickOrderType"],
"status" to firstRow["pickOrderStatus"],
"assignTo" to firstRow["pickOrderAssignTo"]
)
// Group by pick order line ID to create hierarchical structure
val pickOrderLinesMap = filteredResults
.groupBy { it["pickOrderLineId"] as Number }
.map { (pickOrderLineId, lots) ->
val firstLot = lots.first()
// Item information (same for all lots of this line)
val itemInfo = mapOf(
"id" to firstLot["itemId"],
"code" to firstLot["itemCode"],
"name" to firstLot["itemName"],
"uomCode" to firstLot["uomCode"],
"uomDesc" to firstLot["uomDesc"]
)
// Transform lots for this pick order line
val lotsInfo = lots.map { lot ->
mapOf(
"id" to lot["lotId"],
"lotNo" to lot["lotNo"],
"expiryDate" to lot["expiryDate"],
"location" to lot["location"],
"stockUnit" to lot["stockUnit"],
"availableQty" to lot["availableQty"],
"requiredQty" to lot["requiredQty"],
"actualPickQty" to lot["actualPickQty"],
"inQty" to lot["inQty"],
"outQty" to lot["outQty"],
"holdQty" to lot["holdQty"],
"lotStatus" to lot["lotStatus"],
"lotAvailability" to lot["lotAvailability"],
"processingStatus" to lot["processingStatus"],
"suggestedPickLotId" to lot["suggestedPickLotId"],
"stockOutLineId" to lot["stockOutLineId"],
"stockOutLineStatus" to lot["stockOutLineStatus"],
"stockOutLineQty" to lot["stockOutLineQty"],
"router" to mapOf(
"id" to lot["routerId"],
"index" to lot["routerIndex"],
"route" to lot["routerRoute"],
"area" to lot["routerArea"],
"itemCode" to firstLot["itemId"],
"itemName" to firstLot["itemName"],
"uomId" to firstLot["uomShortDesc"],
"noofCarton" to lot["requiredQty"] // Use required qty as carton count
)
)
}
// Pick order line with item and lots
mapOf(
"id" to pickOrderLineId,
"requiredQty" to firstLot["pickOrderLineRequiredQty"],
"status" to firstLot["pickOrderLineStatus"],
"item" to itemInfo,
"lots" to lotsInfo
)
}
return mapOf(
"pickOrder" to pickOrderInfo as Any?,
"pickOrderLines" to pickOrderLinesMap as Any?
)
}
// Fix the type issues in the getPickOrdersByDateAndStore method
open fun getPickOrdersByDateAndStore(storeId: String): Map<String, Any?> {
println("=== Debug: getPickOrdersByDateAndStore ===")
println("storeId: $storeId")
try {
val today = LocalDate.now()
println("Today's date: $today")
// SQL query to get pick orders grouped by date and store
// Get all available orders for this store
val sql = """
SELECT
dpo.store_id as storeId,
DATE_FORMAT(dpo.created, '%Y-%m-%d') as orderDate,
COUNT(DISTINCT po.id) as orderTotal,
COUNT(DISTINCT CASE WHEN po.status = 'COMPLETED' THEN po.id END) as orderCompleted,
GROUP_CONCAT(DISTINCT po.id ORDER BY po.id) as pickOrderIds,
GROUP_CONCAT(DISTINCT po.code ORDER BY po.id SEPARATOR ',') as pickOrderCodes,
-- Get do_pick_order details
GROUP_CONCAT(
CONCAT(
dpo.id, ':',
dpo.ticket_no, ':',
dpo.pick_order_id
)
ORDER BY dpo.id SEPARATOR '|'
) as doPickOrderDetails
FROM do_pick_order dpo
JOIN pick_order po ON po.id = dpo.pick_order_id
WHERE dpo.deleted = false
AND po.deleted = false
AND dpo.store_id = :storeId
GROUP BY dpo.store_id, DATE_FORMAT(dpo.created, '%Y-%m-%d')
ORDER BY orderDate ASC
""".trimIndent()
println("🔍 Executing SQL: $sql")
println("🔍 With parameters: storeId = $storeId")
val results = jdbcDao.queryForList(sql, mapOf("storeId" to storeId))
println("✅ Found ${results.size} records")
if (results.isEmpty()) {
return mapOf(
"storeId" to storeId,
"orders" to emptyList<Map<String, Any?>>()
)
}
// Analyze each date's completion status
val ordersByDate = results.map { row ->
val orderDate = LocalDate.parse(row["orderDate"] as String)
val orderTotal = (row["orderTotal"] as Number).toInt()
val orderCompleted = (row["orderCompleted"] as Number).toInt()
val isAllCompleted = orderTotal == orderCompleted && orderTotal > 0
mapOf(
"orderDate" to orderDate,
"orderTotal" to orderTotal,
"orderCompleted" to orderCompleted,
"isAllCompleted" to isAllCompleted,
"rawData" to row
)
}
println("📊 Orders by date analysis:")
ordersByDate.forEach { order ->
println(" Date: ${order["orderDate"]}, Total: ${order["orderTotal"]}, Completed: ${order["orderCompleted"]}, AllCompleted: ${order["isAllCompleted"]}")
}
// Simple logic: Find the first incomplete date and return it + next 2 days
val ordersToReturn = mutableListOf<Map<String, Any?>>()
// Find the first incomplete order date
val firstIncompleteDate = ordersByDate
.firstOrNull { !(it["isAllCompleted"] as Boolean) }
if (firstIncompleteDate != null) {
val startDate = firstIncompleteDate["orderDate"] as LocalDate
println("�� First incomplete date: $startDate")
// Get the incomplete date + next 2 days (total 3 days)
val targetDates = (0..2).map { days -> startDate.plusDays(days.toLong()) }
println("🎯 Target dates to return: $targetDates")
// Only return dates that actually exist in the database
ordersToReturn.addAll(
ordersByDate.filter { order ->
val orderDate = order["orderDate"] as LocalDate
targetDates.contains(orderDate)
}.map { it["rawData"] as Map<String, Any?> }
)
} else {
println("🎯 All orders are completed, returning empty list")
}
// Transform results into the required structure
val finalOrders = ordersToReturn.map { row ->
val doPickOrderDetails = (row["doPickOrderDetails"] as String?)?.split("|") ?: emptyList()
val doPickOrderList = doPickOrderDetails.map { detail ->
val parts = detail.split(":")
if (parts.size >= 3) {
mapOf<String, Any?>(
"doPickOrderId" to parts[0].toLongOrNull(),
"ticketNo" to parts[1],
"pickOrderId" to parts[2].toLongOrNull()
)
} else {
mapOf<String, Any?>(
"doPickOrderId" to null,
"ticketNo" to "",
"pickOrderId" to null
)
}
}
mapOf<String, Any?>(
"orderTotal" to row["orderTotal"],
"orderCompleted" to row["orderCompleted"],
"orderDate" to row["orderDate"],
"pickOrderIds" to ((row["pickOrderIds"] as String?)?.split(",")?.mapNotNull { it.toLongOrNull() } ?: emptyList<Long>()),
"pickOrderCodes" to ((row["pickOrderCodes"] as String?)?.split(",") ?: emptyList<String>()),
"doPickOrderDetails" to doPickOrderList
)
}
println("✅ Final orders to return: ${finalOrders.size}")
finalOrders.forEach { order ->
println(" 📅 Date: ${order["orderDate"]}, Total: ${order["orderTotal"]}, Completed: ${order["orderCompleted"]}")
}
return mapOf<String, Any?>(
"storeId" to storeId,
"orders" to finalOrders
)
} catch (e: Exception) {
println("❌ Error in getPickOrdersByDateAndStore: ${e.message}")
e.printStackTrace()
return mapOf<String, Any?>(
"storeId" to storeId,
"orders" to emptyList<Map<String, Any?>>(),
"error" to e.message
)
}
}
}

+ 16
- 0
src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt 查看文件

@@ -266,4 +266,20 @@ class PickOrderController(
fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(@PathVariable userId: Long): List<Map<String, Any>> {
return pickOrderService.getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId)
}
@GetMapping("/all-lots-hierarchical/{userId}")
fun getAllPickOrderLotsHierarchical(@PathVariable userId: Long): Map<String, Any?> {
return pickOrderService.getAllPickOrderLotsWithDetailsHierarchical(userId)
}
@PostMapping("/auto-assign-release-by-ticket")
fun autoAssignAndReleasePickOrderByTicket(
@RequestParam storeId: String,
@RequestParam ticketNo: String,
@RequestParam userId: Long
): MessageResponse {
return pickOrderService.autoAssignAndReleasePickOrderByStoreAndTicket(storeId, ticketNo, userId)
}
@GetMapping("/orders-by-store/{storeId}")
fun getPickOrdersByStore(@PathVariable storeId: String): Map<String, Any?> {
return pickOrderService.getPickOrdersByDateAndStore(storeId)
}
}

正在加载...
取消
保存