Bläddra i källkod

update

master
CANCERYS\kw093 2 månader sedan
förälder
incheckning
303ff9260a
7 ändrade filer med 265 tillägg och 33 borttagningar
  1. +27
    -0
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DeliveryOrderRepository.kt
  2. +6
    -1
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt
  3. +54
    -9
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt
  4. +22
    -7
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt
  5. +124
    -0
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt
  6. +20
    -2
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DoPickOrderController.kt
  7. +12
    -14
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt

+ 27
- 0
src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DeliveryOrderRepository.kt Visa fil

@@ -3,12 +3,39 @@ package com.ffii.fpsms.modules.deliveryOrder.entity
import com.ffii.core.support.AbstractRepository
import com.ffii.fpsms.modules.deliveryOrder.entity.models.DeliveryOrderInfo
import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Repository
import java.io.Serializable
import java.time.LocalDateTime

@Repository
interface DeliveryOrderRepository : AbstractRepository<DeliveryOrder, Long> {
@Query("""
select d from DeliveryOrder d
where d.deleted = false
and (:code is null or d.code like concat('%', :code, '%'))
and (:shopName is null or d.shop.name like concat('%', :shopName, '%'))
and (:status is null or d.status = :status)
and (:orderFrom is null or d.orderDate >= :orderFrom)
and (:orderTo is null or d.orderDate < :orderTo)
and (:etaFrom is null or d.estimatedArrivalDate >= :etaFrom)
and (:etaTo is null or d.estimatedArrivalDate < :etaTo)
""")
fun search(
@Param("code") code: String?,
@Param("shopName") shopName: String?,
@Param("status") status: String?,
@Param("orderFrom") orderFrom: LocalDateTime?,
@Param("orderTo") orderTo: LocalDateTime?,
@Param("etaFrom") etaFrom: LocalDateTime?,
@Param("etaTo") etaTo: LocalDateTime?,
pageable: Pageable
): Page<DeliveryOrder>

fun findTopByM18DataLogIdAndDeletedIsFalseOrderByModifiedDesc(m18datalogId: Serializable): DeliveryOrder?

fun findDeliveryOrderInfoByDeletedIsFalse(): List<DeliveryOrderInfo>


+ 6
- 1
src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt Visa fil

@@ -10,7 +10,7 @@ import org.springframework.data.jpa.repository.Query
import org.springframework.stereotype.Repository
import java.io.Serializable
import java.time.LocalDateTime
import java.time.LocalDate
@Repository
interface DoPickOrderRepository : JpaRepository<DoPickOrder, Long> {
fun findByTicketNoStartingWith(prefix: String): List<DoPickOrder>
@@ -24,4 +24,9 @@ interface DoPickOrderRepository : JpaRepository<DoPickOrder, Long> {
// 在 DoPickOrderRepository 中添加这个方法
fun findByHandledByAndTicketStatusIn(handledBy: Long, status: List<DoPickOrderStatus>): List<DoPickOrder>
fun findByStoreIdAndTicketStatusIn(storeId: String, status: List<DoPickOrderStatus>): List<DoPickOrder>
fun findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn(
storeId: String,
requiredDeliveryDate: LocalDate,
status: List<DoPickOrderStatus>
): List<DoPickOrder>
}

+ 54
- 9
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt Visa fil

@@ -61,11 +61,10 @@ import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecord
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecordRepository
import com.ffii.fpsms.modules.deliveryOrder.web.models.ExportDNLabelsRequest
import com.ffii.fpsms.modules.deliveryOrder.web.models.PrintDNLabelsRequest
import com.ffii.fpsms.modules.pickOrder.entity.RouterRepository
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository
import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository
import com.ffii.fpsms.modules.stock.service.InventoryLotService
import com.ffii.fpsms.modules.pickOrder.entity.Router
import net.sf.jasperreports.engine.JasperPrintManager
import net.sf.jasperreports.engine.JRPrintPage
import com.ffii.fpsms.modules.stock.entity.SuggestPickLotRepository
@@ -91,7 +90,6 @@ open class DeliveryOrderService(
private val pickOrderLineRepository: PickOrderLineRepository,
private val printerService: PrinterService,
private val doPickOrderRecordRepository: DoPickOrderRecordRepository,
private val routerRepository: RouterRepository,
private val purchaseOrderRepository: PurchaseOrderRepository,
private val inventoryLotService: InventoryLotService,
private val suggestedPickLotRepository: SuggestPickLotRepository,
@@ -276,7 +274,53 @@ open class DeliveryOrderService(
open fun searchCodeAndShopName(code: String?, shopName: String?): List<DeliveryOrderInfo> {
return deliveryOrderRepository.findAllByCodeContainsAndShopNameContainsAndDeletedIsFalse(code, shopName);
}

open fun getWarehouseOrderByItemId(itemId: Long): Int? {
val inventoryLots = inventoryLotService.findByItemId(itemId)
if (inventoryLots.isNotEmpty()) {
val inventoryLotId = inventoryLots.first().id?.toInt()
return inventoryLotId?.let { lotId ->
// 查询 warehouse 的 order 字段
val sql = """
SELECT w.`order` as warehouseOrder
FROM fpsmsdb.inventory_lot_line ill
JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
WHERE ill.inventoryLotId = :lotId
AND ill.deleted = false
ORDER BY w.`order` ASC
LIMIT 1
""".trimIndent()
val result = jdbcDao.queryForList(sql, mapOf("lotId" to lotId))
if (result.isNotEmpty()) {
(result.first()["warehouseOrder"] as Number?)?.toInt()
} else null
}
}
return null
}
// ✅ 新增方法2:获取 warehouse 的 code 字段(用于显示路由)
open fun getWarehouseCodeByItemId(itemId: Long): String? {
val inventoryLots = inventoryLotService.findByItemId(itemId)
if (inventoryLots.isNotEmpty()) {
val inventoryLotId = inventoryLots.first().id?.toInt()
return inventoryLotId?.let { lotId ->
val sql = """
SELECT w.code as warehouseCode
FROM fpsmsdb.inventory_lot_line ill
JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
WHERE ill.inventoryLotId = :lotId
AND ill.deleted = false
ORDER BY w.`order` ASC
LIMIT 1
""".trimIndent()
val result = jdbcDao.queryForList(sql, mapOf("lotId" to lotId))
if (result.isNotEmpty()) {
result.first()["warehouseCode"] as String?
} else null
}
}
return null
}

open fun updateDeliveryOrderStatus(request: SaveDeliveryOrderStatusRequest): SaveDeliveryOrderResponse {
val deliveryOrder = checkNotNull(
@@ -614,8 +658,8 @@ return MessageResponse(
)

// ... existing code ...
}
}
/*
open fun getRouteAndIndexByInventoryLotId(inventoryLotId: Int): List<Router> {
return routerRepository.findByInventoryLotIdAndDeletedFalse(inventoryLotId)
.sortedWith(compareBy(
@@ -636,6 +680,7 @@ return MessageResponse(
}
}
}

open fun getRouteByItemId(itemId: Long): String? {
val inventoryLots = inventoryLotService.findByItemId(itemId)
if (inventoryLots.isNotEmpty()){
@@ -658,7 +703,7 @@ open fun getRouterIndexByItemId(itemId: Long): Int? {
}
return null
}
*/
open fun getLotNumbersForPickOrderByItemId(itemId: Long, pickOrderId: Long): String {
try {
val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrderId)
@@ -706,7 +751,7 @@ open fun getRouterIndexByItemId(itemId: Long): Int? {
for (info in deliveryNoteInfo) {
val sortedLines = info.deliveryOrderLines.sortedBy { line ->
line.itemId?.let { itemId ->
getRouterIndexByItemId(itemId)
getWarehouseOrderByItemId(itemId) // ✅ 改用 warehouse order
} ?: Int.MAX_VALUE
}

@@ -722,7 +767,7 @@ open fun getRouterIndexByItemId(itemId: Long): Int? {
field["shortName"] = line.uomShortDesc ?:""

val route = line.itemId?.let { itemId ->
getRouteByItemId(itemId)
getWarehouseCodeByItemId(itemId) // ✅ 使用新方法
} ?: ""
field["route"] = route



+ 22
- 7
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt Visa fil

@@ -38,12 +38,14 @@ import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecordRepository
import com.ffii.fpsms.modules.deliveryOrder.web.models.* // ✅ 导入
import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus

@Service
class DoPickOrderService(
private val doPickOrderRepository: DoPickOrderRepository,
private val doPickOrderRecordRepository: DoPickOrderRecordRepository,
private val userRepository: UserRepository,
private val pickOrderRepository: PickOrderRepository
private val pickOrderRepository: PickOrderRepository,

) {
fun findReleasedDoPickOrders(): List<DoPickOrder> {
return doPickOrderRepository.findByTicketStatusIn(
@@ -182,13 +184,21 @@ class DoPickOrderService(
}
return doPickOrderRepository.saveAll(doPickOrders)
}
fun getSummaryByStore(storeId: String): StoreLaneSummary {
// ✅ 修改:查询所有状态的订单,不只是 pending
val allRecords = doPickOrderRepository.findByStoreIdAndTicketStatusIn(
fun getSummaryByStore(storeId: String, requiredDate: LocalDate?): StoreLaneSummary {
// ✅ 使用传入的日期,如果没有传入则使用今天
val targetDate = requiredDate ?: LocalDate.now()
println("🔍 DEBUG: Getting summary for store=$storeId, date=$targetDate")
// ✅ 修改:按日期查询订单
val allRecords = doPickOrderRepository.findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn(
storeId,
targetDate,
listOf(DoPickOrderStatus.pending, DoPickOrderStatus.released, DoPickOrderStatus.completed)
)
println("🔍 DEBUG: Found ${allRecords.size} records for date $targetDate")
val grouped = allRecords.groupBy { it.truckDepartureTime to it.truckLanceCode }
.mapValues { (_, list) ->
LaneBtn(
@@ -217,7 +227,6 @@ class DoPickOrderService(
return StoreLaneSummary(storeId = storeId, rows = timeGroups)
}

// ✅ 修复:把 assignByLane 移到类里面
fun assignByLane(request: AssignByLaneRequest): MessageResponse {
val existingOrders = doPickOrderRepository.findByHandledByAndTicketStatusIn(
@@ -232,6 +241,7 @@ class DoPickOrderService(
errorPosition = null, entity = null
)
}
val candidates = doPickOrderRepository
.findByStoreIdAndTicketStatusOrderByTruckDepartureTimeAsc(
request.storeId,
@@ -256,19 +266,21 @@ class DoPickOrderService(
val user = userRepository.findById(request.userId).orElse(null)
val handlerName = user?.name ?: "Unknown"
// ✅ 更新 do_pick_order
// ✅ 更新 do_pick_order - 保持原有的卡车信息
firstOrder.handledBy = request.userId
firstOrder.handlerName = handlerName
firstOrder.ticketStatus = DoPickOrderStatus.released
firstOrder.ticketReleaseTime = LocalDateTime.now()
// ✅ 重要:不要修改 truckDepartureTime 和 truckLanceCode
// 这些信息应该保持用户选择的值
doPickOrderRepository.save(firstOrder)
// ✅ 同步更新 pick_order 表
if (firstOrder.pickOrderId != null) {
val pickOrder = pickOrderRepository.findById(firstOrder.pickOrderId!!).orElse(null)
if (pickOrder != null) {
// ✅ 修复:assignTo 需要 User 对象,不是 Long
val user = userRepository.findById(request.userId).orElse(null)
pickOrder.assignTo = user
pickOrder.status = PickOrderStatus.RELEASED
@@ -299,4 +311,7 @@ class DoPickOrderService(
)
)
}
// 在 DoPickOrderService 类中添加这个方法


} // ✅ 类结束

+ 124
- 0
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt Visa fil

@@ -0,0 +1,124 @@
package com.ffii.fpsms.modules.deliveryOrder.service

import com.ffii.fpsms.modules.deliveryOrder.web.models.ReleaseDoRequest
import com.ffii.fpsms.modules.master.web.models.MessageResponse
import org.springframework.stereotype.Service
import java.time.Instant
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicInteger
import kotlin.math.min

data class BatchReleaseJobStatus(
val jobId: String,
val total: Int,
val success: AtomicInteger = AtomicInteger(0),
val failed: MutableList<Pair<Long, String>> = mutableListOf(),
@Volatile var running: Boolean = true,
val startedAt: Long = Instant.now().toEpochMilli(),
@Volatile var finishedAt: Long? = null
)

@Service
class DoReleaseCoordinatorService(
private val deliveryOrderService: DeliveryOrderService
) {
// 可按机器/DB调优:CPU核数 * 2 或固定 8~12
private val poolSize = Runtime.getRuntime().availableProcessors().coerceAtLeast(4) * 2
private val executor = Executors.newFixedThreadPool(min(poolSize, 12))
private val jobs = ConcurrentHashMap<String, BatchReleaseJobStatus>()

fun startBatchReleaseAsync(ids: List<Long>, userId: Long): MessageResponse {
if (ids.isEmpty()) {
return MessageResponse(id = null, code = "NO_IDS", name = null, type = null,
message = "No IDs provided", errorPosition = null, entity = null)
}
val jobId = UUID.randomUUID().toString()
val status = BatchReleaseJobStatus(jobId = jobId, total = ids.size)
jobs[jobId] = status
// 可按压测调优
val chunkSize = 20
val chunks = ids.chunked(chunkSize)
executor.submit {
try {
chunks.forEach { chunk ->
val futures = chunk.map { id ->
executor.submit<Unit> {
var attempts = 0
while (attempts < 2) {
try {
val res = deliveryOrderService.releaseDeliveryOrder(
ReleaseDoRequest(id = id, userId = userId)
)
val code = res.code ?: "OK"
// 成功码放宽(关键修复)
if (code in setOf("SUCCESS", "OK", "PARTIAL_SUCCESS")) {
status.success.incrementAndGet()
} else {
synchronized(status.failed) {
status.failed.add(id to (res.message ?: "Release failed: $code"))
}
}
// 打印以便核对统计
println("DEBUG release DO id=$id -> code=${res.code}, msg=${res.message}")
break
} catch (e: Exception) {
attempts++
val msg = e.message ?: ""
// 死锁重试一次(1213)
if (attempts < 2 && msg.contains("Deadlock", ignoreCase = true)) {
Thread.sleep(150)
continue
}
synchronized(status.failed) {
status.failed.add(id to (e.message ?: "Unknown error"))
}
break
}
}
}
}
// 等待当前块全部完成,避免任务堆积
futures.forEach { f -> try { f.get() } catch (_: Exception) {} }
}
} finally {
status.running = false
status.finishedAt = Instant.now().toEpochMilli()
}
}
return MessageResponse(
id = null, code = "STARTED", name = null, type = null,
message = "Batch release started", errorPosition = null,
entity = mapOf("jobId" to jobId, "total" to ids.size)
)
}
fun getBatchReleaseProgress(jobId: String): MessageResponse {
val s = jobs[jobId] ?: return MessageResponse(
id = null, code = "NOT_FOUND", name = null, type = null,
message = "Job not found", errorPosition = null, entity = null
)
val finished = s.success.get() + s.failed.size
val progress = (finished.toDouble() / s.total).coerceIn(0.0, 1.0)

return MessageResponse(
id = null, code = if (s.running) "RUNNING" else "FINISHED", name = null, type = null,
message = null, errorPosition = null, entity = mapOf(
"jobId" to s.jobId,
"total" to s.total,
"finished" to finished,
"success" to s.success.get(),
"failedCount" to s.failed.size,
"failed" to s.failed.take(50), // 避免超大返回,可分页
"running" to s.running,
"progress" to progress,
"startedAt" to s.startedAt,
"finishedAt" to s.finishedAt
)
)
}
}

+ 20
- 2
src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DoPickOrderController.kt Visa fil

@@ -37,11 +37,14 @@ import com.ffii.fpsms.modules.deliveryOrder.service.DoPickOrderService
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRepository
import com.ffii.fpsms.modules.deliveryOrder.web.models.AssignByStoreRequest
import com.ffii.fpsms.modules.deliveryOrder.web.models.*
import org.springframework.format.annotation.DateTimeFormat
import com.ffii.fpsms.modules.deliveryOrder.service.DoReleaseCoordinatorService
@RestController
@RequestMapping("/doPickOrder")
class DoPickOrderController(
private val doPickOrderService: DoPickOrderService,
private val doPickOrderRepository: DoPickOrderRepository,
private val doReleaseCoordinatorService: DoReleaseCoordinatorService
) {
@PostMapping("/assign-by-store")
fun assignPickOrderByStore(@RequestBody request: AssignByStoreRequest): MessageResponse {
@@ -58,11 +61,26 @@ class DoPickOrderController(
return doPickOrderService.findReleasedDoPickOrders()
}
@GetMapping("/summary-by-store")
fun getSummaryByStore(@RequestParam storeId: String): StoreLaneSummary {
return doPickOrderService.getSummaryByStore(storeId)
fun getSummaryByStore(
@RequestParam storeId: String,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) requiredDate: LocalDate?
): StoreLaneSummary {
return doPickOrderService.getSummaryByStore(storeId, requiredDate)
}
@PostMapping("/assign-by-lane")
fun assignByLane(@RequestBody request: AssignByLaneRequest): MessageResponse {
return doPickOrderService.assignByLane(request)
}
@PostMapping("/batch-release/async")
fun startBatchReleaseAsync(
@RequestBody ids: List<Long>,
@RequestParam(defaultValue = "1") userId: Long
): MessageResponse {
return doReleaseCoordinatorService.startBatchReleaseAsync(ids, userId)
}

@GetMapping("/batch-release/progress/{jobId}")
fun getBatchReleaseProgress(@PathVariable jobId: String): MessageResponse {
return doReleaseCoordinatorService.getBatchReleaseProgress(jobId)
}
}

+ 12
- 14
src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt Visa fil

@@ -50,7 +50,6 @@ import kotlin.jvm.optionals.getOrNull
import com.ffii.fpsms.modules.pickOrder.entity.projection.PickOrderGroupInfo
import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository
import com.ffii.fpsms.modules.pickOrder.entity.TruckRepository
import com.ffii.fpsms.modules.pickOrder.entity.RouterRepository
import com.ffii.fpsms.modules.deliveryOrder.service.DoPickOrderService
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrder
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecord
@@ -81,7 +80,6 @@ open class PickOrderService(
private val deliveryOrderRepository: DeliveryOrderRepository,
private val truckRepository: TruckRepository,
private val doPickOrderService: DoPickOrderService,
private val routerRepository: RouterRepository,
private val doPickOrderRecordRepository: DoPickOrderRecordRepository,
private val doPickOrderRepository: DoPickOrderRepository,
private val userRepository: UserRepository,
@@ -2700,7 +2698,7 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo
}
println("✅ Filtered result count: ${filteredResults.size}")
/*
// ✅ Add router information for each lot
val enrichedResults = filteredResults.map { row ->
val inventoryLotId = row["debugInventoryLotId"] as? Number
@@ -2759,7 +2757,8 @@ open fun autoAssignAndReleasePickOrderByStoreAndTicket(storeId: String, ticketNo
putAll(routerInfo)
}
}
*/
val enrichedResults = filteredResults
return enrichedResults
}

@@ -3157,10 +3156,11 @@ open fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId: Long): List<Map
COALESCE(uc.udfudesc, 'N/A') as stockUnit,
-- Router Information
r.id as routerId,
ro.`order` as routerIndex,
CONCAT(COALESCE(ro.route_area, ''), COALESCE(r.route, '')) as routerRoute,
ro.route_area as routerArea,
w.id as routerId,
w.`order` as routerIndex,
w.code as routerRoute,
w.code as routerArea,



-- ✅ FIXED: Set quantities to NULL for rejected lots
@@ -3238,8 +3238,6 @@ 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.router_order ro ON r.router_id = ro.id
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
@@ -3253,8 +3251,7 @@ 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(ro.`order`, 999999) ASC,
COALESCE(r.route, 999999) ASC,
COALESCE(w.`order`, 999999) ASC, -- ✅ 改用 warehouse.order
po.code ASC,
i.code ASC,
il.expiryDate ASC,
@@ -3274,7 +3271,7 @@ ORDER BY
}
println("✅ Filtered result count: ${filteredResults.size}")
/*
// ✅ Add router information for each lot
val enrichedResults = filteredResults.map { row ->
val inventoryLotId = row["debugInventoryLotId"] as? Number
@@ -3333,7 +3330,8 @@ ORDER BY
putAll(routerInfo)
}
}
*/
val enrichedResults = filteredResults
return enrichedResults
}
// ... existing code ...


Laddar…
Avbryt
Spara