Browse Source

Merge branch 'master' of https://git.2fi-solutions.com/derek/FPSMS-backend

reset-do-picking-order
kelvin.yau 1 week ago
parent
commit
9d69ea2872
10 changed files with 230 additions and 8 deletions
  1. +67
    -0
      src/main/java/com/ffii/fpsms/modules/common/internalSetup/SetupController.kt
  2. +113
    -1
      src/main/java/com/ffii/fpsms/modules/common/internalSetup/inventorySetup.kt
  3. +4
    -2
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt
  4. +6
    -2
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt
  5. +2
    -1
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt
  6. +1
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/CreateJobOrderRequest.kt
  7. +1
    -0
      src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt
  8. +1
    -0
      src/main/java/com/ffii/fpsms/modules/productProcess/web/model/SaveProductProcessRequest.kt
  9. +8
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLineRepository.kt
  10. +27
    -2
      src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt

+ 67
- 0
src/main/java/com/ffii/fpsms/modules/common/internalSetup/SetupController.kt View File

@@ -207,4 +207,71 @@ class SetupController(
)
return ResponseEntity.ok(mapOf("success" to true, "message" to "Lot stock-in labels printed successfully", "printedCount" to printedCount))
}
data class PrintProgressResult(
val success: Boolean,
val lastIndex: Int,
val totalLots: Int,
val errorMessage: String? = null
)
@PostMapping("/inventory/print-lot-stockin-labels-by-item-ids-v2")
fun printLotStockInLabelsByItemIdsV2(@RequestBody request: Map<String, Any>): ResponseEntity<Map<String, Any>> {
val printerId = (request["printerId"] as? Number)?.toLong()
val printQty = (request["printQty"] as? Number)?.toInt() ?: 1
val fromIndex = (request["fromIndex"] as? Number)?.toInt()
val toIndex = (request["toIndex"] as? Number)?.toInt()
val itemIds = (request["itemIds"] as? List<*>)?.mapNotNull { (it as? Number)?.toLong() } ?: emptyList()
if (printerId == null) {
return ResponseEntity.badRequest().body(
mapOf(
"success" to false,
"message" to "printerId is required"
)
)
}
if (itemIds.isEmpty()) {
return ResponseEntity.badRequest().body(
mapOf(
"success" to false,
"message" to "itemIds is required"
)
)
}
return try {
val result = inventorySetup.printLotStockInLabelsByItemIdsV2(
printerId = printerId,
itemIds = itemIds,
printQty = printQty,
fromIndex = fromIndex,
toIndex = toIndex
)
val body = if (result.success) {
mapOf(
"success" to true,
"message" to "Lot stock-in labels printed successfully",
"lastIndex" to result.lastIndex,
"totalLots" to result.totalLots
)
} else {
mapOf(
"success" to false,
"message" to (result.errorMessage ?: "Printer error"),
"lastIndex" to result.lastIndex,
"totalLots" to result.totalLots
)
}
ResponseEntity.ok(body)
} catch (e: Exception) {
e.printStackTrace()
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
mapOf(
"success" to false,
"message" to (e.message ?: "Unknown error occurred while printing lot stock-in labels")
)
)
}
}
}

+ 113
- 1
src/main/java/com/ffii/fpsms/modules/common/internalSetup/inventorySetup.kt View File

@@ -353,5 +353,117 @@ open class InventorySetup {
}
return printedCount
}

data class PrintProgressResult(
val success: Boolean,
val lastIndex: Int,
val totalLots: Int,
val errorMessage: String? = null
)
fun printLotStockInLabelsByItemIdsV2(
printerId: Long,
itemIds: List<Long>,
printQty: Int = 1,
fromIndex: Int? = null,
toIndex: Int? = null
): PrintProgressResult {
if (itemIds.isEmpty()) {
println("No itemIds provided, nothing to print")
return PrintProgressResult(
success = false,
lastIndex = -1,
totalLots = 0,
errorMessage = "itemIds is empty"
)
}
// 1. 查出并过滤 lot(可以按你 v2 的逻辑过滤 inQty-outQty=0)
val allInventoryLotLines = inventoryLotLineRepository
.findAllByItemIdIn(itemIds)
.filter { it.deleted == false && it.inventoryLot?.stockInLine != null }
if (allInventoryLotLines.isEmpty()) {
println("No inventory lot lines found for itemIds=$itemIds")
return PrintProgressResult(
success = false,
lastIndex = -1,
totalLots = 0,
errorMessage = "no lots found for given itemIds"
)
}
val totalLots = allInventoryLotLines.size
// 如果要过滤 inQty-outQty = 0,就在这里再 filter 一次(你已经写过类似逻辑了)
val lotsToUse = allInventoryLotLines // 或者 nonZeroLots
val effectiveTotal = lotsToUse.size // 这就是你的 totalLots(按你的业务决定)
// 2. 计算本次要打的范围
val startIndex = (fromIndex ?: 0).coerceAtLeast(0)
val endIndex = (toIndex ?: (effectiveTotal - 1)).coerceAtMost(effectiveTotal - 1)
if (startIndex > endIndex || startIndex >= effectiveTotal) {
println("Invalid range: fromIndex=$fromIndex, toIndex=$toIndex, totalLots=$effectiveTotal")
return PrintProgressResult(
success = false,
lastIndex = startIndex - 1,
totalLots = effectiveTotal,
errorMessage = "invalid index range"
)
}
println("Printing range: $startIndex to $endIndex (out of $effectiveTotal)")
// 3. 循环打印,实时更新 lastIndex,出现打印错误时立即返回
var lastIndex = startIndex - 1 // 还没成功打印任何一张时是 startIndex-1
for (globalIndex in startIndex..endIndex) {
val inventoryLotLine = lotsToUse[globalIndex]
val stockInLineId = inventoryLotLine.inventoryLot?.stockInLine?.id
?: return PrintProgressResult(
success = false,
lastIndex = lastIndex,
totalLots = effectiveTotal,
errorMessage = "Stock in line missing for inventoryLotLineId=${inventoryLotLine.id}"
)
try {
println("Processing lot ${globalIndex + 1}/$effectiveTotal: lotNo=${inventoryLotLine.inventoryLot?.lotNo}, stockInLineId=$stockInLineId")
stockInLineService.printQrCode(
PrintQrCodeForSilRequest(
stockInLineId = stockInLineId,
printerId = printerId,
printQty = printQty
)
)
lastIndex = globalIndex
println("✓ Printed label for lotNo=${inventoryLotLine.inventoryLot?.lotNo}")
} catch (e: Exception) {
// 打印异常(最常见:打印机没纸 / 打印机错误)
println("✗ Printer error at index=$globalIndex, lotLineId=${inventoryLotLine.id}: ${e.message}")
e.printStackTrace()
val msg = when {
e.message?.contains("paper", ignoreCase = true) == true ->
"Printer error (maybe out of paper): ${e.message}"
else ->
"Printer error: ${e.message}"
}
return PrintProgressResult(
success = false,
lastIndex = lastIndex, // 最后成功打印到哪一张
totalLots = effectiveTotal,
errorMessage = msg
)
}
}
// 能循环走完,说明这段全部打完
return PrintProgressResult(
success = true,
lastIndex = lastIndex, // 成功场景下,这里会等于 endIndex
totalLots = effectiveTotal,
errorMessage = null
)
}
}

+ 4
- 2
src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt View File

@@ -30,7 +30,8 @@ interface JobOrderInfo {

@get:Value("#{target.stockInLines?.size() > 0 ? target.stockInLines[0].escalationLog.^[status.value == 'pending']?.handler?.id : null}")
val silHandlerId: Long?;

@get:Value("#{target.stockInLines?.size() > 0 ? target.stockInLines[0].lotNo : null}")
val lotNo: String?
val planStart: LocalDateTime?;
// @get:Value("#{target.bom.item.itemUoms.^[salesUnit == true && deleted == false]?.uom}")
//// @get:Value("#{target.bom.item.itemUoms.^[salesUnit == true && deleted == false]?.uom.udfudesc}")
@@ -66,7 +67,8 @@ data class JobOrderInfoWithTypeName(
val productionPriority: Int?,
val status: String,
val jobTypeId: Long?,
val jobTypeName: String?
val jobTypeName: String?,
val lotNo: String?
)
// Job Order
interface JobOrderDetailWithJsonString {


+ 6
- 2
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt View File

@@ -1809,7 +1809,7 @@ open fun getAllJoPickOrders(isDrink: Boolean?): List<AllJoPickOrderResponse> {
val bom = jobOrder.bom

// 按 isDrink 过滤:null 表示不过滤(全部)
if (isDrink != null && bom?.isDrink != isDrink) return@mapNotNull null
if (isDrink != null && bom?.isDrink != isDrink) return@mapNotNull null
println("BOM found: ${bom?.id}")
val item = bom?.item
@@ -1831,7 +1831,10 @@ if (isDrink != null && bom?.isDrink != isDrink) return@mapNotNull null
val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrder.id ?: return@mapNotNull null)
val finishedLines = pickOrderLines.count { it.status == PickOrderLineStatus.COMPLETED }
val jobOrderType = jobOrder.jobTypeId?.let { jobTypeRepository.findById(it).orElse(null) }

val stockInLine = stockInLineRepository.findFirstByJobOrder_IdAndDeletedFalse(
jobOrder.id ?: return@mapNotNull null
)
val lotNo = stockInLine?.lotNo
println("✅ Building response for pick order ${pickOrder.id}")
val floorPickCounts = floorCountsByPickOrderId[pickOrder.id]?.map { row ->
com.ffii.fpsms.modules.jobOrder.web.model.FloorPickCountDto(
@@ -1854,6 +1857,7 @@ if (isDrink != null && bom?.isDrink != isDrink) return@mapNotNull null
//uomId = bom.outputQtyUom?.id : 0L,
uomId = 0,
uomName = bom?.outputQtyUom?: "",
lotNo = lotNo,
jobOrderStatus = jobOrder.status?.value ?: "",
finishedPickOLineCount = finishedLines,
floorPickCounts = floorPickCounts


+ 2
- 1
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt View File

@@ -194,7 +194,8 @@ open class JobOrderService(
productionPriority = info.productionPriority,
status = info.status,
jobTypeId = info.jobTypeId,
jobTypeName = info.jobTypeId?.let { jobTypes[it]?.name }
jobTypeName = info.jobTypeId?.let { jobTypes[it]?.name },
lotNo = info.lotNo
)
}
.filter { info ->


+ 1
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/CreateJobOrderRequest.kt View File

@@ -52,6 +52,7 @@ data class AllJoPickOrderResponse(
val reqQty: BigDecimal,
val uomId: Long,
val uomName: String,
val lotNo: String?,
val jobOrderStatus: String,
val finishedPickOLineCount: Int,
val floorPickCounts: List<FloorPickCountDto> = emptyList()


+ 1
- 0
src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt View File

@@ -1414,6 +1414,7 @@ open class ProductProcessService(
},
RequiredQty = jobOrder?.reqQty?.toInt() ?: 0,
Uom = bomUom?.udfudesc,
lotNo = stockInLine?.lotNo,
productionPriority = productProcesses.productionPriority,
date = productProcesses.date,
bomId = productProcesses.bom?.id,


+ 1
- 0
src/main/java/com/ffii/fpsms/modules/productProcess/web/model/SaveProductProcessRequest.kt View File

@@ -169,6 +169,7 @@ data class AllJoborderProductProcessInfoResponse(
val matchStatus: String?,
val RequiredQty: Int?,
val Uom: String?,
val lotNo: String?,
val jobOrderId: Long?,
val productionPriority: Int?,
val jobOrderCode: String?,


+ 8
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLineRepository.kt View File

@@ -78,4 +78,12 @@ fun findFirstByJobOrder_IdAndDeletedFalse(jobOrderId: Long): StockInLine?
ORDER BY sil.purchaseOrder.id
""")
fun findCompletedDnByReceiptDate(@Param("receiptDate") receiptDate: LocalDate): List<StockInLine>

@Query("""
select max(sil.lotNo)
from StockInLine sil
where sil.deleted = false
and sil.lotNo like concat(:prefix, '%')
""")
fun findLatestLotNoByPrefix(@Param("prefix") prefix: String): String?
}

+ 27
- 2
src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt View File

@@ -110,7 +110,7 @@ open class StockInLineService(
open fun getReceivedStockInLineInfo(stockInLineId: Long): StockInLineInfo {
return stockInLineRepository.findStockInLineInfoByIdAndStatusAndDeletedFalse(id = stockInLineId, status = StockInLineStatus.RECEIVED.status).orElseThrow()
}
/*
open fun assignLotNo(): String {
val prefix = "LT"
// ✅ 每次调用都取今天的日期段
@@ -126,7 +126,32 @@ open class StockInLineService(
latestCode = latestCode
)
}

*/
fun assignLotNo(): String {
val prefix = "LT"
// 建議統一用同一種日期格式,現在 StockInLineService 是 DEFAULT_FORMATTER (LocalDate.today)
val midfix = LocalDate.now().format(CodeGenerator.DEFAULT_FORMATTER)
val fullPrefix = "$prefix-$midfix"
// 1) 今天在 inventory_lot 裡的最大 lotNo
val latestFromInventory =
inventoryLotRepository.findLatestLotNoByPrefix(fullPrefix)
// 2) 今天在 stock_in_line 裡的最大 lotNo(不分來源:JO、PO、盤點...)
val latestFromStockInLine =
stockInLineRepository.findLatestLotNoByPrefix(fullPrefix)
// 3) 兩邊取最大的一個當成 latestCode
val latestCode = listOfNotNull(latestFromInventory, latestFromStockInLine)
.maxOrNull()
// 4) 丟給你現有的 CodeGenerator 產下一號
return CodeGenerator.generateNo(
prefix = prefix,
midfix = midfix,
latestCode = latestCode,
)
}
@Throws(IOException::class)
@Transactional
open fun create(request: SaveStockInLineRequest): MessageResponse {


Loading…
Cancel
Save