Quellcode durchsuchen

change the PO with m18 uom and qty, included stock in PO, putaway process and GRN

master
Fai Luk vor 1 Tag
Ursprung
Commit
c76b1a565e
12 geänderte Dateien mit 193 neuen und 50 gelöschten Zeilen
  1. +4
    -4
      src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt
  2. +19
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt
  3. +4
    -3
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/PSService.kt
  4. +26
    -2
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt
  5. +4
    -3
      src/main/java/com/ffii/fpsms/modules/jobOrder/web/PSController.kt
  6. +21
    -0
      src/main/java/com/ffii/fpsms/modules/master/service/ItemUomService.kt
  7. +29
    -8
      src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt
  8. +17
    -5
      src/main/java/com/ffii/fpsms/modules/purchaseOrder/service/PurchaseOrderService.kt
  9. +9
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLine.kt
  10. +3
    -3
      src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt
  11. +49
    -21
      src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt
  12. +8
    -1
      src/main/java/com/ffii/fpsms/py/PyController.kt

+ 4
- 4
src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt Datei anzeigen

@@ -218,15 +218,15 @@ open class SchedulerService(
)*/ )*/
val tmr = today.plusDays(1L) val tmr = today.plusDays(1L)
var request = M18CommonRequest( var request = M18CommonRequest(
modifiedDateTo = tmr.format(dataStringFormat),
modifiedDateFrom = tmr.format(dataStringFormat)
dDateTo = tmr.format(dataStringFormat),
dDateFrom = tmr.format(dataStringFormat)
) )
m18PurchaseOrderService.savePurchaseOrders(request); m18PurchaseOrderService.savePurchaseOrders(request);


//dDate from tmr to tmr //dDate from tmr to tmr
var requestDO = M18CommonRequest( var requestDO = M18CommonRequest(
modifiedDateTo = tmr.format(dataStringFormat),
modifiedDateFrom = tmr.format(dataStringFormat)
dDateTo = tmr.format(dataStringFormat),
dDateFrom = tmr.format(dataStringFormat)
) )
m18DeliveryOrderService.saveDeliveryOrders(requestDO); m18DeliveryOrderService.saveDeliveryOrders(requestDO);
// logger.info("today: ${today.format(dataStringFormat)}") // logger.info("today: ${today.format(dataStringFormat)}")


+ 19
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt Datei anzeigen

@@ -135,6 +135,25 @@ interface JobOrderRepository : AbstractRepository<JobOrder, Long> {
planStartToExclusive: LocalDateTime, planStartToExclusive: LocalDateTime,
): List<JobOrder> ): List<JobOrder>


@Query(
"""
SELECT DISTINCT jo FROM JobOrder jo
JOIN jo.bom b
JOIN b.bomProcesses bp
JOIN bp.process p
WHERE jo.deleted = false
AND jo.planStart >= :planStartFrom
AND jo.planStart < :planStartToExclusive
AND p.name = :processName
ORDER BY jo.id ASC
"""
)
fun findByDeletedFalseAndPlanStartBetweenAndBomProcessNameOrderByIdAsc(
planStartFrom: LocalDateTime,
planStartToExclusive: LocalDateTime,
processName: String,
): List<JobOrder>

@Query(""" @Query("""
SELECT jo FROM JobOrder jo SELECT jo FROM JobOrder jo
WHERE jo.deleted = false WHERE jo.deleted = false


+ 4
- 3
src/main/java/com/ffii/fpsms/modules/jobOrder/service/PSService.kt Datei anzeigen

@@ -13,10 +13,11 @@ open class PSService(
private val jdbcDao: JdbcDao, private val jdbcDao: JdbcDao,
) { ) {
/** Default: past 30 days including today. */
/** Default: 6 days before today to 1 day after today. */
fun getItemDailyOut(fromDate: LocalDate? = null, toDate: LocalDate? = null): List<Map<String, Any>> { fun getItemDailyOut(fromDate: LocalDate? = null, toDate: LocalDate? = null): List<Map<String, Any>> {
val to = toDate ?: LocalDate.now()
val from = fromDate ?: to.minusDays(29)
val defaultToday = LocalDate.now()
val to = toDate ?: defaultToday.plusDays(1)
val from = fromDate ?: defaultToday.minusDays(6)
val args = mapOf( val args = mapOf(
"fromDate" to from.toString(), "fromDate" to from.toString(),
"toDate" to to.toString(), "toDate" to to.toString(),


+ 26
- 2
src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt Datei anzeigen

@@ -159,7 +159,31 @@ open class PlasticBagPrinterService(


require(normalizedJobOrders.isNotEmpty()) { "No job orders provided" } require(normalizedJobOrders.isNotEmpty()) { "No job orders provided" }


val normalizedCodes = normalizedJobOrders
// Safety check: export only supports job orders whose BOM contains process "包裝".
val requestedJobOrderIds = normalizedJobOrders.map { it.jobOrderId }.distinct()
val allowedRows = jdbcDao.queryForList(
"""
SELECT DISTINCT jo.id AS jobOrderId
FROM job_order jo
JOIN bom b ON b.id = jo.bomId
JOIN bom_process bp ON bp.bomId = b.id
JOIN process p ON p.id = bp.processId
WHERE jo.id IN (:jobOrderIds)
AND jo.deleted = 0
AND p.name = :processName
""".trimIndent(),
mapOf(
"jobOrderIds" to requestedJobOrderIds,
"processName" to "包裝",
)
)
val allowedJobOrderIds = allowedRows
.mapNotNull { (it["jobOrderId"] as? Number)?.toLong() }
.toSet()
val packagingJobOrders = normalizedJobOrders.filter { it.jobOrderId in allowedJobOrderIds }
require(packagingJobOrders.isNotEmpty()) { "No 包裝 process job orders found for export" }

val normalizedCodes = packagingJobOrders
.map { it.itemCode } .map { it.itemCode }
.distinct() .distinct()


@@ -184,7 +208,7 @@ open class PlasticBagPrinterService(
val baos = ByteArrayOutputStream() val baos = ByteArrayOutputStream()
ZipOutputStream(baos).use { zos -> ZipOutputStream(baos).use { zos ->
val addedEntries = linkedSetOf<String>() val addedEntries = linkedSetOf<String>()
normalizedJobOrders.forEach { jobOrder ->
packagingJobOrders.forEach { jobOrder ->
val filename = filenameByCode[jobOrder.itemCode].orEmpty() val filename = filenameByCode[jobOrder.itemCode].orEmpty()
if (filename.isBlank()) return@forEach if (filename.isBlank()) return@forEach




+ 4
- 3
src/main/java/com/ffii/fpsms/modules/jobOrder/web/PSController.kt Datei anzeigen

@@ -40,14 +40,15 @@ class PSController(
return ResponseEntity.ok(results) return ResponseEntity.ok(results)
} }


/** 每日平均出貨量: itemCode, itemName, avgQtyLastMonth, dailyQty, isCoffee, isTea, isLemon. Default: past 30 days. */
/** 每日平均出貨量: itemCode, itemName, avgQtyLastMonth, dailyQty, isCoffee, isTea, isLemon. Default: 6 days before today to 1 day after today. */
@GetMapping("/itemDailyOut.json") @GetMapping("/itemDailyOut.json")
fun itemDailyOut( fun itemDailyOut(
@RequestParam(required = false) fromDate: String?, @RequestParam(required = false) fromDate: String?,
@RequestParam(required = false) toDate: String?, @RequestParam(required = false) toDate: String?,
): ResponseEntity<List<Map<String, Any>>> { ): ResponseEntity<List<Map<String, Any>>> {
val to = toDate?.let { LocalDate.parse(it) } ?: LocalDate.now()
val from = fromDate?.let { LocalDate.parse(it) } ?: to.minusDays(29)
val defaultToday = LocalDate.now()
val to = toDate?.let { LocalDate.parse(it) } ?: defaultToday.plusDays(1)
val from = fromDate?.let { LocalDate.parse(it) } ?: defaultToday.minusDays(6)
val results = psService.getItemDailyOut(from, to) val results = psService.getItemDailyOut(from, to)
return ResponseEntity.ok(results) return ResponseEntity.ok(results)
} }


+ 21
- 0
src/main/java/com/ffii/fpsms/modules/master/service/ItemUomService.kt Datei anzeigen

@@ -178,6 +178,27 @@ open class ItemUomService(
return stockQty.setScale(0, RoundingMode.UP) return stockQty.setScale(0, RoundingMode.UP)
} }


/**
* Convert source quantity from a specific UOM to this item's stock quantity.
* Same as convertQtyToStockQty but rounds down the final stock qty to integer.
*/
open fun convertQtyToStockQtyRoundDown(itemId: Long, uomId: Long, sourceQty: BigDecimal): BigDecimal {
val itemUom = findFirstByItemIdAndUomId(itemId, uomId) ?: return sourceQty
val stockUnit = findStockUnitByItemId(itemId) ?: return BigDecimal.ZERO
val one = BigDecimal.ONE
val calcScale = 10

val baseQty = sourceQty
.multiply(itemUom.ratioN ?: one)
.divide(itemUom.ratioD ?: one, calcScale, RoundingMode.HALF_UP)

val stockQty = baseQty
.multiply(stockUnit.ratioD ?: one)
.divide(stockUnit.ratioN ?: one, calcScale, RoundingMode.HALF_UP)

return stockQty.setScale(0, RoundingMode.DOWN)
}

// See if need to update the response // See if need to update the response
open fun saveItemUom(request: ItemUomRequest): ItemUom { open fun saveItemUom(request: ItemUomRequest): ItemUom {
val itemUom = request.m18Id?.let { findByM18Id(it) } ?: request.id?.let { findById(it) } ?: ItemUom() val itemUom = request.m18Id?.let { findByM18Id(it) } ?: request.id?.let { findById(it) } ?: ItemUom()


+ 29
- 8
src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt Datei anzeigen

@@ -628,7 +628,7 @@ open class ProductionScheduleService(
} }


open fun getNeedQty(): List<NeedQtyRecord> { open fun getNeedQty(): List<NeedQtyRecord> {
val fromDate = java.time.LocalDate.now().minusMonths(1)
val fromDate = java.time.LocalDate.now().minusDays(10)
val toDate = java.time.LocalDate.now() val toDate = java.time.LocalDate.now()
val args = mapOf("fromDate" to fromDate.toString(), "toDate" to toDate.toString()) val args = mapOf("fromDate" to fromDate.toString(), "toDate" to toDate.toString())


@@ -1832,16 +1832,34 @@ open class ProductionScheduleService(
) )


val sql = """ val sql = """
WITH daily_needs AS (
WITH latest_supplier_uom AS (
SELECT x.materialId, x.uomIdM18
FROM (
SELECT
pol.itemId AS materialId,
pol.uomIdM18,
ROW_NUMBER() OVER (
PARTITION BY pol.itemId
ORDER BY COALESCE(po.orderDate, po.created) DESC, pol.id DESC
) AS rn
FROM purchase_order_line pol
JOIN purchase_order po ON po.id = pol.purchaseOrderId
WHERE pol.deleted = 0
AND po.deleted = 0
AND pol.uomIdM18 IS NOT NULL
) x
WHERE x.rn = 1
),
daily_needs AS (
SELECT SELECT
itm.id AS materialId, itm.id AS materialId,
itm.code AS matCode, itm.code AS matCode,
itm.name AS matName, itm.name AS matName,
uomP.udfudesc AS uom,
ceil((iv.onHandQty * (itsm.ratioN / itsm.ratioD)) * (itum.ratioD / itum.ratioN)) as onHandQty,
ceil((iv.unavailableQty * (itsm.ratioN / itsm.ratioD)) * (itum.ratioD / itum.ratioN)) as unavailableQty,
COALESCE(uomM18.code, uomP.code) AS uom,
ceil((iv.onHandQty * (itsm.ratioN / itsm.ratioD)) * (COALESCE(ium18.ratioD, itum.ratioD) / COALESCE(ium18.ratioN, itum.ratioN))) as onHandQty,
ceil((iv.unavailableQty * (itsm.ratioN / itsm.ratioD)) * (COALESCE(ium18.ratioD, itum.ratioD) / COALESCE(ium18.ratioN, itum.ratioN))) as unavailableQty,
COALESCE(( COALESCE((
SELECT SUM(pol.qty)
SELECT ceil(SUM(pol.qty * (itum.ratioN / itum.ratioD) * (COALESCE(ium18.ratioD, itum.ratioD) / COALESCE(ium18.ratioN, itum.ratioN))))
FROM purchase_order_line pol FROM purchase_order_line pol
JOIN purchase_order po ON pol.purchaseOrderId = po.id JOIN purchase_order po ON pol.purchaseOrderId = po.id
WHERE pol.itemId = itm.id WHERE pol.itemId = itm.id
@@ -1849,7 +1867,7 @@ open class ProductionScheduleService(
AND po.completeDate IS NULL AND po.completeDate IS NULL
), 0) AS purchasedQty, ), 0) AS purchasedQty,
DATE(ps.produceAt) AS produceDate, DATE(ps.produceAt) AS produceDate,
ceil( SUM(bm.baseQty * psl.batchNeed) * (itum.ratioD / itum.ratioN) ) AS qtyNeeded
ceil( SUM(bm.baseQty * psl.batchNeed) * (COALESCE(ium18.ratioD, itum.ratioD) / COALESCE(ium18.ratioN, itum.ratioN)) ) AS qtyNeeded
FROM production_schedule_line psl FROM production_schedule_line psl
JOIN production_schedule ps ON psl.prodScheduleId = ps.id JOIN production_schedule ps ON psl.prodScheduleId = ps.id
JOIN items it ON psl.itemId = it.id JOIN items it ON psl.itemId = it.id
@@ -1857,7 +1875,10 @@ open class ProductionScheduleService(
JOIN bom_material bm ON bom.id = bm.bomId JOIN bom_material bm ON bom.id = bm.bomId
JOIN items itm ON bm.itemId = itm.id JOIN items itm ON bm.itemId = itm.id
JOIN item_uom itum ON itm.id = itum.itemId and itum.purchaseUnit = 1 JOIN item_uom itum ON itm.id = itum.itemId and itum.purchaseUnit = 1
join uom_conversion uomP on itum.uomId = uomP.id
JOIN uom_conversion uomP ON itum.uomId = uomP.id
LEFT JOIN latest_supplier_uom lsu ON lsu.materialId = itm.id
LEFT JOIN item_uom ium18 ON ium18.itemId = itm.id AND ium18.uomId = lsu.uomIdM18 AND ium18.deleted = 0
LEFT JOIN uom_conversion uomM18 ON uomM18.id = ium18.uomId
JOIN item_uom itsm ON itm.id = itsm.itemId and itsm.stockUnit = 1 JOIN item_uom itsm ON itm.id = itsm.itemId and itsm.stockUnit = 1
LEFT JOIN inventory iv ON itm.id = iv.itemId LEFT JOIN inventory iv ON itm.id = iv.itemId
WHERE DATE(ps.produceAt) >= DATE_ADD(:fromDate, INTERVAL 1 DAY) WHERE DATE(ps.produceAt) >= DATE_ADD(:fromDate, INTERVAL 1 DAY)


+ 17
- 5
src/main/java/com/ffii/fpsms/modules/purchaseOrder/service/PurchaseOrderService.kt Datei anzeigen

@@ -135,11 +135,11 @@ open fun getPoSummariesByIds(ids: List<Long>): List<PurchaseOrderSummary> {
" SEPARATOR ','" + " SEPARATOR ','" +
" ) as itemName," + " ) as itemName," +
" group_concat(" + " group_concat(" +
" coalesce(uc.udfudesc, \"N/A\") " +
" coalesce(ucm18.udfudesc, uc.udfudesc, \"N/A\") " +
" SEPARATOR ','" + " SEPARATOR ','" +
" ) as itemUom," + " ) as itemUom," +
" group_concat(" + " group_concat(" +
" coalesce(pol.qty, 0) " +
" coalesce(pol.qtyM18, pol.qty, 0) " +
" SEPARATOR ','" + " SEPARATOR ','" +
" ) as itemQty," + " ) as itemQty," +
" group_concat(" + " group_concat(" +
@@ -173,6 +173,7 @@ open fun getPoSummariesByIds(ids: List<Long>): List<PurchaseOrderSummary> {
" ) sil2 on sil2.purchaseOrderLineId = pol.id" + " ) sil2 on sil2.purchaseOrderLineId = pol.id" +
" left join item_uom iu on iu.itemId = pol.itemId and iu.purchaseUnit = true" + " left join item_uom iu on iu.itemId = pol.itemId and iu.purchaseUnit = true" +
" left join uom_conversion uc on uc.id = iu.uomId" + " left join uom_conversion uc on uc.id = iu.uomId" +
" left join uom_conversion ucm18 on ucm18.id = pol.uomIdM18" +
" where po.deleted = false " + " where po.deleted = false " +
" and pol.deleted = false " " and pol.deleted = false "
) )
@@ -264,11 +265,22 @@ open fun getPoSummariesByIds(ids: List<Long>): List<PurchaseOrderSummary> {
} ?: mutableListOf(); } ?: mutableListOf();
val purchaseUnit = thisPol.item?.id?.let { itemUomService.findPurchaseUnitByItemId(it) } val purchaseUnit = thisPol.item?.id?.let { itemUomService.findPurchaseUnitByItemId(it) }
val stockUnit = thisPol.item?.id?.let { itemUomService.findStockUnitByItemId(it).let { iu -> val stockUnit = thisPol.item?.id?.let { itemUomService.findStockUnitByItemId(it).let { iu ->
val itemId = thisPol.item?.id
val qtyM18 = thisPol.qtyM18
val uomM18Id = thisPol.uomM18?.id
val stockQtyFromM18 = if (itemId != null && qtyM18 != null && uomM18Id != null) {
itemUomService.convertQtyToStockQtyRoundDown(itemId, uomM18Id, qtyM18)
} else {
BigDecimal.ZERO
}
StockUomForPoLine( StockUomForPoLine(
id = iu?.id, id = iu?.id,
stockUomCode = iu?.uom?.code, stockUomCode = iu?.uom?.code,
stockUomDesc = iu?.uom?.udfudesc, stockUomDesc = iu?.uom?.udfudesc,
stockQty = iu?.item?.id?.let { iId -> itemUomService.convertPurchaseQtyToStockQty(iId, (thisPol.qty ?: BigDecimal.ZERO)) } ?: BigDecimal.ZERO,
stockQty = if (stockQtyFromM18 > BigDecimal.ZERO) stockQtyFromM18 else {
// fallback to legacy behavior when M18 fields are missing
iu?.item?.id?.let { iId -> itemUomService.convertPurchaseQtyToStockQty(iId, (thisPol.qty ?: BigDecimal.ZERO)) } ?: BigDecimal.ZERO
},
stockRatioN = iu?.ratioN, stockRatioN = iu?.ratioN,
stockRatioD = iu?.ratioD, stockRatioD = iu?.ratioD,
purchaseRatioN = purchaseUnit?.ratioN, purchaseRatioN = purchaseUnit?.ratioN,
@@ -281,13 +293,13 @@ open fun getPoSummariesByIds(ids: List<Long>): List<PurchaseOrderSummary> {
itemId = thisPol.item!!.id!!, itemId = thisPol.item!!.id!!,
itemNo = thisPol.itemNo!!, itemNo = thisPol.itemNo!!,
itemName = thisPol.item!!.name, itemName = thisPol.item!!.name,
qty = thisPol.qty!!,
qty = thisPol.qtyM18 ?: thisPol.qty!!,
// processed = inLine.filter{ it.status == StockInLineStatus.COMPLETE.status}.sumOf { it.acceptedQty }, // processed = inLine.filter{ it.status == StockInLineStatus.COMPLETE.status}.sumOf { it.acceptedQty },
processed = inLine processed = inLine
.filter { line -> line.putAwayLines?.any { it.qty?.let { qty -> qty > BigDecimal.ZERO } == true } ?: false } .filter { line -> line.putAwayLines?.any { it.qty?.let { qty -> qty > BigDecimal.ZERO } == true } ?: false }
.sumOf { line -> line.putAwayLines?.sumOf { it.qty?.takeIf { qty -> qty > BigDecimal.ZERO } ?: BigDecimal.ZERO } ?: BigDecimal.ZERO}, .sumOf { line -> line.putAwayLines?.sumOf { it.qty?.takeIf { qty -> qty > BigDecimal.ZERO } ?: BigDecimal.ZERO } ?: BigDecimal.ZERO},
receivedQty = thisPol.stockInLines.sumOf { it.acceptedQty ?: BigDecimal.ZERO }, receivedQty = thisPol.stockInLines.sumOf { it.acceptedQty ?: BigDecimal.ZERO },
uom = thisPol.uom!!,
uom = thisPol.uomM18 ?: thisPol.uom!!,
price = thisPol.price!!, price = thisPol.price!!,
status = thisPol.status!!.toString(), status = thisPol.status!!.toString(),
stockInLine = inLine, stockInLine = inLine,


+ 9
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLine.kt Datei anzeigen

@@ -127,4 +127,13 @@ open class StockInLine : BaseEntity<Long>() {
open var stockTransferRecord: StockTransferRecord? = null open var stockTransferRecord: StockTransferRecord? = null
fun getReceivedQtyForPol(): BigDecimal? = fun getReceivedQtyForPol(): BigDecimal? =
purchaseOrderLine?.stockInLines?.sumOf { it.acceptedQty ?: BigDecimal.ZERO } purchaseOrderLine?.stockInLines?.sumOf { it.acceptedQty ?: BigDecimal.ZERO }

/**
* Total received qty (in M18 unit) for the same Purchase Order Line.
* Used by QC UI so it shows PO qtyM18 / receivedQty in M18 terms.
*/
fun getReceivedQtyM18ForPol(): BigDecimal? =
purchaseOrderLine?.stockInLines?.sumOf { sil ->
sil.acceptedQtyM18?.let { BigDecimal.valueOf(it.toLong()) } ?: BigDecimal.ZERO
}
} }

+ 3
- 3
src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt Datei anzeigen

@@ -24,13 +24,13 @@ interface StockInLineInfo {
val purchaseOrderId: Long? val purchaseOrderId: Long?
@get:Value("#{target.jobOrder?.id}") @get:Value("#{target.jobOrder?.id}")
val jobOrderId: Long? val jobOrderId: Long?
@get:Value("#{target.receivedQtyForPol}")
@get:Value("#{target.receivedQtyM18ForPol}")
val receivedQty: BigDecimal? val receivedQty: BigDecimal?
val demandQty: BigDecimal? val demandQty: BigDecimal?
val acceptedQty: BigDecimal val acceptedQty: BigDecimal
@get:Value("#{target.acceptedQtyM18 != null ? new java.math.BigDecimal(target.acceptedQtyM18) : null}") @get:Value("#{target.acceptedQtyM18 != null ? new java.math.BigDecimal(target.acceptedQtyM18) : null}")
val purchaseAcceptedQty: BigDecimal? val purchaseAcceptedQty: BigDecimal?
@get:Value("#{target.purchaseOrderLine?.qty}")
@get:Value("#{target.purchaseOrderLine?.qtyM18}")
val qty: BigDecimal? val qty: BigDecimal?
val price: BigDecimal? val price: BigDecimal?
val priceUnit: BigDecimal? val priceUnit: BigDecimal?
@@ -46,7 +46,7 @@ interface StockInLineInfo {
val supplier: String? val supplier: String?
@get:Value("#{target.item?.itemUoms.^[salesUnit == true && deleted == false]?.uom}") //TODO review @get:Value("#{target.item?.itemUoms.^[salesUnit == true && deleted == false]?.uom}") //TODO review
val uom: UomConversion? val uom: UomConversion?
@get:Value("#{target.purchaseOrderLine?.uom?.udfudesc}")
@get:Value("#{target.purchaseOrderLine?.uomM18?.udfudesc}")
val purchaseUomDesc: String? val purchaseUomDesc: String?
@get:Value("#{target.item?.itemUoms.^[stockUnit == true && deleted == false]?.uom?.udfudesc}") @get:Value("#{target.item?.itemUoms.^[stockUnit == true && deleted == false]?.uom?.udfudesc}")
val stockUomDesc: String? val stockUomDesc: String?


+ 49
- 21
src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt Datei anzeigen

@@ -246,14 +246,24 @@ open class StockInLineService(
itemNo = item.code itemNo = item.code
this.stockIn = stockIn this.stockIn = stockIn
// PO-origin: // PO-origin:
// 1) store user-input qty in acceptedQtyM18 (purchase unit)
// 2) calculate stock acceptedQty with round-down
// 1) store user-input qty in acceptedQtyM18 (M18 unit)
// 2) calculate stock acceptedQty by converting from M18 unit
if (pol != null && item.id != null) { if (pol != null && item.id != null) {
acceptedQtyM18 = request.acceptedQty.toInt() acceptedQtyM18 = request.acceptedQty.toInt()
acceptedQty = itemUomService.convertPurchaseQtyToStockQtyRoundDown(
item.id!!,
request.acceptedQty
)
val m18UomId = pol.uomM18?.id
acceptedQty = if (m18UomId != null) {
itemUomService.convertQtyToStockQtyRoundDown(
item.id!!,
m18UomId,
request.acceptedQty
)
} else {
// fallback to legacy: treat request.acceptedQty as purchase unit qty
itemUomService.convertPurchaseQtyToStockQtyRoundDown(
item.id!!,
request.acceptedQty
)
}
} else { } else {
// Non-PO flows: keep legacy behavior // Non-PO flows: keep legacy behavior
acceptedQty = request.acceptedQty acceptedQty = request.acceptedQty
@@ -264,8 +274,19 @@ open class StockInLineService(
this.demandQty = jo?.bom?.outputQty this.demandQty = jo?.bom?.outputQty


} else if (pol != null && item.id != null) { } else if (pol != null && item.id != null) {
pol.qty?.let { polQty ->
this.demandQty = itemUomService.convertPurchaseQtyToStockQty(item.id!!, polQty)
val m18UomId = pol.uomM18?.id
val qtyM18 = pol.qtyM18
this.demandQty = if (m18UomId != null && qtyM18 != null) {
itemUomService.convertQtyToStockQtyRoundDown(
item.id!!,
m18UomId,
qtyM18
)
} else {
// fallback to legacy: treat pol.qty as purchase unit qty
pol.qty?.let { polQty ->
itemUomService.convertPurchaseQtyToStockQty(item.id!!, polQty)
}
} }
} }
dnNo = request.dnNo dnNo = request.dnNo
@@ -527,15 +548,11 @@ open class StockInLineService(
val antValues = byPol.map { (_, silList) -> val antValues = byPol.map { (_, silList) ->
val sil = silList.first() val sil = silList.first()
val pol = sil.purchaseOrderLine!! val pol = sil.purchaseOrderLine!!
// M18 GRN ant expects purchase unit qty; acceptedQty on StockInLine is in stock unit
val totalStockQty = silList.sumOf { it.acceptedQty ?: BigDecimal.ZERO }
val itemId = sil.item?.id ?: pol.item?.id
val totalQtyInPurchaseUnit = if (itemId != null) {
itemUomService.convertStockQtyToPurchaseQty(itemId, totalStockQty)
} else {
logger.warn("[buildGoodsReceiptNoteRequest] No itemId for POL id=${pol.id}, using stock qty as fallback (may be wrong unit for M18)")
totalStockQty
// For PO-origin GRN, M18 ant qty must use the M18 UOM and received M18 qty.
val totalQtyM18 = silList.sumOf {
it.acceptedQtyM18?.let { qty -> BigDecimal.valueOf(qty.toLong()) } ?: BigDecimal.ZERO
} }
val unitIdM18 = pol.uomM18?.m18Id?.toInt()
val unitIdFromDataLog = (pol.m18DataLog?.dataLog?.get("unitId") as? Number)?.toLong()?.toInt() val unitIdFromDataLog = (pol.m18DataLog?.dataLog?.get("unitId") as? Number)?.toLong()?.toInt()
val itemName = (sil.item?.name ?: pol.item?.name).orEmpty() // always non-null for M18 bDesc/bDesc_en val itemName = (sil.item?.name ?: pol.item?.name).orEmpty() // always non-null for M18 bDesc/bDesc_en
GoodsReceiptNoteAntValue( GoodsReceiptNoteAntValue(
@@ -544,13 +561,13 @@ open class StockInLineService(
sourceLot = pol.m18Lot ?: "", sourceLot = pol.m18Lot ?: "",
proId = (sil.item?.m18Id ?: pol.item?.m18Id ?: 0L).toInt(), proId = (sil.item?.m18Id ?: pol.item?.m18Id ?: 0L).toInt(),
locId = 155, locId = 155,
unitId = unitIdFromDataLog ?: (pol.uom?.m18Id ?: 0L).toInt(),
qty = totalQtyInPurchaseUnit.toDouble(),
unitId = unitIdM18 ?: unitIdFromDataLog ?: (pol.uom?.m18Id ?: 0L).toInt(),
qty = totalQtyM18.toDouble(),
up = pol.up?.toDouble() ?: 0.0, up = pol.up?.toDouble() ?: 0.0,
amt = CommonUtils.getAmt( amt = CommonUtils.getAmt(
up = pol.up ?: BigDecimal.ZERO, up = pol.up ?: BigDecimal.ZERO,
discount = pol.m18Discount ?: BigDecimal.ZERO, discount = pol.m18Discount ?: BigDecimal.ZERO,
qty = totalQtyInPurchaseUnit
qty = totalQtyM18
), ),
beId = beId, beId = beId,
flowTypeId = flowTypeId, flowTypeId = flowTypeId,
@@ -735,8 +752,19 @@ open class StockInLineService(
if (this.jobOrder != null && this.jobOrder?.bom != null) { if (this.jobOrder != null && this.jobOrder?.bom != null) {
// For job orders, demandQty comes from BOM's outputQty // For job orders, demandQty comes from BOM's outputQty
this.demandQty = this.jobOrder?.bom?.outputQty ?: this.demandQty this.demandQty = this.jobOrder?.bom?.outputQty ?: this.demandQty
} else if (this.purchaseOrderLine != null && this.item?.id != null && this.purchaseOrderLine?.qty != null) {
this.demandQty = itemUomService.convertPurchaseQtyToStockQty(this.item!!.id!!, this.purchaseOrderLine!!.qty!!)
} else if (this.purchaseOrderLine != null && this.item?.id != null) {
val itemId = this.item!!.id!!
val pol = this.purchaseOrderLine!!
val m18UomId = pol.uomM18?.id
val qtyM18 = pol.qtyM18
this.demandQty = if (m18UomId != null && qtyM18 != null) {
itemUomService.convertQtyToStockQtyRoundDown(itemId, m18UomId, qtyM18)
} else if (pol.qty != null) {
// fallback to legacy fields when M18 fields are missing
itemUomService.convertPurchaseQtyToStockQty(itemId, pol.qty!!)
} else {
this.demandQty
}
} }
// Don't overwrite demandQty with acceptQty from QC form // Don't overwrite demandQty with acceptQty from QC form
this.invoiceNo = request.invoiceNo this.invoiceNo = request.invoiceNo


+ 8
- 1
src/main/java/com/ffii/fpsms/py/PyController.kt Datei anzeigen

@@ -22,6 +22,9 @@ open class PyController(
private val jobOrderRepository: JobOrderRepository, private val jobOrderRepository: JobOrderRepository,
private val stockInLineRepository: StockInLineRepository, private val stockInLineRepository: StockInLineRepository,
) { ) {
companion object {
private const val PACKAGING_PROCESS_NAME = "包裝"
}


/** /**
* List job orders by planStart date. * List job orders by planStart date.
@@ -36,7 +39,11 @@ open class PyController(
val date = planStart ?: LocalDate.now() val date = planStart ?: LocalDate.now()
val dayStart = date.atStartOfDay() val dayStart = date.atStartOfDay()
val dayEndExclusive = date.plusDays(1).atStartOfDay() val dayEndExclusive = date.plusDays(1).atStartOfDay()
val orders = jobOrderRepository.findByDeletedFalseAndPlanStartFromBeforeExclusiveOrderByIdAsc(dayStart, dayEndExclusive)
val orders = jobOrderRepository.findByDeletedFalseAndPlanStartBetweenAndBomProcessNameOrderByIdAsc(
dayStart,
dayEndExclusive,
PACKAGING_PROCESS_NAME,
)
val list = orders.map { jo -> toListItem(jo) } val list = orders.map { jo -> toListItem(jo) }
return ResponseEntity.ok(list) return ResponseEntity.ok(list)
} }


Laden…
Abbrechen
Speichern