| @@ -24,7 +24,6 @@ import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderType | |||||
| import org.slf4j.Logger | import org.slf4j.Logger | ||||
| import org.slf4j.LoggerFactory | import org.slf4j.LoggerFactory | ||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||
| import java.sql.SQLException | |||||
| import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
| import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||
| import kotlin.reflect.full.memberProperties | import kotlin.reflect.full.memberProperties | ||||
| @@ -225,7 +224,7 @@ open class M18DeliveryOrderService( | |||||
| val failDetailList = mutableListOf<Long>() | val failDetailList = mutableListOf<Long>() | ||||
| val failItemDetailList = mutableListOf<Long>() | val failItemDetailList = mutableListOf<Long>() | ||||
| val uomByM18IdCache = mutableMapOf<Long, ItemUom?>() | val uomByM18IdCache = mutableMapOf<Long, ItemUom?>() | ||||
| val itemIdCache = mutableMapOf<Long, Long?>() | |||||
| val itemIdCache = mutableMapOf<Long, Long>() | |||||
| val stockUomIdCache = mutableMapOf<Pair<Long, Long>, Long?>() | val stockUomIdCache = mutableMapOf<Pair<Long, Long>, Long?>() | ||||
| val doRefType = "Delivery Order" | val doRefType = "Delivery Order" | ||||
| @@ -374,14 +373,10 @@ open class M18DeliveryOrderService( | |||||
| // logger.info("${doLineRefType}: Saved M18 Data Log. ID: ${saveM18DeliveryOrderLineLog.id}") | // logger.info("${doLineRefType}: Saved M18 Data Log. ID: ${saveM18DeliveryOrderLineLog.id}") | ||||
| // logger.info("${doLineRefType}: Finding item...") | // logger.info("${doLineRefType}: Finding item...") | ||||
| val itemId: Long? = itemIdCache.getOrPut(line.proId) { | |||||
| val item = itemsService.findByM18Id(line.proId) | |||||
| if (item == null) { | |||||
| m18MasterDataService.saveProduct(line.proId)?.id | |||||
| } else { | |||||
| item.id | |||||
| val itemId: Long? = itemIdCache[line.proId] | |||||
| ?: m18MasterDataService.resolveLocalItemId(line.proId)?.also { | |||||
| itemIdCache[line.proId] = it | |||||
| } | } | ||||
| } | |||||
| val stockUomId: Long? = if (itemId != null) { | val stockUomId: Long? = if (itemId != null) { | ||||
| val key = line.proId to line.unitId // safe key | val key = line.proId to line.unitId // safe key | ||||
| @@ -393,6 +388,23 @@ open class M18DeliveryOrderService( | |||||
| // logger.info("${doLineRefType}: Item ID: ${itemId} | M18 Item ID: ${line.proId}") | // logger.info("${doLineRefType}: Item ID: ${itemId} | M18 Item ID: ${line.proId}") | ||||
| if (itemId == null) { | |||||
| failDetailList.add(line.id) | |||||
| failItemDetailList.add(line.proId) | |||||
| logger.error( | |||||
| "${doLineRefType}: Cannot resolve local item for M18 proId=${line.proId}, skipping line ${line.id}" | |||||
| ) | |||||
| val errorSaveM18DeliveryOrderLineLogRequest = SaveM18DataLogRequest( | |||||
| id = saveM18DeliveryOrderLineLog.id, | |||||
| dataLog = mutableMapOf( | |||||
| "Exception Message" to "Cannot resolve local item for M18 proId=${line.proId}" | |||||
| ), | |||||
| statusEnum = M18DataLogStatus.FAIL | |||||
| ) | |||||
| m18DataLogService.saveM18DataLog(errorSaveM18DeliveryOrderLineLogRequest) | |||||
| return@forEach | |||||
| } | |||||
| try { | try { | ||||
| // Find the delivery_order_line if exist | // Find the delivery_order_line if exist | ||||
| // logger.info("${doLineRefType}: Finding exising delivery order line...") | // logger.info("${doLineRefType}: Finding exising delivery order line...") | ||||
| @@ -454,7 +466,7 @@ open class M18DeliveryOrderService( | |||||
| successDetailList.add(line.id) | successDetailList.add(line.id) | ||||
| // logger.info("${doLineRefType}: Delivery order ID: ${deliveryOrderId} | M18 ID: ${deliveryOrder.id}") | // logger.info("${doLineRefType}: Delivery order ID: ${deliveryOrderId} | M18 ID: ${deliveryOrder.id}") | ||||
| //logger.info("${doLineRefType}: Saved delivery order line. ID: ${saveDeliveryOrderLineResponse.id} | M18 Line ID: ${line.id} | Delivery order ID: ${deliveryOrderId} | M18 ID: ${deliveryOrder.id}") | //logger.info("${doLineRefType}: Saved delivery order line. ID: ${saveDeliveryOrderLineResponse.id} | M18 Line ID: ${line.id} | Delivery order ID: ${deliveryOrderId} | M18 ID: ${deliveryOrder.id}") | ||||
| } catch (e: SQLException) { | |||||
| } catch (e: Exception) { | |||||
| failDetailList.add(line.id) | failDetailList.add(line.id) | ||||
| failItemDetailList.add(line.proId) | failItemDetailList.add(line.proId) | ||||
| // logger.error("${doLineRefType}: Saving Failure!") | // logger.error("${doLineRefType}: Saving Failure!") | ||||
| @@ -188,6 +188,13 @@ open class M18MasterDataService( | |||||
| ) | ) | ||||
| } | } | ||||
| /** 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 | |||||
| } | |||||
| open fun saveProduct(id: Long): MessageResponse? { | open fun saveProduct(id: Long): MessageResponse? { | ||||
| try { | try { | ||||
| ensureCunitSeededForAllIfEmpty() | ensureCunitSeededForAllIfEmpty() | ||||
| @@ -231,9 +238,18 @@ open class M18MasterDataService( | |||||
| ) | ) | ||||
| val savedItem = itemsService.saveItem(saveItemRequest) | 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 | |||||
| } | |||||
| logger.info("Processing item uom...") | logger.info("Processing item uom...") | ||||
| // Find the item uom that ready to delete (not in m18) | // Find the item uom that ready to delete (not in m18) | ||||
| val existingItemUoms = savedItem.id?.let { itemUomService.findAllByItemsId(it) } | |||||
| val existingItemUoms = itemUomService.findAllByItemsId(localItemId) | |||||
| val m18ItemUomIds = price?.map { it.id } ?: listOf() | val m18ItemUomIds = price?.map { it.id } ?: listOf() | ||||
| // Delete the item uom | // Delete the item uom | ||||
| @@ -267,7 +283,7 @@ open class M18MasterDataService( | |||||
| ) | ) | ||||
| val itemUomRequest = ItemUomRequest( | val itemUomRequest = ItemUomRequest( | ||||
| m18UomId = it.unitId, | m18UomId = it.unitId, | ||||
| itemId = savedItem.id, | |||||
| itemId = localItemId, | |||||
| baseUnit = it.basicUnit, | baseUnit = it.basicUnit, | ||||
| stockUnit = it.stkUnit, | stockUnit = it.stkUnit, | ||||
| pickingUnit = it.pickUnit, | pickingUnit = it.pickUnit, | ||||
| @@ -284,12 +300,11 @@ open class M18MasterDataService( | |||||
| deleted = it.expired || endInstant.isBefore(now) | deleted = it.expired || endInstant.isBefore(now) | ||||
| ) | ) | ||||
| // logger.info("saved item id: ${savedItem.id}") | |||||
| itemUomService.saveItemUom(itemUomRequest) | itemUomService.saveItemUom(itemUomRequest) | ||||
| } | } | ||||
| logger.info("Success (M18 Item): ${id} | ${pro.code} | ${pro.desc}") | logger.info("Success (M18 Item): ${id} | ${pro.code} | ${pro.desc}") | ||||
| return savedItem | |||||
| return savedItem.copy(id = localItemId) | |||||
| } else { | } else { | ||||
| logger.error("Fail Message: ${itemDetail?.messages?.get(0)?.msgDetail}") | logger.error("Fail Message: ${itemDetail?.messages?.get(0)?.msgDetail}") | ||||
| logger.error("Fail: Item ID - ${id} Not Found") | logger.error("Fail: Item ID - ${id} Not Found") | ||||
| @@ -404,11 +419,20 @@ open class M18MasterDataService( | |||||
| ) | ) | ||||
| val savedItem = itemsService.saveItem(saveItemRequest) | 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 | |||||
| } | |||||
| logger.info("Processing item uom...") | logger.info("Processing item uom...") | ||||
| // Optional: cache findAllByItemsId if you think it might be called multiple times | |||||
| // (usually not needed here because each savedItem.id is unique) | |||||
| val existingItemUoms = savedItem.id?.let { itemUomService.findAllByItemsId(it) } | |||||
| val existingItemUoms = itemUomService.findAllByItemsId(localItemId) | |||||
| val m18ItemUomIds = price?.map { it.id } ?: listOf() | val m18ItemUomIds = price?.map { it.id } ?: listOf() | ||||
| @@ -442,7 +466,7 @@ open class M18MasterDataService( | |||||
| val itemUomRequest = ItemUomRequest( | val itemUomRequest = ItemUomRequest( | ||||
| m18UomId = it.unitId, | m18UomId = it.unitId, | ||||
| itemId = savedItem.id, | |||||
| itemId = localItemId, | |||||
| baseUnit = it.basicUnit, | baseUnit = it.basicUnit, | ||||
| stockUnit = it.stkUnit, | stockUnit = it.stkUnit, | ||||
| pickingUnit = it.pickUnit, | pickingUnit = it.pickUnit, | ||||
| @@ -439,14 +439,25 @@ open class M18PurchaseOrderService( | |||||
| // logger.info("${poLineRefType}: Saved M18 Data Log. ID: ${saveM18PurchaseOrderLineLog.id}") | // logger.info("${poLineRefType}: Saved M18 Data Log. ID: ${saveM18PurchaseOrderLineLog.id}") | ||||
| // logger.info("${poLineRefType}: Finding item...") | // logger.info("${poLineRefType}: Finding item...") | ||||
| val item = itemsService.findByM18Id(line.proId) | |||||
| val itemId: Long? = if (item == null) { | |||||
| m18MasterDataService.saveProduct(line.proId)?.id | |||||
| } else { | |||||
| item.id | |||||
| } | |||||
| val itemId: Long? = m18MasterDataService.resolveLocalItemId(line.proId) | |||||
| logger.info("${poLineRefType}: Item ID: ${itemId} | M18 Item ID: ${line.proId}") | logger.info("${poLineRefType}: Item ID: ${itemId} | M18 Item ID: ${line.proId}") | ||||
| if (itemId == null) { | |||||
| failDetailList.add(line.id) | |||||
| logger.error( | |||||
| "${poLineRefType}: Cannot resolve local item for M18 proId=${line.proId}, skipping line ${line.id}" | |||||
| ) | |||||
| val errorSaveM18PurchaseOrderLineLogRequest = SaveM18DataLogRequest( | |||||
| id = saveM18PurchaseOrderLineLog.id, | |||||
| dataLog = mutableMapOf( | |||||
| "Exception Message" to "Cannot resolve local item for M18 proId=${line.proId}" | |||||
| ), | |||||
| statusEnum = M18DataLogStatus.FAIL | |||||
| ) | |||||
| m18DataLogService.saveM18DataLog(errorSaveM18PurchaseOrderLineLogRequest) | |||||
| return@forEach | |||||
| } | |||||
| try { | try { | ||||
| // Find the purchase_order_line if exist (stable key: PO + M18 line id) | // Find the purchase_order_line if exist (stable key: PO + M18 line id) | ||||
| // logger.info("${poLineRefType}: Finding exising purchase order line...") | // logger.info("${poLineRefType}: Finding exising purchase order line...") | ||||
| @@ -647,8 +647,21 @@ open class ItemsService( | |||||
| open fun saveItem(request: NewItemRequest): MessageResponse { | open fun saveItem(request: NewItemRequest): MessageResponse { | ||||
| val duplicatedItem = itemsRepository.findByCodeAndTypeAndDeletedFalse(request.code, request.type) | val duplicatedItem = itemsRepository.findByCodeAndTypeAndDeletedFalse(request.code, request.type) | ||||
| if (duplicatedItem != null && duplicatedItem.id != request.id) { | if (duplicatedItem != null && duplicatedItem.id != request.id) { | ||||
| if (request.m18Id != null && request.id == null && duplicatedItem.m18Id == null) { | |||||
| duplicatedItem.m18Id = request.m18Id | |||||
| duplicatedItem.m18LastModifyDate = request.m18LastModifyDate | |||||
| val linked = itemsRepository.saveAndFlush(duplicatedItem) | |||||
| return MessageResponse( | |||||
| id = linked.id, | |||||
| code = linked.code, | |||||
| name = linked.name, | |||||
| type = linked.type.toString(), | |||||
| message = "Linked m18Id to existing item with same code", | |||||
| errorPosition = null, | |||||
| ) | |||||
| } | |||||
| return MessageResponse( | return MessageResponse( | ||||
| id = request.id, | |||||
| id = request.id ?: duplicatedItem.id, | |||||
| code = request.code, | code = request.code, | ||||
| name = request.name, | name = request.name, | ||||
| type = request.type.toString(), | type = request.type.toString(), | ||||