소스 검색

alter for the BOM sync finding PP purchase order supplier and also others for supplier id by code

production
부모
커밋
1c1eadc71d
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>


불러오는 중...
취소
저장