Przeglądaj źródła

if pol sync fail, will send email to tell pol have sync fail

production
CANCERYS\kw093 8 godzin temu
rodzic
commit
42c3ada0df
7 zmienionych plików z 104 dodań i 5 usunięć
  1. +7
    -0
      src/main/java/com/ffii/fpsms/m18/entity/M18DataLogRepository.kt
  2. +2
    -2
      src/main/java/com/ffii/fpsms/m18/service/M18PurchaseOrderService.kt
  3. +27
    -0
      src/main/java/com/ffii/fpsms/modules/common/alert/PoLineFailureAlertSupport.kt
  4. +6
    -0
      src/main/java/com/ffii/fpsms/modules/common/alert/SchedulerSyncAlertProperties.kt
  5. +58
    -3
      src/main/java/com/ffii/fpsms/modules/common/alert/SchedulerSyncAlertService.kt
  6. +2
    -0
      src/main/resources/application-prod.yml
  7. +2
    -0
      src/main/resources/application.yml

+ 7
- 0
src/main/java/com/ffii/fpsms/m18/entity/M18DataLogRepository.kt Wyświetl plik

@@ -3,9 +3,16 @@ package com.ffii.fpsms.m18.entity
import com.ffii.core.support.AbstractRepository
import com.ffii.fpsms.m18.enums.M18DataLogStatus
import org.springframework.stereotype.Repository
import java.time.LocalDateTime

@Repository
interface M18DataLogRepository : AbstractRepository<M18DataLog, Long> {
// find latest m18 data log by m18 id & ref type & status is true & deleted is false (order by id asc limit 1)
fun findTopByM18IdAndRefTypeAndDeletedIsFalseAndStatusOrderByIdDesc(m18Id: Long, refType: String, status: M18DataLogStatus): M18DataLog?

fun findAllByRefTypeAndStatusAndDeletedIsFalseAndCreatedGreaterThanEqualOrderByIdAsc(
refType: String,
status: M18DataLogStatus,
created: LocalDateTime,
): List<M18DataLog>
}

+ 2
- 2
src/main/java/com/ffii/fpsms/m18/service/M18PurchaseOrderService.kt Wyświetl plik

@@ -445,12 +445,12 @@ open class M18PurchaseOrderService(
if (itemId == null) {
failDetailList.add(line.id)
logger.error(
"${poLineRefType}: Cannot resolve local item for M18 proId=${line.proId}, skipping line ${line.id}"
"${poLineRefType}: PO ${mainpo.code}: 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}"
"Exception Message" to "PO ${mainpo.code} Cannot resolve local item for M18 proId=${line.proId}"
),
statusEnum = M18DataLogStatus.FAIL
)


+ 27
- 0
src/main/java/com/ffii/fpsms/modules/common/alert/PoLineFailureAlertSupport.kt Wyświetl plik

@@ -0,0 +1,27 @@
package com.ffii.fpsms.modules.common.alert

/**
* Parses PO line sync failure rows written by [com.ffii.fpsms.m18.service.M18PurchaseOrderService].
* Expected format: "PO {code} Cannot resolve local item for M18 proId={id}".
*/
internal object PoLineFailureAlertSupport {
private val WITH_PO_CODE =
Regex("^PO (\\S+) Cannot resolve local item for M18 proId=(\\d+)$")

/** @return (poCode, bullet line for email) or null when the message cannot be parsed. */
fun parseExceptionMessage(raw: String?): Pair<String, String>? {
val trimmed = raw?.trim().orEmpty()
if (trimmed.isEmpty()) {
return null
}
val match = WITH_PO_CODE.matchEntire(trimmed) ?: return null
val poCode = match.groupValues[1]
val proId = match.groupValues[2]
return poCode to "Cannot resolve local item for M18 proId=$proId"
}

fun buildEmailBody(poCode: String, bulletLines: Collection<String>): String {
val detail = bulletLines.joinToString("\n") { "- $it" }
return "FPSMS PO_LINE FAIL: $poCode\n\n$detail"
}
}

+ 6
- 0
src/main/java/com/ffii/fpsms/modules/common/alert/SchedulerSyncAlertProperties.kt Wyświetl plik

@@ -12,6 +12,7 @@ data class SchedulerSyncAlertProperties(
val email: EmailAlertProperties = EmailAlertProperties(),
val do1: Do1AlertProperties = Do1AlertProperties(),
val presence: PresenceAlertProperties = PresenceAlertProperties(),
val poLine: PoLineAlertProperties = PoLineAlertProperties(),
)

data class SmsProperties(
@@ -65,3 +66,8 @@ data class Do1AlertProperties(
data class PresenceAlertProperties(
val graceMinutesAfterSchedule: Int = 60,
)

/** Purchase order line sync failures from [m18_data_log] (one email per PO code per day). */
data class PoLineAlertProperties(
val enabled: Boolean = true,
)

+ 58
- 3
src/main/java/com/ffii/fpsms/modules/common/alert/SchedulerSyncAlertService.kt Wyświetl plik

@@ -1,7 +1,9 @@
package com.ffii.fpsms.modules.common.alert

import com.ffii.fpsms.m18.entity.M18DataLogRepository
import com.ffii.fpsms.m18.entity.SchedulerSyncLog
import com.ffii.fpsms.m18.entity.SchedulerSyncLogRepository
import com.ffii.fpsms.m18.enums.M18DataLogStatus
import com.ffii.fpsms.modules.common.SettingNames
import com.ffii.fpsms.modules.common.scheduler.service.SchedulerService
import com.ffii.fpsms.modules.settings.entity.Settings
@@ -24,6 +26,7 @@ import kotlin.jvm.optionals.getOrNull
*
* - **DO1**: low volume (&lt; min records), FAILED status, zero records, line failures.
* - **PO / DO2 / master-data**: no SUCCESS log by schedule + grace window.
* - **PO_LINE**: [m18_data_log] purchase order line FAIL rows (one email per PO code per day).
*/
@Service
open class SchedulerSyncAlertService(
@@ -31,23 +34,36 @@ open class SchedulerSyncAlertService(
private val smsSender: SmsSender,
private val syncAlertEmailSender: SyncAlertEmailSender,
private val schedulerSyncLogRepository: SchedulerSyncLogRepository,
private val m18DataLogRepository: M18DataLogRepository,
private val settingsService: SettingsService,
private val webClientBuilder: WebClient.Builder,
@Value("\${scheduler.m18Sync.enabled:false}") private val m18SyncEnabled: Boolean,
) {
private val logger = LoggerFactory.getLogger(SchedulerSyncAlertService::class.java)

private companion object {
const val PO_LINE_REF_TYPE = "Purchase Order Line"
const val PO_LINE_ALERT_JOB = "PO_LINE"
}

/** Master-data sub-jobs written by [SchedulerService.getM18MasterData]. */
private val masterDataSyncTypes =
listOf("Units", "Products", "Vendors", "BusinessUnits", "Currencies")

open fun runChecks(now: LocalDateTime = LocalDateTime.now()): List<String> {
val alerts = mutableListOf<Triple<String, String, String>>()
alerts += checkPoLineFailures(now)

if (!m18SyncEnabled) {
logger.debug("Sync alert skipped (scheduler.m18Sync.enabled=false)")
return emptyList()
for (message in alerts) {
sendAlert(message.first, message.second, message.third)
}
if (alerts.isEmpty()) {
logger.debug("Sync alert skipped (scheduler.m18Sync.enabled=false)")
}
return alerts.map { it.third }
}

val alerts = mutableListOf<Triple<String, String, String>>()
alerts += checkDo1(now)
alerts += checkPresenceSync("PO", SettingNames.SCHEDULE_M18_PO, "0 0 2 * * *", now)
alerts += checkPresenceSync("DO2", SettingNames.SCHEDULE_M18_DO2, SchedulerService.DO2_DEFAULT_CRON, now)
@@ -59,6 +75,45 @@ open class SchedulerSyncAlertService(
return alerts.map { it.third }
}

/**
* Email when M18 PO line sync wrote FAIL rows to [m18_data_log] today.
* Dedupes via [sendAlert] using job [PO_LINE_ALERT_JOB] and PO code (one email per PO per day).
*/
private fun checkPoLineFailures(now: LocalDateTime): List<Triple<String, String, String>> {
if (!properties.poLine.enabled) {
return emptyList()
}

val dayStart = now.toLocalDate().atStartOfDay()
val logs =
m18DataLogRepository.findAllByRefTypeAndStatusAndDeletedIsFalseAndCreatedGreaterThanEqualOrderByIdAsc(
PO_LINE_REF_TYPE,
M18DataLogStatus.FAIL,
dayStart,
)
if (logs.isEmpty()) {
return emptyList()
}

val bulletsByPo = linkedMapOf<String, LinkedHashSet<String>>()
for (log in logs) {
val raw = log.dataLog?.get("Exception Message")?.toString()
val parsed = PoLineFailureAlertSupport.parseExceptionMessage(raw) ?: continue
bulletsByPo.getOrPut(parsed.first) { LinkedHashSet() }.add(parsed.second)
}
if (bulletsByPo.isEmpty()) {
return emptyList()
}

return bulletsByPo.map { (poCode, bullets) ->
Triple(
PO_LINE_ALERT_JOB,
poCode,
PoLineFailureAlertSupport.buildEmailBody(poCode, bullets),
)
}
}

private fun checkDo1(now: LocalDateTime): List<Triple<String, String, String>> {
val runDate = now.toLocalDate()
val scheduledTime = resolveDo1ScheduledTime(runDate) ?: return emptyList()


+ 2
- 0
src/main/resources/application-prod.yml Wyświetl plik

@@ -62,6 +62,8 @@ scheduler:
email:
enabled: true
to-addresses: "[email protected],[email protected]"
po-line:
enabled: true
# From = MAIL.smtp.username in DB (e.g. [email protected] + Gmail app password)

# Laser Bag2 (/laserPrint) TCP auto-send; uses LASER_PRINT host/port/itemCodes from DB and sends first matching job only.


+ 2
- 0
src/main/resources/application.yml Wyświetl plik

@@ -63,6 +63,8 @@ scheduler:
grace-minutes-after-schedule: 30
presence:
grace-minutes-after-schedule: 60
po-line:
enabled: ${SYNC_ALERT_PO_LINE_ENABLED:true}

# Nav: PO stock_in_line pending/receiving within last N days (see ProductProcessService for 工單 QC/上架:今日+昨日).
fpsms:


Ładowanie…
Anuluj
Zapisz