|
|
@@ -0,0 +1,165 @@ |
|
|
|
|
|
package com.ffii.fpsms.modules.report.service |
|
|
|
|
|
|
|
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper |
|
|
|
|
|
import com.ffii.fpsms.m18.entity.M18BomShopSyncLog |
|
|
|
|
|
import com.ffii.fpsms.m18.entity.M18BomShopSyncLogRepository |
|
|
|
|
|
import com.ffii.fpsms.m18.model.GoodsReceiptNoteResponse |
|
|
|
|
|
import com.ffii.fpsms.m18.model.M18BomForShopSaveRequest |
|
|
|
|
|
import com.ffii.fpsms.m18.model.M18UdfProductSaveValue |
|
|
|
|
|
import com.ffii.fpsms.modules.master.entity.BomRepository |
|
|
|
|
|
import com.ffii.fpsms.modules.master.entity.ItemsRepository |
|
|
|
|
|
import org.springframework.stereotype.Service |
|
|
|
|
|
import java.time.LocalDate |
|
|
|
|
|
import java.time.LocalDateTime |
|
|
|
|
|
import java.time.format.DateTimeFormatter |
|
|
|
|
|
|
|
|
|
|
|
@Service |
|
|
|
|
|
open class M18BomShopSyncReportService( |
|
|
|
|
|
private val m18BomShopSyncLogRepository: M18BomShopSyncLogRepository, |
|
|
|
|
|
private val bomRepository: BomRepository, |
|
|
|
|
|
private val itemsRepository: ItemsRepository, |
|
|
|
|
|
private val objectMapper: ObjectMapper, |
|
|
|
|
|
) { |
|
|
|
|
|
private val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") |
|
|
|
|
|
private val versionFromHeaderCode = Regex("V(\\d+)$", RegexOption.IGNORE_CASE) |
|
|
|
|
|
|
|
|
|
|
|
open fun searchBomShopSyncHistoryReport( |
|
|
|
|
|
syncDateStart: String?, |
|
|
|
|
|
syncDateEnd: String?, |
|
|
|
|
|
finishedItemCode: String?, |
|
|
|
|
|
syncStatus: String?, |
|
|
|
|
|
): Map<String, Any> { |
|
|
|
|
|
val start = parseDateStart(syncDateStart) |
|
|
|
|
|
val end = parseDateEnd(syncDateEnd) |
|
|
|
|
|
val itemFilter = finishedItemCode?.trim()?.takeIf { it.isNotEmpty() } |
|
|
|
|
|
val statusFilter = syncStatus?.trim()?.lowercase()?.takeIf { it.isNotEmpty() && it != "all" } |
|
|
|
|
|
|
|
|
|
|
|
val logs = m18BomShopSyncLogRepository.searchForReport( |
|
|
|
|
|
syncDateStart = start, |
|
|
|
|
|
syncDateEnd = end, |
|
|
|
|
|
finishedItemCode = itemFilter, |
|
|
|
|
|
syncStatus = statusFilter, |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
val syncRows = mutableListOf<Map<String, Any?>>() |
|
|
|
|
|
val materialRows = mutableListOf<Map<String, Any?>>() |
|
|
|
|
|
var successCount = 0 |
|
|
|
|
|
var failedCount = 0 |
|
|
|
|
|
var skippedUnchangedCount = 0 |
|
|
|
|
|
|
|
|
|
|
|
for (log in logs) { |
|
|
|
|
|
val bomId = log.bomId ?: continue |
|
|
|
|
|
val bom = bomRepository.findByIdAndDeletedIsFalse(bomId) |
|
|
|
|
|
val finishedCode = log.finishedItemCode?.trim().orEmpty() |
|
|
|
|
|
val itemName = |
|
|
|
|
|
finishedCode.takeIf { it.isNotEmpty() } |
|
|
|
|
|
?.let { itemsRepository.findByCodeAndDeletedFalse(it)?.name?.trim() } |
|
|
|
|
|
?: bom?.name?.trim()?.takeIf { it.isNotEmpty() } |
|
|
|
|
|
val headerCode = log.m18HeaderCode?.trim().orEmpty() |
|
|
|
|
|
val version = extractVersion(headerCode) |
|
|
|
|
|
val status = resolveSyncStatus(log) |
|
|
|
|
|
when (status) { |
|
|
|
|
|
"SUCCESS" -> successCount++ |
|
|
|
|
|
"SKIPPED_UNCHANGED" -> skippedUnchangedCount++ |
|
|
|
|
|
else -> failedCount++ |
|
|
|
|
|
} |
|
|
|
|
|
val syncAt = log.created?.format(dateTimeFormatter) |
|
|
|
|
|
val failureReason = resolveFailureReason(log).takeIf { status == "FAILED" } |
|
|
|
|
|
|
|
|
|
|
|
syncRows.add( |
|
|
|
|
|
linkedMapOf( |
|
|
|
|
|
"syncLogId" to log.id, |
|
|
|
|
|
"syncDateTime" to syncAt, |
|
|
|
|
|
"bomId" to bomId, |
|
|
|
|
|
"bomRoutingCode" to bom?.code, |
|
|
|
|
|
"finishedItemCode" to finishedCode.ifEmpty { null }, |
|
|
|
|
|
"finishedItemName" to itemName, |
|
|
|
|
|
"m18HeaderCode" to headerCode.ifEmpty { null }, |
|
|
|
|
|
"version" to version, |
|
|
|
|
|
"m18RecordId" to log.m18RecordId?.takeIf { it > 0L }, |
|
|
|
|
|
"syncStatus" to status, |
|
|
|
|
|
"synced" to log.synced, |
|
|
|
|
|
"m18ApiStatus" to log.m18ApiStatus, |
|
|
|
|
|
"failureReason" to failureReason, |
|
|
|
|
|
"message" to log.message, |
|
|
|
|
|
), |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
val materials = parseMaterialLines(log) |
|
|
|
|
|
for (line in materials) { |
|
|
|
|
|
materialRows.add( |
|
|
|
|
|
linkedMapOf( |
|
|
|
|
|
"syncLogId" to log.id, |
|
|
|
|
|
"syncDateTime" to syncAt, |
|
|
|
|
|
"bomId" to bomId, |
|
|
|
|
|
"finishedItemCode" to finishedCode.ifEmpty { null }, |
|
|
|
|
|
"m18HeaderCode" to headerCode.ifEmpty { null }, |
|
|
|
|
|
"version" to version, |
|
|
|
|
|
"syncStatus" to status, |
|
|
|
|
|
"lineNo" to line.itemNo?.trim(), |
|
|
|
|
|
"materialName" to line.udfIngredients?.trim(), |
|
|
|
|
|
"udfProductM18Id" to line.udfProduct, |
|
|
|
|
|
"udfBaseUnit" to line.udfBaseUnit, |
|
|
|
|
|
"udfQty" to line.udfqty, |
|
|
|
|
|
"udfSupplierM18Id" to line.udfSupplier, |
|
|
|
|
|
"udfPurchaseUnitM18Id" to line.udfpurchaseUnit, |
|
|
|
|
|
), |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return mapOf( |
|
|
|
|
|
"summary" to mapOf( |
|
|
|
|
|
"totalAttempts" to logs.size, |
|
|
|
|
|
"success" to successCount, |
|
|
|
|
|
"skippedUnchanged" to skippedUnchangedCount, |
|
|
|
|
|
"failed" to failedCount, |
|
|
|
|
|
"syncDateStart" to syncDateStart, |
|
|
|
|
|
"syncDateEnd" to syncDateEnd, |
|
|
|
|
|
), |
|
|
|
|
|
"syncRows" to syncRows, |
|
|
|
|
|
"materialRows" to materialRows, |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun parseDateStart(raw: String?): LocalDateTime? { |
|
|
|
|
|
if (raw.isNullOrBlank()) return null |
|
|
|
|
|
return LocalDate.parse(raw.trim().replace("/", "-")).atStartOfDay() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun parseDateEnd(raw: String?): LocalDateTime? { |
|
|
|
|
|
if (raw.isNullOrBlank()) return null |
|
|
|
|
|
return LocalDate.parse(raw.trim().replace("/", "-")).atTime(23, 59, 59, 999_999_999) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun extractVersion(headerCode: String?): String? { |
|
|
|
|
|
if (headerCode.isNullOrBlank()) return null |
|
|
|
|
|
return versionFromHeaderCode.find(headerCode.trim())?.groupValues?.get(1) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun resolveSyncStatus(log: M18BomShopSyncLog): String = when { |
|
|
|
|
|
!log.synced -> "FAILED" |
|
|
|
|
|
log.message?.contains("skippedUnchanged", ignoreCase = true) == true -> "SKIPPED_UNCHANGED" |
|
|
|
|
|
else -> "SUCCESS" |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun resolveFailureReason(log: M18BomShopSyncLog): String? { |
|
|
|
|
|
log.message?.trim()?.takeIf { it.isNotEmpty() }?.let { return it } |
|
|
|
|
|
val json = log.responseJson?.trim().orEmpty().ifEmpty { return null } |
|
|
|
|
|
return runCatching { |
|
|
|
|
|
val resp = objectMapper.readValue(json, GoodsReceiptNoteResponse::class.java) |
|
|
|
|
|
resp.messages |
|
|
|
|
|
.joinToString("; ") { m -> m.msgDetail?.trim().orEmpty().ifEmpty { m.msgCode.orEmpty() } } |
|
|
|
|
|
.trim() |
|
|
|
|
|
.ifEmpty { null } |
|
|
|
|
|
}.getOrNull() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun parseMaterialLines(log: M18BomShopSyncLog): List<M18UdfProductSaveValue> { |
|
|
|
|
|
val json = log.requestJson?.trim().orEmpty().ifEmpty { return emptyList() } |
|
|
|
|
|
return runCatching { |
|
|
|
|
|
objectMapper.readValue(json, M18BomForShopSaveRequest::class.java) |
|
|
|
|
|
.udfproduct.values |
|
|
|
|
|
}.getOrElse { emptyList() } |
|
|
|
|
|
} |
|
|
|
|
|
} |