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

@@ -31,15 +31,16 @@ interface JournalLogRepository : JpaRepository<JournalLog, Long>
interface TransferRepository : JpaRepository<TransferRecord, String> {
/**
* 거래 원장 원자적 upsert. 신규면 INSERT, 이미 있으면(Dior/타 소비자가 먼저 생성) UPDATE.
* created_at은 최초 값 유지(갱신 제외). Dior-Hermes 동시 삽입 경합에서 중복키 예외 제거.
* created_at은 최초 값 유지(갱신 제외). acsp_at(정산시각)은 Hermes가 기록하고, pdng_at은 SET에서
* 제외해 Dior가 기록한 값을 보존한다. Dior-Hermes 동시 삽입 경합에서 중복키 예외 제거.
*/
@Modifying
@Query(
value = """
INSERT INTO transfer
(bmi, global_seq, status, sender_code, receiver_code, amount, origin_center, created_at, updated_at, reason)
(bmi, global_seq, status, sender_code, receiver_code, amount, origin_center, created_at, updated_at, reason, acsp_at)
VALUES
(:bmi, :seq, :status, :sender, :receiver, :amount, :origin, :createdAt, :updatedAt, :reason)
(:bmi, :seq, :status, :sender, :receiver, :amount, :origin, :createdAt, :updatedAt, :reason, :acspAt)
ON CONFLICT (bmi) DO UPDATE SET
global_seq = EXCLUDED.global_seq,
status = EXCLUDED.status,
@@ -48,7 +49,8 @@ interface TransferRepository : JpaRepository<TransferRecord, String> {
amount = EXCLUDED.amount,
origin_center = EXCLUDED.origin_center,
updated_at = EXCLUDED.updated_at,
reason = EXCLUDED.reason
reason = EXCLUDED.reason,
acsp_at = EXCLUDED.acsp_at
""",
nativeQuery = true,
)
@@ -63,6 +65,7 @@ interface TransferRepository : JpaRepository<TransferRecord, String> {
@Param("createdAt") createdAt: Long,
@Param("updatedAt") updatedAt: Long,
@Param("reason") reason: String?,
@Param("acspAt") acspAt: Long,
): Int
}

View File

@@ -64,6 +64,7 @@ class SettlementApplier(
bmi = e.core.bmi, seq = e.globalSeq, status = status.name,
sender = e.core.senderCode, receiver = e.core.receiverCode, amount = e.core.amount,
origin = e.originCenter, createdAt = e.seqEpochMillis, updatedAt = now, reason = reason,
acspAt = now,
)
journalLogs.findById(e.globalSeq).ifPresent { it.applied = true; journalLogs.save(it) }
return ResultMessage(