package com.ffii.fpsms.m18.service import com.ffii.core.utils.JwtTokenUtil import com.ffii.fpsms.api.service.ApiCallerService import com.ffii.fpsms.m18.M18Config import com.ffii.fpsms.m18.model.* import com.ffii.fpsms.m18.utils.CommonUtils import com.ffii.fpsms.m18.web.models.M18CommonRequest import com.ffii.fpsms.modules.master.entity.UomConversion import com.ffii.fpsms.modules.master.enums.ShopType import com.ffii.fpsms.modules.master.service.* import com.ffii.fpsms.modules.master.web.models.* import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.stereotype.Service import java.math.BigDecimal import java.time.LocalDateTime import java.time.format.DateTimeFormatter import com.ffii.fpsms.m18.model.SyncResult import com.ffii.fpsms.modules.master.entity.Items import java.time.Instant import java.time.LocalDate import java.time.ZoneId @Service open class M18MasterDataService( val m18Config: M18Config, val apiCallerService: ApiCallerService, val itemsService: ItemsService, val shopService: ShopService, val uomConversionService: UomConversionService, val currencyService: CurrencyService, val itemUomService: ItemUomService, val bomService: BomService, val bomMaterialService: BomMaterialService, val m18CunitService: M18CunitService, ) { val logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java) val commonUtils = CommonUtils() val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") private val one = BigDecimal.ONE private val ratio2268 = BigDecimal("2268") private val ratio5 = BigDecimal("5") // M18 Conditions // val lastModifyDate = LocalDate.now().minusDays(1) // val lastModifyDateConds = "lastModifyDate=largerThan=$lastModifyDate" val seriesIdList = listOf(m18Config.SERIESID_SC, m18Config.SERIESID_SE, m18Config.SERIESID_SF, m18Config.SERIESID_SR) val seriesIdConds = "(" + commonUtils.listToString(seriesIdList.filterNotNull(), "seriesId=unequal=", "=or=") + ")" val beIdList = listOf(m18Config.BEID_PF, m18Config.BEID_PP, m18Config.BEID_TOA) val beIdConds = "(" + commonUtils.listToString(beIdList.filterNotNull(), "beId=equal=", "=or=") + ")" // val beIdConds = commonUtils.BEID_CONDS // M18 API val M18_COMMON_FETCH_LIST_API = "/search/search" val M18_COMMON_LOAD_LINE_API = "/root/api/read" val M18_LOAD_PRODUCT_API = "${M18_COMMON_LOAD_LINE_API}/${StSearchType.PRODUCT.value}" val M18_LOAD_VENDOR_API = "${M18_COMMON_LOAD_LINE_API}/${StSearchType.VENDOR.value}" val M18_LOAD_UNIT_API = "${M18_COMMON_LOAD_LINE_API}/${StSearchType.UNIT.value}" val M18_LOAD_CURRENCY_API = "${M18_COMMON_LOAD_LINE_API}/${StSearchType.CURRENCY.value}" val M18_LOAD_BOM_API = "${M18_COMMON_LOAD_LINE_API}/${StSearchType.BOM.value}" val M18_LOAD_BUSINESS_UNIT_API = "${M18_COMMON_LOAD_LINE_API}/${StSearchType.BUSINESS_UNIT.value}" // for shop po? private fun isBothOne(ratioN: BigDecimal?, ratioD: BigDecimal?): Boolean { return ratioN?.compareTo(one) == 0 && ratioD?.compareTo(one) == 0 } private fun isPair2268And5(ratioN: BigDecimal?, ratioD: BigDecimal?): Boolean { return ratioN?.compareTo(ratio2268) == 0 && ratioD?.compareTo(ratio5) == 0 } /** * Problematic ratio cases that should be corrected by m18_cunit: * 1) uomId=4 and ratioN/ratioD are not both 1 * 2) uomId=785 and ratioN/ratioD do not contain 453 */ private fun shouldSyncRatioFromCunit(unitId: Long, ratioN: BigDecimal?, ratioD: BigDecimal?): Boolean { val caseUom4 = unitId == 4L && !isBothOne(ratioN, ratioD) val caseUom785 = unitId == 785L && !isPair2268And5(ratioN, ratioD) return caseUom4 || caseUom785 } /** * Same rule as [shouldSyncRatioFromCunit], but based on local uom_conversion.id. */ private fun shouldSyncRatioFromCunitByLocalUomId(localUomId: Long?, ratioN: BigDecimal?, ratioD: BigDecimal?): Boolean { if (localUomId == null) return false val caseUom4 = localUomId == 4L && !isBothOne(ratioN, ratioD) val caseUom785 = localUomId == 785L && !isPair2268And5(ratioN, ratioD) return caseUom4 || caseUom785 } /** * Safety net: when m18_cunit has no rows, force a full unit sync first so cunit ratios are available. */ open fun ensureCunitSeededForAllIfEmpty() { val cunitRows = m18CunitService.countActiveRows() if (cunitRows > 0L) return logger.warn("m18_cunit has 0 rows; triggering full unit sync first to seed cunit data for all units.") val result = saveUnits(M18CommonRequest()) logger.warn( "m18_cunit seed sync finished: processed=${result.totalProcessed}, " + "success=${result.totalSuccess}, fail=${result.totalFail}" ) } // --------------------------------------------- Common Function --------------------------------------------- /// private inline fun getList( stSearch: String?, params: String? = null, conds: String? = null, request: M18CommonRequest, ): T? { val lastModifyDateFromConds = request.modifiedDateFrom?.let { "lastModifyDate=largerOrEqual=${it}" } val lastModifyDateToConds = request.modifiedDateTo?.let{ "lastModifyDate=lessOrEqual=${it}" } val haveFromAndTo = lastModifyDateFromConds != null && lastModifyDateToConds != null val finalConds = if (lastModifyDateFromConds == null && lastModifyDateToConds == null) { conds } else { conds + "=and=(${lastModifyDateFromConds ?: ""}${if(haveFromAndTo) "=and=" else ""}${lastModifyDateToConds ?: ""})" } val request = M18CommonListRequest( stSearch = stSearch, params = params, conds = finalConds ) val response = apiCallerService.get( M18_COMMON_FETCH_LIST_API, request ).block() return response } private inline fun getLine( id: Long, params: String?, api: String, ): T? { val request = M18CommonLineRequest( id = id, params = params, ) val response = apiCallerService.get( api, request ).block() return response } // --------------------------------------------- Product --------------------------------------------- /// open fun getProducts(request: M18CommonRequest): M18ProductListResponse? { // seems no beId return getList( stSearch = StSearchType.PRODUCT.value, params = null, conds = seriesIdConds, request = request ) // val itemsParams = M18CommonListRequest( // stSearch = StSearchType.PRODUCT.value, // params = null, // conds = seriesIdConds // ) // // val items = apiCallerService.get( // M18_COMMON_FETCH_LIST_API, // itemsParams // ).block() // // return items } open fun getProduct(id: Long): M18ProductResponse? { logger.info("M18 Product ID: $id") return getLine( id = id, params = null, api = M18_LOAD_PRODUCT_API ) } /** Resolve local items.id for an M18 product id; sync from M18 when missing. */ open fun resolveLocalItemId(m18ItemId: Long): Long? { itemsService.findByM18Id(m18ItemId)?.id?.let { return it } saveProduct(m18ItemId)?.id?.let { return it } return itemsService.findByM18Id(m18ItemId)?.id } private fun mapM18ProductType(udfProducttype: String?): String { return when (udfProducttype) { M18ItemType.CONSUMABLES.type -> ItemType.CONSUMABLES.type M18ItemType.NONCONSUMABLES.type -> ItemType.NONCONSUMABLES.type M18ItemType.FG.type -> ItemType.FG.type M18ItemType.SFG.type -> ItemType.SFG.type M18ItemType.ITEM.type -> ItemType.ITEM.type else -> ItemType.MATERIAL.type } } open fun saveProduct(id: Long): MessageResponse? { try { ensureCunitSeededForAllIfEmpty() val itemDetail = getProduct(id) val pro = itemDetail?.data?.pro?.get(0) val price = itemDetail?.data?.price if (itemDetail != null && pro != null) { val mappedType = mapM18ProductType(pro.udfProducttype) val existingItem = itemsService.findByM18Id(id) val saveItemRequest = NewItemRequest( code = pro.code, name = pro.desc, // type = if (pro.seriesId == m18Config.SERIESID_PF) ProductType.MATERIAL // else ItemType.PRODUCT, type = mappedType, id = existingItem?.id, description = pro.desc, remarks = null, shelfLife = null, countryOfOrigin = null, maxQty = null, m18Id = id, m18LastModifyDate = commonUtils.timestampToLocalDateTime(pro.lastModifyDate), qcCategoryId = null, store_id = null, warehouse = null, area = null, slot = null, LocationCode = null, isEgg = null, isFee = null, isBag = null, qcType = null, ) val savedItem = itemsService.saveItem(saveItemRequest) val localItemId = savedItem.id if (localItemId == null) { logger.error("saveItem returned null id for M18 item $id (code=${pro.code}): ${savedItem.message}") return null } if (savedItem.errorPosition == "code") { logger.error("saveItem duplicate code for M18 item $id (code=${pro.code}): ${savedItem.message}") return null } if (ItemM18IdRemapSupport.isM18IdLinkOnly(existingItem == null, savedItem.message)) { logger.warn( "Linked M18 product id=$id code=${pro.code} to existing local item id=$localItemId; skip UOM rebuild" ) return savedItem.copy(id = localItemId) } logger.info("Processing item uom...") // Find the item uom that ready to delete (not in m18) val existingItemUoms = itemUomService.findAllByItemsId(localItemId) val m18ItemUomIds = price?.map { it.id } ?: listOf() // Delete the item uom logger.info("Deleting item uom...") // logger.info("Item Uom: ${existingItemUoms?.map { it.m18Id }}") // logger.info("M18: ${m18ItemUomIds}") existingItemUoms?.filter { it.m18Id !in m18ItemUomIds }?.mapNotNull { it.id } ?.let { itemUomService.deleteItemUoms(it) } // Update the item uom logger.info("Updating item uom...") val hasBaseUom4 = price?.any { p -> p.basicUnit && (uomConversionService.findByM18Id(p.unitId)?.id == 4L) } == true val forceCunitForAllRows = hasBaseUom4 && (price?.any { p -> val localBaseId = uomConversionService.findByM18Id(p.unitId)?.id shouldSyncRatioFromCunitByLocalUomId(localBaseId, p.ratioN, p.ratioD) } == true) price?.forEach { val endMillis = it.endDate val endInstant = Instant.ofEpochMilli(endMillis) val now = Instant.now() val localUomId = uomConversionService.findByM18Id(it.unitId)?.id val keepOriginalItemRatio = forceCunitForAllRows val useUnitRatios = forceCunitForAllRows val unitRatios = if (useUnitRatios) m18CunitService.resolveRatiosByM18UnitId(it.unitId) else null logger.info( "ItemUom ratio apply (single): itemM18Id=$id m18UomId=${it.unitId} localUomId=$localUomId " + "hasBaseUom4=$hasBaseUom4 forceCunitForAllRows=$forceCunitForAllRows useCunit=$useUnitRatios oldRatioN=${it.ratioN} oldRatioD=${it.ratioD} " + "cunitRatioN=${unitRatios?.ratioN} cunitRatioD=${unitRatios?.ratioD}" ) val itemUomRequest = ItemUomRequest( m18UomId = it.unitId, itemId = localItemId, baseUnit = it.basicUnit, stockUnit = it.stkUnit, pickingUnit = it.pickUnit, salesUnit = it.saleUnit, purchaseUnit = it.purUnit, price = null, currencyId = null, m18Id = it.id, m18LastModifyDate = commonUtils.timestampToLocalDateTime(pro.lastModifyDate), ratioD = if (useUnitRatios) (unitRatios?.ratioD ?: it.ratioD) else it.ratioD, ratioN = if (useUnitRatios) (unitRatios?.ratioN ?: it.ratioN) else it.ratioN, itemRatioD = if (keepOriginalItemRatio) it.ratioD else null, itemRatioN = if (keepOriginalItemRatio) it.ratioN else null, deleted = it.expired || endInstant.isBefore(now) ) itemUomService.saveItemUom(itemUomRequest) } logger.info("Success (M18 Item): ${id} | ${pro.code} | ${pro.desc}") return savedItem.copy(id = localItemId) } else { logger.error("Fail Message: ${itemDetail?.messages?.get(0)?.msgDetail}") logger.error("Fail: Item ID - ${id} Not Found") return null } } catch (e: Exception) { logger.error("Exception") logger.error("Fail Message: ${e.message}") logger.error("Fail: Item ID - ${id}") return null } } /** Sync one product/material from M18 by item code (search list, then load line — same idea as PO/DO by code). */ open fun saveProductByCode(code: String): SyncResult { val trimmed = code.trim() if (trimmed.isEmpty()) { return SyncResult(totalProcessed = 1, totalSuccess = 0, totalFail = 1, query = "empty code") } ensureCunitSeededForAllIfEmpty() val fromLocal = itemsService.findByCode(trimmed)?.m18Id val m18Id = fromLocal ?: run { val conds = "(code=equal=$trimmed)" val listResponse = try { getList( stSearch = StSearchType.PRODUCT.value, params = null, conds = conds, request = M18CommonRequest(), ) } catch (e: Exception) { logger.error("(saveProductByCode) M18 search failed: ${e.message}", e) null } listResponse?.values?.firstOrNull()?.id } if (m18Id == null) { return SyncResult( totalProcessed = 1, totalSuccess = 0, totalFail = 1, query = "code=equal=$trimmed", ) } val result = saveProduct(m18Id) return if (result != null) { SyncResult(totalProcessed = 1, totalSuccess = 1, totalFail = 0, query = "code=equal=$trimmed") } else { SyncResult(totalProcessed = 1, totalSuccess = 0, totalFail = 1, query = "code=equal=$trimmed") } } open fun saveProducts(request: M18CommonRequest): SyncResult { logger.info("--------------------------------------------Start - Saving M18 Products / Materials--------------------------------------------") ensureCunitSeededForAllIfEmpty() val items = getProducts(request) val exampleProducts = listOf(10946L, 3825L) // ── New: cache for findByM18Id ───────────────────────────────────────── // M18 item.id → internal Item entity (or just the id if you only need id) val itemCache = mutableMapOf() val successList = mutableListOf() val failList = mutableListOf() val values = items?.values?.sortedBy { it.id } if (values != null) { values.forEach { item -> // if (item.id in exampleProducts) { // keep your debug filter if needed try { val itemDetail = getProduct(item.id) val pro = itemDetail?.data?.pro?.get(0) val price = itemDetail?.data?.price if (itemDetail != null && pro != null) { val mappedType = mapM18ProductType(pro.udfProducttype) // ── Use cache instead of direct call ──────────────────────── val existingItem = itemCache.getOrPut(item.id) { itemsService.findByM18Id(item.id) } val saveItemRequest = NewItemRequest( code = pro.code, name = pro.desc, type = mappedType, id = existingItem?.id, description = pro.desc, remarks = null, shelfLife = null, countryOfOrigin = null, maxQty = null, m18Id = item.id, m18LastModifyDate = commonUtils.timestampToLocalDateTime(pro.lastModifyDate), qcCategoryId = null, store_id = null, warehouse = null, area = null, slot = null, LocationCode = null, isEgg = null, isFee = null, isBag = null, qcType = null ) val savedItem = itemsService.saveItem(saveItemRequest) val localItemId = savedItem.id if (localItemId == null) { failList.add(item.id) logger.error("saveItem returned null id for M18 item ${item.id} (code=${pro.code}): ${savedItem.message}") return@forEach } if (savedItem.errorPosition == "code") { failList.add(item.id) logger.error("saveItem duplicate code for M18 item ${item.id} (code=${pro.code}): ${savedItem.message}") return@forEach } if (ItemM18IdRemapSupport.isM18IdLinkOnly(existingItem == null, savedItem.message)) { logger.warn( "Linked M18 product id=${item.id} code=${pro.code} to existing local item id=$localItemId; skip UOM rebuild" ) successList.add(item.id) return@forEach } logger.info("Processing item uom...") val existingItemUoms = itemUomService.findAllByItemsId(localItemId) val m18ItemUomIds = price?.map { it.id } ?: listOf() // Delete old UOMs not present in M18 logger.info("Deleting item uom...") existingItemUoms?.filter { it.m18Id !in m18ItemUomIds }?.mapNotNull { it.id } ?.let { itemUomService.deleteItemUoms(it) } // Update / create UOMs from M18 logger.info("Updating item uom...") val hasBaseUom4 = price?.any { p -> p.basicUnit && (uomConversionService.findByM18Id(p.unitId)?.id == 4L) } == true val forceCunitForAllRows = hasBaseUom4 && (price?.any { p -> val localBaseId = uomConversionService.findByM18Id(p.unitId)?.id shouldSyncRatioFromCunitByLocalUomId(localBaseId, p.ratioN, p.ratioD) } == true) price?.forEach { val endMillis = it.endDate val endInstant = Instant.ofEpochMilli(endMillis) val now = Instant.now() val localUomId = uomConversionService.findByM18Id(it.unitId)?.id val keepOriginalItemRatio = forceCunitForAllRows val useUnitRatios = forceCunitForAllRows val unitRatios = if (useUnitRatios) m18CunitService.resolveRatiosByM18UnitId(it.unitId) else null logger.info( "ItemUom ratio apply (bulk): itemM18Id=${item.id} m18UomId=${it.unitId} localUomId=$localUomId " + "hasBaseUom4=$hasBaseUom4 forceCunitForAllRows=$forceCunitForAllRows useCunit=$useUnitRatios oldRatioN=${it.ratioN} oldRatioD=${it.ratioD} " + "cunitRatioN=${unitRatios?.ratioN} cunitRatioD=${unitRatios?.ratioD}" ) val itemUomRequest = ItemUomRequest( m18UomId = it.unitId, itemId = localItemId, baseUnit = it.basicUnit, stockUnit = it.stkUnit, pickingUnit = it.pickUnit, salesUnit = it.saleUnit, purchaseUnit = it.purUnit, price = null, currencyId = null, m18Id = it.id, m18LastModifyDate = commonUtils.timestampToLocalDateTime(pro.lastModifyDate), ratioD = if (useUnitRatios) (unitRatios?.ratioD ?: it.ratioD) else it.ratioD, ratioN = if (useUnitRatios) (unitRatios?.ratioN ?: it.ratioN) else it.ratioN, itemRatioD = if (keepOriginalItemRatio) it.ratioD else null, itemRatioN = if (keepOriginalItemRatio) it.ratioN else null, deleted = it.expired || endInstant.isBefore(now) ) itemUomService.saveItemUom(itemUomRequest) } successList.add(item.id) logger.info("Success Count ${successList.size}: ${item.id} | ${pro.code} | ${pro.desc}") } else { failList.add(item.id) logger.error("Fail Message: ${itemDetail?.messages?.get(0)?.msgDetail}") logger.error("Fail Count ${failList.size}: Item ID - ${item.id} Not Found") } } catch (e: Exception) { failList.add(item.id) logger.error("Exception") logger.error("Fail Message: ${e.message}") logger.error("Fail Count ${failList.size}: Item ID - ${item.id}") } // } // end of exampleProducts filter } } else { logger.error("Items List is null. May occur errors.") } logger.info("Total Success (${successList.size})") if (failList.size > 0) { logger.error("Total Fail (${failList.size}): $failList") } logger.info("--------------------------------------------End - Saving M18 Products / Materials--------------------------------------------") return SyncResult( totalProcessed = successList.size + failList.size, totalSuccess = successList.size, totalFail = failList.size ) } /** * Targeted re-sync for locally identified problematic item_uom ratios: * - uomId = 4 and ratioN != 1 * - uomId = 785 and ratioN != 453 * * Uses local item_uom + item.m18Id to fetch only affected products from M18 and re-save their item_uom. */ open fun resyncProblemItemUomsFromM18(): SyncResult { logger.info("--------------------------------------------Start - Re-sync Problem Item UOMs--------------------------------------------") val m18Ids = itemUomService.findDistinctProblemItemM18IdsForUomResync().distinct().sorted() val successList = mutableListOf() val failList = mutableListOf() logger.warn("Problem item_uom selector found ${m18Ids.size} item(s) for re-sync.") if (m18Ids.isNotEmpty()) { logger.warn("Problem item m18Id sample (up to 20): ${m18Ids.take(20)}") } m18Ids.forEach { m18ItemId -> try { logger.warn("Re-sync item_uom: calling M18 product API for id=$m18ItemId") val result = saveProduct(m18ItemId) if (result != null) { successList.add(m18ItemId) logger.warn("Re-sync item_uom: M18 API sync success for id=$m18ItemId") } else { failList.add(m18ItemId) logger.warn("Re-sync item_uom: M18 API sync returned null for id=$m18ItemId") } } catch (e: Exception) { failList.add(m18ItemId) logger.error("Re-sync problem item uom failed for m18ItemId=$m18ItemId: ${e.message}", e) } } logger.info("Problem item_uom re-sync done. total=${m18Ids.size}, success=${successList.size}, fail=${failList.size}") logger.info("--------------------------------------------End - Re-sync Problem Item UOMs--------------------------------------------") return SyncResult( totalProcessed = m18Ids.size, totalSuccess = successList.size, totalFail = failList.size ) } // --------------------------------------------- Vendor --------------------------------------------- /// open fun getVendors(request: M18CommonRequest): M18VendorListResponse? { return getList( stSearch = StSearchType.VENDOR.value, params = null, conds = beIdConds, request = request ) } open fun getVendor(id: Long): M18VendorResponse? { logger.info("M18 Vendor ID: $id") return getLine( id = id, params = null, api = M18_LOAD_VENDOR_API ) } open fun saveVendors(request: M18CommonRequest) : SyncResult{ logger.info("--------------------------------------------Start - Saving M18 Vendors--------------------------------------------") val vendors = getVendors(request) val exampleVendors = listOf(191L) val successList = mutableListOf() val failList = mutableListOf() val values = vendors?.values?.sortedBy { it.id } if (values != null) { values.forEach { vendor -> // if (vendor.id in exampleVendors) { try { val vendorDetail = getVendor(vendor.id) if (vendorDetail != null && vendorDetail.data?.ven != null) { val ven = vendorDetail.data.ven[0] val saveShopRequest = SaveShopRequest( id = null, code = ven.code, name = ven.descZhTW.ifEmpty { ven.descZhCN.ifEmpty { ven.desc } }, brNo = null, contactNo = ven.tel, contactEmail = ven.email, contactName = null, addr1 = ven.ad1, addr2 = ven.ad2, addr3 = ven.ad3, addr4 = ven.ad4, district = null, type = ShopType.SUPPLIER.value, m18Id = vendor.id, m18LastModifyDate = commonUtils.timestampToLocalDateTime(ven.lastModifyDate) ) shopService.saveShop(saveShopRequest) successList.add(vendor.id) logger.info("Success Count ${successList.size}: ${vendor.id} | ${ven.code} | ${ven.descZhTW.ifEmpty { ven.descZhCN.ifEmpty { ven.desc } }}") } else { failList.add(vendor.id) logger.error("Fail Message: ${vendorDetail?.messages?.get(0)?.msgDetail}") logger.error("Fail Count ${failList.size}: Vendor ID - ${vendor.id} Not Found") } } catch (e: Exception) { failList.add(vendor.id) logger.error("Exception") logger.error("Fail Message: ${e.message}") logger.error("Fail Count ${failList.size}: Vendor ID - ${vendor.id}") } // } } } else { logger.error("Vendor List is null. May occur errors.") } logger.info("Total Success (${successList.size})") if (failList.size > 0) { logger.error("Total Fail (${failList.size}): $failList") } logger.info("--------------------------------------------End - Saving M18 Vendors--------------------------------------------") return SyncResult( totalProcessed = successList.size + failList.size, totalSuccess = successList.size, totalFail = failList.size ) } // --------------------------------------------- Unit (UoM) --------------------------------------------- /// open fun getUnits(request: M18CommonRequest): M18UnitListResponse? { // seems no beId return getList( stSearch = StSearchType.UNIT.value, params = null, conds = null, request = request ) } open fun getUnit(id: Long): M18UnitResponse? { logger.info("M18 Unit ID: $id") return getLine( id = id, params = null, api = M18_LOAD_UNIT_API ) } open fun saveUnits(request: M18CommonRequest): SyncResult { logger.info("--------------------------------------------Start - Saving M18 Units--------------------------------------------") val units = getUnits(request) val successTransformList = mutableListOf() val successSaveList = mutableListOf() val failTransformList = mutableListOf() val failSaveList = mutableListOf() val values = units?.values?.sortedBy { it.id } if (values != null) { val finalUnitList = arrayListOf() // transform unit values.forEach { value -> try { val unitDetail = getUnit(value.id) if (unitDetail != null && unitDetail.data?.unit != null) { val unit = unitDetail.data.unit[0] try { m18CunitService.replaceForUnit(unit.id, unitDetail.data!!) } catch (e: Exception) { logger.error("M18 cunit save failed for unit ${unit.id}: ${e.message}", e) } val tempObject = UomConversionService.BomObject().apply { code = unit.code udfudesc = unit.udfudesc udfShortDesc = unit.udfShortDesc lastModifyDate = commonUtils.timestampToLocalDateTime(unit.lastModifyDate).format(formatter) id = unit.id } finalUnitList += uomConversionService.transformItem(tempObject) successTransformList += unit.id logger.info("Transform Success (M18): ${unit.id}") } else { failTransformList.add(value.id) logger.error("Fail Message: ${unitDetail?.messages?.get(0)?.msgDetail}") logger.error("Fail Count ${failTransformList.size}: Unit ID - ${value.id} Not Found") } } catch (e: Exception) { failTransformList.add(value.id) logger.error("Transform Exception") logger.error("Transform Fail Message: ${e.message}") logger.error("Transform Fail Count ${failTransformList.size}: Unit ID - ${value.id}") } } uomConversionService.calculateSizeInGram(finalUnitList) finalUnitList.forEach { try { uomConversionService.saveUomConversion(it) successSaveList += it.m18Id logger.info("Save Success (M18): ${it.m18Id}") } catch (e: Exception) { failSaveList.add(it.m18Id) logger.error("Save Exception") logger.error("Save Fail Message: ${e.message}") logger.error("Save Fail Count ${failTransformList.size}: Unit ID - ${it.m18Id}") } } } else { logger.error("Unit List is null. May occur errors.") } logger.info("Total Transform Success (${successTransformList.size})") logger.info("Total Save Success (${successSaveList.size})") if (failTransformList.size > 0) { logger.error("Total Transform Fail (${failTransformList.size}): $failTransformList") } if (failSaveList.size > 0) { logger.error("Total Save Fail (${failSaveList.size}): $failSaveList") } logger.info("--------------------------------------------End - Saving M18 Units--------------------------------------------") val processed = values?.size ?: 0 val failed = failTransformList.size + failSaveList.size return SyncResult( totalProcessed = processed, totalSuccess = successSaveList.size, totalFail = failed, query = "stSearch=${StSearchType.UNIT.value}", ) } // --------------------------------------------- Currency --------------------------------------------- /// open fun getCurrencies(request: M18CommonRequest): M18CurrencyListResponse? { return getList( stSearch = StSearchType.CURRENCY.value, params = null, conds = null, request = request ) } open fun getCurrency(id: Long): M18CurrencyResponse? { logger.info("M18 Currency ID: $id") return getLine( id = id, params = null, api = M18_LOAD_CURRENCY_API ) } open fun saveCurrencies(request: M18CommonRequest) : SyncResult{ logger.info("--------------------------------------------Start - Saving M18 Currencies--------------------------------------------") val currencies = getCurrencies(request) val successList = mutableListOf() val failList = mutableListOf() val values = currencies?.values?.sortedBy { it.id } if (values != null) { // save currency values.forEach { currency -> try { val currencyRequest = SaveCurrencyRequest( id = null, code = currency.code, name = currency.sym, description = currency.curDesc, m18Id = currency.id, m18LastModifyDate = LocalDateTime.parse(currency.lastModifyDate, formatter) ) currencyService.saveCurrency(currencyRequest) successList += currency.id logger.info("Save Success (M18): ${currency.id}") } catch (e: Exception) { failList += currency.id logger.error("Exception") logger.error("Fail Message: ${e.message}") logger.error("Fail Count ${failList.size}: Unit ID - ${currency.id}") } } } else { logger.error("Currency List is null. May occur errors.") } logger.info("Total Save Success (${successList.size})") if (failList.size > 0) { logger.error("Total Fail (${failList.size}): $failList") } logger.info("--------------------------------------------End - Saving Currencies--------------------------------------------") return SyncResult( totalProcessed = successList.size + failList.size, totalSuccess = successList.size, totalFail = failList.size ) } // --------------------------------------------- Bom --------------------------------------------- /// open fun getBoms(request: M18CommonRequest): M18BomListResponse? { return getList( stSearch = StSearchType.BOM.value, params = null, conds = beIdConds, request = request ) } open fun getBom(id: Long): M18BomResponse? { logger.info("M18 Bom ID: $id") return getLine( id = id, params = null, api = M18_LOAD_BOM_API ) } open fun saveBoms(request: M18CommonRequest) { logger.info("--------------------------------------------Start - Saving M18 Boms--------------------------------------------") val boms = getBoms(request) val successList = mutableListOf() val successDetailList = mutableListOf() val failList = mutableListOf() val failDetailList = mutableListOf>>() var failDetailCount = 0 val values = boms?.values?.sortedBy { it.id } if (values != null) { values.forEach { bom -> try { val bomDetail = getBom(bom.id) val bomUdfBomForShop = bomDetail?.data?.udfbomforshop?.get(0) val bomUdfProduct = bomDetail?.data?.udfproduct logger.info(bomUdfBomForShop.toString()) logger.info(bomUdfProduct.toString()) if (bomUdfBomForShop != null && bomUdfProduct != null) { // Save Bom val saveBomRequest = SaveBomRequest( // itemId = itemsService.findByNameAndM18UomId(bomUdfBomForShop.desc, bomUdfBomForShop.udfUnit)?.id, code = bomUdfBomForShop.code, name = bomUdfBomForShop.desc, description = bomUdfBomForShop.desc, outputQty = if (bomUdfBomForShop.udfHarvest.trim().toBigDecimalOrNull() != null) bomUdfBomForShop.udfHarvest.trim().toBigDecimal() else BigDecimal(0), outputQtyUom = bomUdfBomForShop.udfHarvestUnit, yield = bomUdfBomForShop.udfYieldratePP, m18UomId = bomUdfBomForShop.udfUnit, m18Id = bomUdfBomForShop.id, m18LastModifyDate = commonUtils.timestampToLocalDateTime(bomUdfBomForShop.lastModifyDate) ) val bomId = bomService.saveBom(saveBomRequest).id successList += bom.id // Save Bom Material logger.info("Start saving bom material...") val tempFailList = mutableListOf() bomUdfProduct.forEach { bomMaterial -> try { val saveBomMaterialRequest = SaveBomMaterialRequest( m18ItemId = bomMaterial.udfProduct, itemName = bomMaterial.udfIngredients, qty = bomMaterial.udfqty, m18UomId = bomMaterial.udfpurchaseUnit, uomName = bomMaterial.udfBaseUnit, bomId = bomId, m18Id = bomMaterial.id, m18LastModifyDate = commonUtils.timestampToLocalDateTime(bomUdfBomForShop.lastModifyDate) ) bomMaterialService.saveBomMaterial(saveBomMaterialRequest) successDetailList += bomMaterial.id } catch (e: Exception) { tempFailList += bomMaterial.id logger.error("(Bom Material) Exception") logger.error("(Bom Material) Fail Message: ${e.message}") logger.error("(Bom Material) Fail Count ${++failDetailCount}: Bom Material ID - ${bomMaterial.id} | Bom ID - ${bom.id}") } } failDetailList += Pair(bom.id, tempFailList) logger.info("Save Success (M18): ${bom.id}") } else { failList.add(bom.id) logger.error("(Bom) Fail Message: ${bomDetail?.messages?.get(0)?.msgDetail}") logger.error("(Bom) Fail Count ${failList.size}: Bom ID - ${bom.id} Not Found") } } catch (e: Exception) { failList += bom.id logger.error("(Bom) Exception") logger.error("(Bom) Fail Message: ${e.message}") logger.error("(Bom) Fail Count ${failList.size}: Bom ID - ${bom.id}") } } } else { logger.error("Currency List is null. May occur errors.") } logger.info("Total Bom Save Success (${successList.size})") logger.info("Total Bom Save Detail Success (${successDetailList.size})") if (failList.size > 0) { logger.error("Total Bom Fail (${failList.size}): $failList") } if (failDetailCount > 0) { logger.error("Total Bom Detail Fail (${failDetailCount}): $failDetailList") } logger.info("--------------------------------------------End - Saving Boms--------------------------------------------") } // --------------------------------------------- Business Unit (Shop) --------------------------------------------- /// open fun getBusinessUnits(request: M18CommonRequest): M18BusinessUnitListResponse? { // seems no beId return getList( stSearch = StSearchType.BUSINESS_UNIT.value, params = null, // conds = beIdConds request = request ) } open fun getBusinessUnit(id: Long): M18BusinessUnitResponse? { logger.info("M18 Business Unit ID: $id") return getLine( id = id, params = null, api = M18_LOAD_BUSINESS_UNIT_API ) } open fun saveBusinessUnits(request: M18CommonRequest) : SyncResult { logger.info("--------------------------------------------Start - Saving M18 Business Units (Shops)--------------------------------------------") val businessUnits = getBusinessUnits(request) val successList = mutableListOf() val failList = mutableListOf() val values = businessUnits?.values?.sortedBy { it.id } val busMessages = if(businessUnits?.messages?.isNotEmpty() == true) businessUnits.messages[0] else null if (values != null) { values.forEach { businessUnit -> // if (vendor.id in exampleVendors) { try { val businessUnitDetail = getBusinessUnit(businessUnit.id) val virdept = businessUnitDetail?.data?.virdept?.get(0) val buMessages = if(businessUnitDetail?.messages?.isNotEmpty() == true) businessUnitDetail?.messages[0] else null if (virdept != null) { val saveShopRequest = SaveShopRequest( id = null, code = virdept.code, name = virdept.descZhTW.ifEmpty { virdept.descZhCN.ifEmpty { virdept.desc } }, brNo = null, contactNo = virdept.tel, contactEmail = virdept.email, contactName = null, addr1 = virdept.addr.ifEmpty { virdept.addr_en }, addr2 = virdept.addr2.ifEmpty { virdept.addr2_en }, addr3 = virdept.addr3.ifEmpty { virdept.addr3_en }, addr4 = null, district = null, type = ShopType.SHOP.value, m18Id = businessUnit.id, m18LastModifyDate = commonUtils.timestampToLocalDateTime(virdept.lastModifyDate) ) shopService.saveShop(saveShopRequest) successList.add(businessUnit.id) logger.info("Success Count ${successList.size}: ${businessUnit.id} | ${virdept.code} | ${virdept.descZhTW.ifEmpty { virdept.descZhCN.ifEmpty { virdept.desc } }}") } else { failList.add(businessUnit.id) logger.error("(Business Unit) Fail Message: ${buMessages?.msgDetail}") logger.error("(Business Unit) Fail Count ${failList.size}: Business Unit ID - ${businessUnit.id} Not Found") } } catch (e: Exception) { failList.add(businessUnit.id) logger.error("(Business Unit) Exception") logger.error("(Business Unit) Fail Message: ${e.message}") logger.error("(Business Unit) Fail Count ${failList.size}: Business Unit ID - ${businessUnit.id}") } // } } } else { logger.error("(Business Unit) Business Unit List is null. May occur errors.") logger.error("(Business Unit)) Fail Message: ${busMessages?.msgDetail}") logger.error("(Business Unit) Business Unit List is null. May occur errors.") } logger.info("Total Success (${successList.size})") if (failList.size > 0) { logger.error("Total Fail (${failList.size}): $failList") } logger.info("--------------------------------------------End - Saving M18 Business Units--------------------------------------------") return SyncResult( totalProcessed = successList.size + failList.size, totalSuccess = successList.size, totalFail = failList.size ) } }