CANCERYS\kw093 1 неделю назад
Родитель
Сommit
5b90496997
8 измененных файлов: 351 добавлений и 4 удалений
  1. +4
    -0
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DeliveryOrderController.kt
  2. +1
    -1
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt
  3. +2
    -2
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckService.kt
  4. +11
    -0
      src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SavePickOrderRequest.kt
  5. +15
    -0
      src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SearchPickOrderRequest.kt
  6. +256
    -1
      src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt
  7. +39
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/StockOutLineController.kt
  8. +23
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockOutRequest.kt

+ 4
- 0
src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DeliveryOrderController.kt Просмотреть файл

@@ -224,4 +224,8 @@ class DeliveryOrderController(
fun printDN(@ModelAttribute request: PrintDNLabelsRequest) {
deliveryOrderService.printDNLabels(request)
}
@GetMapping("/batchPrintQrCode")
fun printQrCodeForDeliveryOrder(@ModelAttribute request: PrintQrCodeForDoRequest) {
stockInLineService.printQrCodeForDeliveryOrder(request)
}
}

+ 1
- 1
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt Просмотреть файл

@@ -282,7 +282,7 @@ open class JobOrderService(
}
jobOrderRepository.save(jo)
val pols = jo.jobms.filter { it.item?.type != "CMB"&& it.item?.type != "item"}.
val pols = jo.jobms.filter { it.item?.type != "CMB"&& it.item?.type != "consumables"}.
map {
SavePickOrderLineRequest(
itemId = it.item?.id,


+ 2
- 2
src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckService.kt Просмотреть файл

@@ -168,7 +168,7 @@ open class TruckService(
departureTime = departureTime,
shopId = shop?.id!!,
shopName = shopName?: "",
shopCode = shopCode,
shopCode = normalizedShopCode,
loadingSequence = loadingSequence
)
saveTruck(truckRequest)
@@ -181,7 +181,7 @@ open class TruckService(
departureTime = departureTime,
shopId = shop?.id!!,
shopName = shopName?: "",
shopCode = shopCode,
shopCode = normalizedShopCode,
loadingSequence = loadingSequence
)
saveTruck(truckRequest)


+ 11
- 0
src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SavePickOrderRequest.kt Просмотреть файл

@@ -20,3 +20,14 @@ data class SavePickOrderRequest (
val pickOrderLine: List<SavePickOrderLineRequest>
)

data class QrPickedLineRequest(
val pickOrderLineId: Long,
val inventoryLotLineId: Long,
val submitQty: BigDecimal,
val targetStatus: String
)
data class QrPickBatchSubmitRequest(
val userId: Long,
val lines: List<QrPickedLineRequest>
)


+ 15
- 0
src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SearchPickOrderRequest.kt Просмотреть файл

@@ -63,6 +63,15 @@ data class FgInfoResponse(
val truckLanceCode: String?,
val departureTime: LocalTime?
)
data class HierarchicalPickOrderResponse(
val fgInfo: FgInfoResponse?,
val pickOrders: List<PickOrderDetailResponse>,
val totalLines: Int,
val pageNum: Int,
val pageSize: Int,
val scannedItemsCount: Int? = null,
val totalItemsCount: Int? = null
)

data class PickOrderDetailResponse(
val pickOrderId: Long?,
@@ -131,4 +140,10 @@ data class StockOutDetailResponse(
val location: String?,
val availableQty: BigDecimal?,
val noLot: Boolean
)

data class DoPickOrderRequest(
val userId: Long,
val pageSize: Int?,
val pageNum: Int?,
)

+ 256
- 1
src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt Просмотреть файл

@@ -31,6 +31,7 @@ import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecord
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRecord
import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRecordRepository

import com.ffii.fpsms.modules.common.CodeGenerator
import org.springframework.context.annotation.Lazy

@@ -46,6 +47,7 @@ open class StockOutLineService(
private val pickOrderRepository: PickOrderRepository,
private val inventoryLotLineRepository: InventoryLotLineRepository,
@Lazy private val suggestedPickLotService: SuggestedPickLotService,
private val inventoryLotRepository: InventoryLotRepository,
private val doPickOrderRepository: DoPickOrderRepository,
private val doPickOrderRecordRepository: DoPickOrderRecordRepository,
private val deliveryOrderRepository: DeliveryOrderRepository,
@@ -665,4 +667,257 @@ private fun updateInventoryTableAfterLotRejection(inventoryLotLine: InventoryLot
e.printStackTrace()
}
}
}

@Transactional(rollbackFor = [Exception::class])
open fun batchSubmit(request: QrPickBatchSubmitRequest): MessageResponse {
val startTime = System.currentTimeMillis()
println(" Start time: ${java.time.LocalDateTime.now()}")
if (request.lines.isEmpty()) {
return MessageResponse(
id = null,
name = "No lines",
code = "EMPTY",
type = "batch_submit",
message = "No scanned lines",
errorPosition = null
)
}

val errors = mutableListOf<String>()
val processedIds = mutableListOf<Long>()

request.lines.forEach { line ->
val lineStartTime = System.currentTimeMillis()
try {
// 1) noLot 情况:等价于前端 handleSubmitAllScanned 里 noLot 分支
if (line.noLot) {
val req = UpdateStockOutLineStatusRequest(
id = line.stockOutLineId,
status = "completed",
qty = 0.0
)
updateStatus(req) // 直接复用已有逻辑(会自动更新行/订单状态)
processedIds += line.stockOutLineId
// 如果需要,这里可以再调记录 issue 的 service,而不是让前端调
return@forEach
}

// 2) 正常有 lot:照抄你前端 handleSubmitAllScanned 的计算逻辑
val required = line.requiredQty ?: BigDecimal.ZERO
val currentActual = line.actualPickQty ?: BigDecimal.ZERO
val submitQty = if (line.requiredQty != null) line.requiredQty else line.requiredQty
val submitQtyNotNull = submitQty ?: BigDecimal.ZERO
val cumulative = currentActual + submitQtyNotNull

var newStatus = "partially_completed"
if (cumulative >= required) {
newStatus = "completed"
}

// 注意:updateStatus 是「在原 qty 上 + qty」,所以这里传的是「本次增量」而不是 cumulative
val updateReq = UpdateStockOutLineStatusRequest(
id = line.stockOutLineId,
status = newStatus,
qty = submitQtyNotNull.toDouble()
)
updateStatus(updateReq) // 内部已经会做 pickOrderLine / pickOrder / DO 完成检查

// 3) 扣库存:等价于前端 updateInventoryLotLineQuantities
if (submitQtyNotNull > BigDecimal.ZERO && line.inventoryLotLineId != null) {
val lotLine = inventoryLotLineRepository.findById(line.inventoryLotLineId).orElseThrow()
val zero = BigDecimal.ZERO
val one = BigDecimal.ONE
val salesUnit = lotLine.inventoryLot?.item?.id?.let { itemId ->
itemUomRespository.findByItemIdAndSalesUnitIsTrueAndDeletedIsFalse(itemId)
}
val ratio = 1.0 //(salesUnit?.ratioN ?: zero).divide(salesUnit?.ratioD ?: one).toDouble()
val baseQty = (submitQtyNotNull.toDouble() / ratio).toBigDecimal()

// 更新 lot line
lotLine.apply {
this.outQty = (this.outQty ?: zero) + baseQty
this.holdQty = (this.holdQty ?: zero) - baseQty
}
inventoryLotLineRepository.save(lotLine)

// 更新 inventory
val inv = inventoryRepository.findByItemId(lotLine.inventoryLot?.item?.id!!).orElseThrow()
inv.apply {
this.onHandQty = (this.onHandQty ?: zero) - baseQty
this.onHoldQty = (this.onHoldQty ?: zero) - baseQty
}
inventoryRepository.save(inv)
}

processedIds += line.stockOutLineId
val lineTime = System.currentTimeMillis() - lineStartTime
// 4) 如果你还想保留「按 consoCode 再单独 check-complete」的接口,也可以在这里再调一次:
// if (newStatus == "completed" && line.pickOrderConsoCode != null) {
// pickOrderService.checkAndCompletePickOrderByConsoCode(line.pickOrderConsoCode)
// }

} catch (e: Exception) {
errors += "stockOutLineId=${line.stockOutLineId}: ${e.message}"
}
}

val msg = if (errors.isEmpty()) {
"Batch submit success (${processedIds.size} lines)."
} else {
"Batch submit partial success (${processedIds.size} lines), errors: ${errors.joinToString("; ")}"
}
val totalTime = System.currentTimeMillis() - startTime
println(" Processed: ${processedIds.size}/${request.lines.size} items")
println(" Total time: ${totalTime}ms (${totalTime / 1000.0}s)")
println(" Average time per item: ${if (processedIds.isNotEmpty()) totalTime / processedIds.size else 0}ms")
println(" End time: ${java.time.LocalDateTime.now()}")
return MessageResponse(
id = null,
name = "batch_submit",
code = if (errors.isEmpty()) "SUCCESS" else "PARTIAL_SUCCESS",
type = "batch_submit",
message = msg,
errorPosition = null,
entity = mapOf(
"processedIds" to processedIds,
"errors" to errors
)
)
}

@Transactional
open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineStatusByQRCodeAndLotNoRequest): MessageResponse {
val startTime = System.currentTimeMillis()
try {
println("=== QR SCAN REQUEST RECEIVED ===")
println(" Request details:")
println(" - stockOutLineId: ${request.stockOutLineId}")
println(" - pickOrderLineId: ${request.pickOrderLineId}")
println(" - inventoryLotNo: ${request.inventoryLotNo}")
println(" - itemId: ${request.itemId}")
println(" - status: ${request.status}")

val stockOutLine = stockOutLineRepository.findById(request.stockOutLineId).orElseThrow {
println(" StockOutLine not found with ID: ${request.stockOutLineId}")
IllegalArgumentException("StockOutLine not found with ID: ${request.stockOutLineId}")
}
println(" Found StockOutLine:")
println(" - ID: ${stockOutLine.id}")
println(" - Current status: ${stockOutLine.status}")
println(" - Qty: ${stockOutLine.qty}")
println(" - PickOrderLine ID: ${stockOutLine.pickOrderLine?.id}")
println(" - Item ID: ${stockOutLine.item?.id}")
println(" - InventoryLotLine ID: ${stockOutLine.inventoryLotLine?.id}")
// 修复:从 stockOutLine.inventoryLotLine 获取 inventoryLot,而不是使用错误的参数
val inventoryLotLine = stockOutLine.inventoryLotLine
if (inventoryLotLine == null) {
println(" StockOutLine has no associated InventoryLotLine")
return MessageResponse(
id = null,
name = "No inventory lot line",
code = "NO_INVENTORY_LOT_LINE",
type = "error",
message = "StockOutLine ${request.stockOutLineId} has no associated InventoryLotLine",
errorPosition = null
)
}
val inventoryLot = inventoryLotLine.inventoryLot
if (inventoryLot == null) {
println(" InventoryLotLine has no associated InventoryLot")
return MessageResponse(
id = null,
name = "No inventory lot",
code = "NO_INVENTORY_LOT",
type = "error",
message = "InventoryLotLine ${inventoryLotLine.id} has no associated InventoryLot",
errorPosition = null
)
}
println("🔍 Checking inventory lot:")
println(" - Found inventory lot:")
println(" - Lot No: ${inventoryLot.lotNo}")
println(" - Item ID: ${inventoryLot.item?.id}")
println(" - InventoryLot ID: ${inventoryLot.id}")

// 修复:比较逻辑
val lotNoMatch = inventoryLot.lotNo == request.inventoryLotNo
val itemIdMatch = inventoryLot.item?.id == request.itemId
println(" Matching results:")
println(" - Lot No match: $lotNoMatch (Expected: ${inventoryLot.lotNo}, Got: ${request.inventoryLotNo})")
println(" - Item ID match: $itemIdMatch (Expected: ${inventoryLot.item?.id}, Got: ${request.itemId})")

if (lotNoMatch && itemIdMatch) {
// 匹配成功,更新状态
println(" MATCH SUCCESS: Lot and Item both match!")
stockOutLine.status = request.status
val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine)
println(" Status updated successfully:")
println(" - New status: ${savedStockOutLine.status}")
println(" - StockOutLine ID: ${savedStockOutLine.id}")
val mappedStockOutLine = stockOutLineRepository.findStockOutLineInfoById(savedStockOutLine.id!!)
println("=== QR SCAN COMPLETED SUCCESSFULLY ===")
return MessageResponse(
id = savedStockOutLine.id,
name = inventoryLot.lotNo,
code = "checked", // 修复:返回操作结果,而不是 consoPickOrderCode
type = request.status,
message = "Stock out line status updated successfully",
errorPosition = null,
entity = mappedStockOutLine
)
} else if (!lotNoMatch && itemIdMatch) {
// Item 匹配但 lotNo 不匹配
println(" LOT NUMBER MISMATCH:")
println(" - Expected lotNo: ${inventoryLot.lotNo}, Got: ${request.inventoryLotNo}")
println(" - Item ID matches: ${inventoryLot.item?.id} == ${request.itemId}")
return MessageResponse(
id = null,
name = "Item match, lot number mismatch",
code = "LOT_NUMBER_MISMATCH",
type = "lot_not_found",
message = "Item match, lot number mismatch. Expected: ${inventoryLot.lotNo}, Got: ${request.inventoryLotNo}",
errorPosition = null
)
} else {
// Item 不匹配
println(" ITEM MISMATCH:")
println(" - Expected itemId: ${inventoryLot.item?.id}, Got: ${request.itemId}")
println(" - Expected lotNo: ${inventoryLot.lotNo}, Got: ${request.inventoryLotNo}")
return MessageResponse(
id = null,
name = "Item mismatch",
code = "ITEM_MISMATCH",
type = "item_not_found",
message = "Item mismatch. Expected: ${inventoryLot.item?.id}, Got: ${request.itemId}",
errorPosition = null
)
}

} catch (e: Exception) {
println(" ERROR updating stock out line status by QR: ${e.message}")
println(" Exception type: ${e.javaClass.simpleName}")
println(" Stack trace:")
e.printStackTrace()
return MessageResponse(
id = null,
name = "Error",
code = "ERROR",
type = "error",
message = "Error updating stock out line: ${e.message}",
errorPosition = null
)
}
}
}

+ 39
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/StockOutLineController.kt Просмотреть файл

@@ -12,6 +12,8 @@ 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
import com.ffii.fpsms.modules.stock.web.model.QrPickBatchSubmitRequest
import com.ffii.fpsms.modules.stock.web.model.UpdateStockOutLineStatusByQRCodeAndLotNoRequest

@RestController
@RequestMapping("/stockOutLine")
@@ -41,4 +43,41 @@ class StockOutLineController(
fun updateStatus(@Valid @RequestBody request: UpdateStockOutLineStatusRequest): MessageResponse {
return stockOutLineService.updateStatus(request)
}

@PostMapping("/batchQrSubmit")
fun batchQrSubmit(@Valid @RequestBody request: QrPickBatchSubmitRequest): MessageResponse {
return stockOutLineService.batchSubmit(request)
}
@PostMapping("/updateStatusByQRCodeAndLotNo")
fun updateStatusByQRCodeAndLotNo(@Valid @RequestBody request: UpdateStockOutLineStatusByQRCodeAndLotNoRequest): MessageResponse {
try {
println("=== 📥 CONTROLLER: updateStatusByQRCodeAndLotNo called ===")
println("📋 Request received:")
println(" - stockOutLineId: ${request.stockOutLineId}")
println(" - pickOrderLineId: ${request.pickOrderLineId}")
println(" - inventoryLotNo: ${request.inventoryLotNo}")
println(" - itemId: ${request.itemId}")
println(" - status: ${request.status}")
val result = stockOutLineService.updateStockOutLineStatusByQRCodeAndLotNo(request)
println("✅ CONTROLLER: Service call completed, returning result")
return result
} catch (e: Exception) {
println("❌ CONTROLLER ERROR: ${e.message}")
println("❌ Exception type: ${e.javaClass.simpleName}")
e.printStackTrace()
// 返回错误响应而不是抛出异常
return MessageResponse(
id = null,
name = "Controller Error",
code = "CONTROLLER_ERROR",
type = "error",
message = "Error in controller: ${e.message}",
errorPosition = null
)
}
}
}

+ 23
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockOutRequest.kt Просмотреть файл

@@ -4,6 +4,7 @@ import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.NotNull
import java.time.LocalDate
import java.time.LocalDateTime
import java.math.BigDecimal
enum class StockOutStatus(val status: String) {
PENDING("pending"),
COMPLETE("completed"),
@@ -59,4 +60,26 @@ data class UpdateStockOutLineStatusRequest(
val status: String,
val qty: Double? = null,
val remarks: String? = null
)
data class QrPickSubmitLineRequest(
val stockOutLineId: Long,
val pickOrderLineId: Long,
val inventoryLotLineId: Long?,
val requiredQty: BigDecimal?,
val actualPickQty: BigDecimal?,
val stockOutLineStatus: String?,
val pickOrderConsoCode: String?,
val noLot: Boolean = false
)
data class QrPickBatchSubmitRequest(
val userId: Long,
val lines: List<QrPickSubmitLineRequest>
)
data class UpdateStockOutLineStatusByQRCodeAndLotNoRequest(
val pickOrderLineId: Long,
val inventoryLotNo: String,
val stockOutLineId: Long,
val itemId: Long,
val status: String
)

Загрузка…
Отмена
Сохранить