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

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