UI: 화면 진입 시 처리결과·거래저널 자동 표시 (서버 감사로그 연동)

- 조회성 서비스는 화면 진입 시 자동 tpcall (실 업무화면처럼 즉시 데이터 표시)
- 거래저널을 서버측 감사로그 cm_log(10,367건, 심화 서비스들이 적재)와 연동:
  이번 세션 호출 + 서버 처리이력 병합, 정상/경고/오류 색상 구분
- 대시보드에 '서비스 처리이력(감사로그)' 패널 추가
- 검증: ACQ_STATUS 자동조회로 처리결과 채워짐(매입 1001: 14,600/30/14,570, 정산완료),
  저널에 ACQUIRE pid=9194·RECONCILE·AUTH_LIMIT·VL_SALES·MG_SEND 실이력 표시

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hyeongwoo-choi 2026-07-21 12:13:29 +00:00
parent c5caef89ca
commit 4cede852a0

View file

@ -29748,7 +29748,17 @@ function jlog(cd, svc, tx, j){
function renderJournalIn(g){ function renderJournalIn(g){
g.innerHTML = '<tr><th>시각</th><th>화면</th><th>서비스</th><th>XA</th><th>결과</th><th>요약</th></tr>' + g.innerHTML = '<tr><th>시각</th><th>화면</th><th>서비스</th><th>XA</th><th>결과</th><th>요약</th></tr>' +
journal.slice(0,30).map(r=>`<tr><td>${r.t}</td><td>${r.cd}</td><td>${r.svc}</td><td>${r.tx}</td> journal.slice(0,30).map(r=>`<tr><td>${r.t}</td><td>${r.cd}</td><td>${r.svc}</td><td>${r.tx}</td>
<td class="${r.ok?'ok':'err'}">${r.ok?'정상':'오류'}</td><td>${r.sum}</td></tr>`).join(''); <td class="${r.ok?'ok':'err'}">${r.ok?'정상':'오류'}</td><td>${r.sum}</td></tr>`).join('')
+ (SRVLOG.length && journal.length<30
? SRVLOG.slice(0, 30-journal.length).map(r=>`<tr><td>${r[3]}</td><td style="color:#889">서버</td><td>${r[0]}</td><td>-</td>
<td class="${r[1]==='E'?'err':'ok'}">${r[1]==='E'?'오류':r[1]==='W'?'경고':'정상'}</td><td>${r[2]}</td></tr>`).join('')
: '');
}
/* 서버측 감사로그(cm_log) — 심화 서비스들이 적재한 실제 처리이력 */
let SRVLOG = [];
async function loadServerJournal(){
const j = await runQuery(`select svc_name, log_level, msg, to_char(created_at,'MM-DD HH24:MI:SS') from cm_log order by log_id desc limit 40`);
if (j.ok) { SRVLOG = j.rows; document.querySelectorAll('.jgrid').forEach(renderJournalIn); }
} }
/* ───────── 조회/현황 화면 정의 ───────── */ /* ───────── 조회/현황 화면 정의 ───────── */
@ -29800,7 +29810,8 @@ async function renderDashIn(pane){
<div style="display:flex; gap:12px"> <div style="display:flex; gap:12px">
<div class="panel" style="flex:1"><h3>당월 가맹점 TOP 7</h3><div class="bd" id="dtop">로딩...</div></div> <div class="panel" style="flex:1"><h3>당월 가맹점 TOP 7</h3><div class="bd" id="dtop">로딩...</div></div>
<div class="panel" style="flex:1"><h3>최근 7영업일 실적</h3><div class="bd" id="ddaily">로딩...</div></div> <div class="panel" style="flex:1"><h3>최근 7영업일 실적</h3><div class="bd" id="ddaily">로딩...</div></div>
</div>`; </div>
<div class="panel"><h3>서비스 처리이력 (감사로그 cm_log)</h3><div class="bd" id="dlog" style="overflow:auto">로딩...</div></div>`;
const k = await runQuery(`select to_char((select max(biz_date) from purchase),'YYYY-MM-DD') 기준일,(select count(*) from purchase where biz_date=(select max(biz_date) from purchase)) 오늘건수,(select coalesce(sum(amount),0) from purchase where biz_date=(select max(biz_date) from purchase)) 오늘매입액,(select count(*) from purchase where date_trunc('month',biz_date)=date_trunc('month',(select max(biz_date) from purchase))) 당월건수,(select coalesce(sum(amount),0) from purchase where date_trunc('month',biz_date)=date_trunc('month',(select max(biz_date) from purchase))) 당월매입액,(select count(*) from merchant where status='ACTIVE') 활성가맹점,(select count(*) from purchase where status in ('A','M','H')) 미정산건`); const k = await runQuery(`select to_char((select max(biz_date) from purchase),'YYYY-MM-DD') 기준일,(select count(*) from purchase where biz_date=(select max(biz_date) from purchase)) 오늘건수,(select coalesce(sum(amount),0) from purchase where biz_date=(select max(biz_date) from purchase)) 오늘매입액,(select count(*) from purchase where date_trunc('month',biz_date)=date_trunc('month',(select max(biz_date) from purchase))) 당월건수,(select coalesce(sum(amount),0) from purchase where date_trunc('month',biz_date)=date_trunc('month',(select max(biz_date) from purchase))) 당월매입액,(select count(*) from merchant where status='ACTIVE') 활성가맹점,(select count(*) from purchase where status in ('A','M','H')) 미정산건`);
document.getElementById('dcards').innerHTML = k.ok document.getElementById('dcards').innerHTML = k.ok
? k.cols.map((n,i)=>`<div class="card"><div class="cl">${n}</div><div class="cv">${fmtN(k.rows[0][i])}</div></div>`).join('') ? k.cols.map((n,i)=>`<div class="card"><div class="cl">${n}</div><div class="cv">${fmtN(k.rows[0][i])}</div></div>`).join('')
@ -29811,6 +29822,9 @@ async function renderDashIn(pane){
grid(document.getElementById('dtop'), t, {psz:7}); grid(document.getElementById('dtop'), t, {psz:7});
const d = await runQuery(`select biz_date 영업일, count(*) 건수, sum(amount) 매입금액, sum(net) 정산액 from purchase group by biz_date order by biz_date desc limit 7`); const d = await runQuery(`select biz_date 영업일, count(*) 건수, sum(amount) 매입금액, sum(net) 정산액 from purchase group by biz_date order by biz_date desc limit 7`);
grid(document.getElementById('ddaily'), d, {psz:7}); grid(document.getElementById('ddaily'), d, {psz:7});
const lg = await runQuery(`select to_char(created_at,'MM-DD HH24:MI:SS') 시각, svc_name 서비스, case log_level when 'E' then '오류' when 'W' then '경고' else '정상' end 구분, msg 내용 from cm_log order by log_id desc limit 40`);
grid(document.getElementById('dlog'), lg, {psz:12, name:'svclog'});
loadServerJournal();
setStatus('대시보드 로드 완료 — 운영 데이터 기준'); setStatus('대시보드 로드 완료 — 운영 데이터 기준');
} }
@ -29900,7 +29914,8 @@ function renderSvcIn(pane, s, nm, note){
<tr><td colspan="2" style="color:#999">실행 전</td></tr></table></div></div> <tr><td colspan="2" style="color:#999">실행 전</td></tr></table></div></div>
<div class="panel"><h3>거래 저널</h3><div class="bd"><table class="grd jgrid"></table></div></div>`; <div class="panel"><h3>거래 저널</h3><div class="bd"><table class="grd jgrid"></table></div></div>`;
renderJournalIn(pane.querySelector('.jgrid')); renderJournalIn(pane.querySelector('.jgrid'));
document.getElementById(`${fid}_run`).onclick = async () => { loadServerJournal();
const doRun = async () => {
const fields = {}; const fields = {};
s.in.forEach((f,i) => { const v = document.getElementById(`${fid}_${i}`).value; if (v!=='') fields[f]=v; }); s.in.forEach((f,i) => { const v = document.getElementById(`${fid}_${i}`).value; if (v!=='') fields[f]=v; });
const tx = document.getElementById(`${fid}_tx`).checked; const tx = document.getElementById(`${fid}_tx`).checked;
@ -29920,6 +29935,9 @@ function renderSvcIn(pane, s, nm, note){
} }
jlog(cd, s.svc, tx, j); jlog(cd, s.svc, tx, j);
}; };
document.getElementById(`${fid}_run`).onclick = doRun;
/* 조회성 서비스는 화면 진입 시 자동 조회 (실 업무화면처럼 즉시 데이터 표시) */
if (READONLY.test(s.svc)) setTimeout(doRun, 60);
} }
/* ───────── 메뉴 ───────── */ /* ───────── 메뉴 ───────── */