Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 
 

755 строки
40 KiB

  1. package com.ffii.fpsms.m18.service
  2. import com.ffii.core.utils.JwtTokenUtil
  3. import com.ffii.fpsms.api.service.ApiCallerService
  4. import com.ffii.fpsms.m18.M18Config
  5. import com.ffii.fpsms.m18.enums.M18DataLogStatus
  6. import com.ffii.fpsms.m18.model.*
  7. import com.ffii.fpsms.m18.utils.CommonUtils
  8. import com.ffii.fpsms.m18.web.models.M18CommonRequest
  9. import com.ffii.fpsms.modules.deliveryOrder.service.DeliveryOrderLineService
  10. import com.ffii.fpsms.modules.deliveryOrder.service.DeliveryOrderService
  11. import com.ffii.fpsms.modules.master.service.ItemUomService
  12. import com.ffii.fpsms.modules.master.service.ItemsService
  13. import com.ffii.fpsms.modules.master.service.ShopService
  14. import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderLineStatus
  15. import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderStatus
  16. import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderType
  17. import com.ffii.fpsms.modules.common.SettingNames
  18. import com.ffii.fpsms.modules.purchaseOrder.service.PurchaseOrderLineService
  19. import com.ffii.fpsms.modules.purchaseOrder.service.PurchaseOrderService
  20. import com.ffii.fpsms.modules.purchaseOrder.web.model.SavePurchaseOrderLineRequest
  21. import com.ffii.fpsms.modules.purchaseOrder.web.model.SavePurchaseOrderRequest
  22. import com.ffii.fpsms.modules.settings.entity.Settings
  23. import com.ffii.fpsms.modules.settings.service.SettingsService
  24. import org.slf4j.Logger
  25. import org.slf4j.LoggerFactory
  26. import org.springframework.stereotype.Service
  27. import java.time.LocalDateTime
  28. import kotlin.reflect.full.memberProperties
  29. import java.time.format.DateTimeFormatter
  30. import com.ffii.fpsms.m18.model.SyncResult
  31. @Service
  32. open class M18PurchaseOrderService(
  33. val m18Config: M18Config,
  34. val apiCallerService: ApiCallerService,
  35. val m18DataLogService: M18DataLogService,
  36. val purchaseOrderService: PurchaseOrderService,
  37. val purchaseOrderLineService: PurchaseOrderLineService,
  38. val deliveryOrderService: DeliveryOrderService,
  39. val deliveryOrderLineService: DeliveryOrderLineService,
  40. val itemsService: ItemsService,
  41. val shopService: ShopService,
  42. val itemUomService: ItemUomService,
  43. val m18MasterDataService: M18MasterDataService,
  44. val settingsService: SettingsService,
  45. ) {
  46. val commonUtils = CommonUtils()
  47. val logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java)
  48. val lastModifyDateStart = "2025-05-14 14:00:00"
  49. val lastModifyDateEnd = "2025-05-14 14:30:00"
  50. private val M18_LAST_MODIFY_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
  51. // val lastModifyDateConds =
  52. // "lastModifyDate=largerOrEqual=${lastModifyDateStart}=and=lastModifyDate=lessOrEqual=${lastModifyDateEnd}"
  53. // val lastModifyDate = LocalDateTime.now().minusMinutes(30)
  54. // val commonConds =
  55. // "(beId=equal=${m18Config.BEID_PF}=or=beId=equal=${m18Config.BEID_PP}=or=beId=equal=${m18Config.BEID_TOA})=and=lastModifyDate=largerOrEqual=${lastModifyDate}"
  56. // M18 API
  57. val M18_LOAD_PURCHASE_ORDER_API = "/root/api/read/po"
  58. val M18_LOAD_PURCHASE_ORDER_BY_CODE_API = "/root/api/read/po"
  59. val M18_FETCH_PURCHASE_ORDER_LIST_API = "/search/search"
  60. // Include material po, oem po
  61. open fun getPurchaseOrdersWithType(request: M18CommonRequest): M18PurchaseOrderListResponseWithType? {
  62. val purchaseOrders = M18PurchaseOrderListResponseWithType(mutableListOf())
  63. val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
  64. val lastDateConds = if (request.modifiedDateFrom != null && request.modifiedDateTo != null) {
  65. val dateFrom = LocalDateTime.parse(request.modifiedDateFrom, formatter).toString()
  66. val dateTo = LocalDateTime.parse(request.modifiedDateTo, formatter).toString()
  67. "lastModifyDate=largerOrEqual=$dateFrom=and=lastModifyDate=lessOrEqual=$dateTo"
  68. } else {
  69. ""
  70. }
  71. val dDateConds = if (request.dDateFrom != null && request.dDateTo != null) {
  72. val dateFrom = LocalDateTime.parse(request.dDateFrom, formatter).toLocalDate().toString()
  73. val dateTo = LocalDateTime.parse(request.dDateTo, formatter).toLocalDate().toString()
  74. "dDate=largerOrEqual=$dateFrom=and=dDate=lessOrEqual=$dateTo"
  75. } else {
  76. ""
  77. }
  78. val dDateEqualConds = if (request.dDateEqual != null) {
  79. val dDateEqual = LocalDateTime.parse(request.dDateEqual, formatter).toLocalDate().toString()
  80. "dDate=equal=$dDateEqual"
  81. } else {
  82. ""
  83. }
  84. // Material PO
  85. val materialPoBuyers =
  86. commonUtils.listToString(listOf(m18Config.BEID_PP, m18Config.BEID_PF), "beId=equal=", "=or=")
  87. val materialPoSupplierNot = commonUtils.listToString(
  88. shopService.findM18VendorIdsByCodeRegexp(m18Config.MATERIAL_PO_SUPPLIER_NOT),
  89. "venId=unequal=",
  90. "=or="
  91. )
  92. var materialPoConds = "(${materialPoBuyers})=and=(${materialPoSupplierNot}))"
  93. if (request.modifiedDateFrom != null && request.modifiedDateTo != null) {
  94. materialPoConds += "=and=(${lastDateConds})"
  95. }
  96. if (request.dDateFrom != null && request.dDateTo != null) {
  97. materialPoConds += "=and=(${dDateConds})"
  98. }
  99. if (request.dDateEqual != null) {
  100. materialPoConds += "=and=(${dDateEqualConds})"
  101. }
  102. println("materialPoConds: ${materialPoConds}")
  103. val materialPoParams = M18PurchaseOrderListRequest(
  104. params = null,
  105. conds = materialPoConds
  106. )
  107. try {
  108. purchaseOrders.valuesWithType += Pair(
  109. PurchaseOrderType.MATERIAL,
  110. apiCallerService.get<M18PurchaseOrderListResponse, M18PurchaseOrderListRequest>(
  111. M18_FETCH_PURCHASE_ORDER_LIST_API,
  112. materialPoParams
  113. ).block()
  114. )
  115. } catch (e: Exception) {
  116. logger.error("(Getting Material Po list) Error on Function - ${e.stackTrace}")
  117. logger.error(e.message)
  118. }
  119. purchaseOrders.query = materialPoConds
  120. // Shop PO
  121. /* val shopPoBuyers = commonUtils.listToString(listOf(m18Config.BEID_TOA), "beId=equal=", "=or=")
  122. val shopPoSupplier = commonUtils.listToString(
  123. shopService.findM18VendorIdsByCodeRegexp(m18Config.SHOP_PO_SUPPLIER),
  124. "venId=equal=",
  125. "=or="
  126. )
  127. val shopPoConds = "(${shopPoBuyers})=and=(${shopPoSupplier})=and=(${lastModifyDateConds})"
  128. println("shopPoConds: ${shopPoConds}")
  129. val shopPoParams = M18PurchaseOrderListRequest(
  130. params = null,
  131. conds = shopPoConds
  132. )
  133. try {
  134. purchaseOrders.valuesWithType += Pair(
  135. PurchaseOrderType.SHOP, apiCallerService.get<M18PurchaseOrderListResponse, M18PurchaseOrderListRequest>(
  136. M18_FETCH_PURCHASE_ORDER_LIST_API,
  137. shopPoParams
  138. ).block()
  139. )
  140. } catch (e: Exception) {
  141. logger.error("(Getting Shop Po list) Error on Function - ${e.stackTrace}")
  142. logger.error(e.message)
  143. } */
  144. // OEM PO
  145. /* val oemPoBuyers = commonUtils.listToString(listOf(m18Config.BEID_PF, m18Config.BEID_PP), "beId=equal=", "=or=")
  146. val oemPoSupplier = commonUtils.listToString(
  147. shopService.findM18VendorIdsByCodeRegexp(m18Config.OEM_PO_SUPPLIER),
  148. "venId=equal=",
  149. "=or="
  150. )
  151. val oemPoConds = "(${oemPoBuyers})=and=(${oemPoSupplier})=and=(${lastModifyDateConds})"
  152. println("oemPoConds: ${oemPoConds}")
  153. val oemPoParams = M18PurchaseOrderListRequest(
  154. params = null,
  155. conds = oemPoConds
  156. )
  157. try {
  158. purchaseOrders.valuesWithType += Pair(
  159. PurchaseOrderType.OEM, apiCallerService.get<M18PurchaseOrderListResponse, M18PurchaseOrderListRequest>(
  160. M18_FETCH_PURCHASE_ORDER_LIST_API,
  161. oemPoParams
  162. ).block()
  163. )
  164. } catch (e: Exception) {
  165. logger.error("(Getting OEM Po list) Error on Function - ${e.stackTrace}")
  166. logger.error(e.message)
  167. } */
  168. return purchaseOrders
  169. }
  170. open fun getPurchaseOrder(id: Long): M18PurchaseOrderResponse? {
  171. val purchaseOrderParams = M18PurchaseOrderRequest(
  172. id = id
  173. )
  174. var purchaseOrder: M18PurchaseOrderResponse? = null
  175. try {
  176. purchaseOrder = apiCallerService.get<M18PurchaseOrderResponse, M18PurchaseOrderRequest>(
  177. M18_LOAD_PURCHASE_ORDER_API,
  178. purchaseOrderParams
  179. ).block()
  180. } catch (e: Exception) {
  181. logger.error("(Getting Po Detail) Error on Function - ${e.stackTrace}")
  182. logger.error(e.message)
  183. }
  184. return purchaseOrder
  185. }
  186. open fun getPurchaseOrderByCode(code: String): M18PurchaseOrderResponse? {
  187. var purchaseOrder: M18PurchaseOrderResponse? = null
  188. try {
  189. purchaseOrder = apiCallerService.get<M18PurchaseOrderResponse>(
  190. M18_LOAD_PURCHASE_ORDER_BY_CODE_API,
  191. mapOf("code" to code)
  192. ).block()
  193. } catch (e: Exception) {
  194. logger.error("(Getting Po Detail By Code) Error on Function - ${e.stackTrace}")
  195. logger.error(e.message)
  196. }
  197. return purchaseOrder
  198. }
  199. private fun resolvePoTypeByBeId(beId: Long?): PurchaseOrderType {
  200. return if (beId?.toString() == m18Config.BEID_TOA) PurchaseOrderType.SHOP else PurchaseOrderType.MATERIAL
  201. }
  202. open fun savePurchaseOrderByCode(code: String): SyncResult {
  203. // Follow the scheduler's original approach:
  204. // 1) use /search/search to find the PO id by code
  205. // 2) then sync by PO id (this reuses the same create/update logic)
  206. val conds = "(code=equal=$code)"
  207. return saveFromPoListSearch(conds, searchPurchaseOrdersByConds(conds))
  208. }
  209. /**
  210. * Stock-in `/po` lookup: create if missing; for a local **pending** PO, pull from M18 only when
  211. * M18 list `lastModifyDate` is after [PurchaseOrder.modified] (last MTMS header write, usually last sync).
  212. * Receiving / completed POs are not refreshed.
  213. */
  214. open fun savePurchaseOrderByCodeIfNewer(code: String): SyncResult {
  215. val trimmed = code.trim()
  216. val localPo = purchaseOrderService.findByCode(trimmed)
  217. if (localPo != null && !isStockInRefreshIfNewerEnabled()) {
  218. logger.info(
  219. "Purchase Order: Skipping if-newer M18 sync — setting ${SettingNames.M18_PO_STOCK_IN_REFRESH_IF_NEWER_ENABLED} " +
  220. "is not true. code=$trimmed"
  221. )
  222. return SyncResult(
  223. totalProcessed = 0,
  224. totalSuccess = 0,
  225. totalFail = 0,
  226. query = "skipped (ifNewer): setting ${SettingNames.M18_PO_STOCK_IN_REFRESH_IF_NEWER_ENABLED} is not true"
  227. )
  228. }
  229. if (localPo != null && localPo.status != PurchaseOrderStatus.PENDING) {
  230. logger.info(
  231. "Purchase Order: Skipping if-newer M18 sync — local PO id=${localPo.id} code=${localPo.code} " +
  232. "status=${localPo.status?.value} (only pending may be refreshed)."
  233. )
  234. return SyncResult(
  235. totalProcessed = 0,
  236. totalSuccess = 0,
  237. totalFail = 0,
  238. query = "skipped (ifNewer): local PO not pending: $trimmed status=${localPo.status?.value}"
  239. )
  240. }
  241. if (localPo == null) {
  242. return savePurchaseOrderByCode(trimmed)
  243. }
  244. val conds = "(code=equal=$trimmed)"
  245. val poListResponse = searchPurchaseOrdersByConds(conds)
  246. val m18Row = poListResponse?.values?.firstOrNull()
  247. val localModified = localPo.modified ?: localPo.created
  248. val m18LastModify = parseM18LastModifyDate(m18Row?.lastModifyDate)
  249. if (m18Row == null || m18LastModify == null || localModified == null || !m18LastModify.isAfter(localModified)) {
  250. logger.info(
  251. "Purchase Order: Skipping if-newer M18 sync — M18 lastModifyDate not after " +
  252. "purchase_order.modified. code=$trimmed localModified=$localModified " +
  253. "m18LastModifyRaw=${m18Row?.lastModifyDate} m18LastModify=$m18LastModify"
  254. )
  255. return SyncResult(
  256. totalProcessed = 0,
  257. totalSuccess = 0,
  258. totalFail = 0,
  259. query = "skipped (ifNewer): M18 lastModifyDate not after purchase_order.modified: " +
  260. "code=$trimmed localModified=$localModified m18LastModify=${m18Row?.lastModifyDate}"
  261. )
  262. }
  263. logger.info(
  264. "Purchase Order: M18 lastModifyDate after purchase_order.modified — syncing. " +
  265. "code=$trimmed localModified=$localModified m18LastModify=$m18LastModify"
  266. )
  267. return saveFromPoListSearch(conds, poListResponse)
  268. }
  269. private fun saveFromPoListSearch(
  270. query: String,
  271. poListResponse: M18PurchaseOrderListResponse?,
  272. ): SyncResult {
  273. val poValues = poListResponse?.values
  274. if (poValues.isNullOrEmpty()) {
  275. return SyncResult(
  276. totalProcessed = 1,
  277. totalSuccess = 0,
  278. totalFail = 1,
  279. query = query
  280. )
  281. }
  282. val request = M18PurchaseOrderListResponseWithType(
  283. valuesWithType = mutableListOf(Pair(PurchaseOrderType.MATERIAL, poListResponse)),
  284. query = query
  285. )
  286. return savePurchaseOrdersWithPreparedList(purchaseOrdersWithType = request)
  287. }
  288. private fun isStockInRefreshIfNewerEnabled(): Boolean =
  289. settingsService.findByName(SettingNames.M18_PO_STOCK_IN_REFRESH_IF_NEWER_ENABLED)
  290. .map { Settings.VALUE_BOOLEAN_TRUE.equals(it.value?.trim(), ignoreCase = true) }
  291. .orElse(false)
  292. private fun parseM18LastModifyDate(raw: String?): LocalDateTime? {
  293. val value = raw?.trim().orEmpty()
  294. if (value.isEmpty()) return null
  295. return try {
  296. LocalDateTime.parse(value, M18_LAST_MODIFY_FORMATTER)
  297. } catch (_: Exception) {
  298. try {
  299. LocalDateTime.parse(value)
  300. } catch (_: Exception) {
  301. value.toLongOrNull()?.let { commonUtils.timestampToLocalDateTime(it) }
  302. }
  303. }
  304. }
  305. private fun searchPurchaseOrdersByConds(conds: String): M18PurchaseOrderListResponse? {
  306. val searchRequest = M18PurchaseOrderListRequest(
  307. stSearch = "po",
  308. params = null,
  309. conds = conds
  310. )
  311. return try {
  312. apiCallerService.get<M18PurchaseOrderListResponse, M18PurchaseOrderListRequest>(
  313. M18_FETCH_PURCHASE_ORDER_LIST_API,
  314. searchRequest
  315. ).block()
  316. } catch (e: Exception) {
  317. logger.error("(Getting Po List By Code) Error on Function - ${e.stackTrace}")
  318. logger.error(e.message)
  319. null
  320. }
  321. }
  322. open fun savePurchaseOrders(request: M18CommonRequest) : SyncResult{
  323. val purchaseOrdersWithType = getPurchaseOrdersWithType(request)
  324. return savePurchaseOrdersWithPreparedList(
  325. purchaseOrdersWithType = purchaseOrdersWithType,
  326. preloadPurchaseOrderDetails = null
  327. )
  328. }
  329. private fun savePurchaseOrdersWithPreparedList(
  330. purchaseOrdersWithType: M18PurchaseOrderListResponseWithType?,
  331. preloadPurchaseOrderDetails: Map<Long, M18PurchaseOrderResponse?>? = null
  332. ): SyncResult {
  333. logger.info("--------------------------------------------Start - Saving M18 Purchase Order--------------------------------------------")
  334. val successList = mutableListOf<Long>()
  335. val successDetailList = mutableListOf<Long>()
  336. val failList = mutableListOf<Long>()
  337. val failDetailList = mutableListOf<Long>()
  338. val affectedItemIds = mutableSetOf<Long>()
  339. val uomByM18IdCache = mutableMapOf<Long, com.ffii.fpsms.modules.master.entity.ItemUom?>()
  340. val poRefType = "Purchase Order"
  341. val poLineRefType = "Purchase Order Line"
  342. if (purchaseOrdersWithType != null) {
  343. // Loop for Purchase Orders (values)
  344. purchaseOrdersWithType.valuesWithType.forEach { purchaseOrderWithType ->
  345. // if success
  346. val purchaseOrdersValues = purchaseOrderWithType.second?.values
  347. // if fail
  348. val purchaseOrdersMessages = purchaseOrderWithType.second?.messages
  349. if (purchaseOrdersValues != null) {
  350. purchaseOrdersValues.forEach { purchaseOrder ->
  351. val purchaseOrderDetail =
  352. preloadPurchaseOrderDetails?.get(purchaseOrder.id) ?: getPurchaseOrder(purchaseOrder.id)
  353. var purchaseOrderId: Long? = null //FP-MTMS
  354. // Process for Purchase Order (mainpo)
  355. // Assume only one PO in the PO (search by PO ID)
  356. val mainpo = purchaseOrderDetail?.data?.mainpo?.get(0)
  357. val pot = purchaseOrderDetail?.data?.pot
  358. val purchaseOrderLineMessage = purchaseOrderDetail?.messages
  359. logger.info("purchaseOrderDetail: data is null? ${purchaseOrderDetail?.data == null} | mainpo is null? ${purchaseOrderDetail?.data?.mainpo == null} | get(0) is null? ${purchaseOrderDetail?.data?.mainpo?.get(0) == null}")
  360. // purchase_order + m18_data_log table
  361. if (mainpo != null) {
  362. val m18PurchaseOrderId = mainpo.id
  363. // Find the latest m18 data log by m18 id & type
  364. // logger.info("${poRefType}: Finding For Latest M18 Data Log...")
  365. val latestPurchaseOrderLog =
  366. m18DataLogService.findLatestM18DataLogWithSuccess(m18PurchaseOrderId, poRefType)
  367. val existingPurchaseOrderForSync =
  368. latestPurchaseOrderLog?.id?.let { purchaseOrderService.findByM18DataLogId(it) }
  369. if (existingPurchaseOrderForSync != null &&
  370. existingPurchaseOrderForSync.status != PurchaseOrderStatus.PENDING
  371. ) {
  372. logger.info(
  373. "${poRefType}: Skipping M18 sync — local PO id=${existingPurchaseOrderForSync.id} " +
  374. "code=${existingPurchaseOrderForSync.code} status=${existingPurchaseOrderForSync.status?.value} " +
  375. "(only pending may be overwritten). M18 ID: $m18PurchaseOrderId"
  376. )
  377. return@forEach
  378. }
  379. // logger.info(latestPurchaseOrderLog.toString())
  380. // Save to m18_data_log table
  381. // logger.info("${poRefType}: Saving for M18 Data Log...")
  382. val mainpoJson =
  383. mainpo::class.memberProperties.associate { prop -> prop.name to prop.getter.call(mainpo) }
  384. .toMutableMap()
  385. val saveM18PurchaseOrderLogRequest = SaveM18DataLogRequest(
  386. id = null,
  387. refType = poRefType,
  388. m18Id = m18PurchaseOrderId,
  389. m18LastModifyDate = commonUtils.timestampToLocalDateTime(mainpo.lastModifyDate),
  390. // dataLog = mainpoJson,
  391. statusEnum = M18DataLogStatus.NOT_PROCESS
  392. )
  393. val saveM18PurchaseOrderLog =
  394. m18DataLogService.saveM18DataLog(saveM18PurchaseOrderLogRequest)
  395. // logger.info("${poRefType}: Saved M18 Data Log. ID: ${saveM18PurchaseOrderLog.id}")
  396. try {
  397. // Find the purchase_order if exist (re-use lookup from pending guard above)
  398. // logger.info("${poRefType}: Finding exising purchase order...")
  399. val existingPurchaseOrder = existingPurchaseOrderForSync
  400. // logger.info("${poRefType}: Exising purchase order ID: ${existingPurchaseOrder?.id}")
  401. // Save to purchase_order table
  402. // logger.info("${poRefType}: Saving purchase order...")
  403. val savePurchaseOrderRequest = SavePurchaseOrderRequest(
  404. id = existingPurchaseOrder?.id,
  405. code = mainpo.code,
  406. m18SupplierId = mainpo.venId,
  407. m18ShopId = mainpo.virDeptId,
  408. m18CurrencyId = mainpo.curId,
  409. orderDate = commonUtils.timestampToLocalDateTime(mainpo.tDate),
  410. estimatedArrivalDate = commonUtils.timestampToLocalDateTime(mainpo.dDate),
  411. completeDate = null,
  412. status = PurchaseOrderStatus.PENDING.value,
  413. type = resolvePoTypeByBeId(mainpo.beId).value,
  414. m18DataLogId = saveM18PurchaseOrderLog.id,
  415. m18BeId = mainpo.beId,
  416. m18CreatedUId = mainpo.createUid,
  417. )
  418. val savePurchaseOrderResponse =
  419. purchaseOrderService.savePurchaseOrder(savePurchaseOrderRequest)
  420. purchaseOrderId = savePurchaseOrderResponse.id
  421. // Update m18_data_log with success
  422. val successSaveM18PurchaseOrderLogRequest = SaveM18DataLogRequest(
  423. id = saveM18PurchaseOrderLog.id,
  424. dataLog = mainpoJson,
  425. statusEnum = M18DataLogStatus.SUCCESS
  426. )
  427. m18DataLogService.saveM18DataLog(successSaveM18PurchaseOrderLogRequest)
  428. // log success info
  429. successList.add(m18PurchaseOrderId)
  430. logger.info("${poRefType}: Saved purchase order. ID: ${savePurchaseOrderResponse.id} | M18 ${poRefType} ID: ${m18PurchaseOrderId}")
  431. } catch (e: Exception) {
  432. failList.add(m18PurchaseOrderId)
  433. // logger.error("${poRefType}: Saving Failure!")
  434. logger.error("Error on Function - ${e.stackTrace} | Type: ${poRefType} | M18 ID: ${m18PurchaseOrderId} | Different? ${mainpo.id}")
  435. logger.error(e.message)
  436. val errorSaveM18PurchaseOrderLogRequest = SaveM18DataLogRequest(
  437. id = saveM18PurchaseOrderLog.id,
  438. dataLog = mutableMapOf(Pair("Exception Message", e.message)),
  439. statusEnum = M18DataLogStatus.FAIL
  440. )
  441. m18DataLogService.saveM18DataLog(errorSaveM18PurchaseOrderLogRequest)
  442. // logger.error("${poRefType}: M18 Data Log Updated! Please see the error. ID: ${saveM18PurchaseOrderLogRequest.id}")
  443. }
  444. // purchase_order_line + m18_data_log
  445. if (pot != null) {
  446. // Loop for Purchase Order Lines (pot)
  447. pot.forEach { line ->
  448. // Find the latest m18 data log by m18 id & type
  449. // logger.info("${poLineRefType}: Finding For Latest M18 Data Log...")
  450. val latestPurchaseOrderLineLog =
  451. m18DataLogService.findLatestM18DataLogWithSuccess(line.id, poLineRefType)
  452. // logger.info("${poLineRefType}: Latest M18 Data Log ID: ${latestPurchaseOrderLineLog?.id}")
  453. // Save to m18_data_log table
  454. // logger.info("${poLineRefType}: Saving for M18 Data Log...")
  455. val lineJson =
  456. line::class.memberProperties.associate { prop ->
  457. prop.name to prop.getter.call(
  458. line
  459. )
  460. }
  461. .toMutableMap()
  462. val saveM18PurchaseOrderLineLogRequest = SaveM18DataLogRequest(
  463. id = null,
  464. refType = poLineRefType,
  465. m18Id = line.id,
  466. m18LastModifyDate = commonUtils.timestampToLocalDateTime(mainpo.lastModifyDate),
  467. // dataLog = lineJson,
  468. statusEnum = M18DataLogStatus.NOT_PROCESS
  469. )
  470. val saveM18PurchaseOrderLineLog =
  471. m18DataLogService.saveM18DataLog(saveM18PurchaseOrderLineLogRequest)
  472. // logger.info("${poLineRefType}: Saved M18 Data Log. ID: ${saveM18PurchaseOrderLineLog.id}")
  473. // logger.info("${poLineRefType}: Finding item...")
  474. val itemId: Long? = m18MasterDataService.resolveLocalItemId(line.proId)
  475. logger.info("${poLineRefType}: Item ID: ${itemId} | M18 Item ID: ${line.proId}")
  476. if (itemId == null) {
  477. failDetailList.add(line.id)
  478. logger.error(
  479. "${poLineRefType}: PO ${mainpo.code}: Cannot resolve local item for M18 proId=${line.proId}, skipping line ${line.id}"
  480. )
  481. val errorSaveM18PurchaseOrderLineLogRequest = SaveM18DataLogRequest(
  482. id = saveM18PurchaseOrderLineLog.id,
  483. dataLog = mutableMapOf(
  484. "Exception Message" to "PO ${mainpo.code} Cannot resolve local item for M18 proId=${line.proId}"
  485. ),
  486. statusEnum = M18DataLogStatus.FAIL
  487. )
  488. m18DataLogService.saveM18DataLog(errorSaveM18PurchaseOrderLineLogRequest)
  489. return@forEach
  490. }
  491. try {
  492. // Find the purchase_order_line if exist (stable key: PO + M18 line id)
  493. // logger.info("${poLineRefType}: Finding exising purchase order line...")
  494. val existingPurchaseOrderLine =
  495. purchaseOrderId?.let { pid ->
  496. purchaseOrderLineService.findPurchaseOrderLineByPurchaseOrderAndM18LineId(
  497. pid,
  498. line.id
  499. )
  500. } ?: latestPurchaseOrderLineLog?.id?.let {
  501. purchaseOrderLineService.findPurchaseOrderLineByM18Id(it)
  502. }
  503. // logger.info("${poLineRefType}: Exising purchase order line ID: ${existingPurchaseOrderLine?.id}")
  504. // Save to purchase_order_line table
  505. // logger.info("${poLineRefType}: Saving purchase order line...")
  506. val purchaseItemUom = itemId?.let { itemUomService.findPurchaseUnitByItemId(it) }
  507. val m18ItemUom = uomByM18IdCache.getOrPut(line.unitId) {
  508. itemUomService.findByM18Id(line.unitId)
  509. }
  510. val qtyM18 = line.qty
  511. val sourceUomId = m18ItemUom?.uom?.id
  512. // qtyM18 is the original qty in M18 line uom.
  513. // System PO line.qty must be qty in purchase unit.
  514. val convertedQty = if (itemId != null && sourceUomId != null) {
  515. itemUomService.convertQtyToPurchaseQty(itemId, sourceUomId, qtyM18)
  516. } else {
  517. qtyM18
  518. }
  519. val savePurchaseOrderLineRequest = SavePurchaseOrderLineRequest(
  520. id = existingPurchaseOrderLine?.id,
  521. itemId = itemId,
  522. uomId = purchaseItemUom?.uom?.id,
  523. purchaseOrderId = purchaseOrderId,
  524. qty = convertedQty,
  525. qtyM18 = qtyM18,
  526. up = line.up,
  527. price = line.amt,
  528. // m18CurrencyId = mainpo.curId,
  529. status = existingPurchaseOrderLine?.status?.value
  530. ?: PurchaseOrderLineStatus.PENDING.value,
  531. m18DataLogId = saveM18PurchaseOrderLineLog.id,
  532. m18Discount = line.disc,
  533. m18Lot = line.lot,
  534. uomIdM18 = sourceUomId
  535. )
  536. val savePurchaseOrderLineResponse =
  537. purchaseOrderLineService.savePurchaseOrderLine(savePurchaseOrderLineRequest)
  538. // Update m18_data_log with success
  539. val successSaveM18PurchaseOrderLineLogRequest = SaveM18DataLogRequest(
  540. id = saveM18PurchaseOrderLineLog.id,
  541. dataLog = lineJson,
  542. statusEnum = M18DataLogStatus.SUCCESS
  543. )
  544. m18DataLogService.saveM18DataLog(successSaveM18PurchaseOrderLineLogRequest)
  545. // log success info
  546. successDetailList.add(line.id)
  547. // logger.info("${poLineRefType}: Purchase order ID: ${purchaseOrderId} | M18 ID: ${purchaseOrder.id}")
  548. logger.info("${poLineRefType}: Saved purchase order line. ID: ${savePurchaseOrderLineResponse.id} | M18 Line ID: ${line.id} | Purchase order ID: ${purchaseOrderId} | M18 ID: ${purchaseOrder.id}")
  549. itemId?.let { affectedItemIds.add(it) }
  550. } catch (e: Exception) {
  551. failDetailList.add(line.id)
  552. // logger.error("${poLineRefType}: Saving Failure!")
  553. logger.error("Error on Function - ${e.stackTrace} | Type: ${poLineRefType} | M18 ID: ${line.id}")
  554. logger.error(e.message)
  555. val errorSaveM18PurchaseOrderLineLogRequest = SaveM18DataLogRequest(
  556. id = saveM18PurchaseOrderLineLog.id,
  557. dataLog = mutableMapOf(Pair("Exception Message", e.message)),
  558. statusEnum = M18DataLogStatus.FAIL
  559. )
  560. m18DataLogService.saveM18DataLog(errorSaveM18PurchaseOrderLineLogRequest)
  561. // logger.error("${poLineRefType}: M18 Data Log Updated! Please see the error. ID: ${saveM18PurchaseOrderLineLog.id}")
  562. }
  563. }
  564. // Mark as deleted any local PO lines that no longer exist in M18 (removed there)
  565. if (purchaseOrderId != null) {
  566. val m18LineIds = pot.map { it.id }.toSet()
  567. val (markedDeleted, deletedItemIds) =
  568. purchaseOrderLineService.markDeletedLinesNotInM18(purchaseOrderId, m18LineIds)
  569. affectedItemIds.addAll(deletedItemIds)
  570. if (markedDeleted > 0) {
  571. logger.info("${poLineRefType}: Marked $markedDeleted line(s) as deleted (not in M18). PO ID: $purchaseOrderId | M18 PO ID: ${purchaseOrder.id}")
  572. }
  573. val (dupesRemoved, dupeItemIds) =
  574. purchaseOrderLineService.markDuplicatePurchaseOrderLinesForSameM18LineId(
  575. purchaseOrderId,
  576. m18LineIds
  577. )
  578. affectedItemIds.addAll(dupeItemIds)
  579. if (dupesRemoved > 0) {
  580. logger.info("${poLineRefType}: Marked $dupesRemoved duplicate line(s) as deleted (same M18 line id). PO ID: $purchaseOrderId | M18 PO ID: ${purchaseOrder.id}")
  581. }
  582. }
  583. } else {
  584. // pot
  585. // logger.error("${poLineRefType}: Saving Failure!")
  586. val saveM18PurchaseOrderLineLogRequest = SaveM18DataLogRequest(
  587. id = null,
  588. refType = "${poLineRefType}",
  589. m18Id = m18PurchaseOrderId,
  590. m18LastModifyDate = commonUtils.timestampToLocalDateTime(mainpo.lastModifyDate),
  591. // dataLog = mutableMapOf(Pair("Error Message", "${poLineRefType} is null")),
  592. dataLog = mutableMapOf(
  593. Pair(
  594. "${poLineRefType} Error Message",
  595. "pot is null"
  596. ),
  597. Pair(
  598. "${poLineRefType} Error Code",
  599. purchaseOrderLineMessage?.get(0)?.msgCode ?: "No Msg Code from M18"
  600. ),
  601. Pair(
  602. "${poLineRefType} Error Detail",
  603. purchaseOrderLineMessage?.get(0)?.msgDetail ?: "No Msg Detail from M18"
  604. ),
  605. ),
  606. statusEnum = M18DataLogStatus.FAIL
  607. )
  608. val errorLog = m18DataLogService.saveM18DataLog(saveM18PurchaseOrderLineLogRequest)
  609. logger.error("${poLineRefType}: M18 Data Log Updated! Please see the error. ID: ${errorLog.id}")
  610. }
  611. } else {
  612. // mainpo
  613. failList.add(purchaseOrder.id)
  614. // logger.error("${poRefType}: Saving Failure!")
  615. val saveM18DataLogRequest = SaveM18DataLogRequest(
  616. id = null,
  617. refType = "${poRefType}",
  618. m18Id = purchaseOrder.id,
  619. // m18LastModifyDate = if(mainpo?.lastModifyDate != null) commonUtils.instantToLocalDateTime(mainpo.lastModifyDate) else LocalDateTime.now(),
  620. m18LastModifyDate = LocalDateTime.now(),
  621. dataLog = mutableMapOf(
  622. Pair(
  623. "${poRefType} Error",
  624. "mainpo is null"
  625. ),
  626. Pair(
  627. "${poRefType} Error Code",
  628. purchaseOrdersMessages?.get(0)?.msgCode ?: "No Msg Code from M18"
  629. ),
  630. Pair(
  631. "${poRefType} Error Detail",
  632. purchaseOrdersMessages?.get(0)?.msgDetail ?: "No Msg Detail from M18"
  633. ),
  634. ),
  635. statusEnum = M18DataLogStatus.FAIL
  636. )
  637. val errorLog = m18DataLogService.saveM18DataLog(saveM18DataLogRequest)
  638. logger.error("${poLineRefType}: M18 Data Log Updated! Please see the error. ID: ${errorLog.id}")
  639. }
  640. }
  641. } else {
  642. logger.error("${poRefType} List is null. May occur errors.")
  643. }
  644. }
  645. } else {
  646. logger.error("${poRefType} List is null. May occur errors.")
  647. }
  648. // End of save. Check result
  649. // logger.info("Total Success (${poRefType}) (${successList.size}): $successList")
  650. logger.info("Total Success (${poRefType}) (${successList.size})")
  651. // if (failList.size > 0) {
  652. logger.error("Total Fail (${poRefType}) (${failList.size}): $failList")
  653. // }
  654. // logger.info("Total Success (${poLineRefType}) (${successDetailList.size}): $successDetailList")
  655. logger.info("Total Success (${poLineRefType}) (${successDetailList.size})")
  656. // if (failDetailList.size > 0) {
  657. logger.error("Total Fail (${poLineRefType}) (${failDetailList.size}): $failDetailList")
  658. // }
  659. val (feeMarked, feeItemIds) = purchaseOrderLineService.markDeletedLinesWithFeeItems()
  660. affectedItemIds.addAll(feeItemIds)
  661. if (feeMarked > 0) {
  662. logger.info("Marked $feeMarked PO line(s) as deleted (isFee items).")
  663. }
  664. if (affectedItemIds.isNotEmpty()) {
  665. itemsService.updateAverageUnitPriceForItems(affectedItemIds)
  666. logger.info("Average unit price updated for ${affectedItemIds.size} item(s) after PO sync.")
  667. }
  668. logger.info("--------------------------------------------End - Saving M18 Purchase Order--------------------------------------------")
  669. return SyncResult(
  670. totalProcessed = successList.size + failList.size,
  671. totalSuccess = successList.size,
  672. totalFail = failList.size,
  673. query = purchaseOrdersWithType?.query ?: ""
  674. )
  675. }
  676. }