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:
rtgs
2026-07-16 13:40:46 +09:00
parent 58ca23b5d9
commit ffbeb35d18
7 changed files with 42 additions and 15 deletions

View File

@@ -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
}

View File

@@ -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)
}
}