25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

660 lines
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.model.SyncResult
  8. import com.ffii.fpsms.m18.utils.CommonUtils
  9. import com.ffii.fpsms.m18.web.models.M18CommonRequest
  10. import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderLineStatus
  11. import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus
  12. import com.ffii.fpsms.modules.deliveryOrder.service.DeliveryOrderLineService
  13. import com.ffii.fpsms.modules.deliveryOrder.service.DeliveryOrderService
  14. import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository
  15. import com.ffii.fpsms.modules.deliveryOrder.web.models.SaveDeliveryOrderLineRequest
  16. import com.ffii.fpsms.modules.deliveryOrder.web.models.SaveDeliveryOrderRequest
  17. import com.ffii.fpsms.modules.master.entity.ItemUom
  18. import com.ffii.fpsms.modules.master.service.ItemUomService
  19. import com.ffii.fpsms.modules.master.service.ItemsService
  20. import com.ffii.fpsms.modules.master.service.ShopService
  21. import com.ffii.fpsms.modules.master.web.models.ConvertUomByItemRequest
  22. import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderType
  23. import org.slf4j.Logger
  24. import org.slf4j.LoggerFactory
  25. import org.springframework.stereotype.Service
  26. import java.time.LocalDateTime
  27. import java.time.format.DateTimeFormatter
  28. import kotlin.reflect.full.memberProperties
  29. import kotlin.text.substring
  30. @Service
  31. open class M18DeliveryOrderService(
  32. val m18Config: M18Config,
  33. val apiCallerService: ApiCallerService,
  34. val m18DataLogService: M18DataLogService,
  35. val deliveryOrderService: DeliveryOrderService,
  36. val deliveryOrderRepository: DeliveryOrderRepository,
  37. val deliveryOrderLineService: DeliveryOrderLineService,
  38. val itemsService: ItemsService,
  39. val shopService: ShopService,
  40. val itemUomService: ItemUomService,
  41. val m18MasterDataService: M18MasterDataService,
  42. ) {
  43. val commonUtils = CommonUtils()
  44. val logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java)
  45. val lastModifyDateStart = "2025-05-14 14:00:00"
  46. val lastModifyDateEnd = "2025-05-14 14:30:00"
  47. // val lastModifyDateConds =
  48. // "lastModifyDate=largerOrEqual=${lastModifyDateStart}=and=lastModifyDate=lessOrEqual=${lastModifyDateEnd}"
  49. // val lastModifyDate = LocalDateTime.now().minusMinutes(30)
  50. // val commonConds =
  51. // "(beId=equal=${m18Config.BEID_PF}=or=beId=equal=${m18Config.BEID_PP}=or=beId=equal=${m18Config.BEID_TOA})=and=lastModifyDate=largerOrEqual=${lastModifyDate}"
  52. // M18 API
  53. // Sharing same API with po
  54. val M18_LOAD_PURCHASE_ORDER_API = "/root/api/read/po"
  55. val M18_FETCH_PURCHASE_ORDER_LIST_API = "/search/search"
  56. // Include shop po
  57. open fun getDeliveryOrdersWithType(request: M18CommonRequest): M18PurchaseOrderListResponseWithType? {
  58. val deliveryOrders = M18PurchaseOrderListResponseWithType(mutableListOf())
  59. val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
  60. val lastDateConds = if (request.modifiedDateFrom != null && request.modifiedDateTo != null) {
  61. val dateFrom = LocalDateTime.parse(request.modifiedDateFrom, formatter).toString()
  62. val dateTo = LocalDateTime.parse(request.modifiedDateTo, formatter).toString()
  63. "lastModifyDate=largerOrEqual=$dateFrom=and=lastModifyDate=lessOrEqual=$dateTo"
  64. } else {
  65. ""
  66. }
  67. val dDateConds = if (request.dDateFrom != null && request.dDateTo != null) {
  68. val dateFrom = LocalDateTime.parse(request.dDateFrom, formatter).toLocalDate().toString()
  69. val dateTo = LocalDateTime.parse(request.dDateTo, formatter).toLocalDate().toString()
  70. "dDate=largerOrEqual=$dateFrom=and=dDate=lessOrEqual=$dateTo"
  71. } else {
  72. ""
  73. }
  74. val dDateEqualConds = if (request.dDateEqual != null) {
  75. val dDateEqual = LocalDateTime.parse(request.dDateEqual, formatter).toLocalDate().toString()
  76. "dDate=equal=$dDateEqual"
  77. } else {
  78. ""
  79. }
  80. // Shop PO — same BE/supplier scope as [buildShopPoScopeConds]
  81. var shopPoConds = buildShopPoScopeConds()
  82. if (request.modifiedDateFrom != null && request.modifiedDateTo != null) {
  83. shopPoConds += "=and=(${lastDateConds})"
  84. }
  85. if (request.dDateFrom != null && request.dDateTo != null) {
  86. shopPoConds += "=and=(${dDateConds})"
  87. }
  88. if (request.dDateEqual != null) {
  89. shopPoConds += "=and=(${dDateEqualConds})"
  90. }
  91. logger.info("shopPoConds: ${shopPoConds}")
  92. val shopPoParams = M18PurchaseOrderListRequest(
  93. params = null,
  94. conds = shopPoConds
  95. )
  96. try {
  97. deliveryOrders.valuesWithType += Pair(
  98. PurchaseOrderType.SHOP, apiCallerService.get<M18PurchaseOrderListResponse, M18PurchaseOrderListRequest>(
  99. M18_FETCH_PURCHASE_ORDER_LIST_API,
  100. shopPoParams
  101. ).block()
  102. )
  103. } catch (e: Exception) {
  104. logger.error("(Getting Shop Po list) Error on Function - ${e.stackTrace}")
  105. logger.error(e.message)
  106. }
  107. deliveryOrders.query = shopPoConds
  108. return deliveryOrders
  109. }
  110. open fun getDeliveryOrder(id: Long): M18PurchaseOrderResponse? {
  111. val deliveryOrderParams = M18PurchaseOrderRequest(
  112. id = id
  113. )
  114. var deliveryOrder: M18PurchaseOrderResponse? = null
  115. try {
  116. deliveryOrder = apiCallerService.get<M18PurchaseOrderResponse, M18PurchaseOrderRequest>(
  117. M18_LOAD_PURCHASE_ORDER_API,
  118. deliveryOrderParams
  119. ).block()
  120. } catch (e: Exception) {
  121. logger.error("(Getting Po Detail) Error on Function - ${e.stackTrace}")
  122. logger.error(e.message)
  123. }
  124. return deliveryOrder
  125. }
  126. open fun saveDeliveryOrders(request: M18CommonRequest, skipExistingDo: Boolean = false): SyncResult {
  127. val deliveryOrdersWithType = getDeliveryOrdersWithType(request)
  128. return saveDeliveryOrdersWithPreparedList(
  129. deliveryOrdersWithType,
  130. syncisExtra = false,
  131. skipExistingDo = skipExistingDo,
  132. )
  133. }
  134. /**
  135. * Shop PO (MTMS DO) BE/supplier scope used by scheduler and by-code sync:
  136. * beId = TOA and venId in [M18Config.SHOP_PO_SUPPLIER] (e.g. P06/P07).
  137. */
  138. private fun buildShopPoScopeConds(): String {
  139. val shopPoBuyers = commonUtils.listToString(listOf(m18Config.BEID_TOA), "beId=equal=", "=or=")
  140. val shopPoSupplier = commonUtils.listToString(
  141. shopService.findM18VendorIdsByCodeRegexp(m18Config.SHOP_PO_SUPPLIER),
  142. "venId=equal=",
  143. "=or="
  144. )
  145. return "(${shopPoBuyers})=and=(${shopPoSupplier})"
  146. }
  147. /**
  148. * Sync a single M18 shop PO / delivery order by document [code], same search pattern as
  149. * [com.ffii.fpsms.m18.service.M18PurchaseOrderService.savePurchaseOrderByCode], including
  150. * the scheduler shop-PO BE/supplier filters (rejects material POs).
  151. *
  152. * @param isExtraSync when true, persist local `delivery_order.isExtra=true` (manual DO(加單) sync).
  153. * No M18-side "加單" filtering is used.
  154. * @param newOnly when true, skip if a non-deleted local DO already exists with the same `code`.
  155. */
  156. open fun saveDeliveryOrderByCode(
  157. code: String,
  158. isExtraSync: Boolean = false,
  159. newOnly: Boolean = false,
  160. ): SyncResult {
  161. val trimmed = code.trim()
  162. if (newOnly && deliveryOrderRepository.existsByCodeAndDeletedIsFalse(trimmed)) {
  163. return SyncResult(
  164. totalProcessed = 1,
  165. totalSuccess = 0,
  166. totalFail = 0,
  167. query = "skipped (newOnly=true): delivery_order.code already exists: $trimmed",
  168. )
  169. }
  170. val conds = "(code=equal=$trimmed)=and=${buildShopPoScopeConds()}"
  171. val searchRequest = M18PurchaseOrderListRequest(
  172. stSearch = "po",
  173. params = null,
  174. conds = conds
  175. )
  176. val doListResponse = try {
  177. apiCallerService.get<M18PurchaseOrderListResponse, M18PurchaseOrderListRequest>(
  178. M18_FETCH_PURCHASE_ORDER_LIST_API,
  179. searchRequest
  180. ).block()
  181. } catch (e: Exception) {
  182. logger.error("(Getting DO list By Code) Error on Function - ${e.stackTrace}")
  183. logger.error(e.message)
  184. null
  185. }
  186. val doValues = doListResponse?.values
  187. if (doValues.isNullOrEmpty()) {
  188. return SyncResult(
  189. totalProcessed = 1,
  190. totalSuccess = 0,
  191. totalFail = 1,
  192. query = conds
  193. )
  194. }
  195. val prepared = M18PurchaseOrderListResponseWithType(
  196. valuesWithType = mutableListOf(Pair(PurchaseOrderType.SHOP, doListResponse)),
  197. query = conds
  198. )
  199. return saveDeliveryOrdersWithPreparedList(prepared, syncisExtra = isExtraSync, skipExistingDo = newOnly)
  200. }
  201. private fun saveDeliveryOrdersWithPreparedList(
  202. deliveryOrdersWithType: M18PurchaseOrderListResponseWithType?,
  203. syncisExtra: Boolean = false,
  204. skipExistingDo: Boolean = false,
  205. ): SyncResult {
  206. logger.info("--------------------------------------------Start - Saving M18 Delivery Order--------------------------------------------")
  207. if (skipExistingDo) {
  208. logger.info("skipExistingDo=true — local delivery orders will not be updated")
  209. }
  210. val successList = mutableListOf<Long>()
  211. val skippedList = mutableListOf<Long>()
  212. val skippedNotPendingList = mutableListOf<String>()
  213. val successDetailList = mutableListOf<Long>()
  214. val failList = mutableListOf<Long>()
  215. val failDetailList = mutableListOf<Long>()
  216. val failItemDetailList = mutableListOf<Long>()
  217. val uomByM18IdCache = mutableMapOf<Long, ItemUom?>()
  218. val itemIdCache = mutableMapOf<Long, Long>()
  219. val stockUomIdCache = mutableMapOf<Pair<Long, Long>, Long?>()
  220. val doRefType = "Delivery Order"
  221. val doLineRefType = "Delivery Order Line"
  222. if (deliveryOrdersWithType != null) {
  223. // Loop for Delivery Orders (values)
  224. deliveryOrdersWithType.valuesWithType.forEach { deliveryOrderWithType ->
  225. val type = deliveryOrderWithType.first
  226. // if success
  227. val deliveryOrdersValues = deliveryOrderWithType.second?.values
  228. // if fail
  229. val deliveryOrdersMessages = deliveryOrderWithType.second?.messages
  230. if (deliveryOrdersValues != null) {
  231. deliveryOrdersValues.forEach { deliveryOrder ->
  232. if (skipExistingDo) {
  233. val latestDeliveryOrderLog =
  234. m18DataLogService.findLatestM18DataLogWithSuccess(deliveryOrder.id, doRefType)
  235. val existingByM18 = latestDeliveryOrderLog?.id?.let {
  236. deliveryOrderService.findByM18DataLogId(it)
  237. }
  238. if (existingByM18 != null && existingByM18.deleted != true) {
  239. logger.info(
  240. "${doRefType}: skipExistingDo — skipping M18 id=${deliveryOrder.id} " +
  241. "code=${existingByM18.code} localId=${existingByM18.id} status=${existingByM18.status}"
  242. )
  243. skippedList.add(deliveryOrder.id)
  244. return@forEach
  245. }
  246. }
  247. val deliveryOrderDetail = getDeliveryOrder(deliveryOrder.id)
  248. var deliveryOrderId: Long? = null //FP-MTMS
  249. // Process for Delivery Order (mainpo)
  250. // Assume only one DO in the DO (search by DO ID)
  251. val mainpo = deliveryOrderDetail?.data?.mainpo?.get(0)
  252. // logger.info("deliveryOrderDetail: data is null? ${deliveryOrderDetail?.data == null} | mainpo is null? ${deliveryOrderDetail?.data?.mainpo == null} | get(0) is null? ${deliveryOrderDetail?.data?.mainpo?.get(0) == null}")
  253. val pot = deliveryOrderDetail?.data?.pot
  254. val deliveryOrderLineMessage = deliveryOrderDetail?.messages
  255. // delivery_order + m18_data_log table
  256. if (mainpo != null) {
  257. if (skipExistingDo && deliveryOrderRepository.existsByCodeAndDeletedIsFalse(mainpo.code)) {
  258. logger.info(
  259. "${doRefType}: skipExistingDo — skipping M18 id=${deliveryOrder.id} code=${mainpo.code} (local DO exists by code)"
  260. )
  261. skippedList.add(deliveryOrder.id)
  262. return@forEach
  263. }
  264. // Find the latest m18 data log by m18 id & type
  265. // logger.info("${doRefType}: Finding For Latest M18 Data Log...")
  266. val latestDeliveryOrderLog =
  267. m18DataLogService.findLatestM18DataLogWithSuccess(deliveryOrder.id, doRefType)
  268. val existingDeliveryOrderForSync =
  269. latestDeliveryOrderLog?.id?.let { deliveryOrderService.findByM18DataLogId(it) }
  270. if (existingDeliveryOrderForSync != null &&
  271. existingDeliveryOrderForSync.deleted != true &&
  272. existingDeliveryOrderForSync.status != DeliveryOrderStatus.PENDING
  273. ) {
  274. val statusValue = existingDeliveryOrderForSync.status?.value ?: "unknown"
  275. logger.info(
  276. "${doRefType}: Skipping M18 sync — local DO id=${existingDeliveryOrderForSync.id} " +
  277. "code=${existingDeliveryOrderForSync.code} status=$statusValue " +
  278. "(only pending may be overwritten; receiving=已放單). M18 ID: ${deliveryOrder.id}"
  279. )
  280. skippedNotPendingList.add(
  281. "code=${existingDeliveryOrderForSync.code} status=$statusValue"
  282. )
  283. return@forEach
  284. }
  285. //logger.info(latestDeliveryOrderLog.toString())
  286. // Save to m18_data_log table
  287. // logger.info("${doRefType}: Saving for M18 Data Log...")
  288. val mainpoJson =
  289. mainpo::class.memberProperties.associate { prop -> prop.name to prop.getter.call(mainpo) }
  290. .toMutableMap()
  291. val saveM18DeliveryOrderLogRequest = SaveM18DataLogRequest(
  292. id = null,
  293. refType = doRefType,
  294. m18Id = deliveryOrder.id,
  295. m18LastModifyDate = commonUtils.timestampToLocalDateTime(mainpo.lastModifyDate),
  296. // dataLog = mainpoJson,
  297. statusEnum = M18DataLogStatus.NOT_PROCESS
  298. )
  299. val saveM18DeliveryOrderLog =
  300. m18DataLogService.saveM18DataLog(saveM18DeliveryOrderLogRequest)
  301. // logger.info("${doRefType}: Saved M18 Data Log. ID: ${saveM18DeliveryOrderLog.id}")
  302. try {
  303. // Find the delivery_order if exist (re-use lookup from pending guard above)
  304. // logger.info("${doRefType}: Finding exising delivery order...")
  305. val existingDeliveryOrder = existingDeliveryOrderForSync
  306. // logger.info("${doRefType}: Exising delivery order ID: ${existingDeliveryOrder?.id}")
  307. // Save to delivery_order table
  308. // logger.info("${doRefType}: Saving delivery order...")
  309. val saveDeliveryOrderRequest = SaveDeliveryOrderRequest(
  310. id = existingDeliveryOrder?.id,
  311. code = mainpo.code,
  312. m18SupplierId = mainpo.venId,
  313. m18ShopId = mainpo.virDeptId,
  314. m18CurrencyId = mainpo.curId,
  315. orderDate = commonUtils.timestampToLocalDateTime(mainpo.tDate),
  316. estimatedArrivalDate = commonUtils.timestampToLocalDateTime(mainpo.dDate),
  317. completeDate = null,
  318. status = DeliveryOrderStatus.PENDING.value,
  319. type = type.value,
  320. m18DataLogId = saveM18DeliveryOrderLog.id,
  321. handlerId = null,
  322. m18BeId = mainpo.beId,
  323. deleted = mainpo.udfIsVoid == true,
  324. isExtra = syncisExtra,
  325. )
  326. val saveDeliveryOrderResponse =
  327. deliveryOrderService.saveDeliveryOrder(saveDeliveryOrderRequest)
  328. deliveryOrderId = saveDeliveryOrderResponse.id
  329. // Update m18_data_log with success
  330. val successSaveM18DeliveryOrderLogRequest = SaveM18DataLogRequest(
  331. id = saveM18DeliveryOrderLog.id,
  332. dataLog = mainpoJson,
  333. statusEnum = M18DataLogStatus.SUCCESS
  334. )
  335. m18DataLogService.saveM18DataLog(successSaveM18DeliveryOrderLogRequest)
  336. // log success info
  337. successList.add(deliveryOrder.id)
  338. logger.info("${doRefType}: Saved delivery order. ID: ${saveDeliveryOrderResponse.id} | M18 ${doRefType} ID: ${deliveryOrder.id}")
  339. } catch (e: Exception) {
  340. failList.add(deliveryOrder.id)
  341. // logger.error("${doRefType}: Saving Failure!")
  342. logger.error("Error on Function - ${e.stackTrace} | Type: ${doRefType} | M18 ID: ${deliveryOrder.id} | Different? ${mainpo.id}")
  343. logger.error(e.message)
  344. val errorSaveM18DeliveryOrderLogRequest = SaveM18DataLogRequest(
  345. id = saveM18DeliveryOrderLog.id,
  346. dataLog = mutableMapOf(Pair("Exception Message", e.message)),
  347. statusEnum = M18DataLogStatus.FAIL
  348. )
  349. m18DataLogService.saveM18DataLog(errorSaveM18DeliveryOrderLogRequest)
  350. // logger.error("${doRefType}: M18 Data Log Updated! Please see the error. ID: ${saveM18DeliveryOrderLogRequest.id}")
  351. }
  352. // delivery_order_line + m18_data_log
  353. if (pot != null) {
  354. val m18LineIds = pot.map { it.id }.toSet()
  355. // Loop for Delivery Order Lines (pot)
  356. pot.forEach { line ->
  357. // Find the latest m18 data log by m18 id & type
  358. // logger.info("${doLineRefType}: Finding For Latest M18 Data Log...")
  359. val latestDeliveryOrderLineLog =
  360. m18DataLogService.findLatestM18DataLogWithSuccess(line.id, doLineRefType)
  361. // logger.info("${doLineRefType}: Latest M18 Data Log ID: ${latestDeliveryOrderLineLog?.id}")
  362. // Save to m18_data_log table
  363. // logger.info("${doLineRefType}: 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 saveM18DeliveryOrderLineLogRequest = SaveM18DataLogRequest(
  372. id = null,
  373. refType = doLineRefType,
  374. m18Id = line.id,
  375. m18LastModifyDate = commonUtils.timestampToLocalDateTime(mainpo.lastModifyDate),
  376. // dataLog = lineJson,
  377. statusEnum = M18DataLogStatus.NOT_PROCESS
  378. )
  379. val saveM18DeliveryOrderLineLog =
  380. m18DataLogService.saveM18DataLog(saveM18DeliveryOrderLineLogRequest)
  381. // logger.info("${doLineRefType}: Saved M18 Data Log. ID: ${saveM18DeliveryOrderLineLog.id}")
  382. // logger.info("${doLineRefType}: Finding item...")
  383. val itemId: Long? = itemIdCache[line.proId]
  384. ?: m18MasterDataService.resolveLocalItemId(line.proId)?.also {
  385. itemIdCache[line.proId] = it
  386. }
  387. val stockUomId: Long? = if (itemId != null) {
  388. val key = line.proId to line.unitId // safe key
  389. stockUomIdCache.getOrPut(key) {
  390. val uom = itemUomService.findByM18Id(line.unitId)
  391. itemUomService.findStockUnitByItemId(itemId)?.uom?.id
  392. }
  393. } else null
  394. // logger.info("${doLineRefType}: Item ID: ${itemId} | M18 Item ID: ${line.proId}")
  395. if (itemId == null) {
  396. failDetailList.add(line.id)
  397. failItemDetailList.add(line.proId)
  398. logger.error(
  399. "${doLineRefType}: Cannot resolve local item for M18 proId=${line.proId}, skipping line ${line.id}"
  400. )
  401. val errorSaveM18DeliveryOrderLineLogRequest = SaveM18DataLogRequest(
  402. id = saveM18DeliveryOrderLineLog.id,
  403. dataLog = mutableMapOf(
  404. "Exception Message" to "Cannot resolve local item for M18 proId=${line.proId}"
  405. ),
  406. statusEnum = M18DataLogStatus.FAIL
  407. )
  408. m18DataLogService.saveM18DataLog(errorSaveM18DeliveryOrderLineLogRequest)
  409. return@forEach
  410. }
  411. try {
  412. // Find the delivery_order_line if exist
  413. // logger.info("${doLineRefType}: Finding exising delivery order line...")
  414. val existingDeliveryOrderLine = latestDeliveryOrderLineLog?.id?.let {
  415. deliveryOrderLineService.findDeliveryOrderLineByM18Id(it)
  416. }
  417. // logger.info("${doLineRefType}: Exising delivery order line ID: ${existingDeliveryOrderLine?.id}")
  418. // Save to delivery_order_line table
  419. // logger.info("${doLineRefType}: Saving delivery order line...")
  420. val itemUom = uomByM18IdCache.getOrPut(line.unitId) {
  421. itemUomService.findByM18Id(line.unitId)
  422. }
  423. val m18UomId = itemUom?.uom?.id
  424. val sourceQty = line.qty
  425. val stockQty =
  426. if (itemId != null && m18UomId != null && m18UomId == stockUomId) {
  427. // M18 line unit is already the stock unit — skip ratio conversion
  428. // (avoids bad qty when item_uom ratioN/ratioD hold spec numbers like 350g).
  429. sourceQty
  430. } else if (itemId != null && m18UomId != null) {
  431. itemUomService.convertQtyToStockQty(itemId, m18UomId, sourceQty)
  432. } else {
  433. sourceQty
  434. }
  435. val saveDeliveryOrderLineRequest = SaveDeliveryOrderLineRequest(
  436. id = existingDeliveryOrderLine?.id,
  437. itemId = itemId,
  438. uomIdM18 = m18UomId,
  439. uomId= stockUomId,
  440. deliveryOrderId = deliveryOrderId,
  441. qtyM18 = sourceQty,
  442. qty = stockQty,
  443. up = line.up,
  444. price = line.amt,
  445. // m18CurrencyId = mainpo.curId,
  446. status = existingDeliveryOrderLine?.status?.value
  447. ?: DeliveryOrderLineStatus.PENDING.value,
  448. m18DataLogId = saveM18DeliveryOrderLineLog.id,
  449. m18Discount = line.disc,
  450. m18Lot = line.lot
  451. )
  452. val saveDeliveryOrderLineResponse =
  453. deliveryOrderLineService.saveDeliveryOrderLine(saveDeliveryOrderLineRequest)
  454. // Update m18_data_log with success
  455. val successSaveM18DeliveryOrderLineLogRequest = SaveM18DataLogRequest(
  456. id = saveM18DeliveryOrderLineLog.id,
  457. dataLog = lineJson,
  458. statusEnum = M18DataLogStatus.SUCCESS
  459. )
  460. m18DataLogService.saveM18DataLog(successSaveM18DeliveryOrderLineLogRequest)
  461. // log success info
  462. successDetailList.add(line.id)
  463. // logger.info("${doLineRefType}: Delivery order ID: ${deliveryOrderId} | M18 ID: ${deliveryOrder.id}")
  464. //logger.info("${doLineRefType}: Saved delivery order line. ID: ${saveDeliveryOrderLineResponse.id} | M18 Line ID: ${line.id} | Delivery order ID: ${deliveryOrderId} | M18 ID: ${deliveryOrder.id}")
  465. } catch (e: Exception) {
  466. failDetailList.add(line.id)
  467. failItemDetailList.add(line.proId)
  468. // logger.error("${doLineRefType}: Saving Failure!")
  469. logger.error("Error on Function - ${e.stackTrace} | Type: ${doLineRefType} | M18 ID: ${line.id}")
  470. logger.error(e.message)
  471. val errorSaveM18DeliveryOrderLineLogRequest = SaveM18DataLogRequest(
  472. id = saveM18DeliveryOrderLineLog.id,
  473. dataLog = mutableMapOf(Pair("Exception Message", e.message)),
  474. statusEnum = M18DataLogStatus.FAIL
  475. )
  476. m18DataLogService.saveM18DataLog(errorSaveM18DeliveryOrderLineLogRequest)
  477. // logger.error("${doLineRefType}: M18 Data Log Updated! Please see the error. ID: ${saveM18DeliveryOrderLineLog.id}")
  478. }
  479. }
  480. if (deliveryOrderId != null) {
  481. val markedDeleted = deliveryOrderLineService.markDeletedLinesMissingFromM18(
  482. deliveryOrderId = deliveryOrderId,
  483. existingM18LineIds = m18LineIds,
  484. m18RefType = doLineRefType
  485. )
  486. if (markedDeleted > 0) {
  487. logger.info("${doLineRefType}: Marked ${markedDeleted} stale line(s) as deleted for deliveryOrderId=${deliveryOrderId}")
  488. }
  489. val dupesRemoved = deliveryOrderLineService.markDuplicateDeliveryOrderLinesForSameM18LineId(
  490. deliveryOrderId = deliveryOrderId,
  491. existingM18LineIds = m18LineIds,
  492. m18RefType = doLineRefType
  493. )
  494. if (dupesRemoved > 0) {
  495. logger.info("${doLineRefType}: Marked ${dupesRemoved} duplicate line(s) as deleted (same M18 line id) for deliveryOrderId=${deliveryOrderId}")
  496. }
  497. }
  498. } else {
  499. // pot
  500. // logger.error("${doLineRefType}: Saving Failure!")
  501. val saveM18DeliveryOrderLineLogRequest = SaveM18DataLogRequest(
  502. id = null,
  503. refType = "${doLineRefType}",
  504. m18Id = deliveryOrder.id,
  505. m18LastModifyDate = commonUtils.timestampToLocalDateTime(mainpo.lastModifyDate),
  506. // dataLog = mutableMapOf(Pair("Error Message", "${doLineRefType} is null")),
  507. dataLog = mutableMapOf(
  508. Pair(
  509. "${doLineRefType} Error Message",
  510. "pot is null"
  511. ),
  512. Pair(
  513. "${doLineRefType} Error Code",
  514. deliveryOrderLineMessage?.get(0)?.msgCode ?: "No Msg Code from M18"
  515. ),
  516. Pair(
  517. "${doLineRefType} Error Detail",
  518. deliveryOrderLineMessage?.get(0)?.msgDetail ?: "No Msg Detail from M18"
  519. ),
  520. ),
  521. statusEnum = M18DataLogStatus.FAIL
  522. )
  523. val errorLog = m18DataLogService.saveM18DataLog(saveM18DeliveryOrderLineLogRequest)
  524. logger.error("${doLineRefType}: M18 Data Log Updated! Please see the error. ID: ${errorLog.id}")
  525. }
  526. } else {
  527. // mainpo
  528. failList.add(deliveryOrder.id)
  529. // logger.error("${doRefType}: Saving Failure!")
  530. val saveM18DataLogRequest = SaveM18DataLogRequest(
  531. id = null,
  532. refType = "${doRefType}",
  533. m18Id = deliveryOrder.id,
  534. // m18LastModifyDate = if(mainpo?.lastModifyDate != null) commonUtils.instantToLocalDateTime(mainpo.lastModifyDate) else LocalDateTime.now(),
  535. m18LastModifyDate = LocalDateTime.now(),
  536. dataLog = mutableMapOf(
  537. Pair(
  538. "${doRefType} Error",
  539. "mainpo is null"
  540. ),
  541. Pair(
  542. "${doRefType} Error Code",
  543. deliveryOrdersMessages?.get(0)?.msgCode ?: "No Msg Code from M18"
  544. ),
  545. Pair(
  546. "${doRefType} Error Detail",
  547. deliveryOrdersMessages?.get(0)?.msgDetail ?: "No Msg Detail from M18"
  548. ),
  549. ),
  550. statusEnum = M18DataLogStatus.FAIL
  551. )
  552. val errorLog = m18DataLogService.saveM18DataLog(saveM18DataLogRequest)
  553. logger.error("${doLineRefType}: M18 Data Log Updated! Please see the error. ID: ${errorLog.id}")
  554. }
  555. }
  556. } else {
  557. logger.error("${doRefType} List is null. May occur errors.")
  558. }
  559. }
  560. } else {
  561. logger.error("${doRefType} List is null. May occur errors.")
  562. }
  563. // End of save. Check result
  564. logger.info("Total Success (${doRefType}) (${successList.size})")
  565. logger.error("Total Fail (${doRefType}) (${failList.size}): $failList")
  566. if (skippedList.isNotEmpty()) {
  567. logger.info("Total Skipped (${doRefType}) (${skippedList.size}): $skippedList")
  568. }
  569. logger.info("Total Success (${doLineRefType}) (${successDetailList.size})")
  570. logger.error("Total Fail (${doLineRefType}) (${failDetailList.size}): $failDetailList")
  571. // Fee cleanup for DO lines (currently disabled; keep for future use)
  572. // val feeMarked = deliveryOrderLineService.markDeletedLinesWithFeeItems()
  573. // if (feeMarked > 0) {
  574. // logger.info("Marked $feeMarked DO line(s) as deleted (isFee items).")
  575. // }
  576. logger.info("--------------------------------------------End - Saving M18 Delivery Order--------------------------------------------")
  577. val skippedExistingSuffix = if (skippedList.isNotEmpty()) " | skipped=${skippedList.size}" else ""
  578. val skippedNotPendingSuffix = skippedNotPendingQuerySuffix(skippedNotPendingList)
  579. return SyncResult(
  580. totalProcessed = successList.size + failList.size + skippedList.size + skippedNotPendingList.size,
  581. totalSuccess = successList.size,
  582. totalFail = failList.size,
  583. query = (deliveryOrdersWithType?.query ?: "") + skippedExistingSuffix + skippedNotPendingSuffix,
  584. )
  585. }
  586. }