|
|
|
@@ -20,7 +20,6 @@ import com.ffii.fpsms.modules.purchaseOrder.service.PurchaseOrderLineService |
|
|
|
import com.ffii.fpsms.modules.purchaseOrder.service.PurchaseOrderService |
|
|
|
import com.ffii.fpsms.modules.purchaseOrder.web.model.SavePurchaseOrderLineRequest |
|
|
|
import com.ffii.fpsms.modules.purchaseOrder.web.model.SavePurchaseOrderRequest |
|
|
|
import com.ffii.fpsms.modules.settings.entity.Settings |
|
|
|
import com.ffii.fpsms.modules.settings.service.SettingsService |
|
|
|
import org.slf4j.Logger |
|
|
|
import org.slf4j.LoggerFactory |
|
|
|
@@ -45,12 +44,18 @@ open class M18PurchaseOrderService( |
|
|
|
val m18MasterDataService: M18MasterDataService, |
|
|
|
val settingsService: SettingsService, |
|
|
|
) { |
|
|
|
companion object { |
|
|
|
fun isStockInLookupPoCode(code: String?): Boolean { |
|
|
|
val trimmed = code?.trim().orEmpty() |
|
|
|
return trimmed.length > 14 && (trimmed.startsWith("PP") || trimmed.startsWith("PF")) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
val commonUtils = CommonUtils() |
|
|
|
val logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java) |
|
|
|
|
|
|
|
val lastModifyDateStart = "2025-05-14 14:00:00" |
|
|
|
val lastModifyDateEnd = "2025-05-14 14:30:00" |
|
|
|
private val M18_LAST_MODIFY_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") |
|
|
|
// val lastModifyDateConds = |
|
|
|
// "lastModifyDate=largerOrEqual=${lastModifyDateStart}=and=lastModifyDate=lessOrEqual=${lastModifyDateEnd}" |
|
|
|
// val lastModifyDate = LocalDateTime.now().minusMinutes(30) |
|
|
|
@@ -237,67 +242,65 @@ open class M18PurchaseOrderService( |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Stock-in `/po` lookup: create if missing; for a local **pending** PO, pull from M18 only when |
|
|
|
* M18 list `lastModifyDate` is after [PurchaseOrder.modified] (last MTMS header write, usually last sync). |
|
|
|
* Receiving / completed POs are not refreshed. |
|
|
|
* Stock-in `/po` lookup. |
|
|
|
* - Missing local PO: always sync from M18 (same as [savePurchaseOrderByCode]). |
|
|
|
* - Local pending + setting [SettingNames.M18_PO_STOCK_IN_REFRESH_IF_NEWER_ENABLED] true: |
|
|
|
* sync from M18 (overwrite pending). Receiving / completed are not refreshed. |
|
|
|
* - Local PO + setting not true: do not call M18. |
|
|
|
*/ |
|
|
|
open fun savePurchaseOrderByCodeIfNewer(code: String): SyncResult { |
|
|
|
open fun savePurchaseOrderByCodeIfNewer(code: String): SyncResult = |
|
|
|
refreshForStockInLookup(code).syncResult |
|
|
|
|
|
|
|
open fun refreshForStockInLookup(code: String): StockInPoRefreshOutcome { |
|
|
|
val trimmed = code.trim() |
|
|
|
val localPo = purchaseOrderService.findByCode(trimmed) |
|
|
|
if (localPo != null && !isStockInRefreshIfNewerEnabled()) { |
|
|
|
logger.info( |
|
|
|
"Purchase Order: Skipping if-newer M18 sync — setting ${SettingNames.M18_PO_STOCK_IN_REFRESH_IF_NEWER_ENABLED} " + |
|
|
|
"is not true. code=$trimmed" |
|
|
|
) |
|
|
|
return SyncResult( |
|
|
|
totalProcessed = 0, |
|
|
|
totalSuccess = 0, |
|
|
|
totalFail = 0, |
|
|
|
query = "skipped (ifNewer): setting ${SettingNames.M18_PO_STOCK_IN_REFRESH_IF_NEWER_ENABLED} is not true" |
|
|
|
) |
|
|
|
} |
|
|
|
if (localPo != null && localPo.status != PurchaseOrderStatus.PENDING) { |
|
|
|
val enabled = isStockInRefreshIfNewerEnabled() |
|
|
|
val localStatus = purchaseOrderService.findActiveStatusByCode(trimmed) |
|
|
|
logger.info( |
|
|
|
"Purchase Order: stock-in ifNewer code={} settingEnabled={} localStatus={}", |
|
|
|
trimmed, |
|
|
|
enabled, |
|
|
|
localStatus ?: "<none>", |
|
|
|
) |
|
|
|
if (localStatus != null && !enabled) { |
|
|
|
logger.info( |
|
|
|
"Purchase Order: Skipping if-newer M18 sync — local PO id=${localPo.id} code=${localPo.code} " + |
|
|
|
"status=${localPo.status?.value} (only pending may be refreshed)." |
|
|
|
"Purchase Order: Skipping M18 sync — setting ${SettingNames.M18_PO_STOCK_IN_REFRESH_IF_NEWER_ENABLED} " + |
|
|
|
"is not true. code=$trimmed status=$localStatus" |
|
|
|
) |
|
|
|
return SyncResult( |
|
|
|
totalProcessed = 0, |
|
|
|
totalSuccess = 0, |
|
|
|
totalFail = 0, |
|
|
|
query = "skipped (ifNewer): local PO not pending: $trimmed status=${localPo.status?.value}" |
|
|
|
return StockInPoRefreshOutcome( |
|
|
|
action = StockInPoRefreshAction.SKIPPED_DISABLED, |
|
|
|
syncResult = SyncResult( |
|
|
|
totalProcessed = 0, |
|
|
|
totalSuccess = 0, |
|
|
|
totalFail = 0, |
|
|
|
query = "skipped (ifNewer): setting ${SettingNames.M18_PO_STOCK_IN_REFRESH_IF_NEWER_ENABLED} is not true" |
|
|
|
), |
|
|
|
) |
|
|
|
} |
|
|
|
if (localPo == null) { |
|
|
|
return savePurchaseOrderByCode(trimmed) |
|
|
|
} |
|
|
|
|
|
|
|
val conds = "(code=equal=$trimmed)" |
|
|
|
val poListResponse = searchPurchaseOrdersByConds(conds) |
|
|
|
val m18Row = poListResponse?.values?.firstOrNull() |
|
|
|
val localModified = localPo.modified ?: localPo.created |
|
|
|
val m18LastModify = parseM18LastModifyDate(m18Row?.lastModifyDate) |
|
|
|
if (m18Row == null || m18LastModify == null || localModified == null || !m18LastModify.isAfter(localModified)) { |
|
|
|
if (localStatus != null && |
|
|
|
!PurchaseOrderStatus.PENDING.value.equals(localStatus, ignoreCase = true) |
|
|
|
) { |
|
|
|
logger.info( |
|
|
|
"Purchase Order: Skipping if-newer M18 sync — M18 lastModifyDate not after " + |
|
|
|
"purchase_order.modified. code=$trimmed localModified=$localModified " + |
|
|
|
"m18LastModifyRaw=${m18Row?.lastModifyDate} m18LastModify=$m18LastModify" |
|
|
|
"Purchase Order: Skipping M18 sync — local PO code=$trimmed status=$localStatus (only pending may be refreshed)." |
|
|
|
) |
|
|
|
return SyncResult( |
|
|
|
totalProcessed = 0, |
|
|
|
totalSuccess = 0, |
|
|
|
totalFail = 0, |
|
|
|
query = "skipped (ifNewer): M18 lastModifyDate not after purchase_order.modified: " + |
|
|
|
"code=$trimmed localModified=$localModified m18LastModify=${m18Row?.lastModifyDate}" |
|
|
|
return StockInPoRefreshOutcome( |
|
|
|
action = StockInPoRefreshAction.SKIPPED_NOT_PENDING, |
|
|
|
syncResult = SyncResult( |
|
|
|
totalProcessed = 0, |
|
|
|
totalSuccess = 0, |
|
|
|
totalFail = 0, |
|
|
|
query = "skipped (ifNewer): local PO not pending: $trimmed status=$localStatus" |
|
|
|
), |
|
|
|
) |
|
|
|
} |
|
|
|
logger.info( |
|
|
|
"Purchase Order: M18 lastModifyDate after purchase_order.modified — syncing. " + |
|
|
|
"code=$trimmed localModified=$localModified m18LastModify=$m18LastModify" |
|
|
|
return StockInPoRefreshOutcome( |
|
|
|
action = StockInPoRefreshAction.SYNCED, |
|
|
|
syncResult = savePurchaseOrderByCode(trimmed), |
|
|
|
) |
|
|
|
return saveFromPoListSearch(conds, poListResponse) |
|
|
|
} |
|
|
|
|
|
|
|
private fun isStockInRefreshIfNewerEnabled(): Boolean = |
|
|
|
settingsService.isBooleanTrueFromDb(SettingNames.M18_PO_STOCK_IN_REFRESH_IF_NEWER_ENABLED) |
|
|
|
|
|
|
|
private fun saveFromPoListSearch( |
|
|
|
query: String, |
|
|
|
poListResponse: M18PurchaseOrderListResponse?, |
|
|
|
@@ -318,25 +321,6 @@ open class M18PurchaseOrderService( |
|
|
|
return savePurchaseOrdersWithPreparedList(purchaseOrdersWithType = request) |
|
|
|
} |
|
|
|
|
|
|
|
private fun isStockInRefreshIfNewerEnabled(): Boolean = |
|
|
|
settingsService.findByName(SettingNames.M18_PO_STOCK_IN_REFRESH_IF_NEWER_ENABLED) |
|
|
|
.map { Settings.VALUE_BOOLEAN_TRUE.equals(it.value?.trim(), ignoreCase = true) } |
|
|
|
.orElse(false) |
|
|
|
|
|
|
|
private fun parseM18LastModifyDate(raw: String?): LocalDateTime? { |
|
|
|
val value = raw?.trim().orEmpty() |
|
|
|
if (value.isEmpty()) return null |
|
|
|
return try { |
|
|
|
LocalDateTime.parse(value, M18_LAST_MODIFY_FORMATTER) |
|
|
|
} catch (_: Exception) { |
|
|
|
try { |
|
|
|
LocalDateTime.parse(value) |
|
|
|
} catch (_: Exception) { |
|
|
|
value.toLongOrNull()?.let { commonUtils.timestampToLocalDateTime(it) } |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private fun searchPurchaseOrdersByConds(conds: String): M18PurchaseOrderListResponse? { |
|
|
|
val searchRequest = M18PurchaseOrderListRequest( |
|
|
|
stSearch = "po", |
|
|
|
@@ -752,4 +736,15 @@ open class M18PurchaseOrderService( |
|
|
|
query = purchaseOrdersWithType?.query ?: "" |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
enum class StockInPoRefreshAction(val headerValue: String) { |
|
|
|
SKIPPED_DISABLED("skipped_disabled"), |
|
|
|
SKIPPED_NOT_PENDING("skipped_not_pending"), |
|
|
|
SYNCED("synced"), |
|
|
|
} |
|
|
|
|
|
|
|
data class StockInPoRefreshOutcome( |
|
|
|
val action: StockInPoRefreshAction, |
|
|
|
val syncResult: SyncResult, |
|
|
|
) |