feat(ledger): 거래 단계별 처리시각 기록·조회 및 pacs.002 결과통보 조회
- transfer에 pdng_at(Dior)·acsp_at(Hermes) 컬럼 추가(V5), Dior/Hermes가 각 단계 처리시각 기록 - Chanel /inquiry 응답에 접수·순번·기록·정산·완결 5단계 시각(rcvdAt/actcAt/pdngAt/acspAt/acccAt) 노출 - 경합 안전 upsert(Dior COALESCE, Hermes acsp_at 갱신·pdng_at 보존) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -23,17 +23,19 @@ class DiorApplication {
|
||||
|
||||
interface TransferRepository : JpaRepository<TransferRecord, String> {
|
||||
/**
|
||||
* 접수내역을 PDNG로 삽입하되, 이미 존재하면(=Hermes/타 소비자가 먼저 만든 경우) 아무것도 안 함.
|
||||
* 원자적 upsert라 Dior-Hermes 동시 삽입 경합에서 중복키 예외가 발생하지 않는다.
|
||||
* 접수내역을 PDNG로 삽입하되, 이미 존재하면(=Hermes/타 소비자가 먼저 만든 경우) 상태·금액 등은 건드리지 않고
|
||||
* 기록시각(pdng_at)만 비어있을 때 채운다(COALESCE). 원자적 upsert라 Dior-Hermes 동시 삽입 경합에서
|
||||
* 중복키 예외가 발생하지 않으며, 어느 쪽이 먼저 행을 만들어도 pdng_at은 유실 없이 기록된다.
|
||||
*/
|
||||
@Modifying
|
||||
@Query(
|
||||
value = """
|
||||
INSERT INTO transfer
|
||||
(bmi, global_seq, status, sender_code, receiver_code, amount, origin_center, created_at, updated_at)
|
||||
(bmi, global_seq, status, sender_code, receiver_code, amount, origin_center, created_at, updated_at, pdng_at)
|
||||
VALUES
|
||||
(:bmi, :seq, 'PDNG', :sender, :receiver, :amount, :origin, :createdAt, :updatedAt)
|
||||
ON CONFLICT (bmi) DO NOTHING
|
||||
(:bmi, :seq, 'PDNG', :sender, :receiver, :amount, :origin, :createdAt, :updatedAt, :pdngAt)
|
||||
ON CONFLICT (bmi) DO UPDATE SET
|
||||
pdng_at = COALESCE(transfer.pdng_at, EXCLUDED.pdng_at)
|
||||
""",
|
||||
nativeQuery = true,
|
||||
)
|
||||
@@ -46,6 +48,7 @@ interface TransferRepository : JpaRepository<TransferRecord, String> {
|
||||
@Param("origin") origin: String,
|
||||
@Param("createdAt") createdAt: Long,
|
||||
@Param("updatedAt") updatedAt: Long,
|
||||
@Param("pdngAt") pdngAt: Long,
|
||||
): Int
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,9 @@ class DiorService(
|
||||
@Transactional
|
||||
fun onJournal(payload: String) {
|
||||
val e = mapper.readValue(payload, JournalEntry::class.java)
|
||||
// 원자적 upsert: 이미 있으면(Hermes/타 소비자가 먼저 만든 경우) 아무 일도 안 함 → 중복키 예외 없음.
|
||||
val inserted = transfers.insertPdngIfAbsent(
|
||||
val now = System.currentTimeMillis()
|
||||
// 원자적 upsert: 신규면 PDNG로 삽입, 이미 있으면 pdng_at(기록시각)만 채움 → 중복키 예외 없음.
|
||||
transfers.insertPdngIfAbsent(
|
||||
bmi = e.core.bmi,
|
||||
seq = e.globalSeq,
|
||||
sender = e.core.senderCode,
|
||||
@@ -33,9 +34,9 @@ class DiorService(
|
||||
amount = e.core.amount,
|
||||
origin = e.originCenter,
|
||||
createdAt = e.seqEpochMillis,
|
||||
updatedAt = System.currentTimeMillis(),
|
||||
updatedAt = now,
|
||||
pdngAt = now,
|
||||
)
|
||||
if (inserted > 0) log.info("PDNG seq=#{} bmi={}", e.globalSeq, e.core.bmi)
|
||||
else log.debug("PDNG skip(exists) seq=#{} bmi={}", e.globalSeq, e.core.bmi)
|
||||
log.info("PDNG seq=#{} bmi={}", e.globalSeq, e.core.bmi)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user