Browse Source

update

master
CANCERYS\kw093 4 weeks ago
parent
commit
cf79a5a4b7
8 changed files with 366 additions and 254 deletions
  1. +186
    -201
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt
  2. +4
    -2
      src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt
  3. +14
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLineRepository.kt
  4. +24
    -2
      src/main/java/com/ffii/fpsms/modules/productProcess/entity/projections/ProductProcessInfo.kt
  5. +135
    -48
      src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt
  6. +1
    -1
      src/main/java/com/ffii/fpsms/modules/productProcess/web/model/SaveProductProcessRequest.kt
  7. +1
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotRepository.kt
  8. +1
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockOutLIneRepository.kt

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

@@ -13,7 +13,7 @@ import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDate
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus
import java.time.LocalDateTime
import com.ffii.core.support.JdbcDao
import com.ffii.fpsms.modules.jobOrder.web.model.SecondScanSubmitRequest
@@ -24,7 +24,11 @@ import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssueRepository
import java.math.BigDecimal
import com.ffii.fpsms.modules.pickOrder.entity.HandleStatus
import com.ffii.fpsms.modules.pickOrder.entity.IssueCategory

import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository
import com.ffii.fpsms.modules.stock.entity.SuggestPickLotRepository
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository
import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository
import com.ffii.fpsms.modules.stock.entity.StockOutLIneRepository
@Service
open class JoPickOrderService(
private val joPickOrderRepository: JoPickOrderRepository,
@@ -33,7 +37,12 @@ open class JoPickOrderService(
private val userService: UserService,
private val pickOrderRepository: PickOrderRepository,
private val jdbcDao: JdbcDao,
private val pickExecutionIssueRepository: PickExecutionIssueRepository
private val pickExecutionIssueRepository: PickExecutionIssueRepository,
private val pickOrderLineRepository: PickOrderLineRepository,
private val suggestPickLotRepository: SuggestPickLotRepository,
private val inventoryLotLineRepository: InventoryLotLineRepository,
private val inventoryLotRepository: InventoryLotRepository,
private val stockOutLineRepository: StockOutLIneRepository
) {
open fun save(record: JoPickOrder): JoPickOrder {
@@ -209,10 +218,8 @@ open class JoPickOrderService(
println("🔍 DEBUG: Found ${allAssignedPickOrders.size} job order pick orders assigned to user $userId")
val visiblePickOrders = allAssignedPickOrders
// Filter based on assignment and status
val filteredPickOrders = if (visiblePickOrders.isNotEmpty()) {
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
@@ -248,223 +255,201 @@ open class JoPickOrderService(
)
}
// 修复:简化 SQL 查询,移除不存在的字段
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,
-- Job Order Information
jo.id as jobOrderId,
jo.code as jobOrderCode,
-- 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,
-- Simplified Router Information using warehouse
w.`order` as routerIndex,
w.code as routerRoute,
w.code as routerArea,
-- 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,
// 使用 Repository 获取数据
try {
val pickOrders = pickOrderRepository.findAllByIdIn(pickOrderIds)
.filter { it.deleted == false && it.assignTo?.id == userId }
-- Stock out line information
sol.id as stockOutLineId,
sol.status as stockOutLineStatus,
COALESCE(sol.qty, 0) as stockOutLineQty,
if (pickOrders.isEmpty()) {
return mapOf(
"pickOrder" to null as Any?,
"pickOrderLines" to emptyList<Map<String, Any>>() as Any?
)
}
-- 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,
val pickOrder = pickOrders.first() // 取第一个(应该只有一个)
val jobOrder = pickOrder.jobOrder ?: return mapOf(
"pickOrder" to null as Any?,
"pickOrderLines" to emptyList<Map<String, Any>>() as Any?
)
-- 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,
// 获取 pick order lines
val pickOrderLines = pickOrderLineRepository.findAllByPickOrderId(pickOrder.id!!)
.filter { it.deleted == false }
-- Calculate remaining available quantity correctly
(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as remainingAfterAllPickOrders,
// 获取所有 pick order line IDs
val pickOrderLineIds = pickOrderLines.map { it.id!! }
-- 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,
// 获取 suggested pick lots
val suggestedPickLots = if (pickOrderLineIds.isNotEmpty()) {
suggestPickLotRepository.findAllByPickOrderLineIdIn(pickOrderLineIds)
.filter { it.deleted == false }
} else {
emptyList()
}
-- 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,
// 获取所有 inventory lot line IDs
val inventoryLotLineIds = suggestedPickLots.mapNotNull { it.suggestedLotLine?.id }
-- JoPickOrder second scan status
jpo.match_status as match_status,
jpo.match_by as match_by,
jpo.match_qty as match_qty,
jpo.ticketReleaseTime as ticketReleaseTime,
jpo.ticketCompleteTime as ticketCompleteTime
// 获取 inventory lot lines
val inventoryLotLines = if (inventoryLotLineIds.isNotEmpty()) {
inventoryLotLineRepository.findAllByIdIn(inventoryLotLineIds)
.filter { it.deleted == false }
} else {
emptyList()
}
FROM fpsmsdb.pick_order po
JOIN fpsmsdb.job_order jo ON jo.id = po.joId
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.inventory_lot il ON il.id = ill.inventoryLotId
LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId -- 直接关联 warehouse
LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.inventoryLotLineId = ill.id AND sol.deleted = false
LEFT JOIN fpsmsdb.jo_pick_order jpo ON jpo.pick_order_id = po.id AND jpo.item_id = pol.itemId
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(w.`order`, 999999) ASC, -- 使用 warehouse.order 排序
po.code ASC,
i.code ASC,
il.expiryDate ASC,
il.lotNo ASC
""".trimIndent()
println("🔍 Executing SQL for job order hierarchical structure: $sql")
println("🔍 With parameters: userId = $userId, pickOrderIds = $pickOrderIdsStr")
// 修复:直接执行 SQL 查询并构建分层结构
try {
val results = jdbcDao.queryForList(sql, mapOf("userId" to userId))
// 获取 inventory lots
val inventoryLotIds = inventoryLotLines.mapNotNull { it.inventoryLot?.id }.distinct()
val inventoryLots = if (inventoryLotIds.isNotEmpty()) {
inventoryLotRepository.findAllByIdIn(inventoryLotIds)
.filter { it.deleted == false }
} else {
emptyList()
}
if (results.isEmpty()) {
return mapOf(
"pickOrder" to null as Any?,
"pickOrderLines" to emptyList<Map<String, Any>>() as Any?
)
// 获取 stock out lines
val stockOutLines = if (pickOrderLineIds.isNotEmpty() && inventoryLotLineIds.isNotEmpty()) {
pickOrderLineIds.flatMap { polId ->
inventoryLotLineIds.flatMap { illId ->
stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(polId, illId)
}
}
} else {
emptyList()
}
// 取得所有 stock out line(含無 lot 情況)
val stockOutLinesByPickOrderLine = pickOrderLineIds.associateWith { polId ->
stockOutLineRepository.findAllByPickOrderLineIdAndDeletedFalse(polId)
}
// 获取 jo_pick_order 记录
val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrder.id!!)
// 构建分层结构
val pickOrderInfo = results.firstOrNull()?.let { firstRow: Map<String, Any?> ->
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"],
"jobOrder" to mapOf(
"id" to firstRow["jobOrderId"],
"code" to firstRow["jobOrderCode"],
"name" to "Job Order ${firstRow["jobOrderCode"]}" // 使用生成的名称
)
// 构建 pick order info
val pickOrderInfo = mapOf(
"id" to pickOrder.id,
"code" to pickOrder.code,
"consoCode" to pickOrder.consoCode,
"targetDate" to pickOrder.targetDate?.let {
"${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}"
},
"type" to pickOrder.type?.value,
"status" to pickOrder.status?.value,
"assignTo" to pickOrder.assignTo?.id,
"jobOrder" to mapOf(
"id" to jobOrder.id,
"code" to jobOrder.code,
"name" to "Job Order ${jobOrder.code}"
)
}
)
// 按 pick order line 分组
val groupedByLine = results.groupBy { row: Map<String, Any?> -> row["pickOrderLineId"] }
val pickOrderLines = groupedByLine.map { (lineId: Any?, lineRows: List<Map<String, Any?>>) ->
val firstLineRow = lineRows.first()
mapOf(
"id" to lineId,
"itemId" to firstLineRow["itemId"],
"itemCode" to firstLineRow["itemCode"],
"itemName" to firstLineRow["itemName"],
"requiredQty" to firstLineRow["pickOrderLineRequiredQty"],
"uomCode" to firstLineRow["uomCode"],
"uomDesc" to firstLineRow["uomDesc"],
"lots" to lineRows.map { row: Map<String, Any?> ->
mapOf(
"lotId" to row["lotId"],
"lotNo" to row["lotNo"],
"expiryDate" to row["expiryDate"],
"location" to row["location"],
"availableQty" to row["availableQty"],
"requiredQty" to row["requiredQty"],
"actualPickQty" to row["actualPickQty"],
"processingStatus" to row["processingStatus"],
"lotAvailability" to row["lotAvailability"],
"pickOrderId" to row["pickOrderId"],
"pickOrderCode" to row["pickOrderCode"],
"pickOrderConsoCode" to row["pickOrderConsoCode"],
"pickOrderLineId" to row["pickOrderLineId"],
"stockOutLineId" to row["stockOutLineId"],
"suggestedPickLotId" to row["suggestedPickLotId"],
"stockOutLineQty" to row["stockOutLineQty"],
"stockOutLineStatus" to row["stockOutLineStatus"],
"routerIndex" to row["routerIndex"],
"routerArea" to row["routerArea"],
"routerRoute" to row["routerRoute"],
"uomShortDesc" to row["uomShortDesc"],
"matchStatus" to row["match_status"],
"matchBy" to row["match_by"],
"matchQty" to row["match_qty"]
)
// 构建 pick order lines with lots
val pickOrderLinesResult = pickOrderLines.map { pol ->
val item = pol.item
val uom = pol.uom
val lineId = pol.id!!
val suggestions = suggestedPickLots.filter { it.pickOrderLine?.id == lineId }
val stockoutsForLine = stockOutLinesByPickOrderLine[lineId].orEmpty()
// 获取该 line 的 suggested pick lots
val lineSuggestedLots = suggestedPickLots.filter { it.pickOrderLine?.id == pol.id }
// 构建 lots 数据
val lots = lineSuggestedLots.mapNotNull { spl ->
val ill = spl.suggestedLotLine
if (ill == null || ill.deleted == true) return@mapNotNull null
val il = ill.inventoryLot
if (il == null || il.deleted == true) return@mapNotNull null
val warehouse = ill.warehouse
// 获取对应的 stock out line
val sol = stockOutLines.firstOrNull {
it.pickOrderLine?.id == pol.id && it.inventoryLotLine?.id == ill.id
}
// 获取对应的 jo_pick_order
val jpo = joPickOrders.firstOrNull { it.itemId == item?.id }
// 计算 available quantity
val availableQty = if (sol?.status == "rejected") {
null
} else {
(ill.inQty ?: BigDecimal.ZERO) - (ill.outQty ?: BigDecimal.ZERO) - (ill.holdQty ?: BigDecimal.ZERO)
}
// 计算 total picked by all pick orders
val totalPickedByAllPickOrders = stockOutLines
.filter { it.inventoryLotLine?.id == ill.id && it.deleted == false }
.filter { it.status in listOf("pending", "checked", "partially_completed", "completed") }
.sumOf { it.qty?.toBigDecimal() ?: BigDecimal.ZERO }
// 计算 lot availability
val lotAvailability = when {
il.expiryDate != null && il.expiryDate!!.isBefore(LocalDate.now()) -> "expired"
sol?.status == "rejected" -> "rejected"
availableQty != null && availableQty <= BigDecimal.ZERO -> "insufficient_stock"
ill.status == InventoryLotLineStatus.UNAVAILABLE -> "status_unavailable"
else -> "available"
}
// 计算 processing status
val processingStatus = when (sol?.status) {
"completed" -> "completed"
"rejected" -> "rejected"
"created" -> "pending"
else -> "pending"
}
mapOf(
"lotId" to ill.id,
"lotNo" to il.lotNo,
"expiryDate" to il.expiryDate?.let {
"${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}"
},
"location" to warehouse?.name,
"availableQty" to availableQty?.toDouble(),
"requiredQty" to (spl.qty?.toDouble() ?: 0.0),
"actualPickQty" to (sol?.qty ?: 0.0),
"processingStatus" to processingStatus,
"lotAvailability" to lotAvailability,
"pickOrderId" to pickOrder.id,
"pickOrderCode" to pickOrder.code,
"pickOrderConsoCode" to pickOrder.consoCode,
"pickOrderLineId" to pol.id,
"stockOutLineId" to sol?.id,
"suggestedPickLotId" to spl.id,
"stockOutLineQty" to (sol?.qty ?: 0.0),
"stockOutLineStatus" to sol?.status,
"routerIndex" to warehouse?.order,
"routerArea" to warehouse?.code,
"routerRoute" to warehouse?.code,
"uomShortDesc" to uom?.udfShortDesc,
"matchStatus" to jpo?.matchStatus?.value,
"matchBy" to jpo?.matchBy,
"matchQty" to jpo?.matchQty
)
}
mapOf(
"id" to pol.id,
"itemId" to item?.id,
"itemCode" to item?.code,
"itemName" to item?.name,
"requiredQty" to pol.qty?.toDouble(),
"uomCode" to uom?.code,
"uomDesc" to uom?.udfudesc,
"lots" to lots
)
}
return mapOf(
"pickOrder" to pickOrderInfo as Any?,
"pickOrderLines" to pickOrderLines as Any?
"pickOrderLines" to pickOrderLinesResult as Any?
)
} catch (e: Exception) {
println("❌ Error executing Job Order SQL: ${e.message}")
println("❌ Error executing Job Order hierarchical query: ${e.message}")
e.printStackTrace()
return mapOf(
"pickOrder" to null as Any?,


+ 4
- 2
src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt View File

@@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import com.ffii.fpsms.modules.jobOrder.service.JoPickOrderService
import com.ffii.fpsms.modules.productProcess.service.ProductProcessService
import com.ffii.fpsms.modules.jobOrder.web.model.*
import com.ffii.fpsms.modules.jobOrder.web.model.ExportPickRecordRequest
import com.ffii.fpsms.modules.jobOrder.web.model.PrintPickRecordRequest
@@ -40,7 +41,8 @@ class JobOrderController(
private val jobOrderService: JobOrderService,
private val jobOrderBomMaterialService: JobOrderBomMaterialService,
private val jobOrderProcessService: JobOrderProcessService,
private val joPickOrderService: JoPickOrderService
private val joPickOrderService: JoPickOrderService,
private val productProcessService: ProductProcessService
) {

@GetMapping("/getRecordByPage")
@@ -87,7 +89,7 @@ class JobOrderController(
}
jobOrderBomMaterialService.createJobOrderBomMaterialsByJoId(jo.id)
jobOrderProcessService.createJobOrderProcessesByJoId(jo.id)
productProcessService.createProductProcessByJobOrderId(jo.id)
return jo
}
@GetMapping("/all-lots-hierarchical/{userId}")


+ 14
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLineRepository.kt View File

@@ -39,4 +39,18 @@ interface ProductionScheduleLineRepository : AbstractRepository<ProductionSchedu
group by psl.id, bm.id, i.id, pp.proportion
""")
fun getBomMaterials(id: Long): List<DetailedProdScheduleLineBomMaterialInterface>?
@Query("""
SELECT psl FROM ProductionScheduleLine psl
JOIN psl.productionSchedule ps
WHERE psl.item.id = :itemId
AND ps.produceAt = :produceAt
AND psl.type = :type
AND ps.deleted = false
ORDER BY psl.itemPriority ASC
""")
fun findByItemIdAndProduceAtAndType(
itemId: Long,
produceAt: LocalDateTime,
type: String
): ProductionScheduleLine?
}

+ 24
- 2
src/main/java/com/ffii/fpsms/modules/productProcess/entity/projections/ProductProcessInfo.kt View File

@@ -24,16 +24,24 @@ data class ProductProcessInfo(
val isDark: String?,
val isDense: Int?,
val isFloat: String?,
val itemId: Long?,
val itemCode: String?,
val itemName: String?,
val outputQtyUom: String?,
val outputQty: Int?,

val productProcessLines: List<ProductProcessLineInfo>?
val productionPriority: String?,
val productProcessLines: List<ProductProcessLineInfo>?,
val totalStockQty: Int?,
val insufficientStockQty: Int?,
val sufficientStockQty: Int?,
val jobOrderLines: List<jobOrderLineInfo>?
)

data class ProductProcessLineInfo(
val id:Long?,
val bomprocessId: Long?,
val operatorId: Long?,
val operatorName: String?,
val equipmentId: Long?,
val handlerId: Long?,
val seqNo: Long?,
@@ -50,8 +58,22 @@ data class ProductProcessLineInfo(
val defectUom: String?,
val outputFromProcessQty: Int?,
val outputFromProcessUom: String?,
val durationInMinutes: Int?,
val prepTimeInMinutes: Int?,
val postProdTimeInMinutes: Int?,
@JsonFormat(pattern = "yyyy-MM-dd")
val startTime: LocalDateTime?,
@JsonFormat(pattern = "yyyy-MM-dd")
val endTime: LocalDateTime?
)
data class jobOrderLineInfo(
val id: Long?,
val itemId: Long?,
val itemCode: String?,
val itemName: String?,
val reqQty: Int?,
val stockQty: Int?,
val uom: String?,
val shortUom: String?,
val availableStatus: String?
)

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

@@ -7,6 +7,7 @@ import com.ffii.fpsms.modules.productProcess.enums.ProductProcessStatus
import com.ffii.fpsms.modules.productProcess.web.model.*
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import com.ffii.fpsms.modules.productProcess.entity.projections.jobOrderLineInfo
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDate
@@ -27,6 +28,14 @@ import com.ffii.fpsms.modules.master.entity.Equipment
import com.ffii.fpsms.modules.master.entity.BomProcess
import com.ffii.fpsms.modules.master.entity.Bom
import com.ffii.fpsms.modules.master.web.models.MessageResponse
import com.ffii.fpsms.modules.jobOrder.entity.JobOrderBomMaterialRepository
import com.ffii.fpsms.modules.master.service.ProductionScheduleService
import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository
import com.ffii.fpsms.modules.master.entity.ProductionScheduleLineRepository
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus
import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus
import com.ffii.fpsms.modules.stock.service.StockInLineService
import com.ffii.fpsms.modules.stock.web.model.SaveStockInLineRequest
@Service
@Transactional
open class ProductProcessService(
@@ -40,6 +49,10 @@ open class ProductProcessService(
private val userRepository: UserRepository,
private val processRepository: ProcessRepository,
private val equipmentRepository: EquipmentRepository,
private val jobOrderBomMaterialRepository: JobOrderBomMaterialRepository,
private val inventoryLotLineRepository: InventoryLotLineRepository,
private val productionScheduleLineRepository: ProductionScheduleLineRepository,
private val stockInLineService: StockInLineService,
) {
open fun findAll(pageable: Pageable): Page<ProductProcess> {
@@ -457,6 +470,62 @@ open class ProductProcessService(
val productProcesses = productProcessRepository.findByJobOrder_Id(joid)
val jobOrder = jobOrderRepository.findById(joid).orElse(null)
val bom = bomRepository.findById(jobOrder?.bom?.id?:0L).orElse(null)
val bomProcess = bomProcessRepository.findByBomId(jobOrder?.bom?.id?:0L)
// get bom materials
val bomMaterials = jobOrderBomMaterialRepository.findAllByJobOrderId(jobOrder?.id?:0)
val itemIds = bomMaterials.mapNotNull { it.item?.id }
// calculate each item's available stock
val stockQtyMap = bomMaterials.mapNotNull { material ->
val itemId = material.item?.id ?: return@mapNotNull null
val availableLots = inventoryLotLineRepository
.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.AVAILABLE)
val stockQty = availableLots.sumOf { lot ->
(lot.inQty ?: BigDecimal.ZERO)
.minus(lot.outQty ?: BigDecimal.ZERO)
.minus(lot.holdQty ?: BigDecimal.ZERO)
}
itemId to stockQty
}.toMap()
// calculate statistics
val totalStockQty = stockQtyMap.values.sumOf { it.toInt() }
val insufficientStockQty = bomMaterials
.filter { material ->
val itemId = material.item?.id ?: 0L
val stockQty = stockQtyMap[itemId] ?: BigDecimal.ZERO
stockQty < (material.reqQty ?: BigDecimal.ZERO)
}
.sumOf { material ->
val itemId = material.item?.id ?: 0L
stockQtyMap[itemId]?.toInt() ?: 0
}
val sufficientStockQty = bomMaterials
.filter { material ->
val itemId = material.item?.id ?: 0L
val stockQty = stockQtyMap[itemId] ?: BigDecimal.ZERO
stockQty >= (material.reqQty ?: BigDecimal.ZERO)
}
.sumOf { material ->
val itemId = material.item?.id ?: 0L
stockQtyMap[itemId]?.toInt() ?: 0
}
// 获取 productionPriority
val itemId = jobOrder?.bom?.item?.id
val planEndDate = jobOrder?.planEnd?.toLocalDate()
val productionPriority = if (itemId != null && planEndDate != null) {
val scheduleLine = productionScheduleLineRepository
.findByItemIdAndProduceAtAndType(
itemId,
planEndDate.atStartOfDay(),
"detailed"
)
scheduleLine?.itemPriority?.toString() ?: "0"
} else {
"0"
}
fun calculateColourScore(value: Int?): String {
return when (value) {
0 -> "淺"
@@ -471,13 +540,16 @@ open class ProductProcessService(
else -> ""
}
}
//val processId = productProcess.id ?: 0
return productProcesses.map { process ->
ProductProcessInfo(
id = process.id?:0,
bomId = process.bom?.id?:0,
jobOrderId = process.jobOrder?.id?:0,
jobOrderCode = jobOrder?.code?:"",
itemId = bom?.item?.id?:0,
itemCode = bom?.item?.code?:"",
itemName = bom?.item?.name?:"",
isDark = calculateColourScore(bom?.isDark?:0),
isDense = bom?.isDense?:0,
isFloat = calculateFloatScore(bom?.isFloat?:0),
@@ -488,11 +560,16 @@ open class ProductProcessService(
startTime = process.startTime?:LocalDateTime.now(),
endTime = process.endTime?:LocalDateTime.now(),
date = process.date?:LocalDate.now(),
productionPriority = productionPriority, // 已经是 String,不需要 ?:0
totalStockQty = totalStockQty,
insufficientStockQty = insufficientStockQty,
sufficientStockQty = sufficientStockQty,
productProcessLines = productProcessLineRepository.findByProductProcess_Id(process.id?:0).map { line ->
ProductProcessLineInfo(
id = line.id?:0,
bomprocessId = line.bomProcess?.id?:0,
operatorId = line.operator?.id?:0,
operatorName = line.operator?.name?:"",
equipmentId = line.equipment?.id?:0,
handlerId = line.handler?.id?:0,
seqNo = line.seqNo?:0,
@@ -500,6 +577,9 @@ open class ProductProcessService(
description = line.description?:"",
equipment_name = line.equipmentType?:"",
status = line.status?:"",
durationInMinutes = line.bomProcess?.durationInMinute?:0,
prepTimeInMinutes = line.bomProcess?.prepTimeInMinute?:0,
postProdTimeInMinutes = line.bomProcess?.postProdTimeInMinute?:0,
byproductId = line.byproduct?.id?:0,
byproductName = line.byproduct?.name?:"",
byproductQty = line.byproductQty?:0,
@@ -512,54 +592,36 @@ open class ProductProcessService(
startTime = line.startTime,
endTime = line.endTime
)
},
jobOrderLines = bomMaterials.map { line ->
val itemId = line.item?.id ?: 0L
val stockQty = stockQtyMap[itemId]?.toInt() ?: 0
val uom = line.uom
val shortUom = uom?.udfShortDesc ?: uom?.udfudesc ?: ""
// 计算 availableStatus
val availableStatus = if (stockQty >= (line.reqQty?.toInt() ?: 0)) {
"available"
} else {
"insufficient"
}
jobOrderLineInfo(
id = line.id?:0,
itemId = itemId,
itemCode = line.item?.code?:"",
itemName = line.item?.name?:"",
reqQty = line.reqQty?.toInt() ?: 0,
stockQty = stockQty,
uom = uom?.udfudesc ?: "",
shortUom = shortUom,
availableStatus = availableStatus
)
}
)
}
}
/*
open fun productProcessDetailfindbyidtest(id: Long): ProductProcessInfo {
val zero = BigDecimal.ZERO
val productProcess = productProcessRepository.findById(id).orElse(null)
val processId = productProcess.id ?: 0

val productProcessLines = productProcessLineRepository.findByProductProcess_Id(processId)
return ProductProcessInfo(
id = productProcess.id?:0,
bomId = productProcess.bom?.id?:0,
jobOrderId = productProcess.jobOrder?.id?:0,
productProcessCode = productProcess.productProcessCode?:"",
status = productProcess.status?:ProductProcessStatus.PENDING,
startTime = productProcess.startTime?:LocalDateTime.now(),
endTime = productProcess.endTime?:LocalDateTime.now(),
date = productProcess.date?:LocalDate.now(),
productProcessLines = productProcessLines.map { line ->
ProductProcessLineInfo(
id = line.id?:0,
bomprocessId = line.bomProcess?.id?:0,
operatorId = line.operator?.id?:0,
equipmentId = line.equipment?.id?:0,
handlerId = line.handler?.id?:0,
seqNo = line.seqNo?:0,
name = line.name?:"",
description = line.description?:"",
equipment_name = line.equipment?.name?:"",
status = line.status?:"",
byproductId = line.byproduct?.id?:0,
byproductName = line.byproduct?.name?:"",
byproductQty = line.byproductQty?:0,
byproductUom = line.byproductUom?:"",
scrapQty = line.scrapQty?:0,
defectQty = line.defectQty?:0,
defectUom = line.defectUom?:"",
outputFromProcessQty = line.outputFromProcessQty?:0,
outputFromProcessUom = line.outputFromProcessUom?:"",
startTime = line.startTime?:LocalDateTime.now(),
endTime = line.endTime?:LocalDateTime.now()
)
}
)
}
*/
open fun createProductProcessByJobOrderId(jobOrderId: Long): MessageResponse {
val jobOrder = jobOrderRepository.findById(jobOrderId).orElse(null)
val bom = bomRepository.findById(jobOrder?.bom?.id ?: 0L).orElse(null)
@@ -792,6 +854,7 @@ open fun updateProductProcessLineQty(request: UpdateProductProcessLineQtyRequest
productProcessLine.byproductUom = byproductUom
}
productProcessLineRepository.save(productProcessLine)
CompleteProductProcessLine(request.productProcessLineId)
return MessageResponse(
id = request.productProcessLineId,
code = "200",
@@ -806,11 +869,14 @@ open fun updateProductProcessLineQty(request: UpdateProductProcessLineQtyRequest
val productProcesses = productProcessRepository.findAllByDeletedIsFalse()
val productProcessIds = productProcesses.map { it.id }

return productProcesses.map { productProcesses ->
val productProcessLines = productProcessLineRepository.findByProductProcess_Id(productProcesses.id?:0L)
val jobOrder = jobOrderRepository.findById(productProcesses.jobOrder?.id?:0L).orElse(null)
val FinishedProductProcessLineCount = productProcessLineRepository.findByProductProcess_Id(productProcesses.id?:0L).count { it.status == "Completed" }
val stockInLine = jobOrder?.stockInLines?.firstOrNull()
val stockInLineId = stockInLine?.id
//val silHandlerId = stockInLine?.escalationLog?.firstOrNull { it.status == "pending" }?.handler?.id

AllJoborderProductProcessInfoResponse(
id = productProcesses.id ?: 0L,
productProcessCode = productProcesses.productProcessCode,
@@ -821,6 +887,7 @@ open fun updateProductProcessLineQty(request: UpdateProductProcessLineQtyRequest
bomId = productProcesses.bom?.id,
itemName = productProcesses.item?.name,
jobOrderId = productProcesses.jobOrder?.id,
stockInLineId = stockInLineId,
jobOrderCode = jobOrder?.code,
productProcessLineCount = productProcessLines.size,
FinishedProductProcessLineCount = FinishedProductProcessLineCount,
@@ -936,10 +1003,30 @@ open fun updateProductProcessLineQty(request: UpdateProductProcessLineQtyRequest
updateProductProcessLineEndTime(productProcessLineId)
updateProductProcessLineStatus(productProcessLineId, "Completed")
val productProcessLine = productProcessLineRepository.findById(productProcessLineId).orElse(null)
val allproductProcessLines=productProcessLineRepository.findByProductProcess_Id(productProcessLine.productProcess?.id?:0L)
val productProcessId = productProcessLine?.productProcess?.id ?: 0L
val allproductProcessLines=productProcessLineRepository.findByProductProcess_Id(productProcessId)
if(allproductProcessLines.all { it.status == "Completed" }) {
updateProductProcessEndTime(productProcessLine.id)
updateProductProcessStatus(productProcessLine.id, ProductProcessStatus.COMPLETED)
updateProductProcessEndTime(productProcessId)
updateProductProcessStatus(productProcessId, ProductProcessStatus.COMPLETED)
val productProcess = productProcessRepository.findById(productProcessId).orElse(null)
val jobOrder = jobOrderRepository.findById(productProcess?.jobOrder?.id?:0L).orElse(null)
if(jobOrder != null) {
jobOrder.status = JobOrderStatus.STORING
jobOrderRepository.save(jobOrder)
stockInLineService.create(
SaveStockInLineRequest(
itemId = productProcess?.item?.id?:0L,
acceptedQty = jobOrder?.reqQty?:BigDecimal.ZERO,
productLotNo = jobOrder?.code,
productionDate = LocalDate.now(),
jobOrderId = jobOrder.id,
acceptQty =jobOrder?.reqQty?:BigDecimal.ZERO ,
expiryDate=null,
status="",
)
)
}
}
return MessageResponse(
id = productProcessLine.id,


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

@@ -147,7 +147,7 @@ data class AllJoborderProductProcessInfoResponse(
val jobOrderCode: String?,
val productProcessLineCount: Int,
val FinishedProductProcessLineCount: Int,
val stockInLineId: Long?,
val lines: List<ProductProcessInfoResponse>
)
data class ProductProcessInfoResponse(


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

@@ -15,4 +15,5 @@ interface InventoryLotRepository: AbstractRepository<InventoryLot, Long> {
select il.lotNo from InventoryLot il where il.lotNo like :prefix% order by il.lotNo desc limit 1
""")
fun findLatestLotNoByPrefix(prefix: String): String?
fun findAllByIdIn(ids: List<Long>): List<InventoryLot>
}

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

@@ -21,4 +21,5 @@ interface StockOutLIneRepository: AbstractRepository<StockOutLine, Long> {
inventoryLotLineId: Long
): List<StockOutLine>
fun existsByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(pickOrderLineId: Long, inventoryLotLineId: Long): Boolean
//fun findAllByPickOrderLineIdAndDeletedFalse(pickOrderLineId: Long): List<StockOutLine>
}

Loading…
Cancel
Save