| @@ -151,9 +151,13 @@ open class M18DeliveryOrderService( | |||||
| return deliveryOrder | return deliveryOrder | ||||
| } | } | ||||
| open fun saveDeliveryOrders(request: M18CommonRequest): SyncResult { | |||||
| open fun saveDeliveryOrders(request: M18CommonRequest, skipExistingDo: Boolean = false): SyncResult { | |||||
| val deliveryOrdersWithType = getDeliveryOrdersWithType(request) | val deliveryOrdersWithType = getDeliveryOrdersWithType(request) | ||||
| return saveDeliveryOrdersWithPreparedList(deliveryOrdersWithType, syncisExtra = false) | |||||
| return saveDeliveryOrdersWithPreparedList( | |||||
| deliveryOrdersWithType, | |||||
| syncisExtra = false, | |||||
| skipExistingDo = skipExistingDo, | |||||
| ) | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -209,16 +213,21 @@ open class M18DeliveryOrderService( | |||||
| query = conds | query = conds | ||||
| ) | ) | ||||
| return saveDeliveryOrdersWithPreparedList(prepared, syncisExtra = isExtraSync) | |||||
| return saveDeliveryOrdersWithPreparedList(prepared, syncisExtra = isExtraSync, skipExistingDo = newOnly) | |||||
| } | } | ||||
| private fun saveDeliveryOrdersWithPreparedList( | private fun saveDeliveryOrdersWithPreparedList( | ||||
| deliveryOrdersWithType: M18PurchaseOrderListResponseWithType?, | deliveryOrdersWithType: M18PurchaseOrderListResponseWithType?, | ||||
| syncisExtra: Boolean = false, | syncisExtra: Boolean = false, | ||||
| skipExistingDo: Boolean = false, | |||||
| ): SyncResult { | ): SyncResult { | ||||
| logger.info("--------------------------------------------Start - Saving M18 Delivery Order--------------------------------------------") | logger.info("--------------------------------------------Start - Saving M18 Delivery Order--------------------------------------------") | ||||
| if (skipExistingDo) { | |||||
| logger.info("skipExistingDo=true — local delivery orders will not be updated") | |||||
| } | |||||
| val successList = mutableListOf<Long>() | val successList = mutableListOf<Long>() | ||||
| val skippedList = mutableListOf<Long>() | |||||
| val successDetailList = mutableListOf<Long>() | val successDetailList = mutableListOf<Long>() | ||||
| val failList = mutableListOf<Long>() | val failList = mutableListOf<Long>() | ||||
| val failDetailList = mutableListOf<Long>() | val failDetailList = mutableListOf<Long>() | ||||
| @@ -241,6 +250,22 @@ open class M18DeliveryOrderService( | |||||
| if (deliveryOrdersValues != null) { | if (deliveryOrdersValues != null) { | ||||
| deliveryOrdersValues.forEach { deliveryOrder -> | deliveryOrdersValues.forEach { deliveryOrder -> | ||||
| if (skipExistingDo) { | |||||
| val latestDeliveryOrderLog = | |||||
| m18DataLogService.findLatestM18DataLogWithSuccess(deliveryOrder.id, doRefType) | |||||
| val existingByM18 = latestDeliveryOrderLog?.id?.let { | |||||
| deliveryOrderService.findByM18DataLogId(it) | |||||
| } | |||||
| if (existingByM18 != null && existingByM18.deleted != true) { | |||||
| logger.info( | |||||
| "${doRefType}: skipExistingDo — skipping M18 id=${deliveryOrder.id} " + | |||||
| "code=${existingByM18.code} localId=${existingByM18.id} status=${existingByM18.status}" | |||||
| ) | |||||
| skippedList.add(deliveryOrder.id) | |||||
| return@forEach | |||||
| } | |||||
| } | |||||
| val deliveryOrderDetail = getDeliveryOrder(deliveryOrder.id) | val deliveryOrderDetail = getDeliveryOrder(deliveryOrder.id) | ||||
| var deliveryOrderId: Long? = null //FP-MTMS | var deliveryOrderId: Long? = null //FP-MTMS | ||||
| @@ -254,6 +279,14 @@ open class M18DeliveryOrderService( | |||||
| // delivery_order + m18_data_log table | // delivery_order + m18_data_log table | ||||
| if (mainpo != null) { | if (mainpo != null) { | ||||
| if (skipExistingDo && deliveryOrderRepository.existsByCodeAndDeletedIsFalse(mainpo.code)) { | |||||
| logger.info( | |||||
| "${doRefType}: skipExistingDo — skipping M18 id=${deliveryOrder.id} code=${mainpo.code} (local DO exists by code)" | |||||
| ) | |||||
| skippedList.add(deliveryOrder.id) | |||||
| return@forEach | |||||
| } | |||||
| // Find the latest m18 data log by m18 id & type | // Find the latest m18 data log by m18 id & type | ||||
| // logger.info("${doRefType}: Finding For Latest M18 Data Log...") | // logger.info("${doRefType}: Finding For Latest M18 Data Log...") | ||||
| val latestDeliveryOrderLog = | val latestDeliveryOrderLog = | ||||
| @@ -573,6 +606,9 @@ open class M18DeliveryOrderService( | |||||
| // End of save. Check result | // End of save. Check result | ||||
| logger.info("Total Success (${doRefType}) (${successList.size})") | logger.info("Total Success (${doRefType}) (${successList.size})") | ||||
| logger.error("Total Fail (${doRefType}) (${failList.size}): $failList") | logger.error("Total Fail (${doRefType}) (${failList.size}): $failList") | ||||
| if (skippedList.isNotEmpty()) { | |||||
| logger.info("Total Skipped (${doRefType}) (${skippedList.size}): $skippedList") | |||||
| } | |||||
| logger.info("Total Success (${doLineRefType}) (${successDetailList.size})") | logger.info("Total Success (${doLineRefType}) (${successDetailList.size})") | ||||
| logger.error("Total Fail (${doLineRefType}) (${failDetailList.size}): $failDetailList") | logger.error("Total Fail (${doLineRefType}) (${failDetailList.size}): $failDetailList") | ||||
| @@ -585,11 +621,12 @@ open class M18DeliveryOrderService( | |||||
| logger.info("--------------------------------------------End - Saving M18 Delivery Order--------------------------------------------") | logger.info("--------------------------------------------End - Saving M18 Delivery Order--------------------------------------------") | ||||
| val skippedSuffix = if (skippedList.isNotEmpty()) " | skipped=${skippedList.size}" else "" | |||||
| return SyncResult( | return SyncResult( | ||||
| totalProcessed = successList.size + failList.size, | |||||
| totalProcessed = successList.size + failList.size + skippedList.size, | |||||
| totalSuccess = successList.size, | totalSuccess = successList.size, | ||||
| totalFail = failList.size, | totalFail = failList.size, | ||||
| query = deliveryOrdersWithType?.query ?: "" | |||||
| query = (deliveryOrdersWithType?.query ?: "") + skippedSuffix, | |||||
| ) | ) | ||||
| } | } | ||||
| } | } | ||||
| @@ -28,6 +28,8 @@ public abstract class SettingNames { | |||||
| public static final String SCHEDULE_M18_DO1 = "SCHEDULE.m18.do1"; | public static final String SCHEDULE_M18_DO1 = "SCHEDULE.m18.do1"; | ||||
| /** Saturday-only DO1 time (default 03:10). Mon–Fri & Sun use [SCHEDULE_M18_DO1] time via a second trigger. */ | /** Saturday-only DO1 time (default 03:10). Mon–Fri & Sun use [SCHEDULE_M18_DO1] time via a second trigger. */ | ||||
| public static final String SCHEDULE_M18_DO1_SAT = "SCHEDULE.m18.do1.sat"; | public static final String SCHEDULE_M18_DO1_SAT = "SCHEDULE.m18.do1.sat"; | ||||
| /** Comma-separated dDates (yyyy-MM-dd) of completed one-time DO1 catch-ups ([scheduler.do1CatchUp]). */ | |||||
| public static final String SCHEDULE_M18_DO1_CATCHUP_DONE_DDATE = "SCHEDULE.m18.do1.catchup.doneDDate"; | |||||
| public static final String SCHEDULE_M18_DO2 = "SCHEDULE.m18.do2"; | public static final String SCHEDULE_M18_DO2 = "SCHEDULE.m18.do2"; | ||||
| /** Daily push FPSMS BOMs → M18 udfBomForShop (default 23:00; requires [M18_BOM_SHOP_SYNC_ENABLED] and scheduler.m18Sync.enabled). */ | /** Daily push FPSMS BOMs → M18 udfBomForShop (default 23:00; requires [M18_BOM_SHOP_SYNC_ENABLED] and scheduler.m18Sync.enabled). */ | ||||
| @@ -26,6 +26,7 @@ import org.springframework.stereotype.Service | |||||
| import java.time.DayOfWeek | import java.time.DayOfWeek | ||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
| import java.time.ZoneId | |||||
| import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||
| import java.util.HashMap | import java.util.HashMap | ||||
| import java.util.concurrent.ScheduledFuture | import java.util.concurrent.ScheduledFuture | ||||
| @@ -44,6 +45,14 @@ open class SchedulerService( | |||||
| /** When false (default), M18 PO / DO1 / DO2 / master-data cron jobs are not registered — use true in production only. */ | /** When false (default), M18 PO / DO1 / DO2 / master-data cron jobs are not registered — use true in production only. */ | ||||
| @Value("\${scheduler.m18Sync.enabled:false}") val m18SyncEnabled: Boolean, | @Value("\${scheduler.m18Sync.enabled:false}") val m18SyncEnabled: Boolean, | ||||
| @Value("\${scheduler.jo.planStart.enabled:true}") val jobOrderPlanStartAutoEnabled: Boolean, | @Value("\${scheduler.jo.planStart.enabled:true}") val jobOrderPlanStartAutoEnabled: Boolean, | ||||
| @Value("\${scheduler.do1CatchUp.enabled:false}") val do1CatchUpEnabled: Boolean, | |||||
| @Value("\${scheduler.do1CatchUp.dDate:}") val do1CatchUpDDate: String, | |||||
| @Value("\${scheduler.do1CatchUp.runAt:}") val do1CatchUpRunAt: String, | |||||
| @Value("\${scheduler.do1CatchUp.skipExistingDo:true}") val do1CatchUpSkipExistingDo: Boolean, | |||||
| @Value("\${scheduler.do1CatchUp2.enabled:false}") val do1CatchUp2Enabled: Boolean, | |||||
| @Value("\${scheduler.do1CatchUp2.dDate:}") val do1CatchUp2DDate: String, | |||||
| @Value("\${scheduler.do1CatchUp2.runAt:}") val do1CatchUp2RunAt: String, | |||||
| @Value("\${scheduler.do1CatchUp2.skipExistingDo:true}") val do1CatchUp2SkipExistingDo: Boolean, | |||||
| val settingsService: SettingsService, | val settingsService: SettingsService, | ||||
| /** | /** | ||||
| * Lookback window for GRN code sync: rows with `created` from **start of (today − N days)** through **now**, | * Lookback window for GRN code sync: rows with `created` from **start of (today − N days)** through **now**, | ||||
| @@ -100,6 +109,9 @@ open class SchedulerService( | |||||
| var scheduledJobOrderPlanStart: ScheduledFuture<*>? = null | var scheduledJobOrderPlanStart: ScheduledFuture<*>? = null | ||||
| var scheduledDo1CatchUp: ScheduledFuture<*>? = null | |||||
| var scheduledDo1CatchUp2: ScheduledFuture<*>? = null | |||||
| //@Volatile | //@Volatile | ||||
| //var scheduledRoughProd: ScheduledFuture<*>? = null | //var scheduledRoughProd: ScheduledFuture<*>? = null | ||||
| @@ -191,10 +203,144 @@ open class SchedulerService( | |||||
| scheduleGrnCodeSync(); | scheduleGrnCodeSync(); | ||||
| scheduleInventoryLotExpiry(); | scheduleInventoryLotExpiry(); | ||||
| scheduleJobOrderPlanStartAuto(); | scheduleJobOrderPlanStartAuto(); | ||||
| scheduleDo1CatchUpOnce(); | |||||
| //scheduleRoughProd(); | //scheduleRoughProd(); | ||||
| //scheduleDetailedProd(); | //scheduleDetailedProd(); | ||||
| } | } | ||||
| /** | |||||
| * One-time DO1 catch-up jobs for fixed dDates (e.g. missed 15/6 → dDate 17/6, 16/6 → dDate 18/6). | |||||
| * Requires [m18SyncEnabled] (production only). Config: scheduler.do1CatchUp / do1CatchUp2 in application-prod.yml. | |||||
| * Completed dDates are stored comma-separated in [SettingNames.SCHEDULE_M18_DO1_CATCHUP_DONE_DDATE]. | |||||
| */ | |||||
| fun scheduleDo1CatchUpOnce() { | |||||
| scheduledDo1CatchUp?.cancel(false) | |||||
| scheduledDo1CatchUp = null | |||||
| scheduledDo1CatchUp2?.cancel(false) | |||||
| scheduledDo1CatchUp2 = null | |||||
| if (!m18SyncEnabled) { | |||||
| logger.info("DO1 catch-up schedulers disabled (scheduler.m18Sync.enabled=false; production only)") | |||||
| return | |||||
| } | |||||
| scheduledDo1CatchUp = scheduleOneDo1CatchUp( | |||||
| scheduledDo1CatchUp, | |||||
| do1CatchUpEnabled, | |||||
| do1CatchUpDDate, | |||||
| do1CatchUpRunAt, | |||||
| do1CatchUpSkipExistingDo, | |||||
| "do1CatchUp", | |||||
| ) | |||||
| scheduledDo1CatchUp2 = scheduleOneDo1CatchUp( | |||||
| scheduledDo1CatchUp2, | |||||
| do1CatchUp2Enabled, | |||||
| do1CatchUp2DDate, | |||||
| do1CatchUp2RunAt, | |||||
| do1CatchUp2SkipExistingDo, | |||||
| "do1CatchUp2", | |||||
| ) | |||||
| } | |||||
| private fun scheduleOneDo1CatchUp( | |||||
| existing: ScheduledFuture<*>?, | |||||
| enabled: Boolean, | |||||
| dDateRaw: String, | |||||
| runAtRaw: String, | |||||
| skipExistingDo: Boolean, | |||||
| configKey: String, | |||||
| ): ScheduledFuture<*>? { | |||||
| existing?.cancel(false) | |||||
| if (!enabled) { | |||||
| return null | |||||
| } | |||||
| val dDateStr = dDateRaw.trim() | |||||
| val runAtStr = runAtRaw.trim() | |||||
| if (dDateStr.isEmpty() || runAtStr.isEmpty()) { | |||||
| logger.warn("{} enabled but dDate or runAt is blank — skipped", configKey) | |||||
| return null | |||||
| } | |||||
| val dDate = try { | |||||
| LocalDate.parse(dDateStr) | |||||
| } catch (e: Exception) { | |||||
| logger.error("Invalid scheduler.{}.dDate={}", configKey, dDateStr) | |||||
| return null | |||||
| } | |||||
| val runAt = try { | |||||
| LocalDateTime.parse(runAtStr) | |||||
| } catch (e: Exception) { | |||||
| logger.error("Invalid scheduler.{}.runAt={}", configKey, runAtStr) | |||||
| return null | |||||
| } | |||||
| if (isDo1CatchUpAlreadyDone(dDate)) { | |||||
| logger.info("DO1 catch-up ({}) already completed for dDate={}", configKey, dDate) | |||||
| return null | |||||
| } | |||||
| val now = LocalDateTime.now() | |||||
| if (!runAt.isAfter(now)) { | |||||
| logger.warn( | |||||
| "DO1 catch-up ({}) runAt={} is not in the future (now={}); use GET /scheduler/trigger/do1-catchup?dDate={}", | |||||
| configKey, | |||||
| runAt, | |||||
| now, | |||||
| dDate, | |||||
| ) | |||||
| return null | |||||
| } | |||||
| val scheduled = taskScheduler.schedule( | |||||
| { runDo1CatchUp(dDate, skipExistingDo) }, | |||||
| runAt.atZone(ZoneId.systemDefault()).toInstant(), | |||||
| ) | |||||
| logger.info( | |||||
| "Scheduled one-time DO1 catch-up ({}) for dDate={} at {} skipExistingDo={}", | |||||
| configKey, | |||||
| dDate, | |||||
| runAt, | |||||
| skipExistingDo, | |||||
| ) | |||||
| return scheduled | |||||
| } | |||||
| private fun getDo1CatchUpDoneDDateSet(): Set<String> { | |||||
| val done = settingsService.findByName(SettingNames.SCHEDULE_M18_DO1_CATCHUP_DONE_DDATE).getOrNull()?.value | |||||
| ?: return emptySet() | |||||
| return done.split(",").map { it.trim() }.filter { it.isNotEmpty() }.toSet() | |||||
| } | |||||
| private fun isDo1CatchUpAlreadyDone(dDate: LocalDate): Boolean { | |||||
| return dDate.toString() in getDo1CatchUpDoneDDateSet() | |||||
| } | |||||
| private fun markDo1CatchUpDone(dDate: LocalDate) { | |||||
| val updated = (getDo1CatchUpDoneDDateSet() + dDate.toString()).sorted() | |||||
| settingsService.update(SettingNames.SCHEDULE_M18_DO1_CATCHUP_DONE_DDATE, updated.joinToString(",")) | |||||
| } | |||||
| open fun runDo1CatchUp(dDate: LocalDate, skipExistingDo: Boolean = true) { | |||||
| if (!m18SyncEnabled) { | |||||
| logger.warn( | |||||
| "DO1 catch-up refused for dDate={}: production only (scheduler.m18Sync.enabled=false)", | |||||
| dDate, | |||||
| ) | |||||
| return | |||||
| } | |||||
| if (isDo1CatchUpAlreadyDone(dDate)) { | |||||
| logger.info("DO1 catch-up already completed for dDate={}", dDate) | |||||
| return | |||||
| } | |||||
| try { | |||||
| getM18Dos1ForDDate(dDate, syncType = "DO1_CATCHUP", skipExistingDo = skipExistingDo) | |||||
| markDo1CatchUpDone(dDate) | |||||
| logger.info("DO1 catch-up completed for dDate={}", dDate) | |||||
| } catch (e: Exception) { | |||||
| logger.error("DO1 catch-up failed for dDate={}: {}", dDate, e.message, e) | |||||
| } | |||||
| } | |||||
| // Scheduler | // Scheduler | ||||
| // --------------------------- FP-MTMS --------------------------- // | // --------------------------- FP-MTMS --------------------------- // | ||||
| //fun scheduleRoughProd() { | //fun scheduleRoughProd() { | ||||
| @@ -480,24 +626,42 @@ open class SchedulerService( | |||||
| open fun getM18Dos1() { | open fun getM18Dos1() { | ||||
| logger.info("DO Scheduler 1 - DO") | logger.info("DO Scheduler 1 - DO") | ||||
| val currentTime = LocalDateTime.now() | |||||
| val today = currentTime.toLocalDate().atStartOfDay() | |||||
| val twoDaysLater = today.plusDays(2L) | |||||
| var requestDO = M18CommonRequest( | |||||
| dDateTo = twoDaysLater.format(dateTimeStringFormat), | |||||
| dDateFrom = twoDaysLater.format(dateTimeStringFormat) | |||||
| ) | |||||
| val result = m18DeliveryOrderService.saveDeliveryOrders(requestDO); | |||||
| val today = LocalDateTime.now().toLocalDate().atStartOfDay() | |||||
| val dDate = today.plusDays(2L).toLocalDate() | |||||
| getM18Dos1ForDDate(dDate, syncType = "DO1") | |||||
| } | |||||
| saveSyncLog( | |||||
| type = "DO1", | |||||
| status = "SUCCESS", | |||||
| result = result, | |||||
| start = currentTime | |||||
| /** DO1 sync for an explicit delivery date (normal DO1 uses run-day + 2 days). */ | |||||
| open fun getM18Dos1ForDDate( | |||||
| dDate: LocalDate, | |||||
| syncType: String = "DO1", | |||||
| skipExistingDo: Boolean = syncType == "DO1_CATCHUP", | |||||
| ) { | |||||
| logger.info("{} sync for dDate={} skipExistingDo={}", syncType, dDate, skipExistingDo) | |||||
| val currentTime = LocalDateTime.now() | |||||
| val dDateStart = dDate.atStartOfDay() | |||||
| val requestDO = M18CommonRequest( | |||||
| dDateTo = dDateStart.format(dateTimeStringFormat), | |||||
| dDateFrom = dDateStart.format(dateTimeStringFormat), | |||||
| ) | ) | ||||
| try { | |||||
| val result = m18DeliveryOrderService.saveDeliveryOrders(requestDO, skipExistingDo = skipExistingDo) | |||||
| saveSyncLog( | |||||
| type = syncType, | |||||
| status = "SUCCESS", | |||||
| result = result?.copy(query = "dDate=$dDate ${result.query}".trim()), | |||||
| start = currentTime, | |||||
| ) | |||||
| } catch (e: Exception) { | |||||
| logger.error("{} sync failed for dDate={}: {}", syncType, dDate, e.message, e) | |||||
| saveSyncLog( | |||||
| type = syncType, | |||||
| status = "FAILED", | |||||
| error = e.message, | |||||
| start = currentTime, | |||||
| ) | |||||
| throw e | |||||
| } | |||||
| } | } | ||||
| private fun saveSyncLog(type: String, status: String, result: SyncResult? = null, error: String? = null, start: LocalDateTime) { | private fun saveSyncLog(type: String, status: String, result: SyncResult? = null, error: String? = null, start: LocalDateTime) { | ||||
| @@ -43,6 +43,16 @@ class SchedulerController( | |||||
| return "M18 DO1 Sync Triggered Successfully" | return "M18 DO1 Sync Triggered Successfully" | ||||
| } | } | ||||
| /** Manual DO1 catch-up for a fixed dDate (production only). Skips existing local DOs by default. */ | |||||
| @GetMapping("/trigger/do1-catchup") | |||||
| fun triggerDo1CatchUp( | |||||
| @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) dDate: LocalDate, | |||||
| @RequestParam(required = false, defaultValue = "true") skipExistingDo: Boolean = true, | |||||
| ): String { | |||||
| schedulerService.runDo1CatchUp(dDate, skipExistingDo = skipExistingDo) | |||||
| return "M18 DO1 catch-up triggered for dDate=$dDate skipExistingDo=$skipExistingDo" | |||||
| } | |||||
| @GetMapping("/trigger/do2") | @GetMapping("/trigger/do2") | ||||
| fun triggerDo2(): String { | fun triggerDo2(): String { | ||||
| schedulerService.getM18Dos2() | schedulerService.getM18Dos2() | ||||
| @@ -40,6 +40,18 @@ scheduler: | |||||
| syncOffsetDays: 10 # from (today − 10) 00:00 to now, rows missing grn_code | syncOffsetDays: 10 # from (today − 10) 00:00 to now, rows missing grn_code | ||||
| inventoryLotExpiry: | inventoryLotExpiry: | ||||
| enabled: true | enabled: true | ||||
| # One-time DO1 catch-ups (only registered when scheduler.m18Sync.enabled=true — production profile). | |||||
| # skipExistingDo defaults true for catch-up: already-synced/picked DOs are not overwritten. | |||||
| do1CatchUp: | |||||
| enabled: true | |||||
| skipExistingDo: true | |||||
| dDate: "2026-06-17" | |||||
| runAt: "2026-06-17T11:30:00" | |||||
| do1CatchUp2: | |||||
| enabled: true | |||||
| skipExistingDo: true | |||||
| dDate: "2026-06-18" | |||||
| runAt: "2026-06-16T15:00:00" | |||||
| # Laser Bag2 (/laserPrint) TCP auto-send; uses LASER_PRINT host/port/itemCodes from DB and sends first matching job only. | # Laser Bag2 (/laserPrint) TCP auto-send; uses LASER_PRINT host/port/itemCodes from DB and sends first matching job only. | ||||
| laser: | laser: | ||||
| @@ -12,7 +12,7 @@ server: | |||||
| # PostCompletedDn GRN: runs daily at 00:01, processes all POs with receipt date = yesterday. | # PostCompletedDn GRN: runs daily at 00:01, processes all POs with receipt date = yesterday. | ||||
| # Set enabled: false to disable. Optional receiptDate: "yyyy-MM-dd" overrides for testing only. | # Set enabled: false to disable. Optional receiptDate: "yyyy-MM-dd" overrides for testing only. | ||||
| # m18Grn.createEnabled: M18 GRN PUT/create — false outside production so UAT/dev never posts GRNs. | # m18Grn.createEnabled: M18 GRN PUT/create — false outside production so UAT/dev never posts GRNs. | ||||
| # m18Sync: M18 cron jobs for PO, DO1, DO2, BOM→M18 udfBomForShop ([SCHEDULE.m18.bom.shop], default 23:00), master data — false outside production (manual /trigger/* still works). | |||||
| # m18Sync: M18 cron jobs for PO, DO1, DO2, DO1 catch-up, BOM→M18 udfBomForShop, master data — false outside production (manual /trigger/* still works except do1-catchup). | |||||
| truck: | truck: | ||||
| lane: | lane: | ||||
| schedule: | schedule: | ||||
| @@ -37,6 +37,11 @@ scheduler: | |||||
| jo: | jo: | ||||
| planStart: | planStart: | ||||
| enabled: true | enabled: true | ||||
| # One-time DO1 catch-up jobs (production only — requires scheduler.m18Sync.enabled=true in application-prod.yml). | |||||
| do1CatchUp: | |||||
| enabled: false | |||||
| do1CatchUp2: | |||||
| enabled: false | |||||
| # Nav: PO stock_in_line pending/receiving within last N days (see ProductProcessService for 工單 QC/上架:今日+昨日). | # Nav: PO stock_in_line pending/receiving within last N days (see ProductProcessService for 工單 QC/上架:今日+昨日). | ||||
| fpsms: | fpsms: | ||||