2 次代码提交

共有 6 个文件被更改,包括 108 次插入30 次删除
  1. +1
    -1
      src/main/java/com/ffii/fpsms/m18/model/M18BomForShopSaveRequest.kt
  2. +78
    -9
      src/main/java/com/ffii/fpsms/m18/service/M18BomForShopService.kt
  3. +19
    -0
      src/main/java/com/ffii/fpsms/m18/service/M18MasterDataService.kt
  4. +2
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/ShopRepository.kt
  5. +6
    -0
      src/main/java/com/ffii/fpsms/modules/master/service/ShopService.kt
  6. +2
    -20
      src/main/java/com/ffii/fpsms/modules/purchaseOrder/entity/PurchaseOrderLineRepository.kt

+ 1
- 1
src/main/java/com/ffii/fpsms/m18/model/M18BomForShopSaveRequest.kt 查看文件

@@ -79,7 +79,7 @@ data class M18UdfProductSaveValue(
val udfIngredients: String? = null,
/** Line UOM: [com.ffii.fpsms.modules.master.entity.UomConversion.code] (same unit as [udfqty]). */
val udfBaseUnit: String? = null,
/** PO supplier [com.ffii.fpsms.modules.master.entity.Shop.m18Id] (via `purchase_order.supplierId`) for latest PO line matching material [com.ffii.fpsms.modules.master.entity.Items.code]. */
/** M18 vendor id from latest synced PO for the material: local [com.ffii.fpsms.modules.master.entity.Shop.m18Id] or M18 `ven` search (PF BE when BOM is PF). */
val udfSupplier: Long? = null,
/**
* M18 UOM id for price/purchase unit on the **M18-linked** PO line (`m18DataLog` present):


+ 78
- 9
src/main/java/com/ffii/fpsms/m18/service/M18BomForShopService.kt 查看文件

@@ -16,6 +16,8 @@ import com.ffii.fpsms.m18.model.M18UdfProductWrapper
import com.ffii.fpsms.modules.master.entity.Bom
import com.ffii.fpsms.modules.master.entity.BomMaterial
import com.ffii.fpsms.modules.master.service.ItemUomService
import com.ffii.fpsms.modules.master.service.ShopService
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLine
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLineRepository
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@@ -40,6 +42,8 @@ open class M18BomForShopService(
private val itemUomService: ItemUomService,
private val purchaseOrderLineRepository: PurchaseOrderLineRepository,
private val m18BomShopSyncLogRepository: M18BomShopSyncLogRepository,
private val shopService: ShopService,
private val m18MasterDataService: M18MasterDataService,
) {
private val logger: Logger = LoggerFactory.getLogger(M18BomForShopService::class.java)

@@ -120,10 +124,14 @@ open class M18BomForShopService(
val (udfHarvest, udfHarvestUnit) = resolveUdfHarvestFields(bom, outputQty)
val udfEffectiveDate = bom.created?.atZone(m18Tz)?.toInstant()?.toEpochMilli()

val targetBeId = resolveTargetBeId(flowTypeId)
val supplierCache = mutableMapOf<String, Long?>()
val lines = bom.bomMaterials
.filter { it.deleted != true }
.sortedBy { it.id ?: 0L }
.mapIndexedNotNull { idx, mat -> toProductLine(mat, idx + 1) }
.mapIndexedNotNull { idx, mat ->
toProductLine(mat, idx + 1, flowTypeId, targetBeId, supplierCache)
}

if (lines.isEmpty()) {
logger.warn("[M18 BOM] BOM id=$bomId code=$routingCode has no materials; skipping M18 save")
@@ -320,7 +328,13 @@ open class M18BomForShopService(
return harvestQty.toPlainString() to unitSuffix
}

private fun toProductLine(mat: BomMaterial, lineNo: Int): M18UdfProductSaveValue? {
private fun toProductLine(
mat: BomMaterial,
lineNo: Int,
flowTypeId: Int,
targetBeId: Long?,
supplierCache: MutableMap<String, Long?>,
): M18UdfProductSaveValue? {
val proId = mat.item?.m18Id?.takeIf { it > 0 } ?: run {
logger.warn("[M18 BOM] material item m18Id missing bomMaterialId=${mat.id} itemId=${mat.item?.id}")
return null
@@ -331,14 +345,12 @@ open class M18BomForShopService(
}
val itemId = mat.item?.id
val latestPoLine = itemId?.let { id ->
purchaseOrderLineRepository.findLatestLinesForBomM18ByItemId(id, PageRequest.of(0, 1)).firstOrNull()
}
val itemCode = mat.item?.code?.trim()?.takeIf { it.isNotEmpty() }
val supplierM18Id = itemCode?.let { code ->
purchaseOrderLineRepository.findLatestPoSupplierM18IdByItemCodeNative(code)
.firstOrNull()
?.takeIf { it > 0L }
pickPreferredPoLine(
purchaseOrderLineRepository.findLatestLinesForBomM18ByItemId(id, PageRequest.of(0, 10)),
targetBeId,
)
}
val supplierM18Id = resolveSupplierM18Id(latestPoLine, flowTypeId, supplierCache)
/**
* M18 line price unit id ([M18PurchaseOrderPot.unitId]): prefer [PurchaseOrderLine.uomM18] from M18 PO sync,
* else [PurchaseOrderLine.uom] when uomM18 is missing.
@@ -362,6 +374,63 @@ open class M18BomForShopService(
)
}

/** Prefer a PO line whose header [com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrder.m18BeId] matches the BOM BE. */
private fun pickPreferredPoLine(lines: List<PurchaseOrderLine>, preferredBeId: Long?): PurchaseOrderLine? {
if (lines.isEmpty()) return null
if (preferredBeId == null) return lines.first()
return lines.firstOrNull { it.purchaseOrder?.m18BeId == preferredBeId } ?: lines.first()
}

private fun resolveTargetBeId(flowTypeId: Int): Long? = when (flowTypeId) {
2 -> m18Config.BEID_PF.toLongOrNull()
3 -> m18Config.BEID_PP.toLongOrNull()
else -> null
}

/**
* Resolves M18 vendor id for BOM material line supplier:
* - PF BOMs: M18 search by supplier code + [M18Config.BEID_PF]
* - PP / other: local [Shop.m18Id] by code, then M18 search with [M18Config.BEID_PP] when needed
*/
private fun resolveSupplierM18Id(
latestPoLine: PurchaseOrderLine?,
flowTypeId: Int,
cache: MutableMap<String, Long?>,
): Long? {
val po = latestPoLine?.purchaseOrder
val supplier = po?.supplier
val directM18Id = supplier?.m18Id?.takeIf { it > 0L }
val supplierCode = supplier?.code?.trim()?.takeIf { it.isNotEmpty() }
val targetBeId = resolveTargetBeId(flowTypeId)
val poBeId = po?.m18BeId

if (supplierCode == null) {
return directM18Id
}
if (directM18Id != null && (targetBeId == null || poBeId == targetBeId)) {
return directM18Id
}

val cacheKey = "$supplierCode|$flowTypeId"
cache[cacheKey]?.let { return it }

val resolved = when (flowTypeId) {
2 -> m18MasterDataService.findVendorM18IdByCode(supplierCode, m18Config.BEID_PF)
?: directM18Id.also {
if (it == null) {
logger.warn("[M18 BOM] PF vendor M18 id not found for supplierCode=$supplierCode")
}
}
3 -> shopService.findVendorByCode(supplierCode)?.m18Id?.takeIf { it > 0L }
?: m18MasterDataService.findVendorM18IdByCode(supplierCode, m18Config.BEID_PP)
?: directM18Id
else -> shopService.findVendorByCode(supplierCode)?.m18Id?.takeIf { it > 0L }
?: directM18Id
}
cache[cacheKey] = resolved
return resolved
}

private fun resolveFlowTypeId(code: String): Int = when {
code.startsWith("TOA") -> 1
code.startsWith("BOMPP") || code.startsWith("PP") -> 3


+ 19
- 0
src/main/java/com/ffii/fpsms/m18/service/M18MasterDataService.kt 查看文件

@@ -557,6 +557,25 @@ open class M18MasterDataService(
)
}

/** M18 vendor id for [code] scoped to [beId] (e.g. [M18Config.BEID_PF] vs [M18Config.BEID_PP]). */
open fun findVendorM18IdByCode(code: String, beId: String): Long? {
val trimmed = code.trim()
if (trimmed.isEmpty() || beId.isBlank()) return null
val conds = "(code=equal=$trimmed)=and=(beId=equal=$beId)"
val listResponse = try {
getList<M18VendorListResponse>(
stSearch = StSearchType.VENDOR.value,
params = null,
conds = conds,
request = M18CommonRequest(),
)
} catch (e: Exception) {
logger.warn("(findVendorM18IdByCode) M18 search failed code=$trimmed beId=$beId: ${e.message}")
null
}
return listResponse?.values?.firstOrNull()?.id?.takeIf { it > 0L }
}

open fun saveVendors(request: M18CommonRequest) : SyncResult{
logger.info("--------------------------------------------Start - Saving M18 Vendors--------------------------------------------")
val vendors = getVendors(request)


+ 2
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/ShopRepository.kt 查看文件

@@ -31,6 +31,8 @@ interface ShopRepository : AbstractRepository<Shop, Long> {

fun findByCode(code: String): Shop?

fun findByCodeAndTypeAndDeletedIsFalse(code: String, type: ShopType): Shop?

@Query(
"""
SELECT s FROM Shop s


+ 6
- 0
src/main/java/com/ffii/fpsms/modules/master/service/ShopService.kt 查看文件

@@ -26,6 +26,12 @@ open class ShopService(
return shopRepository.findByM18IdAndTypeAndDeletedIsFalse(m18Id, ShopType.SUPPLIER)
}

open fun findVendorByCode(code: String): Shop? {
val trimmed = code.trim()
if (trimmed.isEmpty()) return null
return shopRepository.findByCodeAndTypeAndDeletedIsFalse(trimmed, ShopType.SUPPLIER)
}

open fun findShopByM18Id(m18Id: Long): Shop? {
return shopRepository.findByM18IdAndTypeAndDeletedIsFalse(m18Id, ShopType.SHOP)
}


+ 2
- 20
src/main/java/com/ffii/fpsms/modules/purchaseOrder/entity/PurchaseOrderLineRepository.kt 查看文件

@@ -12,7 +12,7 @@ import java.io.Serializable
@Repository
interface PurchaseOrderLineRepository : AbstractRepository<PurchaseOrderLine, Long> {
/**
* Latest POL for BOM/M18 pushes: PO code must start with **`PP`**, row has M18 log, newest `pol.created` first.
* Latest M18-linked POL for BOM shop pushes (any PO type: PF, PP, etc.), newest `pol.created` first.
*/
@Query(
"SELECT pol FROM PurchaseOrderLine pol " +
@@ -21,7 +21,7 @@ interface PurchaseOrderLineRepository : AbstractRepository<PurchaseOrderLine, Lo
"JOIN FETCH pol.uom " +
"LEFT JOIN FETCH pol.uomM18 " +
"WHERE pol.deleted = false AND pol.item.id = :itemId AND pol.m18DataLog IS NOT NULL " +
"AND po.deleted = false AND po.code IS NOT NULL AND po.code LIKE 'PP%' " +
"AND po.deleted = false AND po.code IS NOT NULL " +
"ORDER BY pol.created DESC",
)
fun findLatestLinesForBomM18ByItemId(
@@ -29,24 +29,6 @@ interface PurchaseOrderLineRepository : AbstractRepository<PurchaseOrderLine, Lo
pageable: Pageable,
): List<PurchaseOrderLine>

/**
* Latest **PP** PO (code starts with `PP`, by header `purchase_order.created`) for a material item code:
* supplier `shop.m18Id` from `purchase_order.supplierId`.
*/
@Query(
value =
"SELECT sh.m18Id FROM purchase_order_line pol " +
"LEFT JOIN items it ON pol.itemId = it.id " +
"LEFT JOIN purchase_order po ON pol.purchaseOrderId = po.id " +
"LEFT JOIN shop sh ON po.supplierId = sh.id " +
"LEFT JOIN uom_conversion um ON pol.uomIdM18 = um.id " +
"WHERE pol.deleted = false AND po.deleted = false AND it.deleted = false AND it.code = :itemCode " +
"AND po.code LIKE 'PP%' " +
"ORDER BY po.created DESC LIMIT 1",
nativeQuery = true,
)
fun findLatestPoSupplierM18IdByItemCodeNative(@Param("itemCode") itemCode: String): List<Long>

fun findByM18DataLogIdAndDeletedIsFalse(m18datalogId: Serializable): PurchaseOrderLine?
fun findAllPurchaseOrderLineInfoByPurchaseOrderIdAndDeletedIsFalse(purchaseOrderId: Long): List<PurchaseOrderLineInfo>
fun findAllByPurchaseOrderIdAndDeletedIsFalse(purchaseOrderId: Long): List<PurchaseOrderLine>


正在加载...
取消
保存