Ver código fonte

getting the grn code and set to mtms

master
Fai Luk 8 horas atrás
pai
commit
d753fe16a4
10 arquivos alterados com 169 adições e 0 exclusões
  1. +5
    -0
      src/main/java/com/ffii/fpsms/m18/entity/M18GoodsReceiptNoteLog.kt
  2. +20
    -0
      src/main/java/com/ffii/fpsms/m18/entity/M18GoodsReceiptNoteLogRepository.kt
  3. +36
    -0
      src/main/java/com/ffii/fpsms/m18/service/M18GoodsReceiptNoteService.kt
  4. +54
    -0
      src/main/java/com/ffii/fpsms/m18/service/M18GrnCodeSyncService.kt
  5. +3
    -0
      src/main/java/com/ffii/fpsms/modules/common/SettingNames.java
  6. +34
    -0
      src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt
  7. +6
    -0
      src/main/java/com/ffii/fpsms/modules/common/scheduler/web/SchedulerController.kt
  8. +3
    -0
      src/main/resources/application-prod.yml
  9. +3
    -0
      src/main/resources/application.yml
  10. +5
    -0
      src/main/resources/db/changelog/changes/20260325_01_fai/01_add_grn_code_to_m18_goods_receipt_note_log.sql

+ 5
- 0
src/main/java/com/ffii/fpsms/m18/entity/M18GoodsReceiptNoteLog.kt Ver arquivo

@@ -21,6 +21,11 @@ open class M18GoodsReceiptNoteLog : BaseEntity<Long>() {
@Column(name = "m18_record_id", nullable = false)
open var m18RecordId: Long? = null

/** M18 AN document code (from GET /root/api/read/an, data.mainan.code). */
@Size(max = 60)
@Column(name = "grn_code", length = 60)
open var grnCode: String? = null

/** Stock-in line this GRN line was created from. */
@NotNull
@Column(name = "stock_in_line_id", nullable = false)


+ 20
- 0
src/main/java/com/ffii/fpsms/m18/entity/M18GoodsReceiptNoteLogRepository.kt Ver arquivo

@@ -1,9 +1,29 @@
package com.ffii.fpsms.m18.entity

import com.ffii.core.support.AbstractRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param
import java.time.LocalDateTime

interface M18GoodsReceiptNoteLogRepository : AbstractRepository<M18GoodsReceiptNoteLog, Long> {

/** Returns true if a successful GRN was already created for this PO (avoids core_201 duplicate). */
fun existsByPurchaseOrderIdAndStatusTrue(purchaseOrderId: Long): Boolean

/**
* GRN log rows that need M18 AN code backfill: have record id, no grn_code yet, created in [start, end).
*/
@Query(
"""
SELECT l FROM M18GoodsReceiptNoteLog l
WHERE l.deleted = false
AND l.m18RecordId IS NOT NULL
AND (l.grnCode IS NULL OR l.grnCode = '')
AND l.created >= :start AND l.created < :end
"""
)
fun findNeedingGrnCode(
@Param("start") start: LocalDateTime,
@Param("end") end: LocalDateTime,
): List<M18GoodsReceiptNoteLog>
}

+ 36
- 0
src/main/java/com/ffii/fpsms/m18/service/M18GoodsReceiptNoteService.kt Ver arquivo

@@ -1,6 +1,7 @@
package com.ffii.fpsms.m18.service

import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.ffii.fpsms.api.service.ApiCallerService
@@ -27,6 +28,7 @@ open class M18GoodsReceiptNoteService(
private val logger: Logger = LoggerFactory.getLogger(M18GoodsReceiptNoteService::class.java)

private val M18_SAVE_GOODS_RECEIPT_NOTE_API = "/root/api/save/an"
private val M18_READ_GOODS_RECEIPT_NOTE_API = "/root/api/read/an"
private val MENU_CODE_AN = "an"

// Serialize with UTF-8 characters (no \\uXXXX) so M18 persists bDesc/bDesc_en like Postman
@@ -84,4 +86,38 @@ open class M18GoodsReceiptNoteService(
logger.error("[M18 GRN API] call failed: $fullUrl error=${e.message}", e)
}
}

/**
* Reads AN (GRN) from M18 by record id. GET /root/api/read/an?id={recordId}
* Document code is typically at data.mainan.values[0].code or data.mainan.code.
*/
open fun readAnCodeByRecordId(recordId: Long): String? {
return try {
val node: JsonNode = apiCallerService.get<JsonNode>(
M18_READ_GOODS_RECEIPT_NOTE_API,
mapOf("id" to recordId),
).block() ?: return null
extractAnCodeFromReadResponse(node)
} catch (e: Exception) {
logger.warn("[M18 read AN] id=$recordId failed: ${e.message}", e)
null
}
}

private fun extractAnCodeFromReadResponse(node: JsonNode): String? {
val paths = listOf(
"/data/mainan/values/0/code",
"/data/mainan/code",
"/mainan/values/0/code",
"/mainan/code",
)
for (p in paths) {
val v = node.at(p)
if (!v.isMissingNode && !v.isNull) {
val text = v.asText()
if (!text.isNullOrBlank()) return text
}
}
return null
}
}

+ 54
- 0
src/main/java/com/ffii/fpsms/m18/service/M18GrnCodeSyncService.kt Ver arquivo

@@ -0,0 +1,54 @@
package com.ffii.fpsms.m18.service

import com.ffii.fpsms.m18.entity.M18GoodsReceiptNoteLogRepository
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDate
import java.time.ZoneId

/**
* Fills [com.ffii.fpsms.m18.entity.M18GoodsReceiptNoteLog.grnCode] by calling M18 GET /root/api/read/an?id=...
*/
@Service
open class M18GrnCodeSyncService(
private val m18GoodsReceiptNoteLogRepository: M18GoodsReceiptNoteLogRepository,
private val m18GoodsReceiptNoteService: M18GoodsReceiptNoteService,
) {
private val logger = LoggerFactory.getLogger(M18GrnCodeSyncService::class.java)

/**
* For today's [created] window, backfill grn_code for rows with m18_record_id but empty grn_code.
* One M18 read per distinct [m18_record_id]; all matching log rows get the same code.
*/
@Transactional
open fun syncGrnCodesForDate(day: LocalDate, zoneId: ZoneId = ZoneId.systemDefault()): Int {
val start = day.atStartOfDay(zoneId).toLocalDateTime()
val end = day.plusDays(1).atStartOfDay(zoneId).toLocalDateTime()
val rows = m18GoodsReceiptNoteLogRepository.findNeedingGrnCode(start, end)
if (rows.isEmpty()) {
logger.info("[M18GrnCodeSync] No GRN log rows need grn_code for day=$day")
return 0
}
val byRecord = rows.groupBy { it.m18RecordId }
var updated = 0
for ((recordId, list) in byRecord) {
val rid = recordId ?: continue
val code = m18GoodsReceiptNoteService.readAnCodeByRecordId(rid)
if (code.isNullOrBlank()) {
logger.warn("[M18GrnCodeSync] No code from M18 for m18_record_id=$rid (day=$day)")
continue
}
for (log in list) {
log.grnCode = code
m18GoodsReceiptNoteLogRepository.save(log)
updated++
}
logger.info("[M18GrnCodeSync] Set grn_code=$code for m18_record_id=$rid (${list.size} row(s))")
}
return updated
}

/** Convenience: sync for calendar "today" in the default timezone. */
open fun syncGrnCodesForToday(): Int = syncGrnCodesForDate(LocalDate.now())
}

+ 3
- 0
src/main/java/com/ffii/fpsms/modules/common/SettingNames.java Ver arquivo

@@ -33,6 +33,9 @@ public abstract class SettingNames {
/** Post completed DN and process M18 GRN (cron, e.g. "0 40 23 * * *" for 23:40 daily) */
public static final String SCHEDULE_POST_COMPLETED_DN_GRN = "SCHEDULE.postCompletedDn.grn";

/** Backfill M18 AN document code (grn_code) via GET /root/api/read/an (default 1:20 AM daily) */
public static final String SCHEDULE_GRN_CODE_SYNC = "SCHEDULE.grn.grnCode.m18";

public static final String SCHEDULE_PROD_ROUGH = "SCHEDULE.prod.rough";

public static final String SCHEDULE_PROD_DETAILED = "SCHEDULE.prod.detailed";


+ 34
- 0
src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt Ver arquivo

@@ -2,6 +2,7 @@ package com.ffii.fpsms.modules.common.scheduler.service

import com.ffii.core.utils.JwtTokenUtil
import com.ffii.fpsms.m18.service.M18DeliveryOrderService
import com.ffii.fpsms.m18.service.M18GrnCodeSyncService
import com.ffii.fpsms.m18.service.M18MasterDataService
import com.ffii.fpsms.m18.service.M18PurchaseOrderService
import com.ffii.fpsms.m18.web.models.M18CommonRequest
@@ -30,6 +31,9 @@ import kotlin.jvm.optionals.getOrNull
open class SchedulerService(
@Value("\${scheduler.postCompletedDnGrn.enabled:true}") val postCompletedDnGrnEnabled: Boolean,
@Value("\${scheduler.postCompletedDnGrn.receiptDate:}") val postCompletedDnGrnReceiptDate: String,
@Value("\${scheduler.grnCodeSync.enabled:true}") val grnCodeSyncEnabled: Boolean,
/** 0 = calendar day of run; 1 = previous day (typical for 1:20 AM job to backfill yesterday's GRNs). */
@Value("\${scheduler.grnCodeSync.syncOffsetDays:0}") val grnCodeSyncSyncOffsetDays: Int,
val settingsService: SettingsService,
val taskScheduler: TaskScheduler,
val productionScheduleService: ProductionScheduleService,
@@ -38,6 +42,7 @@ open class SchedulerService(
val m18MasterDataService: M18MasterDataService,
val schedulerSyncLogRepository: SchedulerSyncLogRepository,
val searchCompletedDnService: SearchCompletedDnService,
val m18GrnCodeSyncService: M18GrnCodeSyncService,
) {
var logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java)
val dataStringFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd")
@@ -55,6 +60,8 @@ open class SchedulerService(

var scheduledPostCompletedDnGrn: ScheduledFuture<*>? = null

var scheduledGrnCodeSync: ScheduledFuture<*>? = null

//@Volatile
//var scheduledRoughProd: ScheduledFuture<*>? = null

@@ -99,6 +106,7 @@ open class SchedulerService(
scheduleM18Do2();
scheduleM18MasterData();
schedulePostCompletedDnGrn();
scheduleGrnCodeSync();
//scheduleRoughProd();
//scheduleDetailedProd();
}
@@ -141,6 +149,32 @@ open class SchedulerService(
else try { LocalDate.parse(s) } catch (e: Exception) { LocalDate.now().minusDays(1) }
}

/** Backfill grn_code from M18 read/an for today's logs missing code. Default 1:20 AM daily. Set scheduler.grnCodeSync.enabled=false to disable. */
fun scheduleGrnCodeSync() {
if (!grnCodeSyncEnabled) {
scheduledGrnCodeSync?.cancel(false)
scheduledGrnCodeSync = null
logger.info("GRN code sync scheduler disabled (scheduler.grnCodeSync.enabled=false)")
return
}
commonSchedule(
scheduledGrnCodeSync,
SettingNames.SCHEDULE_GRN_CODE_SYNC,
"0 20 1 * * *",
{ syncGrnCodesFromM18() },
)
}

open fun syncGrnCodesFromM18() {
try {
val day = java.time.LocalDate.now().minusDays(grnCodeSyncSyncOffsetDays.toLong().coerceAtLeast(0))
val updated = m18GrnCodeSyncService.syncGrnCodesForDate(day)
logger.info("Scheduler - M18 GRN code sync done for date=$day, rows updated=$updated")
} catch (e: Exception) {
logger.error("Scheduler - M18 GRN code sync failed: ${e.message}", e)
}
}

/** Post completed DN and create M18 GRN at 00:01 daily; processes all POs with receipt date = yesterday. Set scheduler.postCompletedDnGrn.enabled=false to disable. */
fun schedulePostCompletedDnGrn() {
if (!postCompletedDnGrnEnabled) {


+ 6
- 0
src/main/java/com/ffii/fpsms/modules/common/scheduler/web/SchedulerController.kt Ver arquivo

@@ -55,6 +55,12 @@ class SchedulerController(
return "M18 Master Data Sync Triggered Successfully"
}

@GetMapping("/trigger/grn-code-sync")
fun triggerGrnCodeSync(): String {
schedulerService.syncGrnCodesFromM18()
return "M18 GRN code sync (read/an) triggered"
}

@GetMapping("/trigger/post-completed-dn-grn")
fun triggerPostCompletedDnGrn(
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) receiptDate: LocalDate? = null,


+ 3
- 0
src/main/resources/application-prod.yml Ver arquivo

@@ -22,6 +22,9 @@ spring:
scheduler:
postCompletedDnGrn:
enabled: true
grnCodeSync:
enabled: true
syncOffsetDays: 0 # 0 = m18_goods_receipt_note_log rows created today; use 1 for "yesterday" if you prefer night backfill

m18:
config:


+ 3
- 0
src/main/resources/application.yml Ver arquivo

@@ -15,6 +15,9 @@ scheduler:
postCompletedDnGrn:
enabled: false
# receiptDate: # leave unset for production (uses yesterday)
grnCodeSync:
enabled: false # set true in prod; backfills grn_code from M18 GET /root/api/read/an
syncOffsetDays: 0 # 0=today, 1=yesterday (use 1 for 1:20 AM job to backfill prior day)

spring:
servlet:


+ 5
- 0
src/main/resources/db/changelog/changes/20260325_01_fai/01_add_grn_code_to_m18_goods_receipt_note_log.sql Ver arquivo

@@ -0,0 +1,5 @@
--liquibase formatted sql
--changeset fai:add_grn_code_to_m18_goods_receipt_note_log

ALTER TABLE `m18_goods_receipt_note_log`
ADD COLUMN `grn_code` VARCHAR(60) NULL DEFAULT NULL COMMENT 'M18 AN document code (from read/an mainan.code)' AFTER `m18_record_id`;

Carregando…
Cancelar
Salvar