From ea90bef264502a11f0d6564371ec471b4d12cab6 Mon Sep 17 00:00:00 2001 From: hyeongwoo-choi Date: Mon, 20 Jul 2026 22:09:55 +0000 Subject: [PATCH] =?UTF-8?q?=EA=B7=9C=EB=AA=A8=ED=99=95=EC=9E=A5=20Wave=20A?= =?UTF-8?q?:=20=EC=A0=84=2011=EB=AA=A8=EB=93=88=20svc=2030=E2=86=9275?= =?UTF-8?q?=C2=B7batch=2022=E2=86=9235=20+=20=EC=A0=84=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=9A=B4=EC=98=81=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=8B=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - svc 330→825 (모듈당 +45 고유 실업무: chargeback·재매입·심사워크플로·전문유형별· 대사세분·정산시뮬·지급재처리·복식결산·마감워크플로·마스터심사·검증룰·전문라우팅 등) - batch 242→385 (모듈당 +13), 카피북 헤더·디스패처 advertise 전 75 반영 - db/schema.d/95-module-seed.sql: 전 모듈 테이블 운영데이터(au 8.4k·st 15.9k·mg 16.9k· lg 14.6k·rc 7.7k·py 2.9k·vl 8.5k·cm 10.4k 등, 총 ~171k행) — base 거래와 정합 - 화면 매니페스트 825 재생성, 큐 메모리 튜닝(NDRX_MSGMAX 50→30) - 검증: build DONE modules=11 batches=385, 12서버 runok, xadmin psc 828 AVAIL, 매입체인 XA 커밋(status=S), 새서비스 시드데이터 스모크(ACQ_MERCHSUM/RC_STATS 실값), 전 코퍼스 .pgc 클론 0, 헤드리스 스크린샷 확인 Co-Authored-By: Claude Opus 4.8 (1M context) --- app/build.sh | 15 +- app/conf/app.ini | 2 +- app/src/ac/ac_svr.pgc | 68 + app/src/ac/batch/ac_alert_batch.pgc | 57 + app/src/ac/batch/ac_apprgap_batch.pgc | 62 + app/src/ac/batch/ac_cbaging_batch.pgc | 59 + app/src/ac/batch/ac_ccysum_batch.pgc | 53 + app/src/ac/batch/ac_claimsweep_batch.pgc | 52 + app/src/ac/batch/ac_feewaive_batch.pgc | 48 + app/src/ac/batch/ac_limitwatch_batch.pgc | 68 + app/src/ac/batch/ac_promo_batch.pgc | 54 + app/src/ac/batch/ac_reservepost_batch.pgc | 48 + app/src/ac/batch/ac_settledue_batch.pgc | 56 + app/src/ac/batch/ac_splitpay_batch.pgc | 58 + app/src/ac/batch/ac_taxneg_batch.pgc | 53 + app/src/ac/batch/ac_weekroll_batch.pgc | 61 + app/src/ac/dbio/ac_alert_batch.h | 14 + app/src/ac/dbio/ac_apprgap_batch.h | 15 + app/src/ac/dbio/ac_cbaging_batch.h | 18 + app/src/ac/dbio/ac_ccysum_batch.h | 13 + app/src/ac/dbio/ac_claimsweep_batch.h | 13 + app/src/ac/dbio/ac_feewaive_batch.h | 15 + app/src/ac/dbio/ac_limitwatch_batch.h | 15 + app/src/ac/dbio/ac_promo_batch.h | 15 + app/src/ac/dbio/ac_reservepost_batch.h | 13 + app/src/ac/dbio/ac_settledue_batch.h | 14 + app/src/ac/dbio/ac_splitpay_batch.h | 14 + app/src/ac/dbio/ac_taxneg_batch.h | 14 + app/src/ac/dbio/ac_weekroll_batch.h | 13 + app/src/ac/svc/ACQ_ALERTCHK.h | 23 + app/src/ac/svc/ACQ_ALERTCHK.pgc | 43 + app/src/ac/svc/ACQ_APPRGAP.h | 21 + app/src/ac/svc/ACQ_APPRGAP.pgc | 38 + app/src/ac/svc/ACQ_BULKVERIFY.h | 22 + app/src/ac/svc/ACQ_BULKVERIFY.pgc | 37 + app/src/ac/svc/ACQ_CB_DEFEND.h | 21 + app/src/ac/svc/ACQ_CB_DEFEND.pgc | 40 + app/src/ac/svc/ACQ_CB_OPEN.h | 22 + app/src/ac/svc/ACQ_CB_OPEN.pgc | 43 + app/src/ac/svc/ACQ_CB_QRY.h | 22 + app/src/ac/svc/ACQ_CB_QRY.pgc | 40 + app/src/ac/svc/ACQ_CB_STAT.h | 23 + app/src/ac/svc/ACQ_CB_STAT.pgc | 34 + app/src/ac/svc/ACQ_CCYSUM.h | 23 + app/src/ac/svc/ACQ_CCYSUM.pgc | 40 + app/src/ac/svc/ACQ_CHANSTAT.h | 23 + app/src/ac/svc/ACQ_CHANSTAT.pgc | 45 + app/src/ac/svc/ACQ_CLAIMPROC.h | 21 + app/src/ac/svc/ACQ_CLAIMPROC.pgc | 38 + app/src/ac/svc/ACQ_CLAIMQRY.h | 21 + app/src/ac/svc/ACQ_CLAIMQRY.pgc | 42 + app/src/ac/svc/ACQ_CLAIMREG.h | 21 + app/src/ac/svc/ACQ_CLAIMREG.pgc | 42 + app/src/ac/svc/ACQ_CORRHIST.h | 22 + app/src/ac/svc/ACQ_CORRHIST.pgc | 41 + app/src/ac/svc/ACQ_CTRYSUM.h | 22 + app/src/ac/svc/ACQ_CTRYSUM.pgc | 48 + app/src/ac/svc/ACQ_DAYSTAT.h | 26 + app/src/ac/svc/ACQ_DAYSTAT.pgc | 40 + app/src/ac/svc/ACQ_DUPSCAN.h | 21 + app/src/ac/svc/ACQ_DUPSCAN.pgc | 33 + app/src/ac/svc/ACQ_EDIRESEND.h | 21 + app/src/ac/svc/ACQ_EDIRESEND.pgc | 46 + app/src/ac/svc/ACQ_FEEHIST.h | 22 + app/src/ac/svc/ACQ_FEEHIST.pgc | 32 + app/src/ac/svc/ACQ_FEERECALC.h | 22 + app/src/ac/svc/ACQ_FEERECALC.pgc | 44 + app/src/ac/svc/ACQ_FEEWAIVE.h | 22 + app/src/ac/svc/ACQ_FEEWAIVE.pgc | 43 + app/src/ac/svc/ACQ_FXCONVERT.h | 24 + app/src/ac/svc/ACQ_FXCONVERT.pgc | 43 + app/src/ac/svc/ACQ_FXRATE.h | 22 + app/src/ac/svc/ACQ_FXRATE.pgc | 35 + app/src/ac/svc/ACQ_HOLDQRY.h | 22 + app/src/ac/svc/ACQ_HOLDQRY.pgc | 43 + app/src/ac/svc/ACQ_ISSSTAT.h | 23 + app/src/ac/svc/ACQ_ISSSTAT.pgc | 44 + app/src/ac/svc/ACQ_LIMITUSAGE.h | 23 + app/src/ac/svc/ACQ_LIMITUSAGE.pgc | 37 + app/src/ac/svc/ACQ_MONSTAT.h | 22 + app/src/ac/svc/ACQ_MONSTAT.pgc | 35 + app/src/ac/svc/ACQ_NETCALC.h | 21 + app/src/ac/svc/ACQ_NETCALC.pgc | 46 + app/src/ac/svc/ACQ_NOINTSHARE.h | 23 + app/src/ac/svc/ACQ_NOINTSHARE.pgc | 44 + app/src/ac/svc/ACQ_PARTCXL.h | 23 + app/src/ac/svc/ACQ_PARTCXL.pgc | 51 + app/src/ac/svc/ACQ_PARTRFND.h | 22 + app/src/ac/svc/ACQ_PARTRFND.pgc | 44 + app/src/ac/svc/ACQ_PROMO.h | 22 + app/src/ac/svc/ACQ_PROMO.pgc | 42 + app/src/ac/svc/ACQ_REBUY.h | 22 + app/src/ac/svc/ACQ_REBUY.pgc | 42 + app/src/ac/svc/ACQ_REMATCH.h | 20 + app/src/ac/svc/ACQ_REMATCH.pgc | 43 + app/src/ac/svc/ACQ_RESERVE.h | 22 + app/src/ac/svc/ACQ_RESERVE.pgc | 41 + app/src/ac/svc/ACQ_RESERVECXL.h | 19 + app/src/ac/svc/ACQ_RESERVECXL.pgc | 31 + app/src/ac/svc/ACQ_RESERVEQRY.h | 21 + app/src/ac/svc/ACQ_RESERVEQRY.pgc | 42 + app/src/ac/svc/ACQ_RETURN.h | 22 + app/src/ac/svc/ACQ_RETURN.pgc | 45 + app/src/ac/svc/ACQ_SETTLEDUE.h | 22 + app/src/ac/svc/ACQ_SETTLEDUE.pgc | 36 + app/src/ac/svc/ACQ_SPLITPAY.h | 22 + app/src/ac/svc/ACQ_SPLITPAY.pgc | 43 + app/src/ac/svc/ACQ_SPLITQRY.h | 22 + app/src/ac/svc/ACQ_SPLITQRY.pgc | 43 + app/src/ac/svc/ACQ_TAXCXL.h | 20 + app/src/ac/svc/ACQ_TAXCXL.pgc | 42 + app/src/ac/svc/ACQ_TAXREISSUE.h | 21 + app/src/ac/svc/ACQ_TAXREISSUE.pgc | 39 + app/src/ac/svc/ACQ_TOPMERCH.h | 23 + app/src/ac/svc/ACQ_TOPMERCH.pgc | 44 + app/src/ac/svc/ACQ_VELOCITY.h | 21 + app/src/ac/svc/ACQ_VELOCITY.pgc | 32 + app/src/ac/svc/ACQ_WEEKSTAT.h | 23 + app/src/ac/svc/ACQ_WEEKSTAT.pgc | 44 + app/src/au/au_svr.pgc | 68 + app/src/au/batch/au_blacklist_hit_batch.pgc | 69 + app/src/au/batch/au_cancel_reproc_batch.pgc | 81 + app/src/au/batch/au_delay_watch_batch.pgc | 60 + app/src/au/batch/au_dup_auth_batch.pgc | 65 + app/src/au/batch/au_hourly_stat_batch.pgc | 72 + app/src/au/batch/au_issuer_mix_batch.pgc | 76 + app/src/au/batch/au_limit_restore_batch.pgc | 61 + app/src/au/batch/au_merch_rank_batch.pgc | 71 + app/src/au/batch/au_night_auth_batch.pgc | 63 + app/src/au/batch/au_noauth_detect_batch.pgc | 67 + app/src/au/batch/au_preauth_convert_batch.pgc | 71 + app/src/au/batch/au_standin_settle_batch.pgc | 64 + app/src/au/batch/au_velocity_flag_batch.pgc | 71 + app/src/au/dbio/au_blacklist_hit_batch.h | 18 + app/src/au/dbio/au_cancel_reproc_batch.h | 19 + app/src/au/dbio/au_delay_watch_batch.h | 19 + app/src/au/dbio/au_dup_auth_batch.h | 18 + app/src/au/dbio/au_hourly_stat_batch.h | 19 + app/src/au/dbio/au_issuer_mix_batch.h | 20 + app/src/au/dbio/au_limit_restore_batch.h | 16 + app/src/au/dbio/au_merch_rank_batch.h | 21 + app/src/au/dbio/au_night_auth_batch.h | 19 + app/src/au/dbio/au_noauth_detect_batch.h | 18 + app/src/au/dbio/au_preauth_convert_batch.h | 18 + app/src/au/dbio/au_standin_settle_batch.h | 18 + app/src/au/dbio/au_velocity_flag_batch.h | 18 + app/src/au/svc/APPROVAL_RATE.h | 22 + app/src/au/svc/APPROVAL_RATE.pgc | 42 + app/src/au/svc/AUTH_AMT_ADJUST.h | 19 + app/src/au/svc/AUTH_AMT_ADJUST.pgc | 57 + app/src/au/svc/AUTH_DELAY_MON.h | 22 + app/src/au/svc/AUTH_DELAY_MON.pgc | 39 + app/src/au/svc/AUTH_DUP_CHK.h | 22 + app/src/au/svc/AUTH_DUP_CHK.pgc | 43 + app/src/au/svc/AUTH_EXPIRE_CHK.h | 22 + app/src/au/svc/AUTH_EXPIRE_CHK.pgc | 44 + app/src/au/svc/AUTH_PARTIAL.h | 22 + app/src/au/svc/AUTH_PARTIAL.pgc | 47 + app/src/au/svc/AUTH_RESEND.h | 21 + app/src/au/svc/AUTH_RESEND.pgc | 42 + app/src/au/svc/BIN_ROUTE_INQ.h | 21 + app/src/au/svc/BIN_ROUTE_INQ.pgc | 48 + app/src/au/svc/BLACKLIST_ADD.h | 19 + app/src/au/svc/BLACKLIST_ADD.pgc | 46 + app/src/au/svc/BLACKLIST_DEL.h | 18 + app/src/au/svc/BLACKLIST_DEL.pgc | 39 + app/src/au/svc/BLACKLIST_INQ.h | 21 + app/src/au/svc/BLACKLIST_INQ.pgc | 41 + app/src/au/svc/CANCEL_REPROC.h | 20 + app/src/au/svc/CANCEL_REPROC.pgc | 56 + app/src/au/svc/CANCEL_STATUS.h | 23 + app/src/au/svc/CANCEL_STATUS.pgc | 37 + app/src/au/svc/CARD_DAILY_RPT.h | 26 + app/src/au/svc/CARD_DAILY_RPT.pgc | 46 + app/src/au/svc/CARD_REISSUE.h | 19 + app/src/au/svc/CARD_REISSUE.pgc | 48 + app/src/au/svc/CARD_RESUME.h | 18 + app/src/au/svc/CARD_RESUME.pgc | 42 + app/src/au/svc/CARD_STATE_INQ.h | 24 + app/src/au/svc/CARD_STATE_INQ.pgc | 39 + app/src/au/svc/CARD_SUSPEND.h | 19 + app/src/au/svc/CARD_SUSPEND.pgc | 41 + app/src/au/svc/DECLINE_ANALYZE.h | 22 + app/src/au/svc/DECLINE_ANALYZE.pgc | 54 + app/src/au/svc/FRAUD_CASE_INQ.h | 23 + app/src/au/svc/FRAUD_CASE_INQ.pgc | 42 + app/src/au/svc/FRAUD_RULE_AMT.h | 22 + app/src/au/svc/FRAUD_RULE_AMT.pgc | 51 + app/src/au/svc/FRAUD_RULE_DUP.h | 21 + app/src/au/svc/FRAUD_RULE_DUP.pgc | 55 + app/src/au/svc/FRAUD_RULE_FREQ.h | 19 + app/src/au/svc/FRAUD_RULE_FREQ.pgc | 49 + app/src/au/svc/LIMIT_HIST.h | 22 + app/src/au/svc/LIMIT_HIST.pgc | 49 + app/src/au/svc/LIMIT_RAISE_REQ.h | 21 + app/src/au/svc/LIMIT_RAISE_REQ.pgc | 51 + app/src/au/svc/LIMIT_REMAIN.h | 21 + app/src/au/svc/LIMIT_REMAIN.pgc | 38 + app/src/au/svc/LIMIT_RESTORE.h | 18 + app/src/au/svc/LIMIT_RESTORE.pgc | 41 + app/src/au/svc/LIMIT_USAGE.h | 20 + app/src/au/svc/LIMIT_USAGE.pgc | 40 + app/src/au/svc/MERCH_AUTH_RANK.h | 22 + app/src/au/svc/MERCH_AUTH_RANK.pgc | 49 + app/src/au/svc/NIGHT_POLICY.h | 21 + app/src/au/svc/NIGHT_POLICY.pgc | 45 + app/src/au/svc/NOAUTH_CAPTURE.h | 20 + app/src/au/svc/NOAUTH_CAPTURE.pgc | 43 + app/src/au/svc/OVERSEAS_POLICY.h | 22 + app/src/au/svc/OVERSEAS_POLICY.pgc | 61 + app/src/au/svc/PREAUTH_CANCEL.h | 18 + app/src/au/svc/PREAUTH_CANCEL.pgc | 37 + app/src/au/svc/PREAUTH_EXTEND.h | 19 + app/src/au/svc/PREAUTH_EXTEND.pgc | 44 + app/src/au/svc/PREAUTH_INQ.h | 22 + app/src/au/svc/PREAUTH_INQ.pgc | 36 + app/src/au/svc/RISK_SCORE_INQ.h | 22 + app/src/au/svc/RISK_SCORE_INQ.pgc | 54 + app/src/au/svc/STANDIN_INQ.h | 22 + app/src/au/svc/STANDIN_INQ.pgc | 37 + app/src/au/svc/STANDIN_SETTLE.h | 19 + app/src/au/svc/STANDIN_SETTLE.pgc | 39 + app/src/au/svc/STAT_BYTYPE.h | 24 + app/src/au/svc/STAT_BYTYPE.pgc | 41 + app/src/au/svc/STAT_HOURLY.h | 22 + app/src/au/svc/STAT_HOURLY.pgc | 45 + app/src/au/svc/STAT_ISSUER.h | 22 + app/src/au/svc/STAT_ISSUER.pgc | 59 + app/src/au/svc/TOKEN_VERIFY.h | 20 + app/src/au/svc/TOKEN_VERIFY.pgc | 43 + app/src/au/svc/VELOCITY_HOUR.h | 20 + app/src/au/svc/VELOCITY_HOUR.pgc | 32 + app/src/au/svc/VELOCITY_MERCH.h | 20 + app/src/au/svc/VELOCITY_MERCH.pgc | 41 + app/src/au/svc/VELOCITY_WEEK.h | 21 + app/src/au/svc/VELOCITY_WEEK.pgc | 40 + app/src/cl/batch/cl_apprflow_batch.pgc | 62 + app/src/cl/batch/cl_apprflow_cpy.h | 8 + app/src/cl/batch/cl_audit_batch.pgc | 64 + app/src/cl/batch/cl_audit_cpy.h | 8 + app/src/cl/batch/cl_chnclose_batch.pgc | 54 + app/src/cl/batch/cl_chnclose_cpy.h | 9 + app/src/cl/batch/cl_dupchk_batch.pgc | 57 + app/src/cl/batch/cl_dupchk_cpy.h | 8 + app/src/cl/batch/cl_finalize_batch.pgc | 60 + app/src/cl/batch/cl_finalize_cpy.h | 9 + app/src/cl/batch/cl_gapdays_batch.pgc | 58 + app/src/cl/batch/cl_gapdays_cpy.h | 7 + app/src/cl/batch/cl_halfclose_batch.pgc | 77 + app/src/cl/batch/cl_halfclose_cpy.h | 12 + app/src/cl/batch/cl_holiday_batch.pgc | 60 + app/src/cl/batch/cl_holiday_cpy.h | 8 + app/src/cl/batch/cl_posttrig_batch.pgc | 57 + app/src/cl/batch/cl_posttrig_cpy.h | 7 + app/src/cl/batch/cl_precheck_batch.pgc | 64 + app/src/cl/batch/cl_precheck_cpy.h | 9 + app/src/cl/batch/cl_provclose_batch.pgc | 62 + app/src/cl/batch/cl_provclose_cpy.h | 8 + app/src/cl/batch/cl_slamon_batch.pgc | 62 + app/src/cl/batch/cl_slamon_cpy.h | 9 + app/src/cl/batch/cl_xcheck_batch.pgc | 62 + app/src/cl/batch/cl_xcheck_cpy.h | 10 + app/src/cl/cl_svr.pgc | 70 +- app/src/cl/svc/CL_AMTXCHECK.h | 21 + app/src/cl/svc/CL_AMTXCHECK.pgc | 39 + app/src/cl/svc/CL_APPRHIST.h | 21 + app/src/cl/svc/CL_APPRHIST.pgc | 32 + app/src/cl/svc/CL_APPRREJECT.h | 21 + app/src/cl/svc/CL_APPRREJECT.pgc | 38 + app/src/cl/svc/CL_APPRREQ.h | 21 + app/src/cl/svc/CL_APPRREQ.pgc | 35 + app/src/cl/svc/CL_AUDITTRAIL.h | 21 + app/src/cl/svc/CL_AUDITTRAIL.pgc | 37 + app/src/cl/svc/CL_CALENDAR.h | 21 + app/src/cl/svc/CL_CALENDAR.pgc | 45 + app/src/cl/svc/CL_CHNCLOSE.h | 21 + app/src/cl/svc/CL_CHNCLOSE.pgc | 35 + app/src/cl/svc/CL_CHNVERIFY.h | 21 + app/src/cl/svc/CL_CHNVERIFY.pgc | 34 + app/src/cl/svc/CL_CLOSERATE.h | 21 + app/src/cl/svc/CL_CLOSERATE.pgc | 36 + app/src/cl/svc/CL_CNTXCHECK.h | 21 + app/src/cl/svc/CL_CNTXCHECK.pgc | 37 + app/src/cl/svc/CL_CORPCLOSE.h | 21 + app/src/cl/svc/CL_CORPCLOSE.pgc | 43 + app/src/cl/svc/CL_CXLREASON.h | 21 + app/src/cl/svc/CL_CXLREASON.pgc | 39 + app/src/cl/svc/CL_DELAYLIST.h | 21 + app/src/cl/svc/CL_DELAYLIST.pgc | 38 + app/src/cl/svc/CL_DELAYREG.h | 21 + app/src/cl/svc/CL_DELAYREG.pgc | 36 + app/src/cl/svc/CL_DIVCLOSE.h | 21 + app/src/cl/svc/CL_DIVCLOSE.pgc | 36 + app/src/cl/svc/CL_DUPCHK.h | 21 + app/src/cl/svc/CL_DUPCHK.pgc | 43 + app/src/cl/svc/CL_ESCALATE.h | 21 + app/src/cl/svc/CL_ESCALATE.pgc | 35 + app/src/cl/svc/CL_FEEVERIFY.h | 21 + app/src/cl/svc/CL_FEEVERIFY.pgc | 39 + app/src/cl/svc/CL_FINALIZE.h | 21 + app/src/cl/svc/CL_FINALIZE.pgc | 39 + app/src/cl/svc/CL_FORCECLOSE.h | 21 + app/src/cl/svc/CL_FORCECLOSE.pgc | 37 + app/src/cl/svc/CL_GAPDAYS.h | 21 + app/src/cl/svc/CL_GAPDAYS.pgc | 42 + app/src/cl/svc/CL_HALFCLOSE.h | 21 + app/src/cl/svc/CL_HALFCLOSE.pgc | 45 + app/src/cl/svc/CL_HOLIDAY.h | 21 + app/src/cl/svc/CL_HOLIDAY.pgc | 38 + app/src/cl/svc/CL_LOCKAUDIT.h | 21 + app/src/cl/svc/CL_LOCKAUDIT.pgc | 33 + app/src/cl/svc/CL_LOCKSWEEP.h | 21 + app/src/cl/svc/CL_LOCKSWEEP.pgc | 37 + app/src/cl/svc/CL_MEMOSEARCH.h | 21 + app/src/cl/svc/CL_MEMOSEARCH.pgc | 35 + app/src/cl/svc/CL_MERCLOSE.h | 21 + app/src/cl/svc/CL_MERCLOSE.pgc | 43 + app/src/cl/svc/CL_NETVERIFY.h | 21 + app/src/cl/svc/CL_NETVERIFY.pgc | 35 + app/src/cl/svc/CL_PENDLIST.h | 21 + app/src/cl/svc/CL_PENDLIST.pgc | 36 + app/src/cl/svc/CL_POSTTRIG.h | 21 + app/src/cl/svc/CL_POSTTRIG.pgc | 38 + app/src/cl/svc/CL_PRECHECK.h | 21 + app/src/cl/svc/CL_PRECHECK.pgc | 47 + app/src/cl/svc/CL_PROVCLOSE.h | 21 + app/src/cl/svc/CL_PROVCLOSE.pgc | 42 + app/src/cl/svc/CL_REOPENREQ.h | 21 + app/src/cl/svc/CL_REOPENREQ.pgc | 44 + app/src/cl/svc/CL_RPTCAT.h | 21 + app/src/cl/svc/CL_RPTCAT.pgc | 41 + app/src/cl/svc/CL_RPTCHN.h | 21 + app/src/cl/svc/CL_RPTCHN.pgc | 40 + app/src/cl/svc/CL_RPTISSUER.h | 21 + app/src/cl/svc/CL_RPTISSUER.pgc | 41 + app/src/cl/svc/CL_RPTTOPMER.h | 21 + app/src/cl/svc/CL_RPTTOPMER.pgc | 38 + app/src/cl/svc/CL_SCHEDULE.h | 21 + app/src/cl/svc/CL_SCHEDULE.pgc | 37 + app/src/cl/svc/CL_SEQAUDIT.h | 21 + app/src/cl/svc/CL_SEQAUDIT.pgc | 40 + app/src/cl/svc/CL_SLAMON.h | 21 + app/src/cl/svc/CL_SLAMON.pgc | 38 + app/src/cl/svc/CL_SNAPDIFF.h | 21 + app/src/cl/svc/CL_SNAPDIFF.pgc | 33 + app/src/cl/svc/CL_SNAPXCHECK.h | 21 + app/src/cl/svc/CL_SNAPXCHECK.pgc | 42 + app/src/cl/svc/CL_TREND.h | 21 + app/src/cl/svc/CL_TREND.pgc | 33 + app/src/cl/svc/CL_TRIGSTAT.h | 21 + app/src/cl/svc/CL_TRIGSTAT.pgc | 35 + app/src/cl/svc/CL_YEARPREP.h | 21 + app/src/cl/svc/CL_YEARPREP.pgc | 37 + app/src/cm/batch/cm_codeaudit_batch.pgc | 52 + app/src/cm/batch/cm_fxfill_batch.pgc | 57 + app/src/cm/batch/cm_fxstale_batch.pgc | 56 + app/src/cm/batch/cm_healthsnap_batch.pgc | 54 + app/src/cm/batch/cm_holchk_batch.pgc | 55 + app/src/cm/batch/cm_msgsync_batch.pgc | 63 + app/src/cm/batch/cm_notidigest_batch.pgc | 52 + app/src/cm/batch/cm_reqage_batch.pgc | 56 + app/src/cm/batch/cm_rerunpick_batch.pgc | 56 + app/src/cm/batch/cm_schddue_batch.pgc | 76 + app/src/cm/batch/cm_seqgc_batch.pgc | 56 + app/src/cm/batch/cm_seqreport_batch.pgc | 52 + app/src/cm/batch/cm_warnratio_batch.pgc | 54 + app/src/cm/cm_svr.pgc | 75 +- app/src/cm/dbio/cm_codeaudit_batch.h | 14 + app/src/cm/dbio/cm_fxfill_batch.h | 13 + app/src/cm/dbio/cm_fxstale_batch.h | 17 + app/src/cm/dbio/cm_healthsnap_batch.h | 15 + app/src/cm/dbio/cm_holchk_batch.h | 14 + app/src/cm/dbio/cm_msgsync_batch.h | 15 + app/src/cm/dbio/cm_notidigest_batch.h | 16 + app/src/cm/dbio/cm_reqage_batch.h | 17 + app/src/cm/dbio/cm_rerunpick_batch.h | 14 + app/src/cm/dbio/cm_schddue_batch.h | 15 + app/src/cm/dbio/cm_seqgc_batch.h | 16 + app/src/cm/dbio/cm_seqreport_batch.h | 13 + app/src/cm/dbio/cm_warnratio_batch.h | 18 + app/src/cm/svc/CM_AMT_HANGUL.h | 13 + app/src/cm/svc/CM_AMT_HANGUL.pgc | 59 + app/src/cm/svc/CM_AUDIT_BYLVL.h | 13 + app/src/cm/svc/CM_AUDIT_BYLVL.pgc | 38 + app/src/cm/svc/CM_AUDIT_BYSVC.h | 13 + app/src/cm/svc/CM_AUDIT_BYSVC.pgc | 36 + app/src/cm/svc/CM_AUDIT_RANGE.h | 13 + app/src/cm/svc/CM_AUDIT_RANGE.pgc | 32 + app/src/cm/svc/CM_BATHIST_CNT.h | 13 + app/src/cm/svc/CM_BATHIST_CNT.pgc | 36 + app/src/cm/svc/CM_BATRERUN_REQ.h | 13 + app/src/cm/svc/CM_BATRERUN_REQ.pgc | 39 + app/src/cm/svc/CM_BATSCHD_GET.h | 13 + app/src/cm/svc/CM_BATSCHD_GET.pgc | 27 + app/src/cm/svc/CM_BATSCHD_REG.h | 13 + app/src/cm/svc/CM_BATSCHD_REG.pgc | 36 + app/src/cm/svc/CM_BIZDAY_CNT.h | 13 + app/src/cm/svc/CM_BIZDAY_CNT.pgc | 56 + app/src/cm/svc/CM_CODEGRP_CLOSE.h | 13 + app/src/cm/svc/CM_CODEGRP_CLOSE.pgc | 34 + app/src/cm/svc/CM_CODEGRP_CNT.h | 13 + app/src/cm/svc/CM_CODEGRP_CNT.pgc | 33 + app/src/cm/svc/CM_CODEGRP_HIST.h | 13 + app/src/cm/svc/CM_CODEGRP_HIST.pgc | 33 + app/src/cm/svc/CM_CODEGRP_REG.h | 13 + app/src/cm/svc/CM_CODEGRP_REG.pgc | 39 + app/src/cm/svc/CM_CODEGRP_UPD.h | 13 + app/src/cm/svc/CM_CODEGRP_UPD.pgc | 44 + app/src/cm/svc/CM_DATEDIFF.h | 13 + app/src/cm/svc/CM_DATEDIFF.pgc | 40 + app/src/cm/svc/CM_ENC_HEX.h | 13 + app/src/cm/svc/CM_ENC_HEX.pgc | 50 + app/src/cm/svc/CM_FILE_CKSUM.h | 13 + app/src/cm/svc/CM_FILE_CKSUM.pgc | 30 + app/src/cm/svc/CM_FX_AGE.h | 13 + app/src/cm/svc/CM_FX_AGE.pgc | 35 + app/src/cm/svc/CM_FX_CROSS.h | 13 + app/src/cm/svc/CM_FX_CROSS.pgc | 31 + app/src/cm/svc/CM_FX_HIST.h | 13 + app/src/cm/svc/CM_FX_HIST.pgc | 36 + app/src/cm/svc/CM_FX_PUBLISH.h | 13 + app/src/cm/svc/CM_FX_PUBLISH.pgc | 40 + app/src/cm/svc/CM_FX_VALID.h | 13 + app/src/cm/svc/CM_FX_VALID.pgc | 39 + app/src/cm/svc/CM_HEALTH_DB.h | 13 + app/src/cm/svc/CM_HEALTH_DB.pgc | 31 + app/src/cm/svc/CM_HEALTH_LOG.h | 13 + app/src/cm/svc/CM_HEALTH_LOG.pgc | 36 + app/src/cm/svc/CM_HEALTH_SEQ.h | 13 + app/src/cm/svc/CM_HEALTH_SEQ.pgc | 32 + app/src/cm/svc/CM_HOLIDAY_CNT.h | 13 + app/src/cm/svc/CM_HOLIDAY_CNT.pgc | 35 + app/src/cm/svc/CM_HOLIDAY_DEL.h | 13 + app/src/cm/svc/CM_HOLIDAY_DEL.pgc | 33 + app/src/cm/svc/CM_HOLIDAY_REG.h | 13 + app/src/cm/svc/CM_HOLIDAY_REG.pgc | 39 + app/src/cm/svc/CM_MASK_CARD.h | 13 + app/src/cm/svc/CM_MASK_CARD.pgc | 31 + app/src/cm/svc/CM_MASK_NAME.h | 13 + app/src/cm/svc/CM_MASK_NAME.pgc | 45 + app/src/cm/svc/CM_MASK_PHONE.h | 13 + app/src/cm/svc/CM_MASK_PHONE.pgc | 35 + app/src/cm/svc/CM_MSG_FALLBACK.h | 13 + app/src/cm/svc/CM_MSG_FALLBACK.pgc | 42 + app/src/cm/svc/CM_MSG_GET.h | 13 + app/src/cm/svc/CM_MSG_GET.pgc | 33 + app/src/cm/svc/CM_MSG_SET.h | 13 + app/src/cm/svc/CM_MSG_SET.pgc | 38 + app/src/cm/svc/CM_NOTI_HIST.h | 13 + app/src/cm/svc/CM_NOTI_HIST.pgc | 35 + app/src/cm/svc/CM_NOTI_SEND.h | 13 + app/src/cm/svc/CM_NOTI_SEND.pgc | 33 + app/src/cm/svc/CM_PARAM_APPROVE.h | 13 + app/src/cm/svc/CM_PARAM_APPROVE.pgc | 36 + app/src/cm/svc/CM_PARAM_PENDING.h | 13 + app/src/cm/svc/CM_PARAM_PENDING.pgc | 29 + app/src/cm/svc/CM_PARAM_REJECT.h | 13 + app/src/cm/svc/CM_PARAM_REJECT.pgc | 33 + app/src/cm/svc/CM_PARAM_REQ.h | 13 + app/src/cm/svc/CM_PARAM_REQ.pgc | 31 + app/src/cm/svc/CM_PREVBIZ.h | 13 + app/src/cm/svc/CM_PREVBIZ.pgc | 49 + app/src/cm/svc/CM_SEQ_DAILY.h | 13 + app/src/cm/svc/CM_SEQ_DAILY.pgc | 33 + app/src/cm/svc/CM_SEQ_PEEK.h | 13 + app/src/cm/svc/CM_SEQ_PEEK.pgc | 29 + app/src/cm/svc/CM_SEQ_RESET.h | 13 + app/src/cm/svc/CM_SEQ_RESET.pgc | 42 + app/src/cm/svc/CM_SEQ_TYPED.h | 13 + app/src/cm/svc/CM_SEQ_TYPED.pgc | 36 + app/src/lg/batch/lg_agedpend_batch.pgc | 60 + app/src/lg/batch/lg_baddebt_batch.pgc | 70 + app/src/lg/batch/lg_balproof_batch.pgc | 63 + app/src/lg/batch/lg_closechk_batch.pgc | 70 + app/src/lg/batch/lg_deptpl_batch.pgc | 65 + app/src/lg/batch/lg_dupscan_batch.pgc | 60 + app/src/lg/batch/lg_feerecog_batch.pgc | 74 + app/src/lg/batch/lg_monthclose_batch.pgc | 80 + app/src/lg/batch/lg_payrecon_batch.pgc | 69 + app/src/lg/batch/lg_pendsweep_batch.pgc | 52 + app/src/lg/batch/lg_periodtb_batch.pgc | 61 + app/src/lg/batch/lg_settlerecon_batch.pgc | 76 + app/src/lg/batch/lg_statacct_batch.pgc | 65 + app/src/lg/dbio/lg_agedpend_batch.h | 17 + app/src/lg/dbio/lg_baddebt_batch.h | 15 + app/src/lg/dbio/lg_balproof_batch.h | 15 + app/src/lg/dbio/lg_closechk_batch.h | 17 + app/src/lg/dbio/lg_deptpl_batch.h | 16 + app/src/lg/dbio/lg_dupscan_batch.h | 15 + app/src/lg/dbio/lg_feerecog_batch.h | 15 + app/src/lg/dbio/lg_monthclose_batch.h | 16 + app/src/lg/dbio/lg_payrecon_batch.h | 16 + app/src/lg/dbio/lg_pendsweep_batch.h | 14 + app/src/lg/dbio/lg_periodtb_batch.h | 16 + app/src/lg/dbio/lg_settlerecon_batch.h | 15 + app/src/lg/dbio/lg_statacct_batch.h | 17 + app/src/lg/lg_svr.pgc | 70 +- app/src/lg/svc/LG_ACCRUEREV.h | 21 + app/src/lg/svc/LG_ACCRUEREV.pgc | 56 + app/src/lg/svc/LG_ACCTHIST.h | 21 + app/src/lg/svc/LG_ACCTHIST.pgc | 50 + app/src/lg/svc/LG_ACCTLEDGER.h | 21 + app/src/lg/svc/LG_ACCTLEDGER.pgc | 47 + app/src/lg/svc/LG_ACCTRANGE.h | 21 + app/src/lg/svc/LG_ACCTRANGE.pgc | 38 + app/src/lg/svc/LG_APPROVE.h | 21 + app/src/lg/svc/LG_APPROVE.pgc | 39 + app/src/lg/svc/LG_APPRQUEUE.h | 21 + app/src/lg/svc/LG_APPRQUEUE.pgc | 36 + app/src/lg/svc/LG_BADDEBT.h | 21 + app/src/lg/svc/LG_BADDEBT.pgc | 36 + app/src/lg/svc/LG_BADDEBTRCV.h | 21 + app/src/lg/svc/LG_BADDEBTRCV.pgc | 46 + app/src/lg/svc/LG_BALPROOF.h | 21 + app/src/lg/svc/LG_BALPROOF.pgc | 44 + app/src/lg/svc/LG_BIGLINE.h | 21 + app/src/lg/svc/LG_BIGLINE.pgc | 45 + app/src/lg/svc/LG_BLACKENTRY.h | 21 + app/src/lg/svc/LG_BLACKENTRY.pgc | 58 + app/src/lg/svc/LG_CLOSECHK.h | 21 + app/src/lg/svc/LG_CLOSECHK.pgc | 53 + app/src/lg/svc/LG_DAYCLOSE.h | 21 + app/src/lg/svc/LG_DAYCLOSE.pgc | 48 + app/src/lg/svc/LG_DEPTPL.h | 21 + app/src/lg/svc/LG_DEPTPL.pgc | 47 + app/src/lg/svc/LG_DUPSCAN.h | 21 + app/src/lg/svc/LG_DUPSCAN.pgc | 48 + app/src/lg/svc/LG_FEEACCRUE.h | 21 + app/src/lg/svc/LG_FEEACCRUE.pgc | 41 + app/src/lg/svc/LG_FEERECOG.h | 21 + app/src/lg/svc/LG_FEERECOG.pgc | 43 + app/src/lg/svc/LG_FEESPLIT.h | 21 + app/src/lg/svc/LG_FEESPLIT.pgc | 48 + app/src/lg/svc/LG_LINECOUNT.h | 21 + app/src/lg/svc/LG_LINECOUNT.pgc | 37 + app/src/lg/svc/LG_MASKINQ.h | 21 + app/src/lg/svc/LG_MASKINQ.pgc | 37 + app/src/lg/svc/LG_MEMOAUDIT.h | 21 + app/src/lg/svc/LG_MEMOAUDIT.pgc | 37 + app/src/lg/svc/LG_MERCHPROOF.h | 21 + app/src/lg/svc/LG_MERCHPROOF.pgc | 48 + app/src/lg/svc/LG_MONTHCLOSE.h | 21 + app/src/lg/svc/LG_MONTHCLOSE.pgc | 47 + app/src/lg/svc/LG_NETPOS.h | 21 + app/src/lg/svc/LG_NETPOS.pgc | 26 + app/src/lg/svc/LG_PAYACCRUE.h | 21 + app/src/lg/svc/LG_PAYACCRUE.pgc | 47 + app/src/lg/svc/LG_PAYRECON.h | 21 + app/src/lg/svc/LG_PAYRECON.pgc | 42 + app/src/lg/svc/LG_PENDAGE.h | 21 + app/src/lg/svc/LG_PENDAGE.pgc | 32 + app/src/lg/svc/LG_PENDLIST.h | 21 + app/src/lg/svc/LG_PENDLIST.pgc | 40 + app/src/lg/svc/LG_PERIODTB.h | 21 + app/src/lg/svc/LG_PERIODTB.pgc | 55 + app/src/lg/svc/LG_PLCLOSE.h | 21 + app/src/lg/svc/LG_PLCLOSE.pgc | 63 + app/src/lg/svc/LG_QTRCLOSE.h | 21 + app/src/lg/svc/LG_QTRCLOSE.pgc | 47 + app/src/lg/svc/LG_RECVACCRUE.h | 21 + app/src/lg/svc/LG_RECVACCRUE.pgc | 39 + app/src/lg/svc/LG_REDENTRY.h | 21 + app/src/lg/svc/LG_REDENTRY.pgc | 45 + app/src/lg/svc/LG_REJECT.h | 21 + app/src/lg/svc/LG_REJECT.pgc | 35 + app/src/lg/svc/LG_REOPEN.h | 21 + app/src/lg/svc/LG_REOPEN.pgc | 34 + app/src/lg/svc/LG_SETTLERECON.h | 21 + app/src/lg/svc/LG_SETTLERECON.pgc | 47 + app/src/lg/svc/LG_STATACCT.h | 21 + app/src/lg/svc/LG_STATACCT.pgc | 48 + app/src/lg/svc/LG_STATDC.h | 21 + app/src/lg/svc/LG_STATDC.pgc | 44 + app/src/lg/svc/LG_STATMERCH.h | 21 + app/src/lg/svc/LG_STATMERCH.pgc | 40 + app/src/lg/svc/LG_STATTYPE.h | 21 + app/src/lg/svc/LG_STATTYPE.pgc | 48 + app/src/lg/svc/LG_SUSPENSE.h | 21 + app/src/lg/svc/LG_SUSPENSE.pgc | 50 + app/src/lg/svc/LG_TBDIFF.h | 21 + app/src/lg/svc/LG_TBDIFF.pgc | 43 + app/src/lg/svc/LG_VOIDVOUCHER.h | 21 + app/src/lg/svc/LG_VOIDVOUCHER.pgc | 33 + app/src/lg/svc/LG_VOUCHTRACE.h | 21 + app/src/lg/svc/LG_VOUCHTRACE.pgc | 43 + app/src/lg/svc/LG_ZEROLINE.h | 21 + app/src/lg/svc/LG_ZEROLINE.pgc | 37 + app/src/mg/batch/mg_chanload_batch.pgc | 59 + app/src/mg/batch/mg_dupscan_batch.pgc | 53 + app/src/mg/batch/mg_gapfill_batch.pgc | 55 + app/src/mg/batch/mg_hourstat_batch.pgc | 54 + app/src/mg/batch/mg_keyver_batch.pgc | 53 + app/src/mg/batch/mg_macaudit_batch.pgc | 61 + app/src/mg/batch/mg_mtimix_batch.pgc | 56 + app/src/mg/batch/mg_panaudit_batch.pgc | 63 + app/src/mg/batch/mg_qagesweep_batch.pgc | 50 + app/src/mg/batch/mg_rcdist_batch.pgc | 58 + app/src/mg/batch/mg_reroute_batch.pgc | 45 + app/src/mg/batch/mg_seqsync_batch.pgc | 60 + app/src/mg/batch/mg_timeout_batch.pgc | 49 + app/src/mg/dbio/mg_chanload_batch.h | 10 + app/src/mg/dbio/mg_dupscan_batch.h | 10 + app/src/mg/dbio/mg_gapfill_batch.h | 10 + app/src/mg/dbio/mg_hourstat_batch.h | 10 + app/src/mg/dbio/mg_keyver_batch.h | 10 + app/src/mg/dbio/mg_macaudit_batch.h | 10 + app/src/mg/dbio/mg_mtimix_batch.h | 10 + app/src/mg/dbio/mg_panaudit_batch.h | 10 + app/src/mg/dbio/mg_qagesweep_batch.h | 10 + app/src/mg/dbio/mg_rcdist_batch.h | 10 + app/src/mg/dbio/mg_reroute_batch.h | 10 + app/src/mg/dbio/mg_seqsync_batch.h | 10 + app/src/mg/dbio/mg_timeout_batch.h | 10 + app/src/mg/mg_svr.pgc | 81 +- app/src/mg/svc/MG_ADVICE.h | 12 + app/src/mg/svc/MG_ADVICE.pgc | 39 + app/src/mg/svc/MG_AMTCHECK.h | 12 + app/src/mg/svc/MG_AMTCHECK.pgc | 31 + app/src/mg/svc/MG_AUTHREQ.h | 12 + app/src/mg/svc/MG_AUTHREQ.pgc | 50 + app/src/mg/svc/MG_AUTHRSP.h | 12 + app/src/mg/svc/MG_AUTHRSP.pgc | 34 + app/src/mg/svc/MG_CAPRSP.h | 12 + app/src/mg/svc/MG_CAPRSP.pgc | 35 + app/src/mg/svc/MG_CAPTURE.h | 12 + app/src/mg/svc/MG_CAPTURE.pgc | 35 + app/src/mg/svc/MG_CHANCLOSE.h | 12 + app/src/mg/svc/MG_CHANCLOSE.pgc | 36 + app/src/mg/svc/MG_CHANHEALTH.h | 12 + app/src/mg/svc/MG_CHANHEALTH.pgc | 51 + app/src/mg/svc/MG_CHANOPEN.h | 12 + app/src/mg/svc/MG_CHANOPEN.pgc | 33 + app/src/mg/svc/MG_CHANSWITCH.h | 12 + app/src/mg/svc/MG_CHANSWITCH.pgc | 48 + app/src/mg/svc/MG_DIRECTADPT.h | 12 + app/src/mg/svc/MG_DIRECTADPT.pgc | 45 + app/src/mg/svc/MG_DUPCHK.h | 12 + app/src/mg/svc/MG_DUPCHK.pgc | 35 + app/src/mg/svc/MG_ERRLIST.h | 12 + app/src/mg/svc/MG_ERRLIST.pgc | 33 + app/src/mg/svc/MG_ERRPURGE.h | 12 + app/src/mg/svc/MG_ERRPURGE.pgc | 35 + app/src/mg/svc/MG_ERRRETRY.h | 12 + app/src/mg/svc/MG_ERRRETRY.pgc | 41 + app/src/mg/svc/MG_FROMISO.h | 12 + app/src/mg/svc/MG_FROMISO.pgc | 33 + app/src/mg/svc/MG_GAPSCAN.h | 12 + app/src/mg/svc/MG_GAPSCAN.pgc | 35 + app/src/mg/svc/MG_KEYROTATE.h | 12 + app/src/mg/svc/MG_KEYROTATE.pgc | 30 + app/src/mg/svc/MG_KEYSTATUS.h | 12 + app/src/mg/svc/MG_KEYSTATUS.pgc | 37 + app/src/mg/svc/MG_MACGEN.h | 12 + app/src/mg/svc/MG_MACGEN.pgc | 45 + app/src/mg/svc/MG_MACVERIFY.h | 12 + app/src/mg/svc/MG_MACVERIFY.pgc | 50 + app/src/mg/svc/MG_NETMGMT.h | 12 + app/src/mg/svc/MG_NETMGMT.pgc | 39 + app/src/mg/svc/MG_PANMASK.h | 12 + app/src/mg/svc/MG_PANMASK.pgc | 52 + app/src/mg/svc/MG_QDEPTH.h | 12 + app/src/mg/svc/MG_QDEPTH.pgc | 37 + app/src/mg/svc/MG_REASM.h | 12 + app/src/mg/svc/MG_REASM.pgc | 40 + app/src/mg/svc/MG_REVADVICE.h | 12 + app/src/mg/svc/MG_REVADVICE.pgc | 41 + app/src/mg/svc/MG_REVERSAL.h | 12 + app/src/mg/svc/MG_REVERSAL.pgc | 39 + app/src/mg/svc/MG_RTADD.h | 12 + app/src/mg/svc/MG_RTADD.pgc | 35 + app/src/mg/svc/MG_RTDROP.h | 12 + app/src/mg/svc/MG_RTDROP.pgc | 30 + app/src/mg/svc/MG_RTLIST.h | 12 + app/src/mg/svc/MG_RTLIST.pgc | 47 + app/src/mg/svc/MG_RTSWITCH.h | 12 + app/src/mg/svc/MG_RTSWITCH.pgc | 37 + app/src/mg/svc/MG_SEQAUDIT.h | 12 + app/src/mg/svc/MG_SEQAUDIT.pgc | 40 + app/src/mg/svc/MG_SETTLEREQ.h | 12 + app/src/mg/svc/MG_SETTLEREQ.pgc | 43 + app/src/mg/svc/MG_SETTLERSP.h | 12 + app/src/mg/svc/MG_SETTLERSP.pgc | 50 + app/src/mg/svc/MG_STATDIR.h | 12 + app/src/mg/svc/MG_STATDIR.pgc | 38 + app/src/mg/svc/MG_STATHOUR.h | 12 + app/src/mg/svc/MG_STATHOUR.pgc | 42 + app/src/mg/svc/MG_STATMTI.h | 12 + app/src/mg/svc/MG_STATMTI.pgc | 37 + app/src/mg/svc/MG_STATRC.h | 12 + app/src/mg/svc/MG_STATRC.pgc | 49 + app/src/mg/svc/MG_THROTTLE.h | 12 + app/src/mg/svc/MG_THROTTLE.pgc | 42 + app/src/mg/svc/MG_TIMEOUT.h | 12 + app/src/mg/svc/MG_TIMEOUT.pgc | 36 + app/src/mg/svc/MG_TIMEOUTSET.h | 12 + app/src/mg/svc/MG_TIMEOUTSET.pgc | 31 + app/src/mg/svc/MG_TOISO.h | 12 + app/src/mg/svc/MG_TOISO.pgc | 44 + app/src/mg/svc/MG_TPSCHECK.h | 12 + app/src/mg/svc/MG_TPSCHECK.pgc | 38 + app/src/mg/svc/MG_VAN1ADPT.h | 12 + app/src/mg/svc/MG_VAN1ADPT.pgc | 39 + app/src/mg/svc/MG_VAN2ADPT.h | 12 + app/src/mg/svc/MG_VAN2ADPT.pgc | 45 + app/src/mm/batch/mm_acct_reverify_batch.pgc | 63 + app/src/mm/batch/mm_acct_reverify_cpy.h | 5 + app/src/mm/batch/mm_apply_expire_batch.pgc | 55 + app/src/mm/batch/mm_apply_expire_cpy.h | 6 + app/src/mm/batch/mm_audit_compact_batch.pgc | 65 + app/src/mm/batch/mm_audit_compact_cpy.h | 7 + app/src/mm/batch/mm_bizno_audit_batch.pgc | 68 + app/src/mm/batch/mm_bizno_audit_cpy.h | 5 + app/src/mm/batch/mm_close_sweep_batch.pgc | 47 + app/src/mm/batch/mm_close_sweep_cpy.h | 6 + app/src/mm/batch/mm_codemap_gc_batch.pgc | 46 + app/src/mm/batch/mm_codemap_gc_cpy.h | 4 + app/src/mm/batch/mm_consist_report_batch.pgc | 63 + app/src/mm/batch/mm_consist_report_cpy.h | 8 + app/src/mm/batch/mm_contract_renew_batch.pgc | 62 + app/src/mm/batch/mm_contract_renew_cpy.h | 7 + app/src/mm/batch/mm_grade_demote_batch.pgc | 76 + app/src/mm/batch/mm_grade_demote_cpy.h | 8 + app/src/mm/batch/mm_mcc_reclass_batch.pgc | 56 + app/src/mm/batch/mm_mcc_reclass_cpy.h | 8 + app/src/mm/batch/mm_partner_sync_batch.pgc | 58 + app/src/mm/batch/mm_partner_sync_cpy.h | 5 + app/src/mm/batch/mm_rel_orphan_batch.pgc | 55 + app/src/mm/batch/mm_rel_orphan_cpy.h | 5 + app/src/mm/batch/mm_term_scrap_batch.pgc | 54 + app/src/mm/batch/mm_term_scrap_cpy.h | 6 + app/src/mm/mm_svr.pgc | 92 +- app/src/mm/svc/MM_ACCT_VERIFY.h | 12 + app/src/mm/svc/MM_ACCT_VERIFY.pgc | 42 + app/src/mm/svc/MM_ACCT_VRF_HIST.h | 12 + app/src/mm/svc/MM_ACCT_VRF_HIST.pgc | 39 + app/src/mm/svc/MM_ACCT_VRF_STAT.h | 12 + app/src/mm/svc/MM_ACCT_VRF_STAT.pgc | 28 + app/src/mm/svc/MM_APPLY_APPROVE.h | 12 + app/src/mm/svc/MM_APPLY_APPROVE.pgc | 43 + app/src/mm/svc/MM_APPLY_REG.h | 12 + app/src/mm/svc/MM_APPLY_REG.pgc | 45 + app/src/mm/svc/MM_APPLY_REJECT.h | 12 + app/src/mm/svc/MM_APPLY_REJECT.pgc | 36 + app/src/mm/svc/MM_APPLY_REVIEW.h | 12 + app/src/mm/svc/MM_APPLY_REVIEW.pgc | 40 + app/src/mm/svc/MM_APPLY_STATUS.h | 12 + app/src/mm/svc/MM_APPLY_STATUS.pgc | 42 + app/src/mm/svc/MM_AUDIT_PURGE.h | 12 + app/src/mm/svc/MM_AUDIT_PURGE.pgc | 36 + app/src/mm/svc/MM_AUDIT_RECENT.h | 12 + app/src/mm/svc/MM_AUDIT_RECENT.pgc | 35 + app/src/mm/svc/MM_AUDIT_TRAIL.h | 12 + app/src/mm/svc/MM_AUDIT_TRAIL.pgc | 35 + app/src/mm/svc/MM_BIZNO_CHECK.h | 12 + app/src/mm/svc/MM_BIZNO_CHECK.pgc | 44 + app/src/mm/svc/MM_BIZNO_UPD.h | 12 + app/src/mm/svc/MM_BIZNO_UPD.pgc | 46 + app/src/mm/svc/MM_CHK_FEERATE.h | 12 + app/src/mm/svc/MM_CHK_FEERATE.pgc | 37 + app/src/mm/svc/MM_CHK_LIMIT.h | 12 + app/src/mm/svc/MM_CHK_LIMIT.pgc | 31 + app/src/mm/svc/MM_CHK_TERM.h | 12 + app/src/mm/svc/MM_CHK_TERM.pgc | 30 + app/src/mm/svc/MM_CODEMAP_DEL.h | 12 + app/src/mm/svc/MM_CODEMAP_DEL.pgc | 37 + app/src/mm/svc/MM_CODEMAP_GET.h | 12 + app/src/mm/svc/MM_CODEMAP_GET.pgc | 36 + app/src/mm/svc/MM_CODEMAP_SET.h | 12 + app/src/mm/svc/MM_CODEMAP_SET.pgc | 33 + app/src/mm/svc/MM_CONTRACT_END.h | 12 + app/src/mm/svc/MM_CONTRACT_END.pgc | 43 + app/src/mm/svc/MM_CONTRACT_INQ.h | 12 + app/src/mm/svc/MM_CONTRACT_INQ.pgc | 37 + app/src/mm/svc/MM_CONTRACT_NEW.h | 12 + app/src/mm/svc/MM_CONTRACT_NEW.pgc | 44 + app/src/mm/svc/MM_CONTRACT_RENEW.h | 12 + app/src/mm/svc/MM_CONTRACT_RENEW.pgc | 47 + app/src/mm/svc/MM_GRADE_ADJUST.h | 12 + app/src/mm/svc/MM_GRADE_ADJUST.pgc | 38 + app/src/mm/svc/MM_GRADE_DIST.h | 12 + app/src/mm/svc/MM_GRADE_DIST.pgc | 42 + app/src/mm/svc/MM_GRADE_PREVIEW.h | 12 + app/src/mm/svc/MM_GRADE_PREVIEW.pgc | 43 + app/src/mm/svc/MM_MCC_INQ.h | 12 + app/src/mm/svc/MM_MCC_INQ.pgc | 36 + app/src/mm/svc/MM_MCC_RISKLIST.h | 12 + app/src/mm/svc/MM_MCC_RISKLIST.pgc | 36 + app/src/mm/svc/MM_MCC_UPD.h | 12 + app/src/mm/svc/MM_MCC_UPD.pgc | 37 + app/src/mm/svc/MM_MERCH_CLOSE.h | 12 + app/src/mm/svc/MM_MERCH_CLOSE.pgc | 46 + app/src/mm/svc/MM_MERCH_PAUSE.h | 12 + app/src/mm/svc/MM_MERCH_PAUSE.pgc | 40 + app/src/mm/svc/MM_MERCH_RESUME.h | 12 + app/src/mm/svc/MM_MERCH_RESUME.pgc | 38 + app/src/mm/svc/MM_PARTNER_INQ.h | 12 + app/src/mm/svc/MM_PARTNER_INQ.pgc | 32 + app/src/mm/svc/MM_PARTNER_REG.h | 12 + app/src/mm/svc/MM_PARTNER_REG.pgc | 33 + app/src/mm/svc/MM_PARTNER_STATUS.h | 12 + app/src/mm/svc/MM_PARTNER_STATUS.pgc | 32 + app/src/mm/svc/MM_RATE_APPROVE.h | 12 + app/src/mm/svc/MM_RATE_APPROVE.pgc | 47 + app/src/mm/svc/MM_RATE_REJECT.h | 12 + app/src/mm/svc/MM_RATE_REJECT.pgc | 37 + app/src/mm/svc/MM_RATE_REQ.h | 12 + app/src/mm/svc/MM_RATE_REQ.pgc | 51 + app/src/mm/svc/MM_REL_LINK.h | 12 + app/src/mm/svc/MM_REL_LINK.pgc | 48 + app/src/mm/svc/MM_REL_LIST.h | 12 + app/src/mm/svc/MM_REL_LIST.pgc | 33 + app/src/mm/svc/MM_REL_UNLINK.h | 12 + app/src/mm/svc/MM_REL_UNLINK.pgc | 32 + app/src/mm/svc/MM_TERM_CNT.h | 12 + app/src/mm/svc/MM_TERM_CNT.pgc | 33 + app/src/mm/svc/MM_TERM_MOVE.h | 12 + app/src/mm/svc/MM_TERM_MOVE.pgc | 39 + app/src/mm/svc/MM_TERM_SCRAP.h | 12 + app/src/mm/svc/MM_TERM_SCRAP.pgc | 34 + app/src/mm/svc/MM_TERM_SUSPEND.h | 12 + app/src/mm/svc/MM_TERM_SUSPEND.pgc | 36 + app/src/py/batch/py_acctsync_batch.pgc | 54 + app/src/py/batch/py_acctsync_cpy.h | 6 + app/src/py/batch/py_advrepay_batch.pgc | 63 + app/src/py/batch/py_advrepay_cpy.h | 6 + app/src/py/batch/py_bankfile_batch.pgc | 66 + app/src/py/batch/py_bankfile_cpy.h | 7 + app/src/py/batch/py_dormantstop_batch.pgc | 59 + app/src/py/batch/py_dormantstop_cpy.h | 6 + app/src/py/batch/py_failstat_batch.pgc | 55 + app/src/py/batch/py_failstat_cpy.h | 6 + app/src/py/batch/py_feewaive_batch.pgc | 50 + app/src/py/batch/py_feewaive_cpy.h | 5 + app/src/py/batch/py_holdaging_batch.pgc | 48 + app/src/py/batch/py_holdaging_cpy.h | 6 + app/src/py/batch/py_limitguard_batch.pgc | 53 + app/src/py/batch/py_limitguard_cpy.h | 6 + app/src/py/batch/py_mergesmall_batch.pgc | 60 + app/src/py/batch/py_mergesmall_cpy.h | 7 + app/src/py/batch/py_notify_batch.pgc | 47 + app/src/py/batch/py_notify_cpy.h | 6 + app/src/py/batch/py_schedexpire_batch.pgc | 39 + app/src/py/batch/py_schedexpire_cpy.h | 5 + app/src/py/batch/py_seizescan_batch.pgc | 43 + app/src/py/batch/py_seizescan_cpy.h | 5 + app/src/py/batch/py_xferrecon_batch.pgc | 52 + app/src/py/batch/py_xferrecon_cpy.h | 5 + app/src/py/py_svr.pgc | 70 +- app/src/py/svc/PAY_ACCTCHANGE.h | 19 + app/src/py/svc/PAY_ACCTCHANGE.pgc | 42 + app/src/py/svc/PAY_ACCTLIST.h | 21 + app/src/py/svc/PAY_ACCTLIST.pgc | 46 + app/src/py/svc/PAY_ADVCALC.h | 20 + app/src/py/svc/PAY_ADVCALC.pgc | 39 + app/src/py/svc/PAY_ADVREPAY.h | 19 + app/src/py/svc/PAY_ADVREPAY.pgc | 47 + app/src/py/svc/PAY_APPRBULK.h | 19 + app/src/py/svc/PAY_APPRBULK.pgc | 35 + app/src/py/svc/PAY_APPRLIMIT.h | 19 + app/src/py/svc/PAY_APPRLIMIT.pgc | 48 + app/src/py/svc/PAY_APPRREQ.h | 19 + app/src/py/svc/PAY_APPRREQ.pgc | 42 + app/src/py/svc/PAY_BANKFILE.h | 20 + app/src/py/svc/PAY_BANKFILE.pgc | 48 + app/src/py/svc/PAY_CANCELHIST.h | 21 + app/src/py/svc/PAY_CANCELHIST.pgc | 35 + app/src/py/svc/PAY_DORMANT.h | 19 + app/src/py/svc/PAY_DORMANT.pgc | 33 + app/src/py/svc/PAY_DORMSTOP.h | 19 + app/src/py/svc/PAY_DORMSTOP.pgc | 39 + app/src/py/svc/PAY_FAILRATE.h | 20 + app/src/py/svc/PAY_FAILRATE.pgc | 35 + app/src/py/svc/PAY_FAILSTAT.h | 21 + app/src/py/svc/PAY_FAILSTAT.pgc | 46 + app/src/py/svc/PAY_FAILTOP.h | 19 + app/src/py/svc/PAY_FAILTOP.pgc | 37 + app/src/py/svc/PAY_FEEMIN.h | 19 + app/src/py/svc/PAY_FEEMIN.pgc | 33 + app/src/py/svc/PAY_FEERATE.h | 20 + app/src/py/svc/PAY_FEERATE.pgc | 38 + app/src/py/svc/PAY_FEEWAIVE.h | 19 + app/src/py/svc/PAY_FEEWAIVE.pgc | 39 + app/src/py/svc/PAY_FILEREBUILD.h | 19 + app/src/py/svc/PAY_FILEREBUILD.pgc | 36 + app/src/py/svc/PAY_FILESTAT.h | 19 + app/src/py/svc/PAY_FILESTAT.pgc | 32 + app/src/py/svc/PAY_FILEVERIFY.h | 21 + app/src/py/svc/PAY_FILEVERIFY.pgc | 38 + app/src/py/svc/PAY_HOLDAGING.h | 19 + app/src/py/svc/PAY_HOLDAGING.pgc | 33 + app/src/py/svc/PAY_HOLDLIST.h | 21 + app/src/py/svc/PAY_HOLDLIST.pgc | 44 + app/src/py/svc/PAY_HOLDREASON.h | 18 + app/src/py/svc/PAY_HOLDREASON.pgc | 33 + app/src/py/svc/PAY_LEDGERSUM.h | 21 + app/src/py/svc/PAY_LEDGERSUM.pgc | 46 + app/src/py/svc/PAY_LIMITCHK.h | 20 + app/src/py/svc/PAY_LIMITCHK.pgc | 39 + app/src/py/svc/PAY_LIMITHOLD.h | 19 + app/src/py/svc/PAY_LIMITHOLD.pgc | 43 + app/src/py/svc/PAY_MERGE.h | 20 + app/src/py/svc/PAY_MERGE.pgc | 47 + app/src/py/svc/PAY_NETRECALC.h | 20 + app/src/py/svc/PAY_NETRECALC.pgc | 40 + app/src/py/svc/PAY_NOTIFYEMAIL.h | 19 + app/src/py/svc/PAY_NOTIFYEMAIL.pgc | 38 + app/src/py/svc/PAY_NOTIFYPLAN.h | 19 + app/src/py/svc/PAY_NOTIFYPLAN.pgc | 29 + app/src/py/svc/PAY_NOTIFYSMS.h | 18 + app/src/py/svc/PAY_NOTIFYSMS.pgc | 33 + app/src/py/svc/PAY_REFUND.h | 19 + app/src/py/svc/PAY_REFUND.pgc | 46 + app/src/py/svc/PAY_REPAYNEW.h | 19 + app/src/py/svc/PAY_REPAYNEW.pgc | 46 + app/src/py/svc/PAY_SCHEDCANCEL.h | 19 + app/src/py/svc/PAY_SCHEDCANCEL.pgc | 34 + app/src/py/svc/PAY_SEIZE.h | 19 + app/src/py/svc/PAY_SEIZE.pgc | 41 + app/src/py/svc/PAY_SEIZECHK.h | 19 + app/src/py/svc/PAY_SEIZECHK.pgc | 34 + app/src/py/svc/PAY_SEIZEREL.h | 18 + app/src/py/svc/PAY_SEIZEREL.pgc | 36 + app/src/py/svc/PAY_SPLITRATIO.h | 20 + app/src/py/svc/PAY_SPLITRATIO.pgc | 53 + app/src/py/svc/PAY_STATBANK.h | 21 + app/src/py/svc/PAY_STATBANK.pgc | 43 + app/src/py/svc/PAY_STATCHAN.h | 21 + app/src/py/svc/PAY_STATCHAN.pgc | 43 + app/src/py/svc/PAY_STATDAILY.h | 21 + app/src/py/svc/PAY_STATDAILY.pgc | 50 + app/src/py/svc/PAY_STATRANGE.h | 20 + app/src/py/svc/PAY_STATRANGE.pgc | 32 + app/src/py/svc/PAY_URGENT.h | 21 + app/src/py/svc/PAY_URGENT.pgc | 46 + app/src/py/svc/PAY_XFERMATCH.h | 19 + app/src/py/svc/PAY_XFERMATCH.pgc | 35 + app/src/py/svc/PAY_XFERMISS.h | 19 + app/src/py/svc/PAY_XFERMISS.pgc | 36 + app/src/rc/batch/rc_ageing_batch.pgc | 54 + app/src/rc/batch/rc_cancelrec_batch.pgc | 44 + app/src/rc/batch/rc_carryover_batch.pgc | 50 + app/src/rc/batch/rc_closeday_batch.pgc | 71 + app/src/rc/batch/rc_dupchk_batch.pgc | 60 + app/src/rc/batch/rc_feerecon_batch.pgc | 58 + app/src/rc/batch/rc_fxrecon_batch.pgc | 58 + app/src/rc/batch/rc_holdrsn_batch.pgc | 57 + app/src/rc/batch/rc_notify_batch.pgc | 56 + app/src/rc/batch/rc_orphan_batch.pgc | 53 + app/src/rc/batch/rc_ratestat_batch.pgc | 57 + app/src/rc/batch/rc_retro_batch.pgc | 51 + app/src/rc/batch/rc_threshold_batch.pgc | 52 + app/src/rc/dbio/rc_ageing_batch.h | 6 + app/src/rc/dbio/rc_cancelrec_batch.h | 6 + app/src/rc/dbio/rc_carryover_batch.h | 6 + app/src/rc/dbio/rc_closeday_batch.h | 6 + app/src/rc/dbio/rc_dupchk_batch.h | 6 + app/src/rc/dbio/rc_feerecon_batch.h | 6 + app/src/rc/dbio/rc_fxrecon_batch.h | 6 + app/src/rc/dbio/rc_holdrsn_batch.h | 6 + app/src/rc/dbio/rc_notify_batch.h | 6 + app/src/rc/dbio/rc_orphan_batch.h | 6 + app/src/rc/dbio/rc_ratestat_batch.h | 6 + app/src/rc/dbio/rc_retro_batch.h | 6 + app/src/rc/dbio/rc_threshold_batch.h | 7 + app/src/rc/rc_svr.pgc | 72 +- app/src/rc/svc/RC_4WAY.h | 21 + app/src/rc/svc/RC_4WAY.pgc | 42 + app/src/rc/svc/RC_AGEING.h | 19 + app/src/rc/svc/RC_AGEING.pgc | 40 + app/src/rc/svc/RC_AUDITLOG.h | 19 + app/src/rc/svc/RC_AUDITLOG.pgc | 37 + app/src/rc/svc/RC_AUDITTRAIL.h | 19 + app/src/rc/svc/RC_AUDITTRAIL.pgc | 37 + app/src/rc/svc/RC_BYCATEGORY.h | 20 + app/src/rc/svc/RC_BYCATEGORY.pgc | 44 + app/src/rc/svc/RC_BYCHANNEL.h | 20 + app/src/rc/svc/RC_BYCHANNEL.pgc | 37 + app/src/rc/svc/RC_BYMGROUP.h | 19 + app/src/rc/svc/RC_BYMGROUP.pgc | 35 + app/src/rc/svc/RC_CANCELREC.h | 20 + app/src/rc/svc/RC_CANCELREC.pgc | 38 + app/src/rc/svc/RC_CARRYOVER.h | 18 + app/src/rc/svc/RC_CARRYOVER.pgc | 36 + app/src/rc/svc/RC_CLOSEDAY.h | 20 + app/src/rc/svc/RC_CLOSEDAY.pgc | 54 + app/src/rc/svc/RC_DEPMATCH.h | 19 + app/src/rc/svc/RC_DEPMATCH.pgc | 38 + app/src/rc/svc/RC_DIFFCAUSE.h | 19 + app/src/rc/svc/RC_DIFFCAUSE.pgc | 45 + app/src/rc/svc/RC_DIFFTREND.h | 19 + app/src/rc/svc/RC_DIFFTREND.pgc | 37 + app/src/rc/svc/RC_DUPCHK.h | 17 + app/src/rc/svc/RC_DUPCHK.pgc | 38 + app/src/rc/svc/RC_EDIRECON.h | 20 + app/src/rc/svc/RC_EDIRECON.pgc | 46 + app/src/rc/svc/RC_ESCALATE.h | 17 + app/src/rc/svc/RC_ESCALATE.pgc | 30 + app/src/rc/svc/RC_FEEDIFF.h | 18 + app/src/rc/svc/RC_FEEDIFF.pgc | 34 + app/src/rc/svc/RC_FEERECON.h | 20 + app/src/rc/svc/RC_FEERECON.pgc | 46 + app/src/rc/svc/RC_FXRATECHK.h | 20 + app/src/rc/svc/RC_FXRATECHK.pgc | 36 + app/src/rc/svc/RC_FXRECON.h | 20 + app/src/rc/svc/RC_FXRECON.pgc | 47 + app/src/rc/svc/RC_GAPSCAN.h | 18 + app/src/rc/svc/RC_GAPSCAN.pgc | 33 + app/src/rc/svc/RC_HOLDAGE.h | 18 + app/src/rc/svc/RC_HOLDAGE.pgc | 33 + app/src/rc/svc/RC_HOLDRSN.h | 19 + app/src/rc/svc/RC_HOLDRSN.pgc | 36 + app/src/rc/svc/RC_INSTRECON.h | 20 + app/src/rc/svc/RC_INSTRECON.pgc | 39 + app/src/rc/svc/RC_LIMITCHK.h | 19 + app/src/rc/svc/RC_LIMITCHK.pgc | 45 + app/src/rc/svc/RC_MANAPPRV.h | 19 + app/src/rc/svc/RC_MANAPPRV.pgc | 41 + app/src/rc/svc/RC_MANREG.h | 19 + app/src/rc/svc/RC_MANREG.pgc | 36 + app/src/rc/svc/RC_MANREJECT.h | 19 + app/src/rc/svc/RC_MANREJECT.pgc | 37 + app/src/rc/svc/RC_NOTIFY.h | 20 + app/src/rc/svc/RC_NOTIFY.pgc | 40 + app/src/rc/svc/RC_ORPHAN.h | 17 + app/src/rc/svc/RC_ORPHAN.pgc | 31 + app/src/rc/svc/RC_PARTACQ.h | 20 + app/src/rc/svc/RC_PARTACQ.pgc | 50 + app/src/rc/svc/RC_PAYMATCH.h | 20 + app/src/rc/svc/RC_PAYMATCH.pgc | 45 + app/src/rc/svc/RC_RATEBYISS.h | 20 + app/src/rc/svc/RC_RATEBYISS.pgc | 41 + app/src/rc/svc/RC_RATESTAT.h | 19 + app/src/rc/svc/RC_RATESTAT.pgc | 36 + app/src/rc/svc/RC_REOPEN.h | 17 + app/src/rc/svc/RC_REOPEN.pgc | 36 + app/src/rc/svc/RC_RERUN.h | 20 + app/src/rc/svc/RC_RERUN.pgc | 44 + app/src/rc/svc/RC_RETRO.h | 19 + app/src/rc/svc/RC_RETRO.pgc | 42 + app/src/rc/svc/RC_SNAPSHOT.h | 20 + app/src/rc/svc/RC_SNAPSHOT.pgc | 41 + app/src/rc/svc/RC_SUMCHK.h | 19 + app/src/rc/svc/RC_SUMCHK.pgc | 35 + app/src/rc/svc/RC_TAXRECON.h | 20 + app/src/rc/svc/RC_TAXRECON.pgc | 47 + app/src/rc/svc/RC_THRESHOLD.h | 18 + app/src/rc/svc/RC_THRESHOLD.pgc | 37 + app/src/rc/svc/RC_TOPDIFF.h | 19 + app/src/rc/svc/RC_TOPDIFF.pgc | 36 + app/src/rc/svc/RC_VOIDREC.h | 19 + app/src/rc/svc/RC_VOIDREC.pgc | 39 + app/src/rc/svc/RC_WHTRECON.h | 19 + app/src/rc/svc/RC_WHTRECON.pgc | 38 + app/src/rc/svc/RC_ZEROAMT.h | 17 + app/src/rc/svc/RC_ZEROAMT.pgc | 30 + app/src/st/batch/st_approve_batch.pgc | 48 + app/src/st/batch/st_deposit_batch.pgc | 47 + app/src/st/batch/st_grproll_batch.pgc | 54 + app/src/st/batch/st_incent_batch.pgc | 58 + app/src/st/batch/st_partial_batch.pgc | 54 + app/src/st/batch/st_promo_batch.pgc | 43 + app/src/st/batch/st_ratesim_batch.pgc | 58 + app/src/st/batch/st_recvsweep_batch.pgc | 62 + app/src/st/batch/st_rerun_batch.pgc | 43 + app/src/st/batch/st_retrorate_batch.pgc | 57 + app/src/st/batch/st_vatreport_batch.pgc | 52 + app/src/st/batch/st_weekly_batch.pgc | 42 + app/src/st/batch/st_whtreport_batch.pgc | 51 + app/src/st/dbio/st_approve_batch.h | 6 + app/src/st/dbio/st_deposit_batch.h | 7 + app/src/st/dbio/st_grproll_batch.h | 7 + app/src/st/dbio/st_incent_batch.h | 6 + app/src/st/dbio/st_partial_batch.h | 6 + app/src/st/dbio/st_promo_batch.h | 7 + app/src/st/dbio/st_ratesim_batch.h | 6 + app/src/st/dbio/st_recvsweep_batch.h | 6 + app/src/st/dbio/st_rerun_batch.h | 6 + app/src/st/dbio/st_retrorate_batch.h | 7 + app/src/st/dbio/st_vatreport_batch.h | 6 + app/src/st/dbio/st_weekly_batch.h | 6 + app/src/st/dbio/st_whtreport_batch.h | 6 + app/src/st/st_svr.pgc | 72 +- app/src/st/svc/ST_ADJHIST.h | 19 + app/src/st/svc/ST_ADJHIST.pgc | 39 + app/src/st/svc/ST_APPROVE.h | 18 + app/src/st/svc/ST_APPROVE.pgc | 37 + app/src/st/svc/ST_CAPCHECK.h | 18 + app/src/st/svc/ST_CAPCHECK.pgc | 26 + app/src/st/svc/ST_CARRYOVER.h | 20 + app/src/st/svc/ST_CARRYOVER.pgc | 37 + app/src/st/svc/ST_CHNLSTAT.h | 20 + app/src/st/svc/ST_CHNLSTAT.pgc | 46 + app/src/st/svc/ST_CXLRECALC.h | 18 + app/src/st/svc/ST_CXLRECALC.pgc | 29 + app/src/st/svc/ST_CYCLED1.h | 19 + app/src/st/svc/ST_CYCLED1.pgc | 35 + app/src/st/svc/ST_CYCLED2.h | 20 + app/src/st/svc/ST_CYCLED2.pgc | 37 + app/src/st/svc/ST_CYCLEMON.h | 20 + app/src/st/svc/ST_CYCLEMON.pgc | 39 + app/src/st/svc/ST_CYCLEWEEK.h | 19 + app/src/st/svc/ST_CYCLEWEEK.pgc | 36 + app/src/st/svc/ST_DAILYCLOSE.h | 20 + app/src/st/svc/ST_DAILYCLOSE.pgc | 36 + app/src/st/svc/ST_DEBTCOLLECT.h | 19 + app/src/st/svc/ST_DEBTCOLLECT.pgc | 40 + app/src/st/svc/ST_DEPOSITCUT.h | 19 + app/src/st/svc/ST_DEPOSITCUT.pgc | 39 + app/src/st/svc/ST_DISPUTE.h | 19 + app/src/st/svc/ST_DISPUTE.pgc | 35 + app/src/st/svc/ST_DTLSUM.h | 20 + app/src/st/svc/ST_DTLSUM.pgc | 37 + app/src/st/svc/ST_EDIRECON.h | 20 + app/src/st/svc/ST_EDIRECON.pgc | 36 + app/src/st/svc/ST_FEEPREVIEW.h | 22 + app/src/st/svc/ST_FEEPREVIEW.pgc | 34 + app/src/st/svc/ST_FEERATIO.h | 19 + app/src/st/svc/ST_FEERATIO.pgc | 32 + app/src/st/svc/ST_FXSETTLE.h | 20 + app/src/st/svc/ST_FXSETTLE.pgc | 32 + app/src/st/svc/ST_GRPSETTLE.h | 20 + app/src/st/svc/ST_GRPSETTLE.pgc | 46 + app/src/st/svc/ST_HOLDAMT.h | 18 + app/src/st/svc/ST_HOLDAMT.pgc | 31 + app/src/st/svc/ST_ISSUERSTAT.h | 18 + app/src/st/svc/ST_ISSUERSTAT.pgc | 41 + app/src/st/svc/ST_LEDGERSNAP.h | 19 + app/src/st/svc/ST_LEDGERSNAP.pgc | 33 + app/src/st/svc/ST_LIMITCHK.h | 20 + app/src/st/svc/ST_LIMITCHK.pgc | 35 + app/src/st/svc/ST_MERCHRATE.h | 21 + app/src/st/svc/ST_MERCHRATE.pgc | 32 + app/src/st/svc/ST_MINFEE.h | 20 + app/src/st/svc/ST_MINFEE.pgc | 31 + app/src/st/svc/ST_NEGSWEEP.h | 18 + app/src/st/svc/ST_NEGSWEEP.pgc | 33 + app/src/st/svc/ST_PARTIAL.h | 20 + app/src/st/svc/ST_PARTIAL.pgc | 44 + app/src/st/svc/ST_PAYRETRY.h | 19 + app/src/st/svc/ST_PAYRETRY.pgc | 34 + app/src/st/svc/ST_PAYSTATUS.h | 19 + app/src/st/svc/ST_PAYSTATUS.pgc | 35 + app/src/st/svc/ST_PROMOCALC.h | 21 + app/src/st/svc/ST_PROMOCALC.pgc | 29 + app/src/st/svc/ST_RATESIM.h | 20 + app/src/st/svc/ST_RATESIM.pgc | 40 + app/src/st/svc/ST_RECVOFFSET.h | 19 + app/src/st/svc/ST_RECVOFFSET.pgc | 41 + app/src/st/svc/ST_REOPEN.h | 17 + app/src/st/svc/ST_REOPEN.pgc | 27 + app/src/st/svc/ST_RERUN.h | 19 + app/src/st/svc/ST_RERUN.pgc | 32 + app/src/st/svc/ST_RETRORATE.h | 20 + app/src/st/svc/ST_RETRORATE.pgc | 40 + app/src/st/svc/ST_ROUNDING.h | 19 + app/src/st/svc/ST_ROUNDING.pgc | 34 + app/src/st/svc/ST_SPLITEVEN.h | 20 + app/src/st/svc/ST_SPLITEVEN.pgc | 31 + app/src/st/svc/ST_STATDETAIL.h | 20 + app/src/st/svc/ST_STATDETAIL.pgc | 45 + app/src/st/svc/ST_TAXINVCHK.h | 20 + app/src/st/svc/ST_TAXINVCHK.pgc | 35 + app/src/st/svc/ST_TIERINCENT.h | 20 + app/src/st/svc/ST_TIERINCENT.pgc | 40 + app/src/st/svc/ST_TOPMERCH.h | 18 + app/src/st/svc/ST_TOPMERCH.pgc | 30 + app/src/st/svc/ST_UNPAIDSUM.h | 19 + app/src/st/svc/ST_UNPAIDSUM.pgc | 32 + app/src/st/svc/ST_VATREPORT.h | 20 + app/src/st/svc/ST_VATREPORT.pgc | 35 + app/src/st/svc/ST_WHTREPORT.h | 20 + app/src/st/svc/ST_WHTREPORT.pgc | 37 + app/src/vl/batch/vl_agedopen_batch.pgc | 43 + app/src/vl/batch/vl_apprsum_batch.pgc | 46 + app/src/vl/batch/vl_daterev_batch.pgc | 46 + app/src/vl/batch/vl_duppay_batch.pgc | 56 + app/src/vl/batch/vl_feerecalc_batch.pgc | 47 + app/src/vl/batch/vl_negbal_batch.pgc | 57 + app/src/vl/batch/vl_ratedev_batch.pgc | 47 + app/src/vl/batch/vl_ruleaudit_batch.pgc | 55 + app/src/vl/batch/vl_sumrecon_batch.pgc | 51 + app/src/vl/batch/vl_thrtrim_batch.pgc | 42 + app/src/vl/batch/vl_tristage_batch.pgc | 55 + app/src/vl/batch/vl_unacq_batch.pgc | 45 + app/src/vl/batch/vl_wfstat_batch.pgc | 45 + app/src/vl/dbio/vl_agedopen_batch.h | 12 + app/src/vl/dbio/vl_apprsum_batch.h | 12 + app/src/vl/dbio/vl_daterev_batch.h | 12 + app/src/vl/dbio/vl_duppay_batch.h | 13 + app/src/vl/dbio/vl_feerecalc_batch.h | 12 + app/src/vl/dbio/vl_negbal_batch.h | 12 + app/src/vl/dbio/vl_ratedev_batch.h | 12 + app/src/vl/dbio/vl_ruleaudit_batch.h | 12 + app/src/vl/dbio/vl_sumrecon_batch.h | 12 + app/src/vl/dbio/vl_thrtrim_batch.h | 10 + app/src/vl/dbio/vl_tristage_batch.h | 12 + app/src/vl/dbio/vl_unacq_batch.h | 12 + app/src/vl/dbio/vl_wfstat_batch.h | 12 + app/src/vl/svc/VL_AGEDCONF.h | 12 + app/src/vl/svc/VL_AGEDCONF.pgc | 35 + app/src/vl/svc/VL_AGEDOPEN.h | 12 + app/src/vl/svc/VL_AGEDOPEN.pgc | 28 + app/src/vl/svc/VL_APPRAGG.h | 12 + app/src/vl/svc/VL_APPRAGG.pgc | 33 + app/src/vl/svc/VL_APPRAMT.h | 12 + app/src/vl/svc/VL_APPRAMT.pgc | 30 + app/src/vl/svc/VL_CANCELMIS.h | 12 + app/src/vl/svc/VL_CANCELMIS.pgc | 33 + app/src/vl/svc/VL_DATEREV.h | 12 + app/src/vl/svc/VL_DATEREV.pgc | 27 + app/src/vl/svc/VL_DAYLIMIT.h | 12 + app/src/vl/svc/VL_DAYLIMIT.pgc | 33 + app/src/vl/svc/VL_DUPPAY.h | 12 + app/src/vl/svc/VL_DUPPAY.pgc | 31 + app/src/vl/svc/VL_DUPPAYDAY.h | 12 + app/src/vl/svc/VL_DUPPAYDAY.pgc | 28 + app/src/vl/svc/VL_DUPSETTLE.h | 12 + app/src/vl/svc/VL_DUPSETTLE.pgc | 28 + app/src/vl/svc/VL_FEEADJNET.h | 12 + app/src/vl/svc/VL_FEEADJNET.pgc | 31 + app/src/vl/svc/VL_FEERECALC.h | 12 + app/src/vl/svc/VL_FEERECALC.pgc | 33 + app/src/vl/svc/VL_FXCHK.h | 12 + app/src/vl/svc/VL_FXCHK.pgc | 33 + app/src/vl/svc/VL_INSTCHK.h | 12 + app/src/vl/svc/VL_INSTCHK.pgc | 37 + app/src/vl/svc/VL_LEDGBAL.h | 12 + app/src/vl/svc/VL_LEDGBAL.pgc | 33 + app/src/vl/svc/VL_LEDGDATE.h | 12 + app/src/vl/svc/VL_LEDGDATE.pgc | 25 + app/src/vl/svc/VL_LEDGENTRY.h | 12 + app/src/vl/svc/VL_LEDGENTRY.pgc | 29 + app/src/vl/svc/VL_LIMITNEAR.h | 12 + app/src/vl/svc/VL_LIMITNEAR.pgc | 40 + app/src/vl/svc/VL_MDRRATE.h | 12 + app/src/vl/svc/VL_MDRRATE.pgc | 29 + app/src/vl/svc/VL_NEGBAL.h | 12 + app/src/vl/svc/VL_NEGBAL.pgc | 29 + app/src/vl/svc/VL_NEGNET.h | 12 + app/src/vl/svc/VL_NEGNET.pgc | 31 + app/src/vl/svc/VL_RATEDEV.h | 12 + app/src/vl/svc/VL_RATEDEV.pgc | 33 + app/src/vl/svc/VL_RILEDGER.h | 12 + app/src/vl/svc/VL_RILEDGER.pgc | 37 + app/src/vl/svc/VL_RIMERCH.h | 12 + app/src/vl/svc/VL_RIMERCH.pgc | 29 + app/src/vl/svc/VL_RIPURCH.h | 12 + app/src/vl/svc/VL_RIPURCH.pgc | 31 + app/src/vl/svc/VL_RISETTLE.h | 12 + app/src/vl/svc/VL_RISETTLE.pgc | 27 + app/src/vl/svc/VL_RULEHIST.h | 12 + app/src/vl/svc/VL_RULEHIST.pgc | 29 + app/src/vl/svc/VL_RULEMOD.h | 12 + app/src/vl/svc/VL_RULEMOD.pgc | 39 + app/src/vl/svc/VL_RULEOFF.h | 12 + app/src/vl/svc/VL_RULEOFF.pgc | 28 + app/src/vl/svc/VL_RULEREG.h | 12 + app/src/vl/svc/VL_RULEREG.pgc | 34 + app/src/vl/svc/VL_RULESTAT.h | 12 + app/src/vl/svc/VL_RULESTAT.pgc | 27 + app/src/vl/svc/VL_SUMFEE.h | 12 + app/src/vl/svc/VL_SUMFEE.pgc | 38 + app/src/vl/svc/VL_SUMRECON.h | 12 + app/src/vl/svc/VL_SUMRECON.pgc | 37 + app/src/vl/svc/VL_SUSPACK.h | 12 + app/src/vl/svc/VL_SUSPACK.pgc | 29 + app/src/vl/svc/VL_SUSPACT.h | 12 + app/src/vl/svc/VL_SUSPACT.pgc | 34 + app/src/vl/svc/VL_SUSPFINAL.h | 12 + app/src/vl/svc/VL_SUSPFINAL.pgc | 27 + app/src/vl/svc/VL_SUSPREOPEN.h | 12 + app/src/vl/svc/VL_SUSPREOPEN.pgc | 27 + app/src/vl/svc/VL_SUSPSTAT.h | 12 + app/src/vl/svc/VL_SUSPSTAT.pgc | 32 + app/src/vl/svc/VL_THRAUDIT.h | 12 + app/src/vl/svc/VL_THRAUDIT.pgc | 28 + app/src/vl/svc/VL_THRBUMP.h | 12 + app/src/vl/svc/VL_THRBUMP.pgc | 31 + app/src/vl/svc/VL_THRSET.h | 12 + app/src/vl/svc/VL_THRSET.pgc | 27 + app/src/vl/svc/VL_TRIAGG.h | 12 + app/src/vl/svc/VL_TRIAGG.pgc | 36 + app/src/vl/svc/VL_TRISTAGE.h | 12 + app/src/vl/svc/VL_TRISTAGE.pgc | 35 + app/src/vl/svc/VL_UNACQ.h | 12 + app/src/vl/svc/VL_UNACQ.pgc | 25 + app/src/vl/svc/VL_UNACQAGE.h | 12 + app/src/vl/svc/VL_UNACQAGE.pgc | 29 + app/src/vl/vl_svr.pgc | 92 +- app/ui/index.html | 7276 +++++++++++++++++ app/ui/screens.js | 7276 +++++++++++++++++ db/schema.d/95-module-seed.sql | 912 +++ 1292 files changed, 53968 insertions(+), 20 deletions(-) create mode 100644 app/src/ac/batch/ac_alert_batch.pgc create mode 100644 app/src/ac/batch/ac_apprgap_batch.pgc create mode 100644 app/src/ac/batch/ac_cbaging_batch.pgc create mode 100644 app/src/ac/batch/ac_ccysum_batch.pgc create mode 100644 app/src/ac/batch/ac_claimsweep_batch.pgc create mode 100644 app/src/ac/batch/ac_feewaive_batch.pgc create mode 100644 app/src/ac/batch/ac_limitwatch_batch.pgc create mode 100644 app/src/ac/batch/ac_promo_batch.pgc create mode 100644 app/src/ac/batch/ac_reservepost_batch.pgc create mode 100644 app/src/ac/batch/ac_settledue_batch.pgc create mode 100644 app/src/ac/batch/ac_splitpay_batch.pgc create mode 100644 app/src/ac/batch/ac_taxneg_batch.pgc create mode 100644 app/src/ac/batch/ac_weekroll_batch.pgc create mode 100644 app/src/ac/dbio/ac_alert_batch.h create mode 100644 app/src/ac/dbio/ac_apprgap_batch.h create mode 100644 app/src/ac/dbio/ac_cbaging_batch.h create mode 100644 app/src/ac/dbio/ac_ccysum_batch.h create mode 100644 app/src/ac/dbio/ac_claimsweep_batch.h create mode 100644 app/src/ac/dbio/ac_feewaive_batch.h create mode 100644 app/src/ac/dbio/ac_limitwatch_batch.h create mode 100644 app/src/ac/dbio/ac_promo_batch.h create mode 100644 app/src/ac/dbio/ac_reservepost_batch.h create mode 100644 app/src/ac/dbio/ac_settledue_batch.h create mode 100644 app/src/ac/dbio/ac_splitpay_batch.h create mode 100644 app/src/ac/dbio/ac_taxneg_batch.h create mode 100644 app/src/ac/dbio/ac_weekroll_batch.h create mode 100644 app/src/ac/svc/ACQ_ALERTCHK.h create mode 100644 app/src/ac/svc/ACQ_ALERTCHK.pgc create mode 100644 app/src/ac/svc/ACQ_APPRGAP.h create mode 100644 app/src/ac/svc/ACQ_APPRGAP.pgc create mode 100644 app/src/ac/svc/ACQ_BULKVERIFY.h create mode 100644 app/src/ac/svc/ACQ_BULKVERIFY.pgc create mode 100644 app/src/ac/svc/ACQ_CB_DEFEND.h create mode 100644 app/src/ac/svc/ACQ_CB_DEFEND.pgc create mode 100644 app/src/ac/svc/ACQ_CB_OPEN.h create mode 100644 app/src/ac/svc/ACQ_CB_OPEN.pgc create mode 100644 app/src/ac/svc/ACQ_CB_QRY.h create mode 100644 app/src/ac/svc/ACQ_CB_QRY.pgc create mode 100644 app/src/ac/svc/ACQ_CB_STAT.h create mode 100644 app/src/ac/svc/ACQ_CB_STAT.pgc create mode 100644 app/src/ac/svc/ACQ_CCYSUM.h create mode 100644 app/src/ac/svc/ACQ_CCYSUM.pgc create mode 100644 app/src/ac/svc/ACQ_CHANSTAT.h create mode 100644 app/src/ac/svc/ACQ_CHANSTAT.pgc create mode 100644 app/src/ac/svc/ACQ_CLAIMPROC.h create mode 100644 app/src/ac/svc/ACQ_CLAIMPROC.pgc create mode 100644 app/src/ac/svc/ACQ_CLAIMQRY.h create mode 100644 app/src/ac/svc/ACQ_CLAIMQRY.pgc create mode 100644 app/src/ac/svc/ACQ_CLAIMREG.h create mode 100644 app/src/ac/svc/ACQ_CLAIMREG.pgc create mode 100644 app/src/ac/svc/ACQ_CORRHIST.h create mode 100644 app/src/ac/svc/ACQ_CORRHIST.pgc create mode 100644 app/src/ac/svc/ACQ_CTRYSUM.h create mode 100644 app/src/ac/svc/ACQ_CTRYSUM.pgc create mode 100644 app/src/ac/svc/ACQ_DAYSTAT.h create mode 100644 app/src/ac/svc/ACQ_DAYSTAT.pgc create mode 100644 app/src/ac/svc/ACQ_DUPSCAN.h create mode 100644 app/src/ac/svc/ACQ_DUPSCAN.pgc create mode 100644 app/src/ac/svc/ACQ_EDIRESEND.h create mode 100644 app/src/ac/svc/ACQ_EDIRESEND.pgc create mode 100644 app/src/ac/svc/ACQ_FEEHIST.h create mode 100644 app/src/ac/svc/ACQ_FEEHIST.pgc create mode 100644 app/src/ac/svc/ACQ_FEERECALC.h create mode 100644 app/src/ac/svc/ACQ_FEERECALC.pgc create mode 100644 app/src/ac/svc/ACQ_FEEWAIVE.h create mode 100644 app/src/ac/svc/ACQ_FEEWAIVE.pgc create mode 100644 app/src/ac/svc/ACQ_FXCONVERT.h create mode 100644 app/src/ac/svc/ACQ_FXCONVERT.pgc create mode 100644 app/src/ac/svc/ACQ_FXRATE.h create mode 100644 app/src/ac/svc/ACQ_FXRATE.pgc create mode 100644 app/src/ac/svc/ACQ_HOLDQRY.h create mode 100644 app/src/ac/svc/ACQ_HOLDQRY.pgc create mode 100644 app/src/ac/svc/ACQ_ISSSTAT.h create mode 100644 app/src/ac/svc/ACQ_ISSSTAT.pgc create mode 100644 app/src/ac/svc/ACQ_LIMITUSAGE.h create mode 100644 app/src/ac/svc/ACQ_LIMITUSAGE.pgc create mode 100644 app/src/ac/svc/ACQ_MONSTAT.h create mode 100644 app/src/ac/svc/ACQ_MONSTAT.pgc create mode 100644 app/src/ac/svc/ACQ_NETCALC.h create mode 100644 app/src/ac/svc/ACQ_NETCALC.pgc create mode 100644 app/src/ac/svc/ACQ_NOINTSHARE.h create mode 100644 app/src/ac/svc/ACQ_NOINTSHARE.pgc create mode 100644 app/src/ac/svc/ACQ_PARTCXL.h create mode 100644 app/src/ac/svc/ACQ_PARTCXL.pgc create mode 100644 app/src/ac/svc/ACQ_PARTRFND.h create mode 100644 app/src/ac/svc/ACQ_PARTRFND.pgc create mode 100644 app/src/ac/svc/ACQ_PROMO.h create mode 100644 app/src/ac/svc/ACQ_PROMO.pgc create mode 100644 app/src/ac/svc/ACQ_REBUY.h create mode 100644 app/src/ac/svc/ACQ_REBUY.pgc create mode 100644 app/src/ac/svc/ACQ_REMATCH.h create mode 100644 app/src/ac/svc/ACQ_REMATCH.pgc create mode 100644 app/src/ac/svc/ACQ_RESERVE.h create mode 100644 app/src/ac/svc/ACQ_RESERVE.pgc create mode 100644 app/src/ac/svc/ACQ_RESERVECXL.h create mode 100644 app/src/ac/svc/ACQ_RESERVECXL.pgc create mode 100644 app/src/ac/svc/ACQ_RESERVEQRY.h create mode 100644 app/src/ac/svc/ACQ_RESERVEQRY.pgc create mode 100644 app/src/ac/svc/ACQ_RETURN.h create mode 100644 app/src/ac/svc/ACQ_RETURN.pgc create mode 100644 app/src/ac/svc/ACQ_SETTLEDUE.h create mode 100644 app/src/ac/svc/ACQ_SETTLEDUE.pgc create mode 100644 app/src/ac/svc/ACQ_SPLITPAY.h create mode 100644 app/src/ac/svc/ACQ_SPLITPAY.pgc create mode 100644 app/src/ac/svc/ACQ_SPLITQRY.h create mode 100644 app/src/ac/svc/ACQ_SPLITQRY.pgc create mode 100644 app/src/ac/svc/ACQ_TAXCXL.h create mode 100644 app/src/ac/svc/ACQ_TAXCXL.pgc create mode 100644 app/src/ac/svc/ACQ_TAXREISSUE.h create mode 100644 app/src/ac/svc/ACQ_TAXREISSUE.pgc create mode 100644 app/src/ac/svc/ACQ_TOPMERCH.h create mode 100644 app/src/ac/svc/ACQ_TOPMERCH.pgc create mode 100644 app/src/ac/svc/ACQ_VELOCITY.h create mode 100644 app/src/ac/svc/ACQ_VELOCITY.pgc create mode 100644 app/src/ac/svc/ACQ_WEEKSTAT.h create mode 100644 app/src/ac/svc/ACQ_WEEKSTAT.pgc create mode 100644 app/src/au/batch/au_blacklist_hit_batch.pgc create mode 100644 app/src/au/batch/au_cancel_reproc_batch.pgc create mode 100644 app/src/au/batch/au_delay_watch_batch.pgc create mode 100644 app/src/au/batch/au_dup_auth_batch.pgc create mode 100644 app/src/au/batch/au_hourly_stat_batch.pgc create mode 100644 app/src/au/batch/au_issuer_mix_batch.pgc create mode 100644 app/src/au/batch/au_limit_restore_batch.pgc create mode 100644 app/src/au/batch/au_merch_rank_batch.pgc create mode 100644 app/src/au/batch/au_night_auth_batch.pgc create mode 100644 app/src/au/batch/au_noauth_detect_batch.pgc create mode 100644 app/src/au/batch/au_preauth_convert_batch.pgc create mode 100644 app/src/au/batch/au_standin_settle_batch.pgc create mode 100644 app/src/au/batch/au_velocity_flag_batch.pgc create mode 100644 app/src/au/dbio/au_blacklist_hit_batch.h create mode 100644 app/src/au/dbio/au_cancel_reproc_batch.h create mode 100644 app/src/au/dbio/au_delay_watch_batch.h create mode 100644 app/src/au/dbio/au_dup_auth_batch.h create mode 100644 app/src/au/dbio/au_hourly_stat_batch.h create mode 100644 app/src/au/dbio/au_issuer_mix_batch.h create mode 100644 app/src/au/dbio/au_limit_restore_batch.h create mode 100644 app/src/au/dbio/au_merch_rank_batch.h create mode 100644 app/src/au/dbio/au_night_auth_batch.h create mode 100644 app/src/au/dbio/au_noauth_detect_batch.h create mode 100644 app/src/au/dbio/au_preauth_convert_batch.h create mode 100644 app/src/au/dbio/au_standin_settle_batch.h create mode 100644 app/src/au/dbio/au_velocity_flag_batch.h create mode 100644 app/src/au/svc/APPROVAL_RATE.h create mode 100644 app/src/au/svc/APPROVAL_RATE.pgc create mode 100644 app/src/au/svc/AUTH_AMT_ADJUST.h create mode 100644 app/src/au/svc/AUTH_AMT_ADJUST.pgc create mode 100644 app/src/au/svc/AUTH_DELAY_MON.h create mode 100644 app/src/au/svc/AUTH_DELAY_MON.pgc create mode 100644 app/src/au/svc/AUTH_DUP_CHK.h create mode 100644 app/src/au/svc/AUTH_DUP_CHK.pgc create mode 100644 app/src/au/svc/AUTH_EXPIRE_CHK.h create mode 100644 app/src/au/svc/AUTH_EXPIRE_CHK.pgc create mode 100644 app/src/au/svc/AUTH_PARTIAL.h create mode 100644 app/src/au/svc/AUTH_PARTIAL.pgc create mode 100644 app/src/au/svc/AUTH_RESEND.h create mode 100644 app/src/au/svc/AUTH_RESEND.pgc create mode 100644 app/src/au/svc/BIN_ROUTE_INQ.h create mode 100644 app/src/au/svc/BIN_ROUTE_INQ.pgc create mode 100644 app/src/au/svc/BLACKLIST_ADD.h create mode 100644 app/src/au/svc/BLACKLIST_ADD.pgc create mode 100644 app/src/au/svc/BLACKLIST_DEL.h create mode 100644 app/src/au/svc/BLACKLIST_DEL.pgc create mode 100644 app/src/au/svc/BLACKLIST_INQ.h create mode 100644 app/src/au/svc/BLACKLIST_INQ.pgc create mode 100644 app/src/au/svc/CANCEL_REPROC.h create mode 100644 app/src/au/svc/CANCEL_REPROC.pgc create mode 100644 app/src/au/svc/CANCEL_STATUS.h create mode 100644 app/src/au/svc/CANCEL_STATUS.pgc create mode 100644 app/src/au/svc/CARD_DAILY_RPT.h create mode 100644 app/src/au/svc/CARD_DAILY_RPT.pgc create mode 100644 app/src/au/svc/CARD_REISSUE.h create mode 100644 app/src/au/svc/CARD_REISSUE.pgc create mode 100644 app/src/au/svc/CARD_RESUME.h create mode 100644 app/src/au/svc/CARD_RESUME.pgc create mode 100644 app/src/au/svc/CARD_STATE_INQ.h create mode 100644 app/src/au/svc/CARD_STATE_INQ.pgc create mode 100644 app/src/au/svc/CARD_SUSPEND.h create mode 100644 app/src/au/svc/CARD_SUSPEND.pgc create mode 100644 app/src/au/svc/DECLINE_ANALYZE.h create mode 100644 app/src/au/svc/DECLINE_ANALYZE.pgc create mode 100644 app/src/au/svc/FRAUD_CASE_INQ.h create mode 100644 app/src/au/svc/FRAUD_CASE_INQ.pgc create mode 100644 app/src/au/svc/FRAUD_RULE_AMT.h create mode 100644 app/src/au/svc/FRAUD_RULE_AMT.pgc create mode 100644 app/src/au/svc/FRAUD_RULE_DUP.h create mode 100644 app/src/au/svc/FRAUD_RULE_DUP.pgc create mode 100644 app/src/au/svc/FRAUD_RULE_FREQ.h create mode 100644 app/src/au/svc/FRAUD_RULE_FREQ.pgc create mode 100644 app/src/au/svc/LIMIT_HIST.h create mode 100644 app/src/au/svc/LIMIT_HIST.pgc create mode 100644 app/src/au/svc/LIMIT_RAISE_REQ.h create mode 100644 app/src/au/svc/LIMIT_RAISE_REQ.pgc create mode 100644 app/src/au/svc/LIMIT_REMAIN.h create mode 100644 app/src/au/svc/LIMIT_REMAIN.pgc create mode 100644 app/src/au/svc/LIMIT_RESTORE.h create mode 100644 app/src/au/svc/LIMIT_RESTORE.pgc create mode 100644 app/src/au/svc/LIMIT_USAGE.h create mode 100644 app/src/au/svc/LIMIT_USAGE.pgc create mode 100644 app/src/au/svc/MERCH_AUTH_RANK.h create mode 100644 app/src/au/svc/MERCH_AUTH_RANK.pgc create mode 100644 app/src/au/svc/NIGHT_POLICY.h create mode 100644 app/src/au/svc/NIGHT_POLICY.pgc create mode 100644 app/src/au/svc/NOAUTH_CAPTURE.h create mode 100644 app/src/au/svc/NOAUTH_CAPTURE.pgc create mode 100644 app/src/au/svc/OVERSEAS_POLICY.h create mode 100644 app/src/au/svc/OVERSEAS_POLICY.pgc create mode 100644 app/src/au/svc/PREAUTH_CANCEL.h create mode 100644 app/src/au/svc/PREAUTH_CANCEL.pgc create mode 100644 app/src/au/svc/PREAUTH_EXTEND.h create mode 100644 app/src/au/svc/PREAUTH_EXTEND.pgc create mode 100644 app/src/au/svc/PREAUTH_INQ.h create mode 100644 app/src/au/svc/PREAUTH_INQ.pgc create mode 100644 app/src/au/svc/RISK_SCORE_INQ.h create mode 100644 app/src/au/svc/RISK_SCORE_INQ.pgc create mode 100644 app/src/au/svc/STANDIN_INQ.h create mode 100644 app/src/au/svc/STANDIN_INQ.pgc create mode 100644 app/src/au/svc/STANDIN_SETTLE.h create mode 100644 app/src/au/svc/STANDIN_SETTLE.pgc create mode 100644 app/src/au/svc/STAT_BYTYPE.h create mode 100644 app/src/au/svc/STAT_BYTYPE.pgc create mode 100644 app/src/au/svc/STAT_HOURLY.h create mode 100644 app/src/au/svc/STAT_HOURLY.pgc create mode 100644 app/src/au/svc/STAT_ISSUER.h create mode 100644 app/src/au/svc/STAT_ISSUER.pgc create mode 100644 app/src/au/svc/TOKEN_VERIFY.h create mode 100644 app/src/au/svc/TOKEN_VERIFY.pgc create mode 100644 app/src/au/svc/VELOCITY_HOUR.h create mode 100644 app/src/au/svc/VELOCITY_HOUR.pgc create mode 100644 app/src/au/svc/VELOCITY_MERCH.h create mode 100644 app/src/au/svc/VELOCITY_MERCH.pgc create mode 100644 app/src/au/svc/VELOCITY_WEEK.h create mode 100644 app/src/au/svc/VELOCITY_WEEK.pgc create mode 100644 app/src/cl/batch/cl_apprflow_batch.pgc create mode 100644 app/src/cl/batch/cl_apprflow_cpy.h create mode 100644 app/src/cl/batch/cl_audit_batch.pgc create mode 100644 app/src/cl/batch/cl_audit_cpy.h create mode 100644 app/src/cl/batch/cl_chnclose_batch.pgc create mode 100644 app/src/cl/batch/cl_chnclose_cpy.h create mode 100644 app/src/cl/batch/cl_dupchk_batch.pgc create mode 100644 app/src/cl/batch/cl_dupchk_cpy.h create mode 100644 app/src/cl/batch/cl_finalize_batch.pgc create mode 100644 app/src/cl/batch/cl_finalize_cpy.h create mode 100644 app/src/cl/batch/cl_gapdays_batch.pgc create mode 100644 app/src/cl/batch/cl_gapdays_cpy.h create mode 100644 app/src/cl/batch/cl_halfclose_batch.pgc create mode 100644 app/src/cl/batch/cl_halfclose_cpy.h create mode 100644 app/src/cl/batch/cl_holiday_batch.pgc create mode 100644 app/src/cl/batch/cl_holiday_cpy.h create mode 100644 app/src/cl/batch/cl_posttrig_batch.pgc create mode 100644 app/src/cl/batch/cl_posttrig_cpy.h create mode 100644 app/src/cl/batch/cl_precheck_batch.pgc create mode 100644 app/src/cl/batch/cl_precheck_cpy.h create mode 100644 app/src/cl/batch/cl_provclose_batch.pgc create mode 100644 app/src/cl/batch/cl_provclose_cpy.h create mode 100644 app/src/cl/batch/cl_slamon_batch.pgc create mode 100644 app/src/cl/batch/cl_slamon_cpy.h create mode 100644 app/src/cl/batch/cl_xcheck_batch.pgc create mode 100644 app/src/cl/batch/cl_xcheck_cpy.h create mode 100644 app/src/cl/svc/CL_AMTXCHECK.h create mode 100644 app/src/cl/svc/CL_AMTXCHECK.pgc create mode 100644 app/src/cl/svc/CL_APPRHIST.h create mode 100644 app/src/cl/svc/CL_APPRHIST.pgc create mode 100644 app/src/cl/svc/CL_APPRREJECT.h create mode 100644 app/src/cl/svc/CL_APPRREJECT.pgc create mode 100644 app/src/cl/svc/CL_APPRREQ.h create mode 100644 app/src/cl/svc/CL_APPRREQ.pgc create mode 100644 app/src/cl/svc/CL_AUDITTRAIL.h create mode 100644 app/src/cl/svc/CL_AUDITTRAIL.pgc create mode 100644 app/src/cl/svc/CL_CALENDAR.h create mode 100644 app/src/cl/svc/CL_CALENDAR.pgc create mode 100644 app/src/cl/svc/CL_CHNCLOSE.h create mode 100644 app/src/cl/svc/CL_CHNCLOSE.pgc create mode 100644 app/src/cl/svc/CL_CHNVERIFY.h create mode 100644 app/src/cl/svc/CL_CHNVERIFY.pgc create mode 100644 app/src/cl/svc/CL_CLOSERATE.h create mode 100644 app/src/cl/svc/CL_CLOSERATE.pgc create mode 100644 app/src/cl/svc/CL_CNTXCHECK.h create mode 100644 app/src/cl/svc/CL_CNTXCHECK.pgc create mode 100644 app/src/cl/svc/CL_CORPCLOSE.h create mode 100644 app/src/cl/svc/CL_CORPCLOSE.pgc create mode 100644 app/src/cl/svc/CL_CXLREASON.h create mode 100644 app/src/cl/svc/CL_CXLREASON.pgc create mode 100644 app/src/cl/svc/CL_DELAYLIST.h create mode 100644 app/src/cl/svc/CL_DELAYLIST.pgc create mode 100644 app/src/cl/svc/CL_DELAYREG.h create mode 100644 app/src/cl/svc/CL_DELAYREG.pgc create mode 100644 app/src/cl/svc/CL_DIVCLOSE.h create mode 100644 app/src/cl/svc/CL_DIVCLOSE.pgc create mode 100644 app/src/cl/svc/CL_DUPCHK.h create mode 100644 app/src/cl/svc/CL_DUPCHK.pgc create mode 100644 app/src/cl/svc/CL_ESCALATE.h create mode 100644 app/src/cl/svc/CL_ESCALATE.pgc create mode 100644 app/src/cl/svc/CL_FEEVERIFY.h create mode 100644 app/src/cl/svc/CL_FEEVERIFY.pgc create mode 100644 app/src/cl/svc/CL_FINALIZE.h create mode 100644 app/src/cl/svc/CL_FINALIZE.pgc create mode 100644 app/src/cl/svc/CL_FORCECLOSE.h create mode 100644 app/src/cl/svc/CL_FORCECLOSE.pgc create mode 100644 app/src/cl/svc/CL_GAPDAYS.h create mode 100644 app/src/cl/svc/CL_GAPDAYS.pgc create mode 100644 app/src/cl/svc/CL_HALFCLOSE.h create mode 100644 app/src/cl/svc/CL_HALFCLOSE.pgc create mode 100644 app/src/cl/svc/CL_HOLIDAY.h create mode 100644 app/src/cl/svc/CL_HOLIDAY.pgc create mode 100644 app/src/cl/svc/CL_LOCKAUDIT.h create mode 100644 app/src/cl/svc/CL_LOCKAUDIT.pgc create mode 100644 app/src/cl/svc/CL_LOCKSWEEP.h create mode 100644 app/src/cl/svc/CL_LOCKSWEEP.pgc create mode 100644 app/src/cl/svc/CL_MEMOSEARCH.h create mode 100644 app/src/cl/svc/CL_MEMOSEARCH.pgc create mode 100644 app/src/cl/svc/CL_MERCLOSE.h create mode 100644 app/src/cl/svc/CL_MERCLOSE.pgc create mode 100644 app/src/cl/svc/CL_NETVERIFY.h create mode 100644 app/src/cl/svc/CL_NETVERIFY.pgc create mode 100644 app/src/cl/svc/CL_PENDLIST.h create mode 100644 app/src/cl/svc/CL_PENDLIST.pgc create mode 100644 app/src/cl/svc/CL_POSTTRIG.h create mode 100644 app/src/cl/svc/CL_POSTTRIG.pgc create mode 100644 app/src/cl/svc/CL_PRECHECK.h create mode 100644 app/src/cl/svc/CL_PRECHECK.pgc create mode 100644 app/src/cl/svc/CL_PROVCLOSE.h create mode 100644 app/src/cl/svc/CL_PROVCLOSE.pgc create mode 100644 app/src/cl/svc/CL_REOPENREQ.h create mode 100644 app/src/cl/svc/CL_REOPENREQ.pgc create mode 100644 app/src/cl/svc/CL_RPTCAT.h create mode 100644 app/src/cl/svc/CL_RPTCAT.pgc create mode 100644 app/src/cl/svc/CL_RPTCHN.h create mode 100644 app/src/cl/svc/CL_RPTCHN.pgc create mode 100644 app/src/cl/svc/CL_RPTISSUER.h create mode 100644 app/src/cl/svc/CL_RPTISSUER.pgc create mode 100644 app/src/cl/svc/CL_RPTTOPMER.h create mode 100644 app/src/cl/svc/CL_RPTTOPMER.pgc create mode 100644 app/src/cl/svc/CL_SCHEDULE.h create mode 100644 app/src/cl/svc/CL_SCHEDULE.pgc create mode 100644 app/src/cl/svc/CL_SEQAUDIT.h create mode 100644 app/src/cl/svc/CL_SEQAUDIT.pgc create mode 100644 app/src/cl/svc/CL_SLAMON.h create mode 100644 app/src/cl/svc/CL_SLAMON.pgc create mode 100644 app/src/cl/svc/CL_SNAPDIFF.h create mode 100644 app/src/cl/svc/CL_SNAPDIFF.pgc create mode 100644 app/src/cl/svc/CL_SNAPXCHECK.h create mode 100644 app/src/cl/svc/CL_SNAPXCHECK.pgc create mode 100644 app/src/cl/svc/CL_TREND.h create mode 100644 app/src/cl/svc/CL_TREND.pgc create mode 100644 app/src/cl/svc/CL_TRIGSTAT.h create mode 100644 app/src/cl/svc/CL_TRIGSTAT.pgc create mode 100644 app/src/cl/svc/CL_YEARPREP.h create mode 100644 app/src/cl/svc/CL_YEARPREP.pgc create mode 100644 app/src/cm/batch/cm_codeaudit_batch.pgc create mode 100644 app/src/cm/batch/cm_fxfill_batch.pgc create mode 100644 app/src/cm/batch/cm_fxstale_batch.pgc create mode 100644 app/src/cm/batch/cm_healthsnap_batch.pgc create mode 100644 app/src/cm/batch/cm_holchk_batch.pgc create mode 100644 app/src/cm/batch/cm_msgsync_batch.pgc create mode 100644 app/src/cm/batch/cm_notidigest_batch.pgc create mode 100644 app/src/cm/batch/cm_reqage_batch.pgc create mode 100644 app/src/cm/batch/cm_rerunpick_batch.pgc create mode 100644 app/src/cm/batch/cm_schddue_batch.pgc create mode 100644 app/src/cm/batch/cm_seqgc_batch.pgc create mode 100644 app/src/cm/batch/cm_seqreport_batch.pgc create mode 100644 app/src/cm/batch/cm_warnratio_batch.pgc create mode 100644 app/src/cm/dbio/cm_codeaudit_batch.h create mode 100644 app/src/cm/dbio/cm_fxfill_batch.h create mode 100644 app/src/cm/dbio/cm_fxstale_batch.h create mode 100644 app/src/cm/dbio/cm_healthsnap_batch.h create mode 100644 app/src/cm/dbio/cm_holchk_batch.h create mode 100644 app/src/cm/dbio/cm_msgsync_batch.h create mode 100644 app/src/cm/dbio/cm_notidigest_batch.h create mode 100644 app/src/cm/dbio/cm_reqage_batch.h create mode 100644 app/src/cm/dbio/cm_rerunpick_batch.h create mode 100644 app/src/cm/dbio/cm_schddue_batch.h create mode 100644 app/src/cm/dbio/cm_seqgc_batch.h create mode 100644 app/src/cm/dbio/cm_seqreport_batch.h create mode 100644 app/src/cm/dbio/cm_warnratio_batch.h create mode 100644 app/src/cm/svc/CM_AMT_HANGUL.h create mode 100644 app/src/cm/svc/CM_AMT_HANGUL.pgc create mode 100644 app/src/cm/svc/CM_AUDIT_BYLVL.h create mode 100644 app/src/cm/svc/CM_AUDIT_BYLVL.pgc create mode 100644 app/src/cm/svc/CM_AUDIT_BYSVC.h create mode 100644 app/src/cm/svc/CM_AUDIT_BYSVC.pgc create mode 100644 app/src/cm/svc/CM_AUDIT_RANGE.h create mode 100644 app/src/cm/svc/CM_AUDIT_RANGE.pgc create mode 100644 app/src/cm/svc/CM_BATHIST_CNT.h create mode 100644 app/src/cm/svc/CM_BATHIST_CNT.pgc create mode 100644 app/src/cm/svc/CM_BATRERUN_REQ.h create mode 100644 app/src/cm/svc/CM_BATRERUN_REQ.pgc create mode 100644 app/src/cm/svc/CM_BATSCHD_GET.h create mode 100644 app/src/cm/svc/CM_BATSCHD_GET.pgc create mode 100644 app/src/cm/svc/CM_BATSCHD_REG.h create mode 100644 app/src/cm/svc/CM_BATSCHD_REG.pgc create mode 100644 app/src/cm/svc/CM_BIZDAY_CNT.h create mode 100644 app/src/cm/svc/CM_BIZDAY_CNT.pgc create mode 100644 app/src/cm/svc/CM_CODEGRP_CLOSE.h create mode 100644 app/src/cm/svc/CM_CODEGRP_CLOSE.pgc create mode 100644 app/src/cm/svc/CM_CODEGRP_CNT.h create mode 100644 app/src/cm/svc/CM_CODEGRP_CNT.pgc create mode 100644 app/src/cm/svc/CM_CODEGRP_HIST.h create mode 100644 app/src/cm/svc/CM_CODEGRP_HIST.pgc create mode 100644 app/src/cm/svc/CM_CODEGRP_REG.h create mode 100644 app/src/cm/svc/CM_CODEGRP_REG.pgc create mode 100644 app/src/cm/svc/CM_CODEGRP_UPD.h create mode 100644 app/src/cm/svc/CM_CODEGRP_UPD.pgc create mode 100644 app/src/cm/svc/CM_DATEDIFF.h create mode 100644 app/src/cm/svc/CM_DATEDIFF.pgc create mode 100644 app/src/cm/svc/CM_ENC_HEX.h create mode 100644 app/src/cm/svc/CM_ENC_HEX.pgc create mode 100644 app/src/cm/svc/CM_FILE_CKSUM.h create mode 100644 app/src/cm/svc/CM_FILE_CKSUM.pgc create mode 100644 app/src/cm/svc/CM_FX_AGE.h create mode 100644 app/src/cm/svc/CM_FX_AGE.pgc create mode 100644 app/src/cm/svc/CM_FX_CROSS.h create mode 100644 app/src/cm/svc/CM_FX_CROSS.pgc create mode 100644 app/src/cm/svc/CM_FX_HIST.h create mode 100644 app/src/cm/svc/CM_FX_HIST.pgc create mode 100644 app/src/cm/svc/CM_FX_PUBLISH.h create mode 100644 app/src/cm/svc/CM_FX_PUBLISH.pgc create mode 100644 app/src/cm/svc/CM_FX_VALID.h create mode 100644 app/src/cm/svc/CM_FX_VALID.pgc create mode 100644 app/src/cm/svc/CM_HEALTH_DB.h create mode 100644 app/src/cm/svc/CM_HEALTH_DB.pgc create mode 100644 app/src/cm/svc/CM_HEALTH_LOG.h create mode 100644 app/src/cm/svc/CM_HEALTH_LOG.pgc create mode 100644 app/src/cm/svc/CM_HEALTH_SEQ.h create mode 100644 app/src/cm/svc/CM_HEALTH_SEQ.pgc create mode 100644 app/src/cm/svc/CM_HOLIDAY_CNT.h create mode 100644 app/src/cm/svc/CM_HOLIDAY_CNT.pgc create mode 100644 app/src/cm/svc/CM_HOLIDAY_DEL.h create mode 100644 app/src/cm/svc/CM_HOLIDAY_DEL.pgc create mode 100644 app/src/cm/svc/CM_HOLIDAY_REG.h create mode 100644 app/src/cm/svc/CM_HOLIDAY_REG.pgc create mode 100644 app/src/cm/svc/CM_MASK_CARD.h create mode 100644 app/src/cm/svc/CM_MASK_CARD.pgc create mode 100644 app/src/cm/svc/CM_MASK_NAME.h create mode 100644 app/src/cm/svc/CM_MASK_NAME.pgc create mode 100644 app/src/cm/svc/CM_MASK_PHONE.h create mode 100644 app/src/cm/svc/CM_MASK_PHONE.pgc create mode 100644 app/src/cm/svc/CM_MSG_FALLBACK.h create mode 100644 app/src/cm/svc/CM_MSG_FALLBACK.pgc create mode 100644 app/src/cm/svc/CM_MSG_GET.h create mode 100644 app/src/cm/svc/CM_MSG_GET.pgc create mode 100644 app/src/cm/svc/CM_MSG_SET.h create mode 100644 app/src/cm/svc/CM_MSG_SET.pgc create mode 100644 app/src/cm/svc/CM_NOTI_HIST.h create mode 100644 app/src/cm/svc/CM_NOTI_HIST.pgc create mode 100644 app/src/cm/svc/CM_NOTI_SEND.h create mode 100644 app/src/cm/svc/CM_NOTI_SEND.pgc create mode 100644 app/src/cm/svc/CM_PARAM_APPROVE.h create mode 100644 app/src/cm/svc/CM_PARAM_APPROVE.pgc create mode 100644 app/src/cm/svc/CM_PARAM_PENDING.h create mode 100644 app/src/cm/svc/CM_PARAM_PENDING.pgc create mode 100644 app/src/cm/svc/CM_PARAM_REJECT.h create mode 100644 app/src/cm/svc/CM_PARAM_REJECT.pgc create mode 100644 app/src/cm/svc/CM_PARAM_REQ.h create mode 100644 app/src/cm/svc/CM_PARAM_REQ.pgc create mode 100644 app/src/cm/svc/CM_PREVBIZ.h create mode 100644 app/src/cm/svc/CM_PREVBIZ.pgc create mode 100644 app/src/cm/svc/CM_SEQ_DAILY.h create mode 100644 app/src/cm/svc/CM_SEQ_DAILY.pgc create mode 100644 app/src/cm/svc/CM_SEQ_PEEK.h create mode 100644 app/src/cm/svc/CM_SEQ_PEEK.pgc create mode 100644 app/src/cm/svc/CM_SEQ_RESET.h create mode 100644 app/src/cm/svc/CM_SEQ_RESET.pgc create mode 100644 app/src/cm/svc/CM_SEQ_TYPED.h create mode 100644 app/src/cm/svc/CM_SEQ_TYPED.pgc create mode 100644 app/src/lg/batch/lg_agedpend_batch.pgc create mode 100644 app/src/lg/batch/lg_baddebt_batch.pgc create mode 100644 app/src/lg/batch/lg_balproof_batch.pgc create mode 100644 app/src/lg/batch/lg_closechk_batch.pgc create mode 100644 app/src/lg/batch/lg_deptpl_batch.pgc create mode 100644 app/src/lg/batch/lg_dupscan_batch.pgc create mode 100644 app/src/lg/batch/lg_feerecog_batch.pgc create mode 100644 app/src/lg/batch/lg_monthclose_batch.pgc create mode 100644 app/src/lg/batch/lg_payrecon_batch.pgc create mode 100644 app/src/lg/batch/lg_pendsweep_batch.pgc create mode 100644 app/src/lg/batch/lg_periodtb_batch.pgc create mode 100644 app/src/lg/batch/lg_settlerecon_batch.pgc create mode 100644 app/src/lg/batch/lg_statacct_batch.pgc create mode 100644 app/src/lg/dbio/lg_agedpend_batch.h create mode 100644 app/src/lg/dbio/lg_baddebt_batch.h create mode 100644 app/src/lg/dbio/lg_balproof_batch.h create mode 100644 app/src/lg/dbio/lg_closechk_batch.h create mode 100644 app/src/lg/dbio/lg_deptpl_batch.h create mode 100644 app/src/lg/dbio/lg_dupscan_batch.h create mode 100644 app/src/lg/dbio/lg_feerecog_batch.h create mode 100644 app/src/lg/dbio/lg_monthclose_batch.h create mode 100644 app/src/lg/dbio/lg_payrecon_batch.h create mode 100644 app/src/lg/dbio/lg_pendsweep_batch.h create mode 100644 app/src/lg/dbio/lg_periodtb_batch.h create mode 100644 app/src/lg/dbio/lg_settlerecon_batch.h create mode 100644 app/src/lg/dbio/lg_statacct_batch.h create mode 100644 app/src/lg/svc/LG_ACCRUEREV.h create mode 100644 app/src/lg/svc/LG_ACCRUEREV.pgc create mode 100644 app/src/lg/svc/LG_ACCTHIST.h create mode 100644 app/src/lg/svc/LG_ACCTHIST.pgc create mode 100644 app/src/lg/svc/LG_ACCTLEDGER.h create mode 100644 app/src/lg/svc/LG_ACCTLEDGER.pgc create mode 100644 app/src/lg/svc/LG_ACCTRANGE.h create mode 100644 app/src/lg/svc/LG_ACCTRANGE.pgc create mode 100644 app/src/lg/svc/LG_APPROVE.h create mode 100644 app/src/lg/svc/LG_APPROVE.pgc create mode 100644 app/src/lg/svc/LG_APPRQUEUE.h create mode 100644 app/src/lg/svc/LG_APPRQUEUE.pgc create mode 100644 app/src/lg/svc/LG_BADDEBT.h create mode 100644 app/src/lg/svc/LG_BADDEBT.pgc create mode 100644 app/src/lg/svc/LG_BADDEBTRCV.h create mode 100644 app/src/lg/svc/LG_BADDEBTRCV.pgc create mode 100644 app/src/lg/svc/LG_BALPROOF.h create mode 100644 app/src/lg/svc/LG_BALPROOF.pgc create mode 100644 app/src/lg/svc/LG_BIGLINE.h create mode 100644 app/src/lg/svc/LG_BIGLINE.pgc create mode 100644 app/src/lg/svc/LG_BLACKENTRY.h create mode 100644 app/src/lg/svc/LG_BLACKENTRY.pgc create mode 100644 app/src/lg/svc/LG_CLOSECHK.h create mode 100644 app/src/lg/svc/LG_CLOSECHK.pgc create mode 100644 app/src/lg/svc/LG_DAYCLOSE.h create mode 100644 app/src/lg/svc/LG_DAYCLOSE.pgc create mode 100644 app/src/lg/svc/LG_DEPTPL.h create mode 100644 app/src/lg/svc/LG_DEPTPL.pgc create mode 100644 app/src/lg/svc/LG_DUPSCAN.h create mode 100644 app/src/lg/svc/LG_DUPSCAN.pgc create mode 100644 app/src/lg/svc/LG_FEEACCRUE.h create mode 100644 app/src/lg/svc/LG_FEEACCRUE.pgc create mode 100644 app/src/lg/svc/LG_FEERECOG.h create mode 100644 app/src/lg/svc/LG_FEERECOG.pgc create mode 100644 app/src/lg/svc/LG_FEESPLIT.h create mode 100644 app/src/lg/svc/LG_FEESPLIT.pgc create mode 100644 app/src/lg/svc/LG_LINECOUNT.h create mode 100644 app/src/lg/svc/LG_LINECOUNT.pgc create mode 100644 app/src/lg/svc/LG_MASKINQ.h create mode 100644 app/src/lg/svc/LG_MASKINQ.pgc create mode 100644 app/src/lg/svc/LG_MEMOAUDIT.h create mode 100644 app/src/lg/svc/LG_MEMOAUDIT.pgc create mode 100644 app/src/lg/svc/LG_MERCHPROOF.h create mode 100644 app/src/lg/svc/LG_MERCHPROOF.pgc create mode 100644 app/src/lg/svc/LG_MONTHCLOSE.h create mode 100644 app/src/lg/svc/LG_MONTHCLOSE.pgc create mode 100644 app/src/lg/svc/LG_NETPOS.h create mode 100644 app/src/lg/svc/LG_NETPOS.pgc create mode 100644 app/src/lg/svc/LG_PAYACCRUE.h create mode 100644 app/src/lg/svc/LG_PAYACCRUE.pgc create mode 100644 app/src/lg/svc/LG_PAYRECON.h create mode 100644 app/src/lg/svc/LG_PAYRECON.pgc create mode 100644 app/src/lg/svc/LG_PENDAGE.h create mode 100644 app/src/lg/svc/LG_PENDAGE.pgc create mode 100644 app/src/lg/svc/LG_PENDLIST.h create mode 100644 app/src/lg/svc/LG_PENDLIST.pgc create mode 100644 app/src/lg/svc/LG_PERIODTB.h create mode 100644 app/src/lg/svc/LG_PERIODTB.pgc create mode 100644 app/src/lg/svc/LG_PLCLOSE.h create mode 100644 app/src/lg/svc/LG_PLCLOSE.pgc create mode 100644 app/src/lg/svc/LG_QTRCLOSE.h create mode 100644 app/src/lg/svc/LG_QTRCLOSE.pgc create mode 100644 app/src/lg/svc/LG_RECVACCRUE.h create mode 100644 app/src/lg/svc/LG_RECVACCRUE.pgc create mode 100644 app/src/lg/svc/LG_REDENTRY.h create mode 100644 app/src/lg/svc/LG_REDENTRY.pgc create mode 100644 app/src/lg/svc/LG_REJECT.h create mode 100644 app/src/lg/svc/LG_REJECT.pgc create mode 100644 app/src/lg/svc/LG_REOPEN.h create mode 100644 app/src/lg/svc/LG_REOPEN.pgc create mode 100644 app/src/lg/svc/LG_SETTLERECON.h create mode 100644 app/src/lg/svc/LG_SETTLERECON.pgc create mode 100644 app/src/lg/svc/LG_STATACCT.h create mode 100644 app/src/lg/svc/LG_STATACCT.pgc create mode 100644 app/src/lg/svc/LG_STATDC.h create mode 100644 app/src/lg/svc/LG_STATDC.pgc create mode 100644 app/src/lg/svc/LG_STATMERCH.h create mode 100644 app/src/lg/svc/LG_STATMERCH.pgc create mode 100644 app/src/lg/svc/LG_STATTYPE.h create mode 100644 app/src/lg/svc/LG_STATTYPE.pgc create mode 100644 app/src/lg/svc/LG_SUSPENSE.h create mode 100644 app/src/lg/svc/LG_SUSPENSE.pgc create mode 100644 app/src/lg/svc/LG_TBDIFF.h create mode 100644 app/src/lg/svc/LG_TBDIFF.pgc create mode 100644 app/src/lg/svc/LG_VOIDVOUCHER.h create mode 100644 app/src/lg/svc/LG_VOIDVOUCHER.pgc create mode 100644 app/src/lg/svc/LG_VOUCHTRACE.h create mode 100644 app/src/lg/svc/LG_VOUCHTRACE.pgc create mode 100644 app/src/lg/svc/LG_ZEROLINE.h create mode 100644 app/src/lg/svc/LG_ZEROLINE.pgc create mode 100644 app/src/mg/batch/mg_chanload_batch.pgc create mode 100644 app/src/mg/batch/mg_dupscan_batch.pgc create mode 100644 app/src/mg/batch/mg_gapfill_batch.pgc create mode 100644 app/src/mg/batch/mg_hourstat_batch.pgc create mode 100644 app/src/mg/batch/mg_keyver_batch.pgc create mode 100644 app/src/mg/batch/mg_macaudit_batch.pgc create mode 100644 app/src/mg/batch/mg_mtimix_batch.pgc create mode 100644 app/src/mg/batch/mg_panaudit_batch.pgc create mode 100644 app/src/mg/batch/mg_qagesweep_batch.pgc create mode 100644 app/src/mg/batch/mg_rcdist_batch.pgc create mode 100644 app/src/mg/batch/mg_reroute_batch.pgc create mode 100644 app/src/mg/batch/mg_seqsync_batch.pgc create mode 100644 app/src/mg/batch/mg_timeout_batch.pgc create mode 100644 app/src/mg/dbio/mg_chanload_batch.h create mode 100644 app/src/mg/dbio/mg_dupscan_batch.h create mode 100644 app/src/mg/dbio/mg_gapfill_batch.h create mode 100644 app/src/mg/dbio/mg_hourstat_batch.h create mode 100644 app/src/mg/dbio/mg_keyver_batch.h create mode 100644 app/src/mg/dbio/mg_macaudit_batch.h create mode 100644 app/src/mg/dbio/mg_mtimix_batch.h create mode 100644 app/src/mg/dbio/mg_panaudit_batch.h create mode 100644 app/src/mg/dbio/mg_qagesweep_batch.h create mode 100644 app/src/mg/dbio/mg_rcdist_batch.h create mode 100644 app/src/mg/dbio/mg_reroute_batch.h create mode 100644 app/src/mg/dbio/mg_seqsync_batch.h create mode 100644 app/src/mg/dbio/mg_timeout_batch.h create mode 100644 app/src/mg/svc/MG_ADVICE.h create mode 100644 app/src/mg/svc/MG_ADVICE.pgc create mode 100644 app/src/mg/svc/MG_AMTCHECK.h create mode 100644 app/src/mg/svc/MG_AMTCHECK.pgc create mode 100644 app/src/mg/svc/MG_AUTHREQ.h create mode 100644 app/src/mg/svc/MG_AUTHREQ.pgc create mode 100644 app/src/mg/svc/MG_AUTHRSP.h create mode 100644 app/src/mg/svc/MG_AUTHRSP.pgc create mode 100644 app/src/mg/svc/MG_CAPRSP.h create mode 100644 app/src/mg/svc/MG_CAPRSP.pgc create mode 100644 app/src/mg/svc/MG_CAPTURE.h create mode 100644 app/src/mg/svc/MG_CAPTURE.pgc create mode 100644 app/src/mg/svc/MG_CHANCLOSE.h create mode 100644 app/src/mg/svc/MG_CHANCLOSE.pgc create mode 100644 app/src/mg/svc/MG_CHANHEALTH.h create mode 100644 app/src/mg/svc/MG_CHANHEALTH.pgc create mode 100644 app/src/mg/svc/MG_CHANOPEN.h create mode 100644 app/src/mg/svc/MG_CHANOPEN.pgc create mode 100644 app/src/mg/svc/MG_CHANSWITCH.h create mode 100644 app/src/mg/svc/MG_CHANSWITCH.pgc create mode 100644 app/src/mg/svc/MG_DIRECTADPT.h create mode 100644 app/src/mg/svc/MG_DIRECTADPT.pgc create mode 100644 app/src/mg/svc/MG_DUPCHK.h create mode 100644 app/src/mg/svc/MG_DUPCHK.pgc create mode 100644 app/src/mg/svc/MG_ERRLIST.h create mode 100644 app/src/mg/svc/MG_ERRLIST.pgc create mode 100644 app/src/mg/svc/MG_ERRPURGE.h create mode 100644 app/src/mg/svc/MG_ERRPURGE.pgc create mode 100644 app/src/mg/svc/MG_ERRRETRY.h create mode 100644 app/src/mg/svc/MG_ERRRETRY.pgc create mode 100644 app/src/mg/svc/MG_FROMISO.h create mode 100644 app/src/mg/svc/MG_FROMISO.pgc create mode 100644 app/src/mg/svc/MG_GAPSCAN.h create mode 100644 app/src/mg/svc/MG_GAPSCAN.pgc create mode 100644 app/src/mg/svc/MG_KEYROTATE.h create mode 100644 app/src/mg/svc/MG_KEYROTATE.pgc create mode 100644 app/src/mg/svc/MG_KEYSTATUS.h create mode 100644 app/src/mg/svc/MG_KEYSTATUS.pgc create mode 100644 app/src/mg/svc/MG_MACGEN.h create mode 100644 app/src/mg/svc/MG_MACGEN.pgc create mode 100644 app/src/mg/svc/MG_MACVERIFY.h create mode 100644 app/src/mg/svc/MG_MACVERIFY.pgc create mode 100644 app/src/mg/svc/MG_NETMGMT.h create mode 100644 app/src/mg/svc/MG_NETMGMT.pgc create mode 100644 app/src/mg/svc/MG_PANMASK.h create mode 100644 app/src/mg/svc/MG_PANMASK.pgc create mode 100644 app/src/mg/svc/MG_QDEPTH.h create mode 100644 app/src/mg/svc/MG_QDEPTH.pgc create mode 100644 app/src/mg/svc/MG_REASM.h create mode 100644 app/src/mg/svc/MG_REASM.pgc create mode 100644 app/src/mg/svc/MG_REVADVICE.h create mode 100644 app/src/mg/svc/MG_REVADVICE.pgc create mode 100644 app/src/mg/svc/MG_REVERSAL.h create mode 100644 app/src/mg/svc/MG_REVERSAL.pgc create mode 100644 app/src/mg/svc/MG_RTADD.h create mode 100644 app/src/mg/svc/MG_RTADD.pgc create mode 100644 app/src/mg/svc/MG_RTDROP.h create mode 100644 app/src/mg/svc/MG_RTDROP.pgc create mode 100644 app/src/mg/svc/MG_RTLIST.h create mode 100644 app/src/mg/svc/MG_RTLIST.pgc create mode 100644 app/src/mg/svc/MG_RTSWITCH.h create mode 100644 app/src/mg/svc/MG_RTSWITCH.pgc create mode 100644 app/src/mg/svc/MG_SEQAUDIT.h create mode 100644 app/src/mg/svc/MG_SEQAUDIT.pgc create mode 100644 app/src/mg/svc/MG_SETTLEREQ.h create mode 100644 app/src/mg/svc/MG_SETTLEREQ.pgc create mode 100644 app/src/mg/svc/MG_SETTLERSP.h create mode 100644 app/src/mg/svc/MG_SETTLERSP.pgc create mode 100644 app/src/mg/svc/MG_STATDIR.h create mode 100644 app/src/mg/svc/MG_STATDIR.pgc create mode 100644 app/src/mg/svc/MG_STATHOUR.h create mode 100644 app/src/mg/svc/MG_STATHOUR.pgc create mode 100644 app/src/mg/svc/MG_STATMTI.h create mode 100644 app/src/mg/svc/MG_STATMTI.pgc create mode 100644 app/src/mg/svc/MG_STATRC.h create mode 100644 app/src/mg/svc/MG_STATRC.pgc create mode 100644 app/src/mg/svc/MG_THROTTLE.h create mode 100644 app/src/mg/svc/MG_THROTTLE.pgc create mode 100644 app/src/mg/svc/MG_TIMEOUT.h create mode 100644 app/src/mg/svc/MG_TIMEOUT.pgc create mode 100644 app/src/mg/svc/MG_TIMEOUTSET.h create mode 100644 app/src/mg/svc/MG_TIMEOUTSET.pgc create mode 100644 app/src/mg/svc/MG_TOISO.h create mode 100644 app/src/mg/svc/MG_TOISO.pgc create mode 100644 app/src/mg/svc/MG_TPSCHECK.h create mode 100644 app/src/mg/svc/MG_TPSCHECK.pgc create mode 100644 app/src/mg/svc/MG_VAN1ADPT.h create mode 100644 app/src/mg/svc/MG_VAN1ADPT.pgc create mode 100644 app/src/mg/svc/MG_VAN2ADPT.h create mode 100644 app/src/mg/svc/MG_VAN2ADPT.pgc create mode 100644 app/src/mm/batch/mm_acct_reverify_batch.pgc create mode 100644 app/src/mm/batch/mm_acct_reverify_cpy.h create mode 100644 app/src/mm/batch/mm_apply_expire_batch.pgc create mode 100644 app/src/mm/batch/mm_apply_expire_cpy.h create mode 100644 app/src/mm/batch/mm_audit_compact_batch.pgc create mode 100644 app/src/mm/batch/mm_audit_compact_cpy.h create mode 100644 app/src/mm/batch/mm_bizno_audit_batch.pgc create mode 100644 app/src/mm/batch/mm_bizno_audit_cpy.h create mode 100644 app/src/mm/batch/mm_close_sweep_batch.pgc create mode 100644 app/src/mm/batch/mm_close_sweep_cpy.h create mode 100644 app/src/mm/batch/mm_codemap_gc_batch.pgc create mode 100644 app/src/mm/batch/mm_codemap_gc_cpy.h create mode 100644 app/src/mm/batch/mm_consist_report_batch.pgc create mode 100644 app/src/mm/batch/mm_consist_report_cpy.h create mode 100644 app/src/mm/batch/mm_contract_renew_batch.pgc create mode 100644 app/src/mm/batch/mm_contract_renew_cpy.h create mode 100644 app/src/mm/batch/mm_grade_demote_batch.pgc create mode 100644 app/src/mm/batch/mm_grade_demote_cpy.h create mode 100644 app/src/mm/batch/mm_mcc_reclass_batch.pgc create mode 100644 app/src/mm/batch/mm_mcc_reclass_cpy.h create mode 100644 app/src/mm/batch/mm_partner_sync_batch.pgc create mode 100644 app/src/mm/batch/mm_partner_sync_cpy.h create mode 100644 app/src/mm/batch/mm_rel_orphan_batch.pgc create mode 100644 app/src/mm/batch/mm_rel_orphan_cpy.h create mode 100644 app/src/mm/batch/mm_term_scrap_batch.pgc create mode 100644 app/src/mm/batch/mm_term_scrap_cpy.h create mode 100644 app/src/mm/svc/MM_ACCT_VERIFY.h create mode 100644 app/src/mm/svc/MM_ACCT_VERIFY.pgc create mode 100644 app/src/mm/svc/MM_ACCT_VRF_HIST.h create mode 100644 app/src/mm/svc/MM_ACCT_VRF_HIST.pgc create mode 100644 app/src/mm/svc/MM_ACCT_VRF_STAT.h create mode 100644 app/src/mm/svc/MM_ACCT_VRF_STAT.pgc create mode 100644 app/src/mm/svc/MM_APPLY_APPROVE.h create mode 100644 app/src/mm/svc/MM_APPLY_APPROVE.pgc create mode 100644 app/src/mm/svc/MM_APPLY_REG.h create mode 100644 app/src/mm/svc/MM_APPLY_REG.pgc create mode 100644 app/src/mm/svc/MM_APPLY_REJECT.h create mode 100644 app/src/mm/svc/MM_APPLY_REJECT.pgc create mode 100644 app/src/mm/svc/MM_APPLY_REVIEW.h create mode 100644 app/src/mm/svc/MM_APPLY_REVIEW.pgc create mode 100644 app/src/mm/svc/MM_APPLY_STATUS.h create mode 100644 app/src/mm/svc/MM_APPLY_STATUS.pgc create mode 100644 app/src/mm/svc/MM_AUDIT_PURGE.h create mode 100644 app/src/mm/svc/MM_AUDIT_PURGE.pgc create mode 100644 app/src/mm/svc/MM_AUDIT_RECENT.h create mode 100644 app/src/mm/svc/MM_AUDIT_RECENT.pgc create mode 100644 app/src/mm/svc/MM_AUDIT_TRAIL.h create mode 100644 app/src/mm/svc/MM_AUDIT_TRAIL.pgc create mode 100644 app/src/mm/svc/MM_BIZNO_CHECK.h create mode 100644 app/src/mm/svc/MM_BIZNO_CHECK.pgc create mode 100644 app/src/mm/svc/MM_BIZNO_UPD.h create mode 100644 app/src/mm/svc/MM_BIZNO_UPD.pgc create mode 100644 app/src/mm/svc/MM_CHK_FEERATE.h create mode 100644 app/src/mm/svc/MM_CHK_FEERATE.pgc create mode 100644 app/src/mm/svc/MM_CHK_LIMIT.h create mode 100644 app/src/mm/svc/MM_CHK_LIMIT.pgc create mode 100644 app/src/mm/svc/MM_CHK_TERM.h create mode 100644 app/src/mm/svc/MM_CHK_TERM.pgc create mode 100644 app/src/mm/svc/MM_CODEMAP_DEL.h create mode 100644 app/src/mm/svc/MM_CODEMAP_DEL.pgc create mode 100644 app/src/mm/svc/MM_CODEMAP_GET.h create mode 100644 app/src/mm/svc/MM_CODEMAP_GET.pgc create mode 100644 app/src/mm/svc/MM_CODEMAP_SET.h create mode 100644 app/src/mm/svc/MM_CODEMAP_SET.pgc create mode 100644 app/src/mm/svc/MM_CONTRACT_END.h create mode 100644 app/src/mm/svc/MM_CONTRACT_END.pgc create mode 100644 app/src/mm/svc/MM_CONTRACT_INQ.h create mode 100644 app/src/mm/svc/MM_CONTRACT_INQ.pgc create mode 100644 app/src/mm/svc/MM_CONTRACT_NEW.h create mode 100644 app/src/mm/svc/MM_CONTRACT_NEW.pgc create mode 100644 app/src/mm/svc/MM_CONTRACT_RENEW.h create mode 100644 app/src/mm/svc/MM_CONTRACT_RENEW.pgc create mode 100644 app/src/mm/svc/MM_GRADE_ADJUST.h create mode 100644 app/src/mm/svc/MM_GRADE_ADJUST.pgc create mode 100644 app/src/mm/svc/MM_GRADE_DIST.h create mode 100644 app/src/mm/svc/MM_GRADE_DIST.pgc create mode 100644 app/src/mm/svc/MM_GRADE_PREVIEW.h create mode 100644 app/src/mm/svc/MM_GRADE_PREVIEW.pgc create mode 100644 app/src/mm/svc/MM_MCC_INQ.h create mode 100644 app/src/mm/svc/MM_MCC_INQ.pgc create mode 100644 app/src/mm/svc/MM_MCC_RISKLIST.h create mode 100644 app/src/mm/svc/MM_MCC_RISKLIST.pgc create mode 100644 app/src/mm/svc/MM_MCC_UPD.h create mode 100644 app/src/mm/svc/MM_MCC_UPD.pgc create mode 100644 app/src/mm/svc/MM_MERCH_CLOSE.h create mode 100644 app/src/mm/svc/MM_MERCH_CLOSE.pgc create mode 100644 app/src/mm/svc/MM_MERCH_PAUSE.h create mode 100644 app/src/mm/svc/MM_MERCH_PAUSE.pgc create mode 100644 app/src/mm/svc/MM_MERCH_RESUME.h create mode 100644 app/src/mm/svc/MM_MERCH_RESUME.pgc create mode 100644 app/src/mm/svc/MM_PARTNER_INQ.h create mode 100644 app/src/mm/svc/MM_PARTNER_INQ.pgc create mode 100644 app/src/mm/svc/MM_PARTNER_REG.h create mode 100644 app/src/mm/svc/MM_PARTNER_REG.pgc create mode 100644 app/src/mm/svc/MM_PARTNER_STATUS.h create mode 100644 app/src/mm/svc/MM_PARTNER_STATUS.pgc create mode 100644 app/src/mm/svc/MM_RATE_APPROVE.h create mode 100644 app/src/mm/svc/MM_RATE_APPROVE.pgc create mode 100644 app/src/mm/svc/MM_RATE_REJECT.h create mode 100644 app/src/mm/svc/MM_RATE_REJECT.pgc create mode 100644 app/src/mm/svc/MM_RATE_REQ.h create mode 100644 app/src/mm/svc/MM_RATE_REQ.pgc create mode 100644 app/src/mm/svc/MM_REL_LINK.h create mode 100644 app/src/mm/svc/MM_REL_LINK.pgc create mode 100644 app/src/mm/svc/MM_REL_LIST.h create mode 100644 app/src/mm/svc/MM_REL_LIST.pgc create mode 100644 app/src/mm/svc/MM_REL_UNLINK.h create mode 100644 app/src/mm/svc/MM_REL_UNLINK.pgc create mode 100644 app/src/mm/svc/MM_TERM_CNT.h create mode 100644 app/src/mm/svc/MM_TERM_CNT.pgc create mode 100644 app/src/mm/svc/MM_TERM_MOVE.h create mode 100644 app/src/mm/svc/MM_TERM_MOVE.pgc create mode 100644 app/src/mm/svc/MM_TERM_SCRAP.h create mode 100644 app/src/mm/svc/MM_TERM_SCRAP.pgc create mode 100644 app/src/mm/svc/MM_TERM_SUSPEND.h create mode 100644 app/src/mm/svc/MM_TERM_SUSPEND.pgc create mode 100644 app/src/py/batch/py_acctsync_batch.pgc create mode 100644 app/src/py/batch/py_acctsync_cpy.h create mode 100644 app/src/py/batch/py_advrepay_batch.pgc create mode 100644 app/src/py/batch/py_advrepay_cpy.h create mode 100644 app/src/py/batch/py_bankfile_batch.pgc create mode 100644 app/src/py/batch/py_bankfile_cpy.h create mode 100644 app/src/py/batch/py_dormantstop_batch.pgc create mode 100644 app/src/py/batch/py_dormantstop_cpy.h create mode 100644 app/src/py/batch/py_failstat_batch.pgc create mode 100644 app/src/py/batch/py_failstat_cpy.h create mode 100644 app/src/py/batch/py_feewaive_batch.pgc create mode 100644 app/src/py/batch/py_feewaive_cpy.h create mode 100644 app/src/py/batch/py_holdaging_batch.pgc create mode 100644 app/src/py/batch/py_holdaging_cpy.h create mode 100644 app/src/py/batch/py_limitguard_batch.pgc create mode 100644 app/src/py/batch/py_limitguard_cpy.h create mode 100644 app/src/py/batch/py_mergesmall_batch.pgc create mode 100644 app/src/py/batch/py_mergesmall_cpy.h create mode 100644 app/src/py/batch/py_notify_batch.pgc create mode 100644 app/src/py/batch/py_notify_cpy.h create mode 100644 app/src/py/batch/py_schedexpire_batch.pgc create mode 100644 app/src/py/batch/py_schedexpire_cpy.h create mode 100644 app/src/py/batch/py_seizescan_batch.pgc create mode 100644 app/src/py/batch/py_seizescan_cpy.h create mode 100644 app/src/py/batch/py_xferrecon_batch.pgc create mode 100644 app/src/py/batch/py_xferrecon_cpy.h create mode 100644 app/src/py/svc/PAY_ACCTCHANGE.h create mode 100644 app/src/py/svc/PAY_ACCTCHANGE.pgc create mode 100644 app/src/py/svc/PAY_ACCTLIST.h create mode 100644 app/src/py/svc/PAY_ACCTLIST.pgc create mode 100644 app/src/py/svc/PAY_ADVCALC.h create mode 100644 app/src/py/svc/PAY_ADVCALC.pgc create mode 100644 app/src/py/svc/PAY_ADVREPAY.h create mode 100644 app/src/py/svc/PAY_ADVREPAY.pgc create mode 100644 app/src/py/svc/PAY_APPRBULK.h create mode 100644 app/src/py/svc/PAY_APPRBULK.pgc create mode 100644 app/src/py/svc/PAY_APPRLIMIT.h create mode 100644 app/src/py/svc/PAY_APPRLIMIT.pgc create mode 100644 app/src/py/svc/PAY_APPRREQ.h create mode 100644 app/src/py/svc/PAY_APPRREQ.pgc create mode 100644 app/src/py/svc/PAY_BANKFILE.h create mode 100644 app/src/py/svc/PAY_BANKFILE.pgc create mode 100644 app/src/py/svc/PAY_CANCELHIST.h create mode 100644 app/src/py/svc/PAY_CANCELHIST.pgc create mode 100644 app/src/py/svc/PAY_DORMANT.h create mode 100644 app/src/py/svc/PAY_DORMANT.pgc create mode 100644 app/src/py/svc/PAY_DORMSTOP.h create mode 100644 app/src/py/svc/PAY_DORMSTOP.pgc create mode 100644 app/src/py/svc/PAY_FAILRATE.h create mode 100644 app/src/py/svc/PAY_FAILRATE.pgc create mode 100644 app/src/py/svc/PAY_FAILSTAT.h create mode 100644 app/src/py/svc/PAY_FAILSTAT.pgc create mode 100644 app/src/py/svc/PAY_FAILTOP.h create mode 100644 app/src/py/svc/PAY_FAILTOP.pgc create mode 100644 app/src/py/svc/PAY_FEEMIN.h create mode 100644 app/src/py/svc/PAY_FEEMIN.pgc create mode 100644 app/src/py/svc/PAY_FEERATE.h create mode 100644 app/src/py/svc/PAY_FEERATE.pgc create mode 100644 app/src/py/svc/PAY_FEEWAIVE.h create mode 100644 app/src/py/svc/PAY_FEEWAIVE.pgc create mode 100644 app/src/py/svc/PAY_FILEREBUILD.h create mode 100644 app/src/py/svc/PAY_FILEREBUILD.pgc create mode 100644 app/src/py/svc/PAY_FILESTAT.h create mode 100644 app/src/py/svc/PAY_FILESTAT.pgc create mode 100644 app/src/py/svc/PAY_FILEVERIFY.h create mode 100644 app/src/py/svc/PAY_FILEVERIFY.pgc create mode 100644 app/src/py/svc/PAY_HOLDAGING.h create mode 100644 app/src/py/svc/PAY_HOLDAGING.pgc create mode 100644 app/src/py/svc/PAY_HOLDLIST.h create mode 100644 app/src/py/svc/PAY_HOLDLIST.pgc create mode 100644 app/src/py/svc/PAY_HOLDREASON.h create mode 100644 app/src/py/svc/PAY_HOLDREASON.pgc create mode 100644 app/src/py/svc/PAY_LEDGERSUM.h create mode 100644 app/src/py/svc/PAY_LEDGERSUM.pgc create mode 100644 app/src/py/svc/PAY_LIMITCHK.h create mode 100644 app/src/py/svc/PAY_LIMITCHK.pgc create mode 100644 app/src/py/svc/PAY_LIMITHOLD.h create mode 100644 app/src/py/svc/PAY_LIMITHOLD.pgc create mode 100644 app/src/py/svc/PAY_MERGE.h create mode 100644 app/src/py/svc/PAY_MERGE.pgc create mode 100644 app/src/py/svc/PAY_NETRECALC.h create mode 100644 app/src/py/svc/PAY_NETRECALC.pgc create mode 100644 app/src/py/svc/PAY_NOTIFYEMAIL.h create mode 100644 app/src/py/svc/PAY_NOTIFYEMAIL.pgc create mode 100644 app/src/py/svc/PAY_NOTIFYPLAN.h create mode 100644 app/src/py/svc/PAY_NOTIFYPLAN.pgc create mode 100644 app/src/py/svc/PAY_NOTIFYSMS.h create mode 100644 app/src/py/svc/PAY_NOTIFYSMS.pgc create mode 100644 app/src/py/svc/PAY_REFUND.h create mode 100644 app/src/py/svc/PAY_REFUND.pgc create mode 100644 app/src/py/svc/PAY_REPAYNEW.h create mode 100644 app/src/py/svc/PAY_REPAYNEW.pgc create mode 100644 app/src/py/svc/PAY_SCHEDCANCEL.h create mode 100644 app/src/py/svc/PAY_SCHEDCANCEL.pgc create mode 100644 app/src/py/svc/PAY_SEIZE.h create mode 100644 app/src/py/svc/PAY_SEIZE.pgc create mode 100644 app/src/py/svc/PAY_SEIZECHK.h create mode 100644 app/src/py/svc/PAY_SEIZECHK.pgc create mode 100644 app/src/py/svc/PAY_SEIZEREL.h create mode 100644 app/src/py/svc/PAY_SEIZEREL.pgc create mode 100644 app/src/py/svc/PAY_SPLITRATIO.h create mode 100644 app/src/py/svc/PAY_SPLITRATIO.pgc create mode 100644 app/src/py/svc/PAY_STATBANK.h create mode 100644 app/src/py/svc/PAY_STATBANK.pgc create mode 100644 app/src/py/svc/PAY_STATCHAN.h create mode 100644 app/src/py/svc/PAY_STATCHAN.pgc create mode 100644 app/src/py/svc/PAY_STATDAILY.h create mode 100644 app/src/py/svc/PAY_STATDAILY.pgc create mode 100644 app/src/py/svc/PAY_STATRANGE.h create mode 100644 app/src/py/svc/PAY_STATRANGE.pgc create mode 100644 app/src/py/svc/PAY_URGENT.h create mode 100644 app/src/py/svc/PAY_URGENT.pgc create mode 100644 app/src/py/svc/PAY_XFERMATCH.h create mode 100644 app/src/py/svc/PAY_XFERMATCH.pgc create mode 100644 app/src/py/svc/PAY_XFERMISS.h create mode 100644 app/src/py/svc/PAY_XFERMISS.pgc create mode 100644 app/src/rc/batch/rc_ageing_batch.pgc create mode 100644 app/src/rc/batch/rc_cancelrec_batch.pgc create mode 100644 app/src/rc/batch/rc_carryover_batch.pgc create mode 100644 app/src/rc/batch/rc_closeday_batch.pgc create mode 100644 app/src/rc/batch/rc_dupchk_batch.pgc create mode 100644 app/src/rc/batch/rc_feerecon_batch.pgc create mode 100644 app/src/rc/batch/rc_fxrecon_batch.pgc create mode 100644 app/src/rc/batch/rc_holdrsn_batch.pgc create mode 100644 app/src/rc/batch/rc_notify_batch.pgc create mode 100644 app/src/rc/batch/rc_orphan_batch.pgc create mode 100644 app/src/rc/batch/rc_ratestat_batch.pgc create mode 100644 app/src/rc/batch/rc_retro_batch.pgc create mode 100644 app/src/rc/batch/rc_threshold_batch.pgc create mode 100644 app/src/rc/dbio/rc_ageing_batch.h create mode 100644 app/src/rc/dbio/rc_cancelrec_batch.h create mode 100644 app/src/rc/dbio/rc_carryover_batch.h create mode 100644 app/src/rc/dbio/rc_closeday_batch.h create mode 100644 app/src/rc/dbio/rc_dupchk_batch.h create mode 100644 app/src/rc/dbio/rc_feerecon_batch.h create mode 100644 app/src/rc/dbio/rc_fxrecon_batch.h create mode 100644 app/src/rc/dbio/rc_holdrsn_batch.h create mode 100644 app/src/rc/dbio/rc_notify_batch.h create mode 100644 app/src/rc/dbio/rc_orphan_batch.h create mode 100644 app/src/rc/dbio/rc_ratestat_batch.h create mode 100644 app/src/rc/dbio/rc_retro_batch.h create mode 100644 app/src/rc/dbio/rc_threshold_batch.h create mode 100644 app/src/rc/svc/RC_4WAY.h create mode 100644 app/src/rc/svc/RC_4WAY.pgc create mode 100644 app/src/rc/svc/RC_AGEING.h create mode 100644 app/src/rc/svc/RC_AGEING.pgc create mode 100644 app/src/rc/svc/RC_AUDITLOG.h create mode 100644 app/src/rc/svc/RC_AUDITLOG.pgc create mode 100644 app/src/rc/svc/RC_AUDITTRAIL.h create mode 100644 app/src/rc/svc/RC_AUDITTRAIL.pgc create mode 100644 app/src/rc/svc/RC_BYCATEGORY.h create mode 100644 app/src/rc/svc/RC_BYCATEGORY.pgc create mode 100644 app/src/rc/svc/RC_BYCHANNEL.h create mode 100644 app/src/rc/svc/RC_BYCHANNEL.pgc create mode 100644 app/src/rc/svc/RC_BYMGROUP.h create mode 100644 app/src/rc/svc/RC_BYMGROUP.pgc create mode 100644 app/src/rc/svc/RC_CANCELREC.h create mode 100644 app/src/rc/svc/RC_CANCELREC.pgc create mode 100644 app/src/rc/svc/RC_CARRYOVER.h create mode 100644 app/src/rc/svc/RC_CARRYOVER.pgc create mode 100644 app/src/rc/svc/RC_CLOSEDAY.h create mode 100644 app/src/rc/svc/RC_CLOSEDAY.pgc create mode 100644 app/src/rc/svc/RC_DEPMATCH.h create mode 100644 app/src/rc/svc/RC_DEPMATCH.pgc create mode 100644 app/src/rc/svc/RC_DIFFCAUSE.h create mode 100644 app/src/rc/svc/RC_DIFFCAUSE.pgc create mode 100644 app/src/rc/svc/RC_DIFFTREND.h create mode 100644 app/src/rc/svc/RC_DIFFTREND.pgc create mode 100644 app/src/rc/svc/RC_DUPCHK.h create mode 100644 app/src/rc/svc/RC_DUPCHK.pgc create mode 100644 app/src/rc/svc/RC_EDIRECON.h create mode 100644 app/src/rc/svc/RC_EDIRECON.pgc create mode 100644 app/src/rc/svc/RC_ESCALATE.h create mode 100644 app/src/rc/svc/RC_ESCALATE.pgc create mode 100644 app/src/rc/svc/RC_FEEDIFF.h create mode 100644 app/src/rc/svc/RC_FEEDIFF.pgc create mode 100644 app/src/rc/svc/RC_FEERECON.h create mode 100644 app/src/rc/svc/RC_FEERECON.pgc create mode 100644 app/src/rc/svc/RC_FXRATECHK.h create mode 100644 app/src/rc/svc/RC_FXRATECHK.pgc create mode 100644 app/src/rc/svc/RC_FXRECON.h create mode 100644 app/src/rc/svc/RC_FXRECON.pgc create mode 100644 app/src/rc/svc/RC_GAPSCAN.h create mode 100644 app/src/rc/svc/RC_GAPSCAN.pgc create mode 100644 app/src/rc/svc/RC_HOLDAGE.h create mode 100644 app/src/rc/svc/RC_HOLDAGE.pgc create mode 100644 app/src/rc/svc/RC_HOLDRSN.h create mode 100644 app/src/rc/svc/RC_HOLDRSN.pgc create mode 100644 app/src/rc/svc/RC_INSTRECON.h create mode 100644 app/src/rc/svc/RC_INSTRECON.pgc create mode 100644 app/src/rc/svc/RC_LIMITCHK.h create mode 100644 app/src/rc/svc/RC_LIMITCHK.pgc create mode 100644 app/src/rc/svc/RC_MANAPPRV.h create mode 100644 app/src/rc/svc/RC_MANAPPRV.pgc create mode 100644 app/src/rc/svc/RC_MANREG.h create mode 100644 app/src/rc/svc/RC_MANREG.pgc create mode 100644 app/src/rc/svc/RC_MANREJECT.h create mode 100644 app/src/rc/svc/RC_MANREJECT.pgc create mode 100644 app/src/rc/svc/RC_NOTIFY.h create mode 100644 app/src/rc/svc/RC_NOTIFY.pgc create mode 100644 app/src/rc/svc/RC_ORPHAN.h create mode 100644 app/src/rc/svc/RC_ORPHAN.pgc create mode 100644 app/src/rc/svc/RC_PARTACQ.h create mode 100644 app/src/rc/svc/RC_PARTACQ.pgc create mode 100644 app/src/rc/svc/RC_PAYMATCH.h create mode 100644 app/src/rc/svc/RC_PAYMATCH.pgc create mode 100644 app/src/rc/svc/RC_RATEBYISS.h create mode 100644 app/src/rc/svc/RC_RATEBYISS.pgc create mode 100644 app/src/rc/svc/RC_RATESTAT.h create mode 100644 app/src/rc/svc/RC_RATESTAT.pgc create mode 100644 app/src/rc/svc/RC_REOPEN.h create mode 100644 app/src/rc/svc/RC_REOPEN.pgc create mode 100644 app/src/rc/svc/RC_RERUN.h create mode 100644 app/src/rc/svc/RC_RERUN.pgc create mode 100644 app/src/rc/svc/RC_RETRO.h create mode 100644 app/src/rc/svc/RC_RETRO.pgc create mode 100644 app/src/rc/svc/RC_SNAPSHOT.h create mode 100644 app/src/rc/svc/RC_SNAPSHOT.pgc create mode 100644 app/src/rc/svc/RC_SUMCHK.h create mode 100644 app/src/rc/svc/RC_SUMCHK.pgc create mode 100644 app/src/rc/svc/RC_TAXRECON.h create mode 100644 app/src/rc/svc/RC_TAXRECON.pgc create mode 100644 app/src/rc/svc/RC_THRESHOLD.h create mode 100644 app/src/rc/svc/RC_THRESHOLD.pgc create mode 100644 app/src/rc/svc/RC_TOPDIFF.h create mode 100644 app/src/rc/svc/RC_TOPDIFF.pgc create mode 100644 app/src/rc/svc/RC_VOIDREC.h create mode 100644 app/src/rc/svc/RC_VOIDREC.pgc create mode 100644 app/src/rc/svc/RC_WHTRECON.h create mode 100644 app/src/rc/svc/RC_WHTRECON.pgc create mode 100644 app/src/rc/svc/RC_ZEROAMT.h create mode 100644 app/src/rc/svc/RC_ZEROAMT.pgc create mode 100644 app/src/st/batch/st_approve_batch.pgc create mode 100644 app/src/st/batch/st_deposit_batch.pgc create mode 100644 app/src/st/batch/st_grproll_batch.pgc create mode 100644 app/src/st/batch/st_incent_batch.pgc create mode 100644 app/src/st/batch/st_partial_batch.pgc create mode 100644 app/src/st/batch/st_promo_batch.pgc create mode 100644 app/src/st/batch/st_ratesim_batch.pgc create mode 100644 app/src/st/batch/st_recvsweep_batch.pgc create mode 100644 app/src/st/batch/st_rerun_batch.pgc create mode 100644 app/src/st/batch/st_retrorate_batch.pgc create mode 100644 app/src/st/batch/st_vatreport_batch.pgc create mode 100644 app/src/st/batch/st_weekly_batch.pgc create mode 100644 app/src/st/batch/st_whtreport_batch.pgc create mode 100644 app/src/st/dbio/st_approve_batch.h create mode 100644 app/src/st/dbio/st_deposit_batch.h create mode 100644 app/src/st/dbio/st_grproll_batch.h create mode 100644 app/src/st/dbio/st_incent_batch.h create mode 100644 app/src/st/dbio/st_partial_batch.h create mode 100644 app/src/st/dbio/st_promo_batch.h create mode 100644 app/src/st/dbio/st_ratesim_batch.h create mode 100644 app/src/st/dbio/st_recvsweep_batch.h create mode 100644 app/src/st/dbio/st_rerun_batch.h create mode 100644 app/src/st/dbio/st_retrorate_batch.h create mode 100644 app/src/st/dbio/st_vatreport_batch.h create mode 100644 app/src/st/dbio/st_weekly_batch.h create mode 100644 app/src/st/dbio/st_whtreport_batch.h create mode 100644 app/src/st/svc/ST_ADJHIST.h create mode 100644 app/src/st/svc/ST_ADJHIST.pgc create mode 100644 app/src/st/svc/ST_APPROVE.h create mode 100644 app/src/st/svc/ST_APPROVE.pgc create mode 100644 app/src/st/svc/ST_CAPCHECK.h create mode 100644 app/src/st/svc/ST_CAPCHECK.pgc create mode 100644 app/src/st/svc/ST_CARRYOVER.h create mode 100644 app/src/st/svc/ST_CARRYOVER.pgc create mode 100644 app/src/st/svc/ST_CHNLSTAT.h create mode 100644 app/src/st/svc/ST_CHNLSTAT.pgc create mode 100644 app/src/st/svc/ST_CXLRECALC.h create mode 100644 app/src/st/svc/ST_CXLRECALC.pgc create mode 100644 app/src/st/svc/ST_CYCLED1.h create mode 100644 app/src/st/svc/ST_CYCLED1.pgc create mode 100644 app/src/st/svc/ST_CYCLED2.h create mode 100644 app/src/st/svc/ST_CYCLED2.pgc create mode 100644 app/src/st/svc/ST_CYCLEMON.h create mode 100644 app/src/st/svc/ST_CYCLEMON.pgc create mode 100644 app/src/st/svc/ST_CYCLEWEEK.h create mode 100644 app/src/st/svc/ST_CYCLEWEEK.pgc create mode 100644 app/src/st/svc/ST_DAILYCLOSE.h create mode 100644 app/src/st/svc/ST_DAILYCLOSE.pgc create mode 100644 app/src/st/svc/ST_DEBTCOLLECT.h create mode 100644 app/src/st/svc/ST_DEBTCOLLECT.pgc create mode 100644 app/src/st/svc/ST_DEPOSITCUT.h create mode 100644 app/src/st/svc/ST_DEPOSITCUT.pgc create mode 100644 app/src/st/svc/ST_DISPUTE.h create mode 100644 app/src/st/svc/ST_DISPUTE.pgc create mode 100644 app/src/st/svc/ST_DTLSUM.h create mode 100644 app/src/st/svc/ST_DTLSUM.pgc create mode 100644 app/src/st/svc/ST_EDIRECON.h create mode 100644 app/src/st/svc/ST_EDIRECON.pgc create mode 100644 app/src/st/svc/ST_FEEPREVIEW.h create mode 100644 app/src/st/svc/ST_FEEPREVIEW.pgc create mode 100644 app/src/st/svc/ST_FEERATIO.h create mode 100644 app/src/st/svc/ST_FEERATIO.pgc create mode 100644 app/src/st/svc/ST_FXSETTLE.h create mode 100644 app/src/st/svc/ST_FXSETTLE.pgc create mode 100644 app/src/st/svc/ST_GRPSETTLE.h create mode 100644 app/src/st/svc/ST_GRPSETTLE.pgc create mode 100644 app/src/st/svc/ST_HOLDAMT.h create mode 100644 app/src/st/svc/ST_HOLDAMT.pgc create mode 100644 app/src/st/svc/ST_ISSUERSTAT.h create mode 100644 app/src/st/svc/ST_ISSUERSTAT.pgc create mode 100644 app/src/st/svc/ST_LEDGERSNAP.h create mode 100644 app/src/st/svc/ST_LEDGERSNAP.pgc create mode 100644 app/src/st/svc/ST_LIMITCHK.h create mode 100644 app/src/st/svc/ST_LIMITCHK.pgc create mode 100644 app/src/st/svc/ST_MERCHRATE.h create mode 100644 app/src/st/svc/ST_MERCHRATE.pgc create mode 100644 app/src/st/svc/ST_MINFEE.h create mode 100644 app/src/st/svc/ST_MINFEE.pgc create mode 100644 app/src/st/svc/ST_NEGSWEEP.h create mode 100644 app/src/st/svc/ST_NEGSWEEP.pgc create mode 100644 app/src/st/svc/ST_PARTIAL.h create mode 100644 app/src/st/svc/ST_PARTIAL.pgc create mode 100644 app/src/st/svc/ST_PAYRETRY.h create mode 100644 app/src/st/svc/ST_PAYRETRY.pgc create mode 100644 app/src/st/svc/ST_PAYSTATUS.h create mode 100644 app/src/st/svc/ST_PAYSTATUS.pgc create mode 100644 app/src/st/svc/ST_PROMOCALC.h create mode 100644 app/src/st/svc/ST_PROMOCALC.pgc create mode 100644 app/src/st/svc/ST_RATESIM.h create mode 100644 app/src/st/svc/ST_RATESIM.pgc create mode 100644 app/src/st/svc/ST_RECVOFFSET.h create mode 100644 app/src/st/svc/ST_RECVOFFSET.pgc create mode 100644 app/src/st/svc/ST_REOPEN.h create mode 100644 app/src/st/svc/ST_REOPEN.pgc create mode 100644 app/src/st/svc/ST_RERUN.h create mode 100644 app/src/st/svc/ST_RERUN.pgc create mode 100644 app/src/st/svc/ST_RETRORATE.h create mode 100644 app/src/st/svc/ST_RETRORATE.pgc create mode 100644 app/src/st/svc/ST_ROUNDING.h create mode 100644 app/src/st/svc/ST_ROUNDING.pgc create mode 100644 app/src/st/svc/ST_SPLITEVEN.h create mode 100644 app/src/st/svc/ST_SPLITEVEN.pgc create mode 100644 app/src/st/svc/ST_STATDETAIL.h create mode 100644 app/src/st/svc/ST_STATDETAIL.pgc create mode 100644 app/src/st/svc/ST_TAXINVCHK.h create mode 100644 app/src/st/svc/ST_TAXINVCHK.pgc create mode 100644 app/src/st/svc/ST_TIERINCENT.h create mode 100644 app/src/st/svc/ST_TIERINCENT.pgc create mode 100644 app/src/st/svc/ST_TOPMERCH.h create mode 100644 app/src/st/svc/ST_TOPMERCH.pgc create mode 100644 app/src/st/svc/ST_UNPAIDSUM.h create mode 100644 app/src/st/svc/ST_UNPAIDSUM.pgc create mode 100644 app/src/st/svc/ST_VATREPORT.h create mode 100644 app/src/st/svc/ST_VATREPORT.pgc create mode 100644 app/src/st/svc/ST_WHTREPORT.h create mode 100644 app/src/st/svc/ST_WHTREPORT.pgc create mode 100644 app/src/vl/batch/vl_agedopen_batch.pgc create mode 100644 app/src/vl/batch/vl_apprsum_batch.pgc create mode 100644 app/src/vl/batch/vl_daterev_batch.pgc create mode 100644 app/src/vl/batch/vl_duppay_batch.pgc create mode 100644 app/src/vl/batch/vl_feerecalc_batch.pgc create mode 100644 app/src/vl/batch/vl_negbal_batch.pgc create mode 100644 app/src/vl/batch/vl_ratedev_batch.pgc create mode 100644 app/src/vl/batch/vl_ruleaudit_batch.pgc create mode 100644 app/src/vl/batch/vl_sumrecon_batch.pgc create mode 100644 app/src/vl/batch/vl_thrtrim_batch.pgc create mode 100644 app/src/vl/batch/vl_tristage_batch.pgc create mode 100644 app/src/vl/batch/vl_unacq_batch.pgc create mode 100644 app/src/vl/batch/vl_wfstat_batch.pgc create mode 100644 app/src/vl/dbio/vl_agedopen_batch.h create mode 100644 app/src/vl/dbio/vl_apprsum_batch.h create mode 100644 app/src/vl/dbio/vl_daterev_batch.h create mode 100644 app/src/vl/dbio/vl_duppay_batch.h create mode 100644 app/src/vl/dbio/vl_feerecalc_batch.h create mode 100644 app/src/vl/dbio/vl_negbal_batch.h create mode 100644 app/src/vl/dbio/vl_ratedev_batch.h create mode 100644 app/src/vl/dbio/vl_ruleaudit_batch.h create mode 100644 app/src/vl/dbio/vl_sumrecon_batch.h create mode 100644 app/src/vl/dbio/vl_thrtrim_batch.h create mode 100644 app/src/vl/dbio/vl_tristage_batch.h create mode 100644 app/src/vl/dbio/vl_unacq_batch.h create mode 100644 app/src/vl/dbio/vl_wfstat_batch.h create mode 100644 app/src/vl/svc/VL_AGEDCONF.h create mode 100644 app/src/vl/svc/VL_AGEDCONF.pgc create mode 100644 app/src/vl/svc/VL_AGEDOPEN.h create mode 100644 app/src/vl/svc/VL_AGEDOPEN.pgc create mode 100644 app/src/vl/svc/VL_APPRAGG.h create mode 100644 app/src/vl/svc/VL_APPRAGG.pgc create mode 100644 app/src/vl/svc/VL_APPRAMT.h create mode 100644 app/src/vl/svc/VL_APPRAMT.pgc create mode 100644 app/src/vl/svc/VL_CANCELMIS.h create mode 100644 app/src/vl/svc/VL_CANCELMIS.pgc create mode 100644 app/src/vl/svc/VL_DATEREV.h create mode 100644 app/src/vl/svc/VL_DATEREV.pgc create mode 100644 app/src/vl/svc/VL_DAYLIMIT.h create mode 100644 app/src/vl/svc/VL_DAYLIMIT.pgc create mode 100644 app/src/vl/svc/VL_DUPPAY.h create mode 100644 app/src/vl/svc/VL_DUPPAY.pgc create mode 100644 app/src/vl/svc/VL_DUPPAYDAY.h create mode 100644 app/src/vl/svc/VL_DUPPAYDAY.pgc create mode 100644 app/src/vl/svc/VL_DUPSETTLE.h create mode 100644 app/src/vl/svc/VL_DUPSETTLE.pgc create mode 100644 app/src/vl/svc/VL_FEEADJNET.h create mode 100644 app/src/vl/svc/VL_FEEADJNET.pgc create mode 100644 app/src/vl/svc/VL_FEERECALC.h create mode 100644 app/src/vl/svc/VL_FEERECALC.pgc create mode 100644 app/src/vl/svc/VL_FXCHK.h create mode 100644 app/src/vl/svc/VL_FXCHK.pgc create mode 100644 app/src/vl/svc/VL_INSTCHK.h create mode 100644 app/src/vl/svc/VL_INSTCHK.pgc create mode 100644 app/src/vl/svc/VL_LEDGBAL.h create mode 100644 app/src/vl/svc/VL_LEDGBAL.pgc create mode 100644 app/src/vl/svc/VL_LEDGDATE.h create mode 100644 app/src/vl/svc/VL_LEDGDATE.pgc create mode 100644 app/src/vl/svc/VL_LEDGENTRY.h create mode 100644 app/src/vl/svc/VL_LEDGENTRY.pgc create mode 100644 app/src/vl/svc/VL_LIMITNEAR.h create mode 100644 app/src/vl/svc/VL_LIMITNEAR.pgc create mode 100644 app/src/vl/svc/VL_MDRRATE.h create mode 100644 app/src/vl/svc/VL_MDRRATE.pgc create mode 100644 app/src/vl/svc/VL_NEGBAL.h create mode 100644 app/src/vl/svc/VL_NEGBAL.pgc create mode 100644 app/src/vl/svc/VL_NEGNET.h create mode 100644 app/src/vl/svc/VL_NEGNET.pgc create mode 100644 app/src/vl/svc/VL_RATEDEV.h create mode 100644 app/src/vl/svc/VL_RATEDEV.pgc create mode 100644 app/src/vl/svc/VL_RILEDGER.h create mode 100644 app/src/vl/svc/VL_RILEDGER.pgc create mode 100644 app/src/vl/svc/VL_RIMERCH.h create mode 100644 app/src/vl/svc/VL_RIMERCH.pgc create mode 100644 app/src/vl/svc/VL_RIPURCH.h create mode 100644 app/src/vl/svc/VL_RIPURCH.pgc create mode 100644 app/src/vl/svc/VL_RISETTLE.h create mode 100644 app/src/vl/svc/VL_RISETTLE.pgc create mode 100644 app/src/vl/svc/VL_RULEHIST.h create mode 100644 app/src/vl/svc/VL_RULEHIST.pgc create mode 100644 app/src/vl/svc/VL_RULEMOD.h create mode 100644 app/src/vl/svc/VL_RULEMOD.pgc create mode 100644 app/src/vl/svc/VL_RULEOFF.h create mode 100644 app/src/vl/svc/VL_RULEOFF.pgc create mode 100644 app/src/vl/svc/VL_RULEREG.h create mode 100644 app/src/vl/svc/VL_RULEREG.pgc create mode 100644 app/src/vl/svc/VL_RULESTAT.h create mode 100644 app/src/vl/svc/VL_RULESTAT.pgc create mode 100644 app/src/vl/svc/VL_SUMFEE.h create mode 100644 app/src/vl/svc/VL_SUMFEE.pgc create mode 100644 app/src/vl/svc/VL_SUMRECON.h create mode 100644 app/src/vl/svc/VL_SUMRECON.pgc create mode 100644 app/src/vl/svc/VL_SUSPACK.h create mode 100644 app/src/vl/svc/VL_SUSPACK.pgc create mode 100644 app/src/vl/svc/VL_SUSPACT.h create mode 100644 app/src/vl/svc/VL_SUSPACT.pgc create mode 100644 app/src/vl/svc/VL_SUSPFINAL.h create mode 100644 app/src/vl/svc/VL_SUSPFINAL.pgc create mode 100644 app/src/vl/svc/VL_SUSPREOPEN.h create mode 100644 app/src/vl/svc/VL_SUSPREOPEN.pgc create mode 100644 app/src/vl/svc/VL_SUSPSTAT.h create mode 100644 app/src/vl/svc/VL_SUSPSTAT.pgc create mode 100644 app/src/vl/svc/VL_THRAUDIT.h create mode 100644 app/src/vl/svc/VL_THRAUDIT.pgc create mode 100644 app/src/vl/svc/VL_THRBUMP.h create mode 100644 app/src/vl/svc/VL_THRBUMP.pgc create mode 100644 app/src/vl/svc/VL_THRSET.h create mode 100644 app/src/vl/svc/VL_THRSET.pgc create mode 100644 app/src/vl/svc/VL_TRIAGG.h create mode 100644 app/src/vl/svc/VL_TRIAGG.pgc create mode 100644 app/src/vl/svc/VL_TRISTAGE.h create mode 100644 app/src/vl/svc/VL_TRISTAGE.pgc create mode 100644 app/src/vl/svc/VL_UNACQ.h create mode 100644 app/src/vl/svc/VL_UNACQ.pgc create mode 100644 app/src/vl/svc/VL_UNACQAGE.h create mode 100644 app/src/vl/svc/VL_UNACQAGE.pgc create mode 100644 db/schema.d/95-module-seed.sql diff --git a/app/build.sh b/app/build.sh index 89d533b..c4eae8c 100644 --- a/app/build.sh +++ b/app/build.sh @@ -16,12 +16,15 @@ ## set -euo pipefail -SRC=/app/src -BIN=/app/bin -LIB=/app/lib -OBJ=/app/obj -UBF=/app/ubftab -CONF=/app/conf +# App root = this script's own directory (normally /app in the container, but +# relocatable so an isolated copy — e.g. CI building a throwaway /b — works too). +APP="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SRC=$APP/src +BIN=$APP/bin +LIB=$APP/lib +OBJ=$APP/obj +UBF=$APP/ubftab +CONF=$APP/conf EXINC=/usr/local/include PGINC="$(pg_config --includedir)" PGLIB="$(pg_config --libdir)" diff --git a/app/conf/app.ini b/app/conf/app.ini index d3fbafb..361ce38 100644 --- a/app/conf/app.ini +++ b/app/conf/app.ini @@ -9,7 +9,7 @@ NDRX_IPCKEY=44100 NDRX_LDBAL=0 NDRX_LEV=5 NDRX_LOG=${NDRX_APPHOME}/log/xadmin.log -NDRX_MSGMAX=50 +NDRX_MSGMAX=30 NDRX_MSGSIZEMAX=16000 NDRX_NODEID=1 NDRX_QPATH=/dev/mqueue diff --git a/app/src/ac/ac_svr.pgc b/app/src/ac/ac_svr.pgc index 1cb1f98..23ccd7f 100644 --- a/app/src/ac/ac_svr.pgc +++ b/app/src/ac/ac_svr.pgc @@ -50,6 +50,51 @@ #include "ACQ_RELEASE.h" #include "ACQ_VOID.h" #include "ACQ_REFUND.h" +#include "ACQ_CB_OPEN.h" +#include "ACQ_CB_QRY.h" +#include "ACQ_CB_DEFEND.h" +#include "ACQ_CB_STAT.h" +#include "ACQ_REBUY.h" +#include "ACQ_PARTCXL.h" +#include "ACQ_PARTRFND.h" +#include "ACQ_SPLITPAY.h" +#include "ACQ_SPLITQRY.h" +#include "ACQ_CCYSUM.h" +#include "ACQ_FXRATE.h" +#include "ACQ_CTRYSUM.h" +#include "ACQ_FEERECALC.h" +#include "ACQ_FEEWAIVE.h" +#include "ACQ_FEEHIST.h" +#include "ACQ_CLAIMREG.h" +#include "ACQ_CLAIMPROC.h" +#include "ACQ_CLAIMQRY.h" +#include "ACQ_CORRHIST.h" +#include "ACQ_TAXREISSUE.h" +#include "ACQ_TAXCXL.h" +#include "ACQ_RETURN.h" +#include "ACQ_REMATCH.h" +#include "ACQ_BULKVERIFY.h" +#include "ACQ_LIMITUSAGE.h" +#include "ACQ_RESERVE.h" +#include "ACQ_RESERVECXL.h" +#include "ACQ_RESERVEQRY.h" +#include "ACQ_PROMO.h" +#include "ACQ_NOINTSHARE.h" +#include "ACQ_DAYSTAT.h" +#include "ACQ_WEEKSTAT.h" +#include "ACQ_MONSTAT.h" +#include "ACQ_CHANSTAT.h" +#include "ACQ_ISSSTAT.h" +#include "ACQ_ALERTCHK.h" +#include "ACQ_EDIRESEND.h" +#include "ACQ_SETTLEDUE.h" +#include "ACQ_HOLDQRY.h" +#include "ACQ_DUPSCAN.h" +#include "ACQ_VELOCITY.h" +#include "ACQ_TOPMERCH.h" +#include "ACQ_NETCALC.h" +#include "ACQ_APPRGAP.h" +#include "ACQ_FXCONVERT.h" /* ----- advertise table: {service name, function} (fn is extern from its file) */ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { @@ -68,6 +113,29 @@ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { {"ACQ_APPRMAP", ACQ_APPRMAP}, {"ACQ_SETTLELINK", ACQ_SETTLELINK}, {"ACQ_HOLD", ACQ_HOLD}, {"ACQ_RELEASE", ACQ_RELEASE}, {"ACQ_VOID", ACQ_VOID}, {"ACQ_REFUND", ACQ_REFUND}, + {"ACQ_CB_OPEN", ACQ_CB_OPEN}, {"ACQ_CB_QRY", ACQ_CB_QRY}, + {"ACQ_CB_DEFEND", ACQ_CB_DEFEND},{"ACQ_CB_STAT", ACQ_CB_STAT}, + {"ACQ_REBUY", ACQ_REBUY}, {"ACQ_PARTCXL", ACQ_PARTCXL}, + {"ACQ_PARTRFND", ACQ_PARTRFND}, {"ACQ_SPLITPAY", ACQ_SPLITPAY}, + {"ACQ_SPLITQRY", ACQ_SPLITQRY}, {"ACQ_CCYSUM", ACQ_CCYSUM}, + {"ACQ_FXRATE", ACQ_FXRATE}, {"ACQ_CTRYSUM", ACQ_CTRYSUM}, + {"ACQ_FEERECALC", ACQ_FEERECALC},{"ACQ_FEEWAIVE", ACQ_FEEWAIVE}, + {"ACQ_FEEHIST", ACQ_FEEHIST}, {"ACQ_CLAIMREG", ACQ_CLAIMREG}, + {"ACQ_CLAIMPROC", ACQ_CLAIMPROC},{"ACQ_CLAIMQRY", ACQ_CLAIMQRY}, + {"ACQ_CORRHIST", ACQ_CORRHIST}, {"ACQ_TAXREISSUE", ACQ_TAXREISSUE}, + {"ACQ_TAXCXL", ACQ_TAXCXL}, {"ACQ_RETURN", ACQ_RETURN}, + {"ACQ_REMATCH", ACQ_REMATCH}, {"ACQ_BULKVERIFY", ACQ_BULKVERIFY}, + {"ACQ_LIMITUSAGE", ACQ_LIMITUSAGE},{"ACQ_RESERVE", ACQ_RESERVE}, + {"ACQ_RESERVECXL", ACQ_RESERVECXL},{"ACQ_RESERVEQRY", ACQ_RESERVEQRY}, + {"ACQ_PROMO", ACQ_PROMO}, {"ACQ_NOINTSHARE", ACQ_NOINTSHARE}, + {"ACQ_DAYSTAT", ACQ_DAYSTAT}, {"ACQ_WEEKSTAT", ACQ_WEEKSTAT}, + {"ACQ_MONSTAT", ACQ_MONSTAT}, {"ACQ_CHANSTAT", ACQ_CHANSTAT}, + {"ACQ_ISSSTAT", ACQ_ISSSTAT}, {"ACQ_ALERTCHK", ACQ_ALERTCHK}, + {"ACQ_EDIRESEND", ACQ_EDIRESEND},{"ACQ_SETTLEDUE", ACQ_SETTLEDUE}, + {"ACQ_HOLDQRY", ACQ_HOLDQRY}, {"ACQ_DUPSCAN", ACQ_DUPSCAN}, + {"ACQ_VELOCITY", ACQ_VELOCITY}, {"ACQ_TOPMERCH", ACQ_TOPMERCH}, + {"ACQ_NETCALC", ACQ_NETCALC}, {"ACQ_APPRGAP", ACQ_APPRGAP}, + {"ACQ_FXCONVERT", ACQ_FXCONVERT}, {NULL, NULL} }; diff --git a/app/src/ac/batch/ac_alert_batch.pgc b/app/src/ac/batch/ac_alert_batch.pgc new file mode 100644 index 0000000..7c0662a --- /dev/null +++ b/app/src/ac/batch/ac_alert_batch.pgc @@ -0,0 +1,57 @@ +/* + * ac_alert_batch.pgc - ac 고액매입 경보 스캔 배치 (임계 커서, XA). + * + * ATMI client: 인자로 받은 임계금액(기본 500만원) 이상의 영업일 매입을 + * 개별 라인으로 뿜어내고, 경보 건수·최고액을 요약한다. 임계는 두 번째 + * 인자로 조정 가능해 운영 중 재기동 없이 스캔 수위를 바꾼다. + * Usage: ac_alert_batch [YYYY-MM-DD] [임계금액] + */ +#include +#include +#include +#include +#include "ac_alert_batch.h" + +int main(int argc, char **argv) +{ + ac_alert_ctx_t cx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_thr, h_pid, h_amt; + EXEC SQL END DECLARE SECTION; + + memset(&cx, 0, sizeof(cx)); + cx.threshold = (argc > 2) ? atol(argv[2]) : 5000000L; + if (cx.threshold <= 0) cx.threshold = 5000000L; + h_thr = cx.threshold; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE alb_cur CURSOR FOR + SELECT purchase_id, merchant_id, amount FROM purchase + WHERE biz_date = :h_bizdate AND amount >= :h_thr AND status NOT IN ('C', 'V') + ORDER BY amount DESC; + EXEC SQL OPEN alb_cur; + if (sqlca.sqlcode < 0) { fprintf(stderr, "open FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH alb_cur INTO :h_pid, :h_merch, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE alb_cur; fprintf(stderr, "fetch FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + cx.alerts++; + if (h_amt > cx.peak) cx.peak = h_amt; + printf(" ALERT pid=%ld merchant=%s amount=%ld\n", h_pid, h_merch, h_amt); + } + EXEC SQL CLOSE alb_cur; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> ac_alert_batch COMMIT: bizdate=%s thr=%ld alerts=%ld peak=%ld\n", + bizdate, cx.threshold, cx.alerts, cx.peak); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/ac/batch/ac_apprgap_batch.pgc b/app/src/ac/batch/ac_apprgap_batch.pgc new file mode 100644 index 0000000..9d49807 --- /dev/null +++ b/app/src/ac/batch/ac_apprgap_batch.pgc @@ -0,0 +1,62 @@ +/* + * ac_apprgap_batch.pgc - ac 승인-매입 갭 양방향 대사 배치 (2단 점검, XA). + * + * ATMI client: 승인 없는 매입 금액과 매입 없는 고아 승인 건수를 양방향으로 + * 재고, 거절(approved=false) 승인이 달린 매입 수까지 3개 지표를 산출한다. + * 갭이 있으면 종료코드 2. + * Usage: ac_apprgap_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "ac_apprgap_batch.h" + +int main(int argc, char **argv) +{ + ac_apprgap_ctx_t cx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_noap = 0, h_noamt = 0, h_orphan = 0, h_denied = 0; + EXEC SQL END DECLARE SECTION; + + memset(&cx, 0, sizeof(cx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(90, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + /* (1) 승인 없는 매입: 건수 + 금액 */ + EXEC SQL SELECT count(*), coalesce(sum(u.amount), 0) + INTO :h_noap, :h_noamt + FROM purchase u + WHERE u.biz_date = :h_bizdate AND u.status NOT IN ('C', 'V', 'P') + AND NOT EXISTS (SELECT 1 FROM approval a + WHERE a.purchase_id = u.purchase_id AND a.approved = true); + if (sqlca.sqlcode < 0) { fprintf(stderr, "noappr FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + + /* (2) 매입 없는 고아 승인 */ + EXEC SQL SELECT count(*) INTO :h_orphan + FROM approval a LEFT JOIN purchase u ON u.purchase_id = a.purchase_id + WHERE u.purchase_id IS NULL; + if (sqlca.sqlcode < 0) { fprintf(stderr, "orphan FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + + /* (3) 거절 승인이 달린 영업일 매입 */ + EXEC SQL SELECT count(DISTINCT u.purchase_id) INTO :h_denied + FROM purchase u JOIN approval a ON a.purchase_id = u.purchase_id + WHERE u.biz_date = :h_bizdate AND a.approved = false; + if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_apprgap_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + cx.no_approval = h_noap; cx.no_appr_amt = h_noamt; + cx.orphan_appr = h_orphan; cx.denied = h_denied; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> ac_apprgap_batch COMMIT: bizdate=%s no_appr=%ld(%ld) orphan=%ld denied=%ld\n", + bizdate, cx.no_approval, cx.no_appr_amt, cx.orphan_appr, cx.denied); + + tpclose(); tpterm(); + return (cx.no_approval + cx.orphan_appr) > 0 ? 2 : 0; +} diff --git a/app/src/ac/batch/ac_cbaging_batch.pgc b/app/src/ac/batch/ac_cbaging_batch.pgc new file mode 100644 index 0000000..709eae7 --- /dev/null +++ b/app/src/ac/batch/ac_cbaging_batch.pgc @@ -0,0 +1,59 @@ +/* + * ac_cbaging_batch.pgc - ac 지불거절 경과일 aging 배치 (버킷 집계, XA). + * + * ATMI client: 한 글로벌 트랜잭션 안에서 CB 접수 건을 등록 경과일 기준 + * 3개 버킷(7일 미만/7~30일/30일 초과)으로 나눠 세고, 방어되지 않은 장기 + * 미제 건수를 함께 보고한다. tpcommit 이 XA 2PC 를 마무리한다. + * Usage: ac_cbaging_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "ac_cbaging_batch.h" + +/* 경과일을 aging 버킷 인덱스로 변환 */ +int acb_cb_bucket(long age_days) +{ + if (age_days < 7) return 0; + if (age_days <= 30) return 1; + return 2; +} + +int main(int argc, char **argv) +{ + ac_cbaging_ctx_t cx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_fresh = 0, h_mid = 0, h_stale = 0, h_open = 0; + EXEC SQL END DECLARE SECTION; + + memset(&cx, 0, sizeof(cx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(cx.base_date, bizdate, sizeof(cx.base_date)-1); + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT + count(*) FILTER (WHERE CAST(:h_bizdate AS date) - biz_date < 7), + count(*) FILTER (WHERE CAST(:h_bizdate AS date) - biz_date BETWEEN 7 AND 30), + count(*) FILTER (WHERE CAST(:h_bizdate AS date) - biz_date > 30), + count(*) FILTER (WHERE reason NOT LIKE 'DEFEND:%') + INTO :h_fresh, :h_mid, :h_stale, :h_open + FROM ac_cancel WHERE cancel_type = 'CB'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_cbaging_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + cx.fresh = h_fresh; cx.mid = h_mid; cx.stale = h_stale; cx.undefended = h_open; + (void)acb_cb_bucket(31); + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> ac_cbaging_batch COMMIT: base=%s fresh=%ld mid=%ld stale=%ld undefended=%ld\n", + cx.base_date, cx.fresh, cx.mid, cx.stale, cx.undefended); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/ac/batch/ac_ccysum_batch.pgc b/app/src/ac/batch/ac_ccysum_batch.pgc new file mode 100644 index 0000000..9d30a95 --- /dev/null +++ b/app/src/ac/batch/ac_ccysum_batch.pgc @@ -0,0 +1,53 @@ +/* + * ac_ccysum_batch.pgc - ac 통화별 해외매입 집계 배치 (통화 커서, XA). + * + * ATMI client: 영업일 해외매입 원장을 통화별로 그룹 집계해 통화마다 + * 건수/원통화합계/원화환산합계/평균환율을 한 줄씩 출력한다. + * Usage: ac_ccysum_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "ac_ccysum_batch.h" + +int main(int argc, char **argv) +{ + ac_ccysum_ctx_t cx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_ccy[8]; + long h_n, h_fx, h_krw, h_rate; + EXEC SQL END DECLARE SECTION; + + memset(&cx, 0, sizeof(cx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE ccb_cur CURSOR FOR + SELECT ccy, count(*), sum(fx_amount), sum(krw_amount), avg(rate_bps)::bigint + FROM ac_fx WHERE biz_date = :h_bizdate + GROUP BY ccy ORDER BY sum(krw_amount) DESC; + EXEC SQL OPEN ccb_cur; + if (sqlca.sqlcode < 0) { fprintf(stderr, "open FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH ccb_cur INTO :h_ccy, :h_n, :h_fx, :h_krw, :h_rate; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ccb_cur; fprintf(stderr, "fetch FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + printf(" ccy=%-4s n=%ld fx=%ld krw=%ld avg_rate=%ld\n", h_ccy, h_n, h_fx, h_krw, h_rate); + cx.ccy_kinds++; + cx.krw_total += h_krw; + } + EXEC SQL CLOSE ccb_cur; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> ac_ccysum_batch COMMIT: bizdate=%s ccy_kinds=%ld krw_total=%ld\n", + bizdate, cx.ccy_kinds, cx.krw_total); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/ac/batch/ac_claimsweep_batch.pgc b/app/src/ac/batch/ac_claimsweep_batch.pgc new file mode 100644 index 0000000..d1bfc9f --- /dev/null +++ b/app/src/ac/batch/ac_claimsweep_batch.pgc @@ -0,0 +1,52 @@ +/* + * ac_claimsweep_batch.pgc - ac 장기 미제 클레임 정리 배치 (DELETE, XA). + * + * ATMI client: 기준일로부터 30일이 지난 미종결 클레임 행을 미매입 원장에서 + * 일괄 삭제(자동 종결)한다. 삭제 전 대상 건수를 세어 대조 출력한다. + * Usage: ac_claimsweep_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "ac_claimsweep_batch.h" + +int main(int argc, char **argv) +{ + ac_claimsweep_ctx_t cx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_target = 0, h_deleted; + EXEC SQL END DECLARE SECTION; + + memset(&cx, 0, sizeof(cx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + /* 삭제 대상 사전 계수 (대조용) */ + EXEC SQL SELECT count(*) INTO :h_target FROM ac_unmatch + WHERE reason LIKE 'CLAIM:%' AND biz_date < CAST(:h_bizdate AS date) - 30; + if (sqlca.sqlcode < 0) { fprintf(stderr, "count FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + + EXEC SQL DELETE FROM ac_unmatch + WHERE reason LIKE 'CLAIM:%' AND biz_date < CAST(:h_bizdate AS date) - 30; + if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_claimsweep_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_deleted = sqlca.sqlerrd[2]; + + cx.target = h_target; cx.deleted = h_deleted; + if (cx.target != cx.deleted) { + fprintf(stderr, "ac_claimsweep_batch MISMATCH target=%ld deleted=%ld\n", cx.target, cx.deleted); + tpabort(0); return 1; + } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> ac_claimsweep_batch COMMIT: base=%s swept=%ld\n", bizdate, cx.deleted); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/ac/batch/ac_feewaive_batch.pgc b/app/src/ac/batch/ac_feewaive_batch.pgc new file mode 100644 index 0000000..7962a25 --- /dev/null +++ b/app/src/ac/batch/ac_feewaive_batch.pgc @@ -0,0 +1,48 @@ +/* + * ac_feewaive_batch.pgc - ac 수수료 감면 총액 일 집계 배치 (사유별 분해, XA). + * + * ATMI client: 영업일의 음수 수수료 조정(감면)을 감면(WAIVE)/무이자 분담 + * (NOINT)/기타 사유로 분해 집계한다. 감면 총액은 부호를 뒤집어 보고한다. + * Usage: ac_feewaive_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "ac_feewaive_batch.h" + +int main(int argc, char **argv) +{ + ac_feewaive_ctx_t cx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_waive = 0, h_noint = 0, h_etc = 0, h_rows = 0; + EXEC SQL END DECLARE SECTION; + + memset(&cx, 0, sizeof(cx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT + coalesce(-sum(delta) FILTER (WHERE reason = 'WAIVE'), 0), + coalesce(-sum(delta) FILTER (WHERE reason = 'NOINT'), 0), + coalesce(-sum(delta) FILTER (WHERE reason NOT IN ('WAIVE', 'NOINT')), 0), + count(*) + INTO :h_waive, :h_noint, :h_etc, :h_rows + FROM ac_fee_adj WHERE biz_date = :h_bizdate AND delta < 0; + if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_feewaive_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + cx.waive_sum = h_waive; cx.noint_sum = h_noint; cx.etc_sum = h_etc; cx.rows = h_rows; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> ac_feewaive_batch COMMIT: bizdate=%s rows=%ld waive=%ld noint=%ld etc=%ld\n", + bizdate, cx.rows, cx.waive_sum, cx.noint_sum, cx.etc_sum); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/ac/batch/ac_limitwatch_batch.pgc b/app/src/ac/batch/ac_limitwatch_batch.pgc new file mode 100644 index 0000000..cf6c244 --- /dev/null +++ b/app/src/ac/batch/ac_limitwatch_batch.pgc @@ -0,0 +1,68 @@ +/* + * ac_limitwatch_batch.pgc - ac 가맹점 한도 임박 감시 배치 (조인 커서, XA). + * + * ATMI client: 영업일 사용액을 가맹점 일한도와 조인 집계해 소진율 90% + * (9000bps)를 넘긴 가맹점을 골라 경고 라인을 출력한다. C 헬퍼가 소진율을 + * bps 로 환산한다. + * Usage: ac_limitwatch_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "ac_limitwatch_batch.h" + +/* 소진율 bps 계산 (한도 0 이면 만점 처리) */ +long acb_usage_bps(long used, long limit) +{ + if (limit <= 0) return 10000L; + return (used * 10000L) / limit; +} + +int main(int argc, char **argv) +{ + ac_limitwatch_ctx_t cx; + long bps; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_used, h_limit; + EXEC SQL END DECLARE SECTION; + + memset(&cx, 0, sizeof(cx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE lwb_cur CURSOR FOR + SELECT m.merchant_id, coalesce(sum(u.amount), 0), m.daily_limit + FROM merchant m JOIN purchase u + ON u.merchant_id = m.merchant_id AND u.biz_date = :h_bizdate + AND u.status NOT IN ('C', 'V') + GROUP BY m.merchant_id, m.daily_limit; + EXEC SQL OPEN lwb_cur; + if (sqlca.sqlcode < 0) { fprintf(stderr, "open FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH lwb_cur INTO :h_merch, :h_used, :h_limit; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE lwb_cur; fprintf(stderr, "fetch FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + cx.scanned++; + bps = acb_usage_bps(h_used, h_limit); + if (bps > 9000L) { + cx.warned++; + printf(" ** 한도 임박 merchant=%s used=%ld limit=%ld usage=%ld bps\n", + h_merch, h_used, h_limit, bps); + } + } + EXEC SQL CLOSE lwb_cur; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> ac_limitwatch_batch COMMIT: bizdate=%s scanned=%ld warned=%ld\n", + bizdate, cx.scanned, cx.warned); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/ac/batch/ac_promo_batch.pgc b/app/src/ac/batch/ac_promo_batch.pgc new file mode 100644 index 0000000..b42f0f5 --- /dev/null +++ b/app/src/ac/batch/ac_promo_batch.pgc @@ -0,0 +1,54 @@ +/* + * ac_promo_batch.pgc - ac 프로모션 매입 집계 배치 (분류 집계 + 원장 대조, XA). + * + * ATMI client: 영업일 프로모션 분류(PROMO) 매입의 건수/총액과 면제 수수료 + * 추정액(가맹점 MDR 기준)을 집계하고, PROMO 원장 분개 합계와 대조한다. + * Usage: ac_promo_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "ac_promo_batch.h" + +int main(int argc, char **argv) +{ + ac_promo_ctx_t cx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_n = 0, h_amt = 0, h_freefee = 0, h_ledger = 0; + EXEC SQL END DECLARE SECTION; + + memset(&cx, 0, sizeof(cx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + /* 프로모션 매입 + 면제 수수료 추정(MDR 재적용) */ + EXEC SQL SELECT count(*), coalesce(sum(u.amount), 0), + coalesce(sum(u.amount * m.mdr_bps / 10000), 0) + INTO :h_n, :h_amt, :h_freefee + FROM purchase u JOIN merchant m ON m.merchant_id = u.merchant_id + WHERE u.biz_date = :h_bizdate AND u.category = 'PROMO' AND u.status <> 'C'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "promo agg FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + + /* PROMO 원장 분개 합계 대조 */ + EXEC SQL SELECT coalesce(sum(amount), 0) INTO :h_ledger + FROM ledger WHERE entry_type = 'PROMO' AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_promo_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + cx.promo_count = h_n; cx.promo_amount = h_amt; + cx.waived_fee = h_freefee; cx.ledger_sum = h_ledger; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> ac_promo_batch COMMIT: bizdate=%s n=%ld amt=%ld waived_fee=%ld ledger=%ld %s\n", + bizdate, cx.promo_count, cx.promo_amount, cx.waived_fee, cx.ledger_sum, + cx.promo_amount == cx.ledger_sum ? "(대조 일치)" : "(대조 불일치)"); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/ac/batch/ac_reservepost_batch.pgc b/app/src/ac/batch/ac_reservepost_batch.pgc new file mode 100644 index 0000000..e0c2b59 --- /dev/null +++ b/app/src/ac/batch/ac_reservepost_batch.pgc @@ -0,0 +1,48 @@ +/* + * ac_reservepost_batch.pgc - ac 매입 예약 전환 배치 (상태 전이, XA). + * + * ATMI client: 예약일이 도래한 예약(P) 매입을 접수(A)로 일괄 전환하고, + * 전환 후에도 남은 미래 예약 건수를 세어 함께 보고한다. + * Usage: ac_reservepost_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "ac_reservepost_batch.h" + +int main(int argc, char **argv) +{ + ac_reservepost_ctx_t cx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_left = 0; + EXEC SQL END DECLARE SECTION; + + memset(&cx, 0, sizeof(cx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + /* 예약일 도래 건 전환: P → A */ + EXEC SQL UPDATE purchase SET status = 'A', updated_at = now() + WHERE status = 'P' AND biz_date <= CAST(:h_bizdate AS date); + if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_reservepost_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + cx.posted = sqlca.sqlerrd[2]; + + /* 미래 예약 잔량 */ + EXEC SQL SELECT count(*) INTO :h_left FROM purchase WHERE status = 'P'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "left FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + cx.remaining = h_left; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> ac_reservepost_batch COMMIT: base=%s posted=%ld remaining=%ld\n", + bizdate, cx.posted, cx.remaining); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/ac/batch/ac_settledue_batch.pgc b/app/src/ac/batch/ac_settledue_batch.pgc new file mode 100644 index 0000000..8d985a6 --- /dev/null +++ b/app/src/ac/batch/ac_settledue_batch.pgc @@ -0,0 +1,56 @@ +/* + * ac_settledue_batch.pgc - ac 정산예정 대기열 집계 배치 (NOT EXISTS, XA). + * + * ATMI client: 대사완료(M) 상태이나 정산행이 아직 없는 매입을 가맹점별로 + * 묶어 정산예정액을 산출한다. 가맹점별 라인 출력 후 전체 합계로 마감. + * Usage: ac_settledue_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "ac_settledue_batch.h" + +int main(int argc, char **argv) +{ + ac_settledue_ctx_t cx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_n, h_net; + EXEC SQL END DECLARE SECTION; + + memset(&cx, 0, sizeof(cx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(90, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE sdb_cur CURSOR FOR + SELECT u.merchant_id, count(*), sum(u.net) + FROM purchase u + WHERE u.biz_date = :h_bizdate AND u.status = 'M' + AND NOT EXISTS (SELECT 1 FROM settlement s WHERE s.purchase_id = u.purchase_id) + GROUP BY u.merchant_id ORDER BY u.merchant_id; + EXEC SQL OPEN sdb_cur; + if (sqlca.sqlcode < 0) { fprintf(stderr, "open FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH sdb_cur INTO :h_merch, :h_n, :h_net; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE sdb_cur; fprintf(stderr, "fetch FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + printf(" merchant=%s due_n=%ld due_net=%ld\n", h_merch, h_n, h_net); + cx.merchants++; + cx.due_count += h_n; + cx.due_net += h_net; + } + EXEC SQL CLOSE sdb_cur; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> ac_settledue_batch COMMIT: bizdate=%s merchants=%ld due_n=%ld due_net=%ld\n", + bizdate, cx.merchants, cx.due_count, cx.due_net); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/ac/batch/ac_splitpay_batch.pgc b/app/src/ac/batch/ac_splitpay_batch.pgc new file mode 100644 index 0000000..c3ded35 --- /dev/null +++ b/app/src/ac/batch/ac_splitpay_batch.pgc @@ -0,0 +1,58 @@ +/* + * ac_splitpay_batch.pgc - ac 분할납부 스케줄 정합 배치 (그룹 커서, XA). + * + * ATMI client: 영업일에 등록된 분할납부 스케줄을 매입 단위로 그룹 커서 + * 순회하며 스케줄 합계와 매입 원금의 불일치 건을 찾아낸다. 불일치가 + * 하나라도 있으면 종료코드 2 로 마감 라인에 신호를 준다. + * Usage: ac_splitpay_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "ac_splitpay_batch.h" + +int main(int argc, char **argv) +{ + ac_splitpay_ctx_t cx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_pid, h_sched, h_orig; + EXEC SQL END DECLARE SECTION; + + memset(&cx, 0, sizeof(cx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE spb_cur CURSOR FOR + SELECT i.purchase_id, sum(i.month_amount), max(u.amount) + FROM ac_install i JOIN purchase u ON u.purchase_id = i.purchase_id + WHERE i.biz_date = :h_bizdate + GROUP BY i.purchase_id; + EXEC SQL OPEN spb_cur; + if (sqlca.sqlcode < 0) { fprintf(stderr, "open FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH spb_cur INTO :h_pid, :h_sched, :h_orig; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE spb_cur; fprintf(stderr, "fetch FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + cx.plans++; + cx.sched_total += h_sched; + if (h_sched != h_orig) { + cx.mismatch++; + printf(" !! 스케줄 불일치 pid=%ld sched=%ld orig=%ld\n", h_pid, h_sched, h_orig); + } + } + EXEC SQL CLOSE spb_cur; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> ac_splitpay_batch COMMIT: bizdate=%s plans=%ld sched_total=%ld mismatch=%ld\n", + bizdate, cx.plans, cx.sched_total, cx.mismatch); + + tpclose(); tpterm(); + return cx.mismatch > 0 ? 2 : 0; +} diff --git a/app/src/ac/batch/ac_taxneg_batch.pgc b/app/src/ac/batch/ac_taxneg_batch.pgc new file mode 100644 index 0000000..ae96385 --- /dev/null +++ b/app/src/ac/batch/ac_taxneg_batch.pgc @@ -0,0 +1,53 @@ +/* + * ac_taxneg_batch.pgc - ac 세금계산서 상쇄 검증 배치 (부호 검증, XA). + * + * ATMI client: 영업일의 음수(취소) 세금계산서를 검사한다. 음수 계산서의 + * 부가세가 공급가의 10% 상쇄와 어긋난 건, 그리고 매입별 계산서 잔액이 + * 음수로 뒤집힌(과상쇄) 매입 수를 찾아 보고한다. + * Usage: ac_taxneg_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "ac_taxneg_batch.h" + +int main(int argc, char **argv) +{ + ac_taxneg_ctx_t cx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_neg = 0, h_badvat = 0, h_over = 0; + EXEC SQL END DECLARE SECTION; + + memset(&cx, 0, sizeof(cx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + /* 음수 계산서 건수 + 부가세 상쇄식 위반 건수 */ + EXEC SQL SELECT count(*), + count(*) FILTER (WHERE vat <> supply / 10) + INTO :h_neg, :h_badvat + FROM ac_tax_invoice WHERE biz_date = :h_bizdate AND supply < 0; + if (sqlca.sqlcode < 0) { fprintf(stderr, "neg scan FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + + /* 과상쇄: 매입별 공급가 잔액이 음수인 매입 수 */ + EXEC SQL SELECT count(*) INTO :h_over + FROM (SELECT purchase_id FROM ac_tax_invoice + GROUP BY purchase_id HAVING sum(supply) < 0) o; + if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_taxneg_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + cx.neg_rows = h_neg; cx.bad_vat = h_badvat; cx.over_offset = h_over; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> ac_taxneg_batch COMMIT: bizdate=%s neg=%ld bad_vat=%ld over_offset=%ld\n", + bizdate, cx.neg_rows, cx.bad_vat, cx.over_offset); + + tpclose(); tpterm(); + return (cx.bad_vat + cx.over_offset) > 0 ? 2 : 0; +} diff --git a/app/src/ac/batch/ac_weekroll_batch.pgc b/app/src/ac/batch/ac_weekroll_batch.pgc new file mode 100644 index 0000000..f6b510b --- /dev/null +++ b/app/src/ac/batch/ac_weekroll_batch.pgc @@ -0,0 +1,61 @@ +/* + * ac_weekroll_batch.pgc - ac 주간 가맹점 집계 롤업 배치 (INSERT..SELECT, XA). + * + * ATMI client: 기준일 포함 직전 7일 매입을 가맹점별로 롤업해 기준일 키의 + * merchant_summary 로 밀어넣는다(ON CONFLICT 갱신). 반영 행수와 주간 + * 총액을 되읽어 보고한다. + * Usage: ac_weekroll_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "ac_weekroll_batch.h" + +int main(int argc, char **argv) +{ + ac_weekroll_ctx_t cx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_gross = 0; + EXEC SQL END DECLARE SECTION; + + memset(&cx, 0, sizeof(cx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + /* 주간 롤업: 직전 7일 → 기준일 키 upsert */ + EXEC SQL INSERT INTO merchant_summary (merchant_id, biz_date, txn_count, gross_amount, fee_amount, net_amount, updated_at) + SELECT merchant_id, CAST(:h_bizdate AS date), count(*), sum(amount), sum(fee), sum(net), now() + FROM purchase + WHERE biz_date > CAST(:h_bizdate AS date) - 7 + AND biz_date <= CAST(:h_bizdate AS date) + AND status NOT IN ('C', 'V', 'P') + GROUP BY merchant_id + ON CONFLICT (merchant_id, biz_date) DO UPDATE + SET txn_count = EXCLUDED.txn_count, + gross_amount = EXCLUDED.gross_amount, + fee_amount = EXCLUDED.fee_amount, + net_amount = EXCLUDED.net_amount, + updated_at = now(); + if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_weekroll_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + cx.rolled = sqlca.sqlerrd[2]; + + /* 반영 결과 되읽기 */ + EXEC SQL SELECT coalesce(sum(gross_amount), 0) INTO :h_gross + FROM merchant_summary WHERE biz_date = CAST(:h_bizdate AS date); + if (sqlca.sqlcode < 0) { fprintf(stderr, "readback FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + cx.week_gross = h_gross; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> ac_weekroll_batch COMMIT: base=%s rolled=%ld week_gross=%ld\n", + bizdate, cx.rolled, cx.week_gross); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/ac/dbio/ac_alert_batch.h b/app/src/ac/dbio/ac_alert_batch.h new file mode 100644 index 0000000..38001c8 --- /dev/null +++ b/app/src/ac/dbio/ac_alert_batch.h @@ -0,0 +1,14 @@ +/* + * ac_alert_batch.h - copybook for ac_alert_batch (XA client). + * 고액매입 경보 스캔 구조체. Kept in dbio/ for -I resolution. + */ +#ifndef AC_ALERT_BATCH_H +#define AC_ALERT_BATCH_H + +typedef struct { + long threshold; /* 고액 임계금액 */ + long alerts; /* 경보 건수 */ + long peak; /* 최고 금액 */ +} ac_alert_ctx_t; + +#endif /* AC_ALERT_BATCH_H */ diff --git a/app/src/ac/dbio/ac_apprgap_batch.h b/app/src/ac/dbio/ac_apprgap_batch.h new file mode 100644 index 0000000..c0f4892 --- /dev/null +++ b/app/src/ac/dbio/ac_apprgap_batch.h @@ -0,0 +1,15 @@ +/* + * ac_apprgap_batch.h - copybook for ac_apprgap_batch (XA client). + * 승인-매입 갭 양방향 대사 구조체. Kept in dbio/ for -I resolution. + */ +#ifndef AC_APPRGAP_BATCH_H +#define AC_APPRGAP_BATCH_H + +typedef struct { + long no_approval; /* 승인 없는 매입 건수 */ + long no_appr_amt; /* 그 금액 */ + long orphan_appr; /* 매입 없는 승인 */ + long denied; /* 거절 승인 달린 매입 */ +} ac_apprgap_ctx_t; + +#endif /* AC_APPRGAP_BATCH_H */ diff --git a/app/src/ac/dbio/ac_cbaging_batch.h b/app/src/ac/dbio/ac_cbaging_batch.h new file mode 100644 index 0000000..60bfdf9 --- /dev/null +++ b/app/src/ac/dbio/ac_cbaging_batch.h @@ -0,0 +1,18 @@ +/* + * ac_cbaging_batch.h - copybook for ac_cbaging_batch (XA client). + * 지불거절 aging 버킷 구조체 + 버킷 분류 헬퍼. Kept in dbio/ for -I resolution. + */ +#ifndef AC_CBAGING_BATCH_H +#define AC_CBAGING_BATCH_H + +typedef struct { + char base_date[16]; + long fresh; /* 경과 7일 미만 */ + long mid; /* 7~30일 */ + long stale; /* 30일 초과 */ + long undefended; /* 방어 미착수 건수 */ +} ac_cbaging_ctx_t; + +int acb_cb_bucket(long age_days); /* 0=fresh, 1=mid, 2=stale */ + +#endif /* AC_CBAGING_BATCH_H */ diff --git a/app/src/ac/dbio/ac_ccysum_batch.h b/app/src/ac/dbio/ac_ccysum_batch.h new file mode 100644 index 0000000..75acfc5 --- /dev/null +++ b/app/src/ac/dbio/ac_ccysum_batch.h @@ -0,0 +1,13 @@ +/* + * ac_ccysum_batch.h - copybook for ac_ccysum_batch (XA client). + * 통화별 해외매입 집계 구조체. Kept in dbio/ for -I resolution. + */ +#ifndef AC_CCYSUM_BATCH_H +#define AC_CCYSUM_BATCH_H + +typedef struct { + long ccy_kinds; /* 통화 종수 */ + long krw_total; /* 원화 환산 총액 */ +} ac_ccysum_ctx_t; + +#endif /* AC_CCYSUM_BATCH_H */ diff --git a/app/src/ac/dbio/ac_claimsweep_batch.h b/app/src/ac/dbio/ac_claimsweep_batch.h new file mode 100644 index 0000000..251e9a1 --- /dev/null +++ b/app/src/ac/dbio/ac_claimsweep_batch.h @@ -0,0 +1,13 @@ +/* + * ac_claimsweep_batch.h - copybook for ac_claimsweep_batch (XA client). + * 장기 미제 클레임 정리 구조체. Kept in dbio/ for -I resolution. + */ +#ifndef AC_CLAIMSWEEP_BATCH_H +#define AC_CLAIMSWEEP_BATCH_H + +typedef struct { + long target; /* 삭제 대상 사전 계수 */ + long deleted; /* 실제 삭제 행 수 */ +} ac_claimsweep_ctx_t; + +#endif /* AC_CLAIMSWEEP_BATCH_H */ diff --git a/app/src/ac/dbio/ac_feewaive_batch.h b/app/src/ac/dbio/ac_feewaive_batch.h new file mode 100644 index 0000000..775b427 --- /dev/null +++ b/app/src/ac/dbio/ac_feewaive_batch.h @@ -0,0 +1,15 @@ +/* + * ac_feewaive_batch.h - copybook for ac_feewaive_batch (XA client). + * 수수료 감면 사유별 집계 구조체. Kept in dbio/ for -I resolution. + */ +#ifndef AC_FEEWAIVE_BATCH_H +#define AC_FEEWAIVE_BATCH_H + +typedef struct { + long rows; /* 감면(음수 delta) 행 수 */ + long waive_sum; /* WAIVE 감면 총액(양수 표기) */ + long noint_sum; /* 무이자 분담 총액 */ + long etc_sum; /* 기타 사유 총액 */ +} ac_feewaive_ctx_t; + +#endif /* AC_FEEWAIVE_BATCH_H */ diff --git a/app/src/ac/dbio/ac_limitwatch_batch.h b/app/src/ac/dbio/ac_limitwatch_batch.h new file mode 100644 index 0000000..2063b3b --- /dev/null +++ b/app/src/ac/dbio/ac_limitwatch_batch.h @@ -0,0 +1,15 @@ +/* + * ac_limitwatch_batch.h - copybook for ac_limitwatch_batch (XA client). + * 한도 임박 감시 구조체 + 소진율 헬퍼. Kept in dbio/ for -I resolution. + */ +#ifndef AC_LIMITWATCH_BATCH_H +#define AC_LIMITWATCH_BATCH_H + +typedef struct { + long scanned; /* 검사 가맹점 수 */ + long warned; /* 90% 초과 경고 수 */ +} ac_limitwatch_ctx_t; + +long acb_usage_bps(long used, long limit); + +#endif /* AC_LIMITWATCH_BATCH_H */ diff --git a/app/src/ac/dbio/ac_promo_batch.h b/app/src/ac/dbio/ac_promo_batch.h new file mode 100644 index 0000000..6d58f38 --- /dev/null +++ b/app/src/ac/dbio/ac_promo_batch.h @@ -0,0 +1,15 @@ +/* + * ac_promo_batch.h - copybook for ac_promo_batch (XA client). + * 프로모션 매입 집계 구조체. Kept in dbio/ for -I resolution. + */ +#ifndef AC_PROMO_BATCH_H +#define AC_PROMO_BATCH_H + +typedef struct { + long promo_count; + long promo_amount; + long waived_fee; /* MDR 기준 면제 수수료 추정액 */ + long ledger_sum; /* PROMO 원장 분개 합계 */ +} ac_promo_ctx_t; + +#endif /* AC_PROMO_BATCH_H */ diff --git a/app/src/ac/dbio/ac_reservepost_batch.h b/app/src/ac/dbio/ac_reservepost_batch.h new file mode 100644 index 0000000..2f60b7c --- /dev/null +++ b/app/src/ac/dbio/ac_reservepost_batch.h @@ -0,0 +1,13 @@ +/* + * ac_reservepost_batch.h - copybook for ac_reservepost_batch (XA client). + * 매입 예약 전환 구조체. Kept in dbio/ for -I resolution. + */ +#ifndef AC_RESERVEPOST_BATCH_H +#define AC_RESERVEPOST_BATCH_H + +typedef struct { + long posted; /* P→A 전환 건수 */ + long remaining; /* 미래 예약 잔량 */ +} ac_reservepost_ctx_t; + +#endif /* AC_RESERVEPOST_BATCH_H */ diff --git a/app/src/ac/dbio/ac_settledue_batch.h b/app/src/ac/dbio/ac_settledue_batch.h new file mode 100644 index 0000000..0ae2aa9 --- /dev/null +++ b/app/src/ac/dbio/ac_settledue_batch.h @@ -0,0 +1,14 @@ +/* + * ac_settledue_batch.h - copybook for ac_settledue_batch (XA client). + * 정산예정 대기열 집계 구조체. Kept in dbio/ for -I resolution. + */ +#ifndef AC_SETTLEDUE_BATCH_H +#define AC_SETTLEDUE_BATCH_H + +typedef struct { + long merchants; /* 정산 대기 가맹점 수 */ + long due_count; /* 정산예정 건수 */ + long due_net; /* 정산예정 총액 */ +} ac_settledue_ctx_t; + +#endif /* AC_SETTLEDUE_BATCH_H */ diff --git a/app/src/ac/dbio/ac_splitpay_batch.h b/app/src/ac/dbio/ac_splitpay_batch.h new file mode 100644 index 0000000..f9c283f --- /dev/null +++ b/app/src/ac/dbio/ac_splitpay_batch.h @@ -0,0 +1,14 @@ +/* + * ac_splitpay_batch.h - copybook for ac_splitpay_batch (XA client). + * 분할납부 스케줄 정합 집계 구조체. Kept in dbio/ for -I resolution. + */ +#ifndef AC_SPLITPAY_BATCH_H +#define AC_SPLITPAY_BATCH_H + +typedef struct { + long plans; /* 검사한 분할 플랜 수 */ + long sched_total; /* 스케줄 금액 합계 */ + long mismatch; /* 원금 불일치 플랜 수 */ +} ac_splitpay_ctx_t; + +#endif /* AC_SPLITPAY_BATCH_H */ diff --git a/app/src/ac/dbio/ac_taxneg_batch.h b/app/src/ac/dbio/ac_taxneg_batch.h new file mode 100644 index 0000000..8294e2f --- /dev/null +++ b/app/src/ac/dbio/ac_taxneg_batch.h @@ -0,0 +1,14 @@ +/* + * ac_taxneg_batch.h - copybook for ac_taxneg_batch (XA client). + * 음수 세금계산서 검증 구조체. Kept in dbio/ for -I resolution. + */ +#ifndef AC_TAXNEG_BATCH_H +#define AC_TAXNEG_BATCH_H + +typedef struct { + long neg_rows; /* 음수 계산서 건수 */ + long bad_vat; /* 부가세 상쇄식 위반 */ + long over_offset; /* 과상쇄 매입 수 */ +} ac_taxneg_ctx_t; + +#endif /* AC_TAXNEG_BATCH_H */ diff --git a/app/src/ac/dbio/ac_weekroll_batch.h b/app/src/ac/dbio/ac_weekroll_batch.h new file mode 100644 index 0000000..257c7f3 --- /dev/null +++ b/app/src/ac/dbio/ac_weekroll_batch.h @@ -0,0 +1,13 @@ +/* + * ac_weekroll_batch.h - copybook for ac_weekroll_batch (XA client). + * 주간 가맹점 롤업 구조체. Kept in dbio/ for -I resolution. + */ +#ifndef AC_WEEKROLL_BATCH_H +#define AC_WEEKROLL_BATCH_H + +typedef struct { + long rolled; /* upsert 반영 행수 */ + long week_gross; /* 주간 총액 되읽기 */ +} ac_weekroll_ctx_t; + +#endif /* AC_WEEKROLL_BATCH_H */ diff --git a/app/src/ac/svc/ACQ_ALERTCHK.h b/app/src/ac/svc/ACQ_ALERTCHK.h new file mode 100644 index 0000000..83a865e --- /dev/null +++ b/app/src/ac/svc/ACQ_ALERTCHK.h @@ -0,0 +1,23 @@ +/* + * ACQ_ALERTCHK.h - ac 서비스 ACQ_ALERTCHK 카피북 (copybook / record header). + * 매입 알림 점검 레코드. + * Request fields: T_MERCHANT, T_BIZDATE, T_AMT1(임계금액; 0=기본 100만) + * Response fields: T_RC(1=경보), T_MSG, T_ARG1(고액 건수), T_ARG2(보류 건수) + */ +#ifndef ACQ_ALERTCHK_H +#define ACQ_ALERTCHK_H + +#include "ac_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long threshold; /* 고액 임계금액 */ + long big_count; + long hold_count; + char message[160]; +} ac_alertchk_rec_t; + +void ACQ_ALERTCHK(TPSVCINFO *p); + +#endif /* ACQ_ALERTCHK_H */ diff --git a/app/src/ac/svc/ACQ_ALERTCHK.pgc b/app/src/ac/svc/ACQ_ALERTCHK.pgc new file mode 100644 index 0000000..f87f850 --- /dev/null +++ b/app/src/ac/svc/ACQ_ALERTCHK.pgc @@ -0,0 +1,43 @@ +/* + * ACQ_ALERTCHK.pgc - ac 모듈 서비스 ACQ_ALERTCHK (one service = one file). + * 매입 알림 점검: 가맹점의 당일 고액거래(임계금액 이상)와 보류 적체 건수를 + * 점검해 경보 여부와 알림 메시지를 만들어 돌려준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_ALERTCHK.h" + +void ACQ_ALERTCHK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long threshold; + char msg[160]; + EXEC SQL BEGIN DECLARE SECTION; + long h_thr, h_big = 0, h_hold = 0; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + threshold = getl(b, T_AMT1); + if (threshold <= 0) threshold = 1000000L; /* 기본 임계: 100만원 */ + h_thr = threshold; + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL SELECT count(*) FILTER (WHERE amount >= :h_thr), + count(*) FILTER (WHERE status = 'H') + INTO :h_big, :h_hold + FROM purchase WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("ACQ_ALERTCHK FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + if (h_big > 0 || h_hold > 0) { + snprintf(msg, sizeof(msg), "ALERT merchant=%s big=%ld hold=%ld thr=%ld", + h_merch, h_big, h_hold, threshold); + Bchg(b, T_MSG, 0, msg, 0L); + setl(b, T_RC, 1); /* 경보 */ + } else { + Bchg(b, T_MSG, 0, "CLEAR", 0L); + setl(b, T_RC, 0); /* 정상 */ + } + setl(b, T_ARG1, h_big); + setl(b, T_ARG2, h_hold); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_APPRGAP.h b/app/src/ac/svc/ACQ_APPRGAP.h new file mode 100644 index 0000000..83a3235 --- /dev/null +++ b/app/src/ac/svc/ACQ_APPRGAP.h @@ -0,0 +1,21 @@ +/* + * ACQ_APPRGAP.h - ac 서비스 ACQ_APPRGAP 카피북 (copybook / record header). + * 승인-매입 갭 점검 레코드. + * Request fields: T_BIZDATE + * Response fields: T_ARG1(무승인 매입), T_AMT1(금액), T_ARG2(고아 승인), T_RC + */ +#ifndef ACQ_APPRGAP_H +#define ACQ_APPRGAP_H + +#include "ac_svc.h" + +typedef struct { + char biz_date[16]; + long no_approval; /* 승인 없는 매입 건수 */ + long no_appr_amt; + long orphan_appr; /* 매입 없는 승인 건수 */ +} ac_apprgap_rec_t; + +void ACQ_APPRGAP(TPSVCINFO *p); + +#endif /* ACQ_APPRGAP_H */ diff --git a/app/src/ac/svc/ACQ_APPRGAP.pgc b/app/src/ac/svc/ACQ_APPRGAP.pgc new file mode 100644 index 0000000..2b784eb --- /dev/null +++ b/app/src/ac/svc/ACQ_APPRGAP.pgc @@ -0,0 +1,38 @@ +/* + * ACQ_APPRGAP.pgc - ac 모듈 서비스 ACQ_APPRGAP (one service = one file). + * 승인-매입 갭 점검: 승인 없는 매입(무승인 매입)과 매입 없는 승인(고아 + * 승인)을 양방향으로 세어 갭 규모를 보고한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_APPRGAP.h" + +void ACQ_APPRGAP(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_noappr = 0, h_orphan = 0, h_amt = 0; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + /* 승인 없는 매입: 건수 + 금액 */ + EXEC SQL SELECT count(*), coalesce(sum(u.amount), 0) + INTO :h_noappr, :h_amt + FROM purchase u + WHERE u.biz_date = :h_bizdate AND u.status NOT IN ('C', 'V', 'P') + AND NOT EXISTS (SELECT 1 FROM approval a WHERE a.purchase_id = u.purchase_id AND a.approved = true); + if (sqlca.sqlcode < 0) FAIL(b); + + /* 매입 없는 고아 승인 */ + EXEC SQL SELECT count(*) INTO :h_orphan + FROM approval a LEFT JOIN purchase u ON u.purchase_id = a.purchase_id + WHERE u.purchase_id IS NULL; + if (sqlca.sqlcode < 0) { userlog("ACQ_APPRGAP FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_ARG1, h_noappr); /* 무승인 매입 건수 */ + setl(b, T_AMT1, h_amt); /* 무승인 매입 금액 */ + setl(b, T_ARG2, h_orphan); /* 고아 승인 건수 */ + setl(b, T_RC, (h_noappr + h_orphan) > 0 ? 1 : 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_BULKVERIFY.h b/app/src/ac/svc/ACQ_BULKVERIFY.h new file mode 100644 index 0000000..8f91c1f --- /dev/null +++ b/app/src/ac/svc/ACQ_BULKVERIFY.h @@ -0,0 +1,22 @@ +/* + * ACQ_BULKVERIFY.h - ac 서비스 ACQ_BULKVERIFY 카피북 (copybook / record header). + * 대량매입 검증 레코드. + * Request fields: T_MERCHANT, T_BIZDATE + * Response fields: T_COUNT, T_ARG1(수수료 오류), T_ARG2(정산금 오류), T_RC + */ +#ifndef ACQ_BULKVERIFY_H +#define ACQ_BULKVERIFY_H + +#include "ac_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long total; + long fee_bad; /* MDR 재계산 불일치 */ + long net_bad; /* amount-fee 불일치 */ +} ac_bulkverify_rec_t; + +void ACQ_BULKVERIFY(TPSVCINFO *p); + +#endif /* ACQ_BULKVERIFY_H */ diff --git a/app/src/ac/svc/ACQ_BULKVERIFY.pgc b/app/src/ac/svc/ACQ_BULKVERIFY.pgc new file mode 100644 index 0000000..8796e75 --- /dev/null +++ b/app/src/ac/svc/ACQ_BULKVERIFY.pgc @@ -0,0 +1,37 @@ +/* + * ACQ_BULKVERIFY.pgc - ac 모듈 서비스 ACQ_BULKVERIFY (one service = one file). + * 대량매입 검증: 가맹점·영업일 단위로 전 건의 수수료(MDR 재계산 대비)와 + * 정산금(amount-fee 대비) 정합을 일괄 점검해 오류 건수를 보고한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_BULKVERIFY.h" + +void ACQ_BULKVERIFY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long bps = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_bps, h_total = 0, h_feebad = 0, h_netbad = 0; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + if (acdb_merchant_mdr(h_merch, &bps) < 0) { setl(b, T_RC, 5); FAIL(b); } + h_bps = bps; + + EXEC SQL SELECT count(*), + count(*) FILTER (WHERE fee <> amount * :h_bps / 10000), + count(*) FILTER (WHERE net <> amount - fee) + INTO :h_total, :h_feebad, :h_netbad + FROM purchase + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status NOT IN ('C', 'V'); + if (sqlca.sqlcode < 0) { userlog("ACQ_BULKVERIFY FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_COUNT, h_total); + setl(b, T_ARG1, h_feebad); /* 수수료 오류 건수 */ + setl(b, T_ARG2, h_netbad); /* 정산금 오류 건수 */ + setl(b, T_RC, (h_feebad + h_netbad) > 0 ? 1 : 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_CB_DEFEND.h b/app/src/ac/svc/ACQ_CB_DEFEND.h new file mode 100644 index 0000000..081c34a --- /dev/null +++ b/app/src/ac/svc/ACQ_CB_DEFEND.h @@ -0,0 +1,21 @@ +/* + * ACQ_CB_DEFEND.h - ac 서비스 ACQ_CB_DEFEND 카피북 (copybook / record header). + * 지불거절 방어 레코드. + * Request fields: T_CANCEL_ID, T_REASON(방어 증빙) + * Response fields: T_PURCHASE_ID, T_RC(0=복원, 1=상태 유지, 4=CB 없음) + */ +#ifndef ACQ_CB_DEFEND_H +#define ACQ_CB_DEFEND_H + +#include "ac_svc.h" + +typedef struct { + long cancel_id; /* CB 접수 ID */ + char evidence[96]; /* 방어 증빙 사유 */ + long purchase_id; /* 복원 대상 매입 */ + long rc; +} ac_cb_defend_rec_t; + +void ACQ_CB_DEFEND(TPSVCINFO *p); + +#endif /* ACQ_CB_DEFEND_H */ diff --git a/app/src/ac/svc/ACQ_CB_DEFEND.pgc b/app/src/ac/svc/ACQ_CB_DEFEND.pgc new file mode 100644 index 0000000..468e7de --- /dev/null +++ b/app/src/ac/svc/ACQ_CB_DEFEND.pgc @@ -0,0 +1,40 @@ +/* + * ACQ_CB_DEFEND.pgc - ac 모듈 서비스 ACQ_CB_DEFEND (one service = one file). + * 지불거절 방어: 접수된 CB 건에 방어 증빙 사유를 덧붙이고, 보류 중이던 + * 원매입을 대사완료(M) 상태로 복원한다. 방어 결과는 T_RC 로 응답. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_CB_DEFEND.h" + +void ACQ_CB_DEFEND(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char evid[96]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid, h_pid = 0, h_upd; + char h_evid[110]; + EXEC SQL END DECLARE SECTION; + + h_cid = getl(b, T_CANCEL_ID); + gets_(b, T_REASON, evid, sizeof(evid)); + if (evid[0] == 0) strcpy(evid, "EVIDENCE"); + snprintf(h_evid, sizeof(h_evid), "DEFEND:%s", evid); + + EXEC SQL SELECT purchase_id INTO :h_pid FROM ac_cancel + WHERE cancel_id = :h_cid AND cancel_type = 'CB'; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 4); FAIL(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL UPDATE ac_cancel SET reason = :h_evid WHERE cancel_id = :h_cid; + if (sqlca.sqlcode < 0) FAIL(b); + + /* 방어 접수 시 보류(H) 원매입을 대사완료로 복원 */ + EXEC SQL UPDATE purchase SET status = 'M', updated_at = now() + WHERE purchase_id = :h_pid AND status = 'H'; + if (sqlca.sqlcode < 0) { userlog("ACQ_CB_DEFEND FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + h_upd = sqlca.sqlerrd[2]; + + setl(b, T_PURCHASE_ID, h_pid); + setl(b, T_RC, h_upd > 0 ? 0 : 1); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_CB_OPEN.h b/app/src/ac/svc/ACQ_CB_OPEN.h new file mode 100644 index 0000000..8e33298 --- /dev/null +++ b/app/src/ac/svc/ACQ_CB_OPEN.h @@ -0,0 +1,22 @@ +/* + * ACQ_CB_OPEN.h - ac 서비스 ACQ_CB_OPEN 카피북 (copybook / record header). + * 지불거절 접수 레코드. + * Request fields: T_PURCHASE_ID, T_AMOUNT, T_REASON, T_BIZDATE + * Response fields: T_CANCEL_ID, T_RC + */ +#ifndef ACQ_CB_OPEN_H +#define ACQ_CB_OPEN_H + +#include "ac_svc.h" + +typedef struct { + long purchase_id; + long cb_amount; /* 지불거절 금액 */ + char reason[128]; /* 접수 사유 */ + char biz_date[16]; + long cancel_id; /* 생성된 CB 접수 ID */ +} ac_cb_open_rec_t; + +void ACQ_CB_OPEN(TPSVCINFO *p); + +#endif /* ACQ_CB_OPEN_H */ diff --git a/app/src/ac/svc/ACQ_CB_OPEN.pgc b/app/src/ac/svc/ACQ_CB_OPEN.pgc new file mode 100644 index 0000000..c85ddaa --- /dev/null +++ b/app/src/ac/svc/ACQ_CB_OPEN.pgc @@ -0,0 +1,43 @@ +/* + * ACQ_CB_OPEN.pgc - ac 모듈 서비스 ACQ_CB_OPEN (one service = one file). + * 지불거절(chargeback) 접수: 대사완료/정산완료 매입만 접수 가능, ac_cancel 에 + * CB 유형 행을 생성하고 원매입을 보류(H) 상태로 전이한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_CB_OPEN.h" + +void ACQ_CB_OPEN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char reason[128]; + EXEC SQL BEGIN DECLARE SECTION; + long h_id, h_pid, h_amt, h_orig; + char h_st[4], h_bizdate[16], h_reason[140]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + h_amt = getl(b, T_AMOUNT); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + gets_(b, T_REASON, reason, sizeof(reason)); + snprintf(h_reason, sizeof(h_reason), "CB:%s", reason[0] ? reason : "ISSUER"); + + /* 원매입 상태·금액 검증: 대사완료(M) 또는 정산완료(S)만 지불거절 대상 */ + EXEC SQL SELECT status, amount INTO :h_st, :h_orig FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + if (h_st[0] != 'M' && h_st[0] != 'S') { setl(b, T_RC, 2); FAIL(b); } + if (h_amt <= 0 || h_amt > h_orig) { setl(b, T_RC, 3); FAIL(b); } + + EXEC SQL SELECT nextval('ac_cancel_seq') INTO :h_id; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO ac_cancel (cancel_id, purchase_id, cancel_type, amount, reason, biz_date) + VALUES (:h_id, :h_pid, 'CB', :h_amt, :h_reason, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + + /* 분쟁 중 원매입은 보류 전이 */ + EXEC SQL UPDATE purchase SET status = 'H', updated_at = now() WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) { userlog("ACQ_CB_OPEN FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_CANCEL_ID, h_id); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_CB_QRY.h b/app/src/ac/svc/ACQ_CB_QRY.h new file mode 100644 index 0000000..d41174a --- /dev/null +++ b/app/src/ac/svc/ACQ_CB_QRY.h @@ -0,0 +1,22 @@ +/* + * ACQ_CB_QRY.h - ac 서비스 ACQ_CB_QRY 카피북 (copybook / record header). + * 지불거절 목록조회 레코드. + * Request fields: T_MERCHANT, T_BIZDATE + * Response fields: T_COUNT, T_AMOUNT, T_ID1(occ 0..4 = 최근 접수 ID) + */ +#ifndef ACQ_CB_QRY_H +#define ACQ_CB_QRY_H + +#include "ac_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long cb_count; /* CB 건수 */ + long cb_total; /* CB 금액 합계 */ + long recent_ids[5]; /* 최근 접수 ID */ +} ac_cb_qry_rec_t; + +void ACQ_CB_QRY(TPSVCINFO *p); + +#endif /* ACQ_CB_QRY_H */ diff --git a/app/src/ac/svc/ACQ_CB_QRY.pgc b/app/src/ac/svc/ACQ_CB_QRY.pgc new file mode 100644 index 0000000..f2a6681 --- /dev/null +++ b/app/src/ac/svc/ACQ_CB_QRY.pgc @@ -0,0 +1,40 @@ +/* + * ACQ_CB_QRY.pgc - ac 모듈 서비스 ACQ_CB_QRY (one service = one file). + * 지불거절 목록조회: 가맹점·영업일 기준 CB 접수 건을 커서로 훑어 건수/합계를 + * 집계하고 최근 5건의 접수 ID 를 다중 occurrence 로 반환한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_CB_QRY.h" + +void ACQ_CB_QRY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long cnt = 0, total = 0, shown = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid, h_amt; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL DECLARE cbq_cur CURSOR FOR + SELECT c.cancel_id, c.amount + FROM ac_cancel c JOIN purchase u ON u.purchase_id = c.purchase_id + WHERE c.cancel_type = 'CB' AND c.biz_date = :h_bizdate AND u.merchant_id = :h_merch + ORDER BY c.cancel_id DESC; + EXEC SQL OPEN cbq_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cbq_cur INTO :h_cid, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cbq_cur; FAIL(b); } + cnt++; total += h_amt; + if (shown < 5) { Badd(b, T_ID1, (char *)&h_cid, 0L); shown++; } + } + EXEC SQL CLOSE cbq_cur; + + setl(b, T_COUNT, cnt); + setl(b, T_AMOUNT, total); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_CB_STAT.h b/app/src/ac/svc/ACQ_CB_STAT.h new file mode 100644 index 0000000..445ff82 --- /dev/null +++ b/app/src/ac/svc/ACQ_CB_STAT.h @@ -0,0 +1,23 @@ +/* + * ACQ_CB_STAT.h - ac 서비스 ACQ_CB_STAT 카피북 (copybook / record header). + * 지불거절 일별 통계 레코드. + * Request fields: T_BIZDATE + * Response fields: T_COUNT, T_GROSS, T_AMT1(평균), T_AMT2(최대), T_ARG1(방어건수) + */ +#ifndef ACQ_CB_STAT_H +#define ACQ_CB_STAT_H + +#include "ac_svc.h" + +typedef struct { + char biz_date[16]; + long cb_count; + long cb_sum; + long cb_avg; + long cb_max; + long defended; /* 방어 완료 건수 */ +} ac_cb_stat_rec_t; + +void ACQ_CB_STAT(TPSVCINFO *p); + +#endif /* ACQ_CB_STAT_H */ diff --git a/app/src/ac/svc/ACQ_CB_STAT.pgc b/app/src/ac/svc/ACQ_CB_STAT.pgc new file mode 100644 index 0000000..3d34192 --- /dev/null +++ b/app/src/ac/svc/ACQ_CB_STAT.pgc @@ -0,0 +1,34 @@ +/* + * ACQ_CB_STAT.pgc - ac 모듈 서비스 ACQ_CB_STAT (one service = one file). + * 지불거절 일별 통계: 영업일 기준 CB 건수·합계·평균·최대 금액과 방어 완료 + * (DEFEND 사유) 건수를 한 번의 FILTER 집계로 산출한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_CB_STAT.h" + +void ACQ_CB_STAT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0, h_sum = 0, h_avg = 0, h_max = 0, h_def = 0; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL SELECT count(*), + coalesce(sum(amount), 0), + coalesce(avg(amount)::bigint, 0), + coalesce(max(amount), 0), + count(*) FILTER (WHERE reason LIKE 'DEFEND:%') + INTO :h_cnt, :h_sum, :h_avg, :h_max, :h_def + FROM ac_cancel WHERE cancel_type = 'CB' AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("ACQ_CB_STAT FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + setl(b, T_AMT1, h_avg); + setl(b, T_AMT2, h_max); + setl(b, T_ARG1, h_def); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_CCYSUM.h b/app/src/ac/svc/ACQ_CCYSUM.h new file mode 100644 index 0000000..b91a554 --- /dev/null +++ b/app/src/ac/svc/ACQ_CCYSUM.h @@ -0,0 +1,23 @@ +/* + * ACQ_CCYSUM.h - ac 서비스 ACQ_CCYSUM 카피북 (copybook / record header). + * 통화별 매입 집계 레코드. + * Request fields: T_CCY(선택), T_BIZDATE + * Response fields: T_COUNT, T_FX_AMT, T_AMOUNT(원화), T_ARG1(통화 종수) + */ +#ifndef ACQ_CCYSUM_H +#define ACQ_CCYSUM_H + +#include "ac_svc.h" + +typedef struct { + char ccy[8]; /* 통화코드(빈값=전체) */ + char biz_date[16]; + long fx_count; + long fx_sum; /* 원통화 합계 */ + long krw_sum; /* 원화 환산 합계 */ + long ccy_kinds; /* 통화 종수 */ +} ac_ccysum_rec_t; + +void ACQ_CCYSUM(TPSVCINFO *p); + +#endif /* ACQ_CCYSUM_H */ diff --git a/app/src/ac/svc/ACQ_CCYSUM.pgc b/app/src/ac/svc/ACQ_CCYSUM.pgc new file mode 100644 index 0000000..7364313 --- /dev/null +++ b/app/src/ac/svc/ACQ_CCYSUM.pgc @@ -0,0 +1,40 @@ +/* + * ACQ_CCYSUM.pgc - ac 모듈 서비스 ACQ_CCYSUM (one service = one file). + * 통화별 매입 집계: 통화코드를 지정하면 해당 통화의 원통화/원화 합계를, + * 미지정 시 영업일 전체의 통화 종수와 원화 환산 총액을 집계한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_CCYSUM.h" + +void ACQ_CCYSUM(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0, h_fx = 0, h_krw = 0, h_nccy = 0; + char h_ccy[8], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CCY, h_ccy, sizeof(h_ccy)); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + if (h_ccy[0]) { + /* 단일 통화 집계 */ + EXEC SQL SELECT count(*), coalesce(sum(fx_amount),0), coalesce(sum(krw_amount),0) + INTO :h_cnt, :h_fx, :h_krw + FROM ac_fx WHERE ccy = :h_ccy AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + h_nccy = 1; + } else { + /* 전체 통화 요약: 통화 종수 + 원화 환산 총액 */ + EXEC SQL SELECT count(*), count(DISTINCT ccy), coalesce(sum(krw_amount),0) + INTO :h_cnt, :h_nccy, :h_krw + FROM ac_fx WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("ACQ_CCYSUM FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + } + + setl(b, T_COUNT, h_cnt); + setl(b, T_FX_AMT, h_fx); + setl(b, T_AMOUNT, h_krw); + setl(b, T_ARG1, h_nccy); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_CHANSTAT.h b/app/src/ac/svc/ACQ_CHANSTAT.h new file mode 100644 index 0000000..8997965 --- /dev/null +++ b/app/src/ac/svc/ACQ_CHANSTAT.h @@ -0,0 +1,23 @@ +/* + * ACQ_CHANSTAT.h - ac 서비스 ACQ_CHANSTAT 카피북 (copybook / record header). + * 매입 채널별 통계 레코드. + * Request fields: T_BIZDATE + * Response fields: T_CHANNEL(최대 채널), T_AMT1, T_ARG1(종수), T_STR2/T_AMT3(occ) + */ +#ifndef ACQ_CHANSTAT_H +#define ACQ_CHANSTAT_H + +#include "ac_svc.h" + +typedef struct { + char biz_date[16]; + char best_channel[16]; /* 최대 매출 채널 */ + long best_amount; + long kinds; /* 채널 종수 */ + char channels[6][16]; + long amounts[6]; +} ac_chanstat_rec_t; + +void ACQ_CHANSTAT(TPSVCINFO *p); + +#endif /* ACQ_CHANSTAT_H */ diff --git a/app/src/ac/svc/ACQ_CHANSTAT.pgc b/app/src/ac/svc/ACQ_CHANSTAT.pgc new file mode 100644 index 0000000..0e3dbd2 --- /dev/null +++ b/app/src/ac/svc/ACQ_CHANSTAT.pgc @@ -0,0 +1,45 @@ +/* + * ACQ_CHANSTAT.pgc - ac 모듈 서비스 ACQ_CHANSTAT (one service = one file). + * 매입 채널별 통계: 영업일 채널 그룹 집계를 커서로 순회하며 채널 종수와 + * 최대 매출 채널을 찾고, 채널명/금액 쌍을 다중 occurrence 로 반환한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_CHANSTAT.h" + +void ACQ_CHANSTAT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long kinds = 0, best = 0; + char bestchan[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_sum, h_n; + char h_chan[16], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + bestchan[0] = 0; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL DECLARE chs_cur CURSOR FOR + SELECT channel, count(*), sum(amount) FROM purchase + WHERE biz_date = :h_bizdate AND status <> 'C' + GROUP BY channel ORDER BY sum(amount) DESC; + EXEC SQL OPEN chs_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH chs_cur INTO :h_chan, :h_n, :h_sum; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE chs_cur; FAIL(b); } + if (kinds == 0) { /* 정렬 첫 행 = 최대 매출 채널 */ + strncpy(bestchan, h_chan, sizeof(bestchan)-1); bestchan[sizeof(bestchan)-1] = 0; + best = h_sum; + } + if (kinds < 6) { Badd(b, T_STR2, h_chan, 0L); Badd(b, T_AMT3, (char *)&h_sum, 0L); } + kinds++; + } + EXEC SQL CLOSE chs_cur; + + if (bestchan[0]) Bchg(b, T_CHANNEL, 0, bestchan, 0L); + setl(b, T_AMT1, best); + setl(b, T_ARG1, kinds); /* 채널 종수 */ + OK(b); +} diff --git a/app/src/ac/svc/ACQ_CLAIMPROC.h b/app/src/ac/svc/ACQ_CLAIMPROC.h new file mode 100644 index 0000000..0cd8897 --- /dev/null +++ b/app/src/ac/svc/ACQ_CLAIMPROC.h @@ -0,0 +1,21 @@ +/* + * ACQ_CLAIMPROC.h - ac 서비스 ACQ_CLAIMPROC 카피북 (copybook / record header). + * 클레임 처리 종결 레코드. + * Request fields: T_ID1(클레임 ID), T_STR1(처리결과), T_BIZDATE + * Response fields: T_PURCHASE_ID, T_MSG, T_RC(4=클레임 아님) + */ +#ifndef ACQ_CLAIMPROC_H +#define ACQ_CLAIMPROC_H + +#include "ac_svc.h" + +typedef struct { + long claim_id; + char result[64]; /* 처리 결과 코드 */ + char biz_date[16]; + long purchase_id; +} ac_claimproc_rec_t; + +void ACQ_CLAIMPROC(TPSVCINFO *p); + +#endif /* ACQ_CLAIMPROC_H */ diff --git a/app/src/ac/svc/ACQ_CLAIMPROC.pgc b/app/src/ac/svc/ACQ_CLAIMPROC.pgc new file mode 100644 index 0000000..e784287 --- /dev/null +++ b/app/src/ac/svc/ACQ_CLAIMPROC.pgc @@ -0,0 +1,38 @@ +/* + * ACQ_CLAIMPROC.pgc - ac 모듈 서비스 ACQ_CLAIMPROC (one service = one file). + * 클레임 처리(종결): 등록된 클레임 행을 삭제하고 처리 결과를 원장에 + * CLAIMRES 분개(금액 0)로 남긴다. 클레임이 아니거나 없으면 T_RC=4. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_CLAIMPROC.h" + +void ACQ_CLAIMPROC(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], result[64]; + EXEC SQL BEGIN DECLARE SECTION; + long h_uid, h_pid = 0, h_del; + EXEC SQL END DECLARE SECTION; + + h_uid = getl(b, T_ID1); + gets_(b, T_STR1, result, sizeof(result)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (result[0] == 0) strcpy(result, "ACCEPTED"); + + EXEC SQL SELECT purchase_id INTO :h_pid FROM ac_unmatch + WHERE unmatch_id = :h_uid AND reason LIKE 'CLAIM:%'; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 4); FAIL(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL DELETE FROM ac_unmatch WHERE unmatch_id = :h_uid; + if (sqlca.sqlcode < 0) FAIL(b); + h_del = sqlca.sqlerrd[2]; + + /* 처리 흔적을 원장에 남긴다 (금액 0, 유형 CLAIMRES) */ + if (h_del > 0 && acdb_insert_ledger(h_pid, "CLAIMRES", 0, bizdate) < 0) FAIL(b); + + Bchg(b, T_MSG, 0, result, 0L); + setl(b, T_PURCHASE_ID, h_pid); + setl(b, T_RC, h_del > 0 ? 0 : 1); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_CLAIMQRY.h b/app/src/ac/svc/ACQ_CLAIMQRY.h new file mode 100644 index 0000000..4860dce --- /dev/null +++ b/app/src/ac/svc/ACQ_CLAIMQRY.h @@ -0,0 +1,21 @@ +/* + * ACQ_CLAIMQRY.h - ac 서비스 ACQ_CLAIMQRY 카피북 (copybook / record header). + * 클레임 목록조회 레코드. + * Request fields: T_BIZDATE + * Response fields: T_COUNT, T_ARG1(수록 건수), T_ID1/T_STR1(occ 0..4) + */ +#ifndef ACQ_CLAIMQRY_H +#define ACQ_CLAIMQRY_H + +#include "ac_svc.h" + +typedef struct { + char biz_date[16]; + long total; /* 미종결 클레임 총건수 */ + long claim_ids[5]; + char reasons[5][128]; +} ac_claimqry_rec_t; + +void ACQ_CLAIMQRY(TPSVCINFO *p); + +#endif /* ACQ_CLAIMQRY_H */ diff --git a/app/src/ac/svc/ACQ_CLAIMQRY.pgc b/app/src/ac/svc/ACQ_CLAIMQRY.pgc new file mode 100644 index 0000000..fe8cadb --- /dev/null +++ b/app/src/ac/svc/ACQ_CLAIMQRY.pgc @@ -0,0 +1,42 @@ +/* + * ACQ_CLAIMQRY.pgc - ac 모듈 서비스 ACQ_CLAIMQRY (one service = one file). + * 클레임 목록조회: 영업일의 미종결 클레임을 커서로 순회, 총건수와 함께 + * 상위 5건의 클레임 ID/사유를 다중 occurrence 로 내려준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_CLAIMQRY.h" + +void ACQ_CLAIMQRY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long total = 0, listed = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_uid; + char h_reason[128], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL DECLARE clq_cur CURSOR FOR + SELECT unmatch_id, reason FROM ac_unmatch + WHERE reason LIKE 'CLAIM:%' AND biz_date = :h_bizdate + ORDER BY unmatch_id; + EXEC SQL OPEN clq_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH clq_cur INTO :h_uid, :h_reason; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE clq_cur; FAIL(b); } + total++; + if (listed < 5) { + Badd(b, T_ID1, (char *)&h_uid, 0L); + Badd(b, T_STR1, h_reason, 0L); + listed++; + } + } + EXEC SQL CLOSE clq_cur; + + setl(b, T_COUNT, total); + setl(b, T_ARG1, listed); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_CLAIMREG.h b/app/src/ac/svc/ACQ_CLAIMREG.h new file mode 100644 index 0000000..c4370eb --- /dev/null +++ b/app/src/ac/svc/ACQ_CLAIMREG.h @@ -0,0 +1,21 @@ +/* + * ACQ_CLAIMREG.h - ac 서비스 ACQ_CLAIMREG 카피북 (copybook / record header). + * 클레임 등록 레코드. + * Request fields: T_PURCHASE_ID, T_REASON, T_BIZDATE + * Response fields: T_ID1(클레임 ID), T_RC(2=중복, 5=매입 없음) + */ +#ifndef ACQ_CLAIMREG_H +#define ACQ_CLAIMREG_H + +#include "ac_svc.h" + +typedef struct { + long purchase_id; + char reason[100]; /* 이의신청 사유 */ + char biz_date[16]; + long claim_id; /* ac_unmatch 행 ID */ +} ac_claimreg_rec_t; + +void ACQ_CLAIMREG(TPSVCINFO *p); + +#endif /* ACQ_CLAIMREG_H */ diff --git a/app/src/ac/svc/ACQ_CLAIMREG.pgc b/app/src/ac/svc/ACQ_CLAIMREG.pgc new file mode 100644 index 0000000..58f06eb --- /dev/null +++ b/app/src/ac/svc/ACQ_CLAIMREG.pgc @@ -0,0 +1,42 @@ +/* + * ACQ_CLAIMREG.pgc - ac 모듈 서비스 ACQ_CLAIMREG (one service = one file). + * 클레임 등록: 가맹점 이의신청을 미매입 원장(ac_unmatch)에 CLAIM 사유로 + * 기록한다. 동일 매입에 미종결 클레임이 이미 있으면 중복 등록을 막는다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_CLAIMREG.h" + +void ACQ_CLAIMREG(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char why[100]; + EXEC SQL BEGIN DECLARE SECTION; + long h_id, h_pid, h_exist = 0, h_have = 0; + char h_reason[120], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_REASON, why, sizeof(why)); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + snprintf(h_reason, sizeof(h_reason), "CLAIM:%s", why[0] ? why : "MERCHANT"); + + EXEC SQL SELECT count(*) INTO :h_exist FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_exist == 0) { setl(b, T_RC, 5); FAIL(b); } + + /* 미종결 클레임 중복 방지 */ + EXEC SQL SELECT count(*) INTO :h_have FROM ac_unmatch + WHERE purchase_id = :h_pid AND reason LIKE 'CLAIM:%'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_have > 0) { setl(b, T_RC, 2); FAIL(b); } + + EXEC SQL SELECT nextval('ac_unmatch_seq') INTO :h_id; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO ac_unmatch (unmatch_id, purchase_id, reason, biz_date) + VALUES (:h_id, :h_pid, :h_reason, :h_bizdate); + if (sqlca.sqlcode < 0) { userlog("ACQ_CLAIMREG FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_ID1, h_id); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_CORRHIST.h b/app/src/ac/svc/ACQ_CORRHIST.h new file mode 100644 index 0000000..9923e01 --- /dev/null +++ b/app/src/ac/svc/ACQ_CORRHIST.h @@ -0,0 +1,22 @@ +/* + * ACQ_CORRHIST.h - ac 서비스 ACQ_CORRHIST 카피북 (copybook / record header). + * 정정 이력조회 레코드. + * Request fields: T_PURCHASE_ID + * Response fields: T_COUNT, T_ARG1(취소류 건수), T_ADJ/T_DELTA(occ 0..9) + */ +#ifndef ACQ_CORRHIST_H +#define ACQ_CORRHIST_H + +#include "ac_svc.h" + +typedef struct { + long purchase_id; + long adj_rows; /* 수수료 정정 건수(최대 10) */ + long cancel_rows; /* 취소류 정정 건수 */ + long adj_ids[10]; + long deltas[10]; +} ac_corrhist_rec_t; + +void ACQ_CORRHIST(TPSVCINFO *p); + +#endif /* ACQ_CORRHIST_H */ diff --git a/app/src/ac/svc/ACQ_CORRHIST.pgc b/app/src/ac/svc/ACQ_CORRHIST.pgc new file mode 100644 index 0000000..ffa0735 --- /dev/null +++ b/app/src/ac/svc/ACQ_CORRHIST.pgc @@ -0,0 +1,41 @@ +/* + * ACQ_CORRHIST.pgc - ac 모듈 서비스 ACQ_CORRHIST (one service = one file). + * 정정 이력조회: 매입 건에 붙은 수수료 조정 이력(최근 10건)을 역순 커서로 + * 내려주고, 취소계 정정(ac_cancel) 건수를 별도 카운트로 함께 응답한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_CORRHIST.h" + +void ACQ_CORRHIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rows = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_adj, h_delta, h_cxl = 0; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + + EXEC SQL DECLARE crh_cur CURSOR FOR + SELECT adj_id, delta FROM ac_fee_adj + WHERE purchase_id = :h_pid ORDER BY adj_id DESC LIMIT 10; + EXEC SQL OPEN crh_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH crh_cur INTO :h_adj, :h_delta; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE crh_cur; FAIL(b); } + Badd(b, T_ADJ, (char *)&h_adj, 0L); + Badd(b, T_DELTA, (char *)&h_delta, 0L); + rows++; + } + EXEC SQL CLOSE crh_cur; + + /* 취소·환불류 정정 건수 병기 */ + EXEC SQL SELECT count(*) INTO :h_cxl FROM ac_cancel WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) { userlog("ACQ_CORRHIST FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_COUNT, rows); + setl(b, T_ARG1, h_cxl); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_CTRYSUM.h b/app/src/ac/svc/ACQ_CTRYSUM.h new file mode 100644 index 0000000..65efb38 --- /dev/null +++ b/app/src/ac/svc/ACQ_CTRYSUM.h @@ -0,0 +1,22 @@ +/* + * ACQ_CTRYSUM.h - ac 서비스 ACQ_CTRYSUM 카피북 (copybook / record header). + * 국가권역별 매입 집계 레코드. + * Request fields: T_BIZDATE + * Response fields: T_ARG1(미주), T_ARG2(일본), T_ARG3(유럽), T_ARG4(기타), T_GROSS + */ +#ifndef ACQ_CTRYSUM_H +#define ACQ_CTRYSUM_H + +#include "ac_svc.h" + +typedef struct { + char biz_date[16]; + long amer_krw; /* 미주권 원화 합계 */ + long japan_krw; + long europe_krw; + long etc_krw; +} ac_ctrysum_rec_t; + +void ACQ_CTRYSUM(TPSVCINFO *p); + +#endif /* ACQ_CTRYSUM_H */ diff --git a/app/src/ac/svc/ACQ_CTRYSUM.pgc b/app/src/ac/svc/ACQ_CTRYSUM.pgc new file mode 100644 index 0000000..9717d75 --- /dev/null +++ b/app/src/ac/svc/ACQ_CTRYSUM.pgc @@ -0,0 +1,48 @@ +/* + * ACQ_CTRYSUM.pgc - ac 모듈 서비스 ACQ_CTRYSUM (one service = one file). + * 국가별 매입 집계: 통화코드를 국가권역(미주/일본/유럽/기타)으로 매핑하는 + * C 분류기를 거쳐 해외매입 원화금액을 권역별 버킷에 누적한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_CTRYSUM.h" + +/* 통화코드 → 권역 인덱스 (0=미주, 1=일본, 2=유럽, 3=기타) */ +static int ac_ccy_region(const char *ccy) +{ + if (strcmp(ccy, "USD") == 0 || strcmp(ccy, "CAD") == 0) return 0; + if (strcmp(ccy, "JPY") == 0) return 1; + if (strcmp(ccy, "EUR") == 0 || strcmp(ccy, "GBP") == 0 || strcmp(ccy, "CHF") == 0) return 2; + return 3; +} + +void ACQ_CTRYSUM(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long bucket[4] = {0, 0, 0, 0}; + EXEC SQL BEGIN DECLARE SECTION; + long h_krw; + char h_ccy[8], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL DECLARE ctr_cur CURSOR FOR + SELECT ccy, sum(krw_amount) FROM ac_fx + WHERE biz_date = :h_bizdate GROUP BY ccy; + EXEC SQL OPEN ctr_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH ctr_cur INTO :h_ccy, :h_krw; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ctr_cur; FAIL(b); } + bucket[ac_ccy_region(h_ccy)] += h_krw; + } + EXEC SQL CLOSE ctr_cur; + + setl(b, T_ARG1, bucket[0]); /* 미주 */ + setl(b, T_ARG2, bucket[1]); /* 일본 */ + setl(b, T_ARG3, bucket[2]); /* 유럽 */ + setl(b, T_ARG4, bucket[3]); /* 기타 */ + setl(b, T_GROSS, bucket[0] + bucket[1] + bucket[2] + bucket[3]); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_DAYSTAT.h b/app/src/ac/svc/ACQ_DAYSTAT.h new file mode 100644 index 0000000..8a2140b --- /dev/null +++ b/app/src/ac/svc/ACQ_DAYSTAT.h @@ -0,0 +1,26 @@ +/* + * ACQ_DAYSTAT.h - ac 서비스 ACQ_DAYSTAT 카피북 (copybook / record header). + * 매입 일별 상태 통계 레코드. + * Request fields: T_BIZDATE + * Response fields: T_ARG1..T_ARG6(상태별 건수), T_COUNT, T_GROSS + */ +#ifndef ACQ_DAYSTAT_H +#define ACQ_DAYSTAT_H + +#include "ac_svc.h" + +typedef struct { + char biz_date[16]; + long cnt_accept; /* A */ + long cnt_matched; /* M */ + long cnt_settled; /* S */ + long cnt_cancel; /* C */ + long cnt_hold; /* H */ + long cnt_void; /* V */ + long total; + long gross; +} ac_daystat_rec_t; + +void ACQ_DAYSTAT(TPSVCINFO *p); + +#endif /* ACQ_DAYSTAT_H */ diff --git a/app/src/ac/svc/ACQ_DAYSTAT.pgc b/app/src/ac/svc/ACQ_DAYSTAT.pgc new file mode 100644 index 0000000..19733b4 --- /dev/null +++ b/app/src/ac/svc/ACQ_DAYSTAT.pgc @@ -0,0 +1,40 @@ +/* + * ACQ_DAYSTAT.pgc - ac 모듈 서비스 ACQ_DAYSTAT (one service = one file). + * 매입 일별 상태 통계: 영업일 전 건을 상태별(접수/대사/정산/취소/보류/무효) + * FILTER 집계 한 방으로 나눠 세고, 총건수·총액을 함께 내려준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_DAYSTAT.h" + +void ACQ_DAYSTAT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_a = 0, h_m = 0, h_s = 0, h_c = 0, h_h = 0, h_v = 0, h_tot = 0, h_sum = 0; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL SELECT count(*) FILTER (WHERE status = 'A'), + count(*) FILTER (WHERE status = 'M'), + count(*) FILTER (WHERE status = 'S'), + count(*) FILTER (WHERE status = 'C'), + count(*) FILTER (WHERE status = 'H'), + count(*) FILTER (WHERE status = 'V'), + count(*), + coalesce(sum(amount), 0) + INTO :h_a, :h_m, :h_s, :h_c, :h_h, :h_v, :h_tot, :h_sum + FROM purchase WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("ACQ_DAYSTAT FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_ARG1, h_a); /* 접수 */ + setl(b, T_ARG2, h_m); /* 대사완료 */ + setl(b, T_ARG3, h_s); /* 정산완료 */ + setl(b, T_ARG4, h_c); /* 취소 */ + setl(b, T_ARG5, h_h); /* 보류 */ + setl(b, T_ARG6, h_v); /* 무효 */ + setl(b, T_COUNT, h_tot); + setl(b, T_GROSS, h_sum); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_DUPSCAN.h b/app/src/ac/svc/ACQ_DUPSCAN.h new file mode 100644 index 0000000..25a6113 --- /dev/null +++ b/app/src/ac/svc/ACQ_DUPSCAN.h @@ -0,0 +1,21 @@ +/* + * ACQ_DUPSCAN.h - ac 서비스 ACQ_DUPSCAN 카피북 (copybook / record header). + * 중복매입 스캔 레코드. + * Request fields: T_BIZDATE + * Response fields: T_COUNT(의심 그룹), T_ARG1(연루 건수), T_AMT1(연루 총액), T_RC + */ +#ifndef ACQ_DUPSCAN_H +#define ACQ_DUPSCAN_H + +#include "ac_svc.h" + +typedef struct { + char biz_date[16]; + long dup_groups; /* (가맹점,금액) 중복 그룹 수 */ + long dup_rows; + long dup_amount; +} ac_dupscan_rec_t; + +void ACQ_DUPSCAN(TPSVCINFO *p); + +#endif /* ACQ_DUPSCAN_H */ diff --git a/app/src/ac/svc/ACQ_DUPSCAN.pgc b/app/src/ac/svc/ACQ_DUPSCAN.pgc new file mode 100644 index 0000000..f022969 --- /dev/null +++ b/app/src/ac/svc/ACQ_DUPSCAN.pgc @@ -0,0 +1,33 @@ +/* + * ACQ_DUPSCAN.pgc - ac 모듈 서비스 ACQ_DUPSCAN (one service = one file). + * 중복매입 스캔: 영업일 내 (가맹점, 금액) 조합이 2건 이상인 그룹을 부질의로 + * 찾아 의심 그룹 수와 중복 연루 총액을 보고한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_DUPSCAN.h" + +void ACQ_DUPSCAN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_groups = 0, h_rows = 0, h_amt = 0; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL SELECT count(*), coalesce(sum(dup_n), 0), coalesce(sum(dup_amt), 0) + INTO :h_groups, :h_rows, :h_amt + FROM (SELECT count(*) AS dup_n, sum(amount) AS dup_amt + FROM purchase + WHERE biz_date = :h_bizdate AND status NOT IN ('C', 'V') + GROUP BY merchant_id, amount + HAVING count(*) > 1) g; + if (sqlca.sqlcode < 0) { userlog("ACQ_DUPSCAN FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_COUNT, h_groups); /* 의심 그룹 수 */ + setl(b, T_ARG1, h_rows); /* 연루 매입 건수 */ + setl(b, T_AMT1, h_amt); /* 연루 총액 */ + setl(b, T_RC, h_groups > 0 ? 1 : 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_EDIRESEND.h b/app/src/ac/svc/ACQ_EDIRESEND.h new file mode 100644 index 0000000..c998c03 --- /dev/null +++ b/app/src/ac/svc/ACQ_EDIRESEND.h @@ -0,0 +1,21 @@ +/* + * ACQ_EDIRESEND.h - ac 서비스 ACQ_EDIRESEND 카피북 (copybook / record header). + * EDI 재전송 레코드. + * Request fields: T_DOCNO(원본 문서번호) + * Response fields: T_ID1(재전송 EDI ID), T_DOCNO(-R 문서번호), T_AMOUNT, T_RC + */ +#ifndef ACQ_EDIRESEND_H +#define ACQ_EDIRESEND_H + +#include "ac_svc.h" + +typedef struct { + char doc_no[64]; /* 원본 문서번호 */ + char resend_doc[72]; /* -R 접미 재전송 문서번호 */ + long new_edi_id; + long amount; +} ac_ediresend_rec_t; + +void ACQ_EDIRESEND(TPSVCINFO *p); + +#endif /* ACQ_EDIRESEND_H */ diff --git a/app/src/ac/svc/ACQ_EDIRESEND.pgc b/app/src/ac/svc/ACQ_EDIRESEND.pgc new file mode 100644 index 0000000..86f96d2 --- /dev/null +++ b/app/src/ac/svc/ACQ_EDIRESEND.pgc @@ -0,0 +1,46 @@ +/* + * ACQ_EDIRESEND.pgc - ac 모듈 서비스 ACQ_EDIRESEND (one service = one file). + * EDI 재전송: 문서번호의 최신 EDI 원장을 찾아 재전송 사본(-R 접미 문서번호) + * 을 발행한다. 이미 재전송본이면 다시 만들지 않는다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_EDIRESEND.h" + +void ACQ_EDIRESEND(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + size_t dlen; + EXEC SQL BEGIN DECLARE SECTION; + long h_new, h_amt = 0; + char h_doc[64], h_redoc[72], h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_DOCNO, h_doc, sizeof(h_doc)); + if (h_doc[0] == 0) { setl(b, T_RC, 8); FAIL(b); } + + /* 재전송본(-R 접미) 자체는 재전송 금지 */ + dlen = strlen(h_doc); + if (dlen >= 2 && strcmp(h_doc + dlen - 2, "-R") == 0) { setl(b, T_RC, 2); FAIL(b); } + + /* 최신 원본 EDI 행 조회 */ + EXEC SQL SELECT merchant_id, amount, to_char(biz_date, 'YYYY-MM-DD') + INTO :h_merch, :h_amt, :h_bizdate + FROM ac_edi WHERE doc_no = :h_doc + ORDER BY edi_id DESC LIMIT 1; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 4); FAIL(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + snprintf(h_redoc, sizeof(h_redoc), "%s-R", h_doc); + + EXEC SQL SELECT nextval('ac_edi_seq') INTO :h_new; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO ac_edi (edi_id, merchant_id, doc_no, amount, biz_date) + VALUES (:h_new, :h_merch, :h_redoc, :h_amt, :h_bizdate); + if (sqlca.sqlcode < 0) { userlog("ACQ_EDIRESEND FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + Bchg(b, T_DOCNO, 0, h_redoc, 0L); + setl(b, T_ID1, h_new); + setl(b, T_AMOUNT, h_amt); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_FEEHIST.h b/app/src/ac/svc/ACQ_FEEHIST.h new file mode 100644 index 0000000..ba26b04 --- /dev/null +++ b/app/src/ac/svc/ACQ_FEEHIST.h @@ -0,0 +1,22 @@ +/* + * ACQ_FEEHIST.h - ac 서비스 ACQ_FEEHIST 카피북 (copybook / record header). + * 수수료 조정 이력 집계 레코드. + * Request fields: T_PURCHASE_ID + * Response fields: T_COUNT, T_DELTA(순증감), T_AMT1(증액), T_AMT2(감액), T_RC + */ +#ifndef ACQ_FEEHIST_H +#define ACQ_FEEHIST_H + +#include "ac_svc.h" + +typedef struct { + long purchase_id; + long adj_count; + long net_delta; /* 순증감 합계 */ + long up_sum; /* 증액분 */ + long down_sum; /* 감액분 */ +} ac_feehist_rec_t; + +void ACQ_FEEHIST(TPSVCINFO *p); + +#endif /* ACQ_FEEHIST_H */ diff --git a/app/src/ac/svc/ACQ_FEEHIST.pgc b/app/src/ac/svc/ACQ_FEEHIST.pgc new file mode 100644 index 0000000..dd40009 --- /dev/null +++ b/app/src/ac/svc/ACQ_FEEHIST.pgc @@ -0,0 +1,32 @@ +/* + * ACQ_FEEHIST.pgc - ac 모듈 서비스 ACQ_FEEHIST (one service = one file). + * 수수료 조정 이력 집계: 매입 건의 조정 건수, 순증감 합계, 증액분/감액분을 + * FILTER 집계로 한 번에 산출한다. 순증감이 0 이 아니면 T_RC=1 로 표시. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_FEEHIST.h" + +void ACQ_FEEHIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_n = 0, h_net = 0, h_up = 0, h_dn = 0; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + + EXEC SQL SELECT count(*), + coalesce(sum(delta), 0), + coalesce(sum(delta) FILTER (WHERE delta > 0), 0), + coalesce(sum(delta) FILTER (WHERE delta < 0), 0) + INTO :h_n, :h_net, :h_up, :h_dn + FROM ac_fee_adj WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) { userlog("ACQ_FEEHIST FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_COUNT, h_n); + setl(b, T_DELTA, h_net); /* 순증감 */ + setl(b, T_AMT1, h_up); /* 증액 합계 */ + setl(b, T_AMT2, h_dn); /* 감액 합계(음수) */ + setl(b, T_RC, h_net != 0 ? 1 : 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_FEERECALC.h b/app/src/ac/svc/ACQ_FEERECALC.h new file mode 100644 index 0000000..8e432af --- /dev/null +++ b/app/src/ac/svc/ACQ_FEERECALC.h @@ -0,0 +1,22 @@ +/* + * ACQ_FEERECALC.h - ac 서비스 ACQ_FEERECALC 카피북 (copybook / record header). + * 수수료 재산정 레코드. + * Request fields: T_PURCHASE_ID, T_BIZDATE + * Response fields: T_FEE(재산정 수수료), T_DELTA(차액), T_ADJ, T_RC + */ +#ifndef ACQ_FEERECALC_H +#define ACQ_FEERECALC_H + +#include "ac_svc.h" + +typedef struct { + long purchase_id; + char biz_date[16]; + long new_fee; /* 현행 MDR 재산정 수수료 */ + long delta; /* 기존 대비 차액 */ + long adj_id; +} ac_feerecalc_rec_t; + +void ACQ_FEERECALC(TPSVCINFO *p); + +#endif /* ACQ_FEERECALC_H */ diff --git a/app/src/ac/svc/ACQ_FEERECALC.pgc b/app/src/ac/svc/ACQ_FEERECALC.pgc new file mode 100644 index 0000000..2de429c --- /dev/null +++ b/app/src/ac/svc/ACQ_FEERECALC.pgc @@ -0,0 +1,44 @@ +/* + * ACQ_FEERECALC.pgc - ac 모듈 서비스 ACQ_FEERECALC (one service = one file). + * 수수료 재산정: 가맹점의 현행 MDR 로 수수료를 다시 계산해 기존 수수료와의 + * 차액이 있으면 조정 이력을 남기고 purchase 의 fee/net 을 보정한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_FEERECALC.h" + +void ACQ_FEERECALC(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long bps = 0, newfee, delta, adj; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt = 0, h_fee = 0, h_newfee; + char h_merch[64]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + + EXEC SQL SELECT merchant_id, amount, fee INTO :h_merch, :h_amt, :h_fee + FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + + if (acdb_merchant_mdr(h_merch, &bps) < 0) FAIL(b); + newfee = acq_fee(h_amt, bps); + delta = newfee - h_fee; + + if (delta != 0) { + adj = acdb_insert_fee_adj(h_pid, delta, "RECALC", bizdate); + if (adj < 0) FAIL(b); + h_newfee = newfee; + EXEC SQL UPDATE purchase SET fee = :h_newfee, net = amount - :h_newfee, updated_at = now() + WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) { userlog("ACQ_FEERECALC FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + setl(b, T_ADJ, adj); + } + + setl(b, T_FEE, newfee); + setl(b, T_DELTA, delta); + setl(b, T_RC, delta == 0 ? 1 : 0); /* 1=차액 없음 */ + OK(b); +} diff --git a/app/src/ac/svc/ACQ_FEEWAIVE.h b/app/src/ac/svc/ACQ_FEEWAIVE.h new file mode 100644 index 0000000..1ebc1d5 --- /dev/null +++ b/app/src/ac/svc/ACQ_FEEWAIVE.h @@ -0,0 +1,22 @@ +/* + * ACQ_FEEWAIVE.h - ac 서비스 ACQ_FEEWAIVE 카피북 (copybook / record header). + * 수수료 감면 레코드. + * Request fields: T_PURCHASE_ID, T_ARG1(감면율 bps), T_BIZDATE + * Response fields: T_AMT1(감면액), T_ADJ, T_RC + */ +#ifndef ACQ_FEEWAIVE_H +#define ACQ_FEEWAIVE_H + +#include "ac_svc.h" + +typedef struct { + long purchase_id; + long waive_bps; /* 감면율 (basis points) */ + char biz_date[16]; + long waive_amount; /* 감면액 */ + long adj_id; +} ac_feewaive_rec_t; + +void ACQ_FEEWAIVE(TPSVCINFO *p); + +#endif /* ACQ_FEEWAIVE_H */ diff --git a/app/src/ac/svc/ACQ_FEEWAIVE.pgc b/app/src/ac/svc/ACQ_FEEWAIVE.pgc new file mode 100644 index 0000000..a414413 --- /dev/null +++ b/app/src/ac/svc/ACQ_FEEWAIVE.pgc @@ -0,0 +1,43 @@ +/* + * ACQ_FEEWAIVE.pgc - ac 모듈 서비스 ACQ_FEEWAIVE (one service = one file). + * 수수료 감면: 요청 감면율(bps)만큼 수수료를 깎아 가맹점 정산금에 돌려준다. + * 음수 delta 조정 이력을 남기고 purchase 의 fee 차감 / net 증액을 반영. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_FEEWAIVE.h" + +void ACQ_FEEWAIVE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rate, waive, adj; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_fee = 0, h_w; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + rate = getl(b, T_ARG1); /* 감면율 bps (1~10000) */ + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (rate < 1 || rate > 10000) { setl(b, T_RC, 9); FAIL(b); } + + EXEC SQL SELECT fee INTO :h_fee FROM purchase + WHERE purchase_id = :h_pid AND status NOT IN ('C', 'V'); + if (sqlca.sqlcode == 100) { setl(b, T_RC, 5); FAIL(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + waive = acq_fee(h_fee, rate); /* fee * rate / 10000 */ + if (waive <= 0) { setl(b, T_RC, 1); setl(b, T_AMT1, 0); OK(b); } + + adj = acdb_insert_fee_adj(h_pid, -waive, "WAIVE", bizdate); + if (adj < 0) FAIL(b); + + h_w = waive; + EXEC SQL UPDATE purchase SET fee = fee - :h_w, net = net + :h_w, updated_at = now() + WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) { userlog("ACQ_FEEWAIVE FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_AMT1, waive); + setl(b, T_ADJ, adj); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_FXCONVERT.h b/app/src/ac/svc/ACQ_FXCONVERT.h new file mode 100644 index 0000000..89d18ae --- /dev/null +++ b/app/src/ac/svc/ACQ_FXCONVERT.h @@ -0,0 +1,24 @@ +/* + * ACQ_FXCONVERT.h - ac 서비스 ACQ_FXCONVERT 카피북 (copybook / record header). + * 통화 환산 등록 레코드. + * Request fields: T_PURCHASE_ID, T_CCY, T_FX_AMT, T_ARG1(환율 bps), T_BIZDATE + * Response fields: T_ID1(fx 원장 ID), T_AMOUNT(원화 환산액), T_RC + */ +#ifndef ACQ_FXCONVERT_H +#define ACQ_FXCONVERT_H + +#include "ac_svc.h" + +typedef struct { + long purchase_id; + char ccy[8]; + long fx_amount; /* 원통화 금액 */ + long rate_bps; /* 적용환율 */ + char biz_date[16]; + long fx_id; + long krw_amount; /* 원화 환산액 */ +} ac_fxconvert_rec_t; + +void ACQ_FXCONVERT(TPSVCINFO *p); + +#endif /* ACQ_FXCONVERT_H */ diff --git a/app/src/ac/svc/ACQ_FXCONVERT.pgc b/app/src/ac/svc/ACQ_FXCONVERT.pgc new file mode 100644 index 0000000..6671ded --- /dev/null +++ b/app/src/ac/svc/ACQ_FXCONVERT.pgc @@ -0,0 +1,43 @@ +/* + * ACQ_FXCONVERT.pgc - ac 모듈 서비스 ACQ_FXCONVERT (one service = one file). + * 통화 환산 등록: 해외매입의 원통화 금액에 적용환율(bps)을 곱해 원화를 + * 산출하고 ac_fx 원장에 기록, 매입 채널을 FOREIGN 으로 표시한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_FXCONVERT.h" + +void ACQ_FXCONVERT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long krw; + EXEC SQL BEGIN DECLARE SECTION; + long h_fxid, h_pid, h_fx, h_rate, h_krw; + char h_ccy[8], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + h_fx = getl(b, T_FX_AMT); + h_rate = getl(b, T_ARG1); /* 환율 bps: KRW = fx * rate / 10000 */ + gets_(b, T_CCY, h_ccy, sizeof(h_ccy)); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_fx <= 0 || h_rate <= 0 || h_ccy[0] == 0) { setl(b, T_RC, 3); FAIL(b); } + + krw = (h_fx * h_rate) / 10000L; + h_krw = krw; + + EXEC SQL SELECT nextval('ac_fx_seq') INTO :h_fxid; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO ac_fx (fx_id, purchase_id, ccy, fx_amount, krw_amount, rate_bps, biz_date) + VALUES (:h_fxid, :h_pid, :h_ccy, :h_fx, :h_krw, :h_rate, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + + /* 해외매입 채널 표시 (존재하는 매입에만 반영) */ + EXEC SQL UPDATE purchase SET channel = 'FOREIGN', updated_at = now() + WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) { userlog("ACQ_FXCONVERT FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_ID1, h_fxid); + setl(b, T_AMOUNT, krw); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_FXRATE.h b/app/src/ac/svc/ACQ_FXRATE.h new file mode 100644 index 0000000..ed5cb5c --- /dev/null +++ b/app/src/ac/svc/ACQ_FXRATE.h @@ -0,0 +1,22 @@ +/* + * ACQ_FXRATE.h - ac 서비스 ACQ_FXRATE 카피북 (copybook / record header). + * 통화 적용환율 조회 레코드. + * Request fields: T_CCY, T_BIZDATE + * Response fields: T_ARG1(평균 bps), T_ARG2(최저), T_ARG3(최고), T_COUNT, T_RC + */ +#ifndef ACQ_FXRATE_H +#define ACQ_FXRATE_H + +#include "ac_svc.h" + +typedef struct { + char ccy[8]; + char biz_date[16]; + long avg_bps; /* 평균환율 bps */ + long min_bps; + long max_bps; +} ac_fxrate_rec_t; + +void ACQ_FXRATE(TPSVCINFO *p); + +#endif /* ACQ_FXRATE_H */ diff --git a/app/src/ac/svc/ACQ_FXRATE.pgc b/app/src/ac/svc/ACQ_FXRATE.pgc new file mode 100644 index 0000000..9fe4715 --- /dev/null +++ b/app/src/ac/svc/ACQ_FXRATE.pgc @@ -0,0 +1,35 @@ +/* + * ACQ_FXRATE.pgc - ac 모듈 서비스 ACQ_FXRATE (one service = one file). + * 통화 적용환율 조회: 해외매입 원장(ac_fx)에서 특정 통화의 최근 영업일 + * 평균/최저/최고 환율(bps)을 조회한다. 건수가 없으면 T_RC=1. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_FXRATE.h" + +void ACQ_FXRATE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_n = 0, h_avg = 0, h_min = 0, h_max = 0; + char h_ccy[8], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CCY, h_ccy, sizeof(h_ccy)); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_ccy[0] == 0) { setl(b, T_RC, 8); FAIL(b); } + + EXEC SQL SELECT count(*), + coalesce(avg(rate_bps)::bigint, 0), + coalesce(min(rate_bps), 0), + coalesce(max(rate_bps), 0) + INTO :h_n, :h_avg, :h_min, :h_max + FROM ac_fx WHERE ccy = :h_ccy AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("ACQ_FXRATE FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_ARG1, h_avg); + setl(b, T_ARG2, h_min); + setl(b, T_ARG3, h_max); + setl(b, T_COUNT, h_n); + setl(b, T_RC, h_n > 0 ? 0 : 1); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_HOLDQRY.h b/app/src/ac/svc/ACQ_HOLDQRY.h new file mode 100644 index 0000000..71efaa6 --- /dev/null +++ b/app/src/ac/svc/ACQ_HOLDQRY.h @@ -0,0 +1,22 @@ +/* + * ACQ_HOLDQRY.h - ac 서비스 ACQ_HOLDQRY 카피북 (copybook / record header). + * 보류 매입 조회 레코드. + * Request fields: T_MERCHANT + * Response fields: T_COUNT, T_GROSS, T_ID2/T_AMT4(occ 0..4), T_RC(1=보류 존재) + */ +#ifndef ACQ_HOLDQRY_H +#define ACQ_HOLDQRY_H + +#include "ac_svc.h" + +typedef struct { + char merchant_id[64]; + long hold_count; + long hold_total; + long top_pids[5]; + long top_amounts[5]; +} ac_holdqry_rec_t; + +void ACQ_HOLDQRY(TPSVCINFO *p); + +#endif /* ACQ_HOLDQRY_H */ diff --git a/app/src/ac/svc/ACQ_HOLDQRY.pgc b/app/src/ac/svc/ACQ_HOLDQRY.pgc new file mode 100644 index 0000000..9f31e19 --- /dev/null +++ b/app/src/ac/svc/ACQ_HOLDQRY.pgc @@ -0,0 +1,43 @@ +/* + * ACQ_HOLDQRY.pgc - ac 모듈 서비스 ACQ_HOLDQRY (one service = one file). + * 보류 매입 조회: 가맹점의 보류(H) 건을 최근 갱신순 커서로 훑어 건수·총액을 + * 집계하고 상위 5건의 매입 ID/금액을 occurrence 로 내려준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_HOLDQRY.h" + +void ACQ_HOLDQRY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long cnt = 0, total = 0, top = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt; + char h_merch[64]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + + EXEC SQL DECLARE hlq_cur CURSOR FOR + SELECT purchase_id, amount FROM purchase + WHERE merchant_id = :h_merch AND status = 'H' + ORDER BY updated_at DESC; + EXEC SQL OPEN hlq_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH hlq_cur INTO :h_pid, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE hlq_cur; FAIL(b); } + cnt++; total += h_amt; + if (top < 5) { + Badd(b, T_ID2, (char *)&h_pid, 0L); + Badd(b, T_AMT4, (char *)&h_amt, 0L); + top++; + } + } + EXEC SQL CLOSE hlq_cur; + + setl(b, T_COUNT, cnt); + setl(b, T_GROSS, total); + setl(b, T_RC, cnt > 0 ? 1 : 0); /* 1=보류 존재 */ + OK(b); +} diff --git a/app/src/ac/svc/ACQ_ISSSTAT.h b/app/src/ac/svc/ACQ_ISSSTAT.h new file mode 100644 index 0000000..65da035 --- /dev/null +++ b/app/src/ac/svc/ACQ_ISSSTAT.h @@ -0,0 +1,23 @@ +/* + * ACQ_ISSSTAT.h - ac 서비스 ACQ_ISSSTAT 카피북 (copybook / record header). + * 매입 발급사별 통계 레코드. + * Request fields: T_BIZDATE + * Response fields: T_ISSUER(1위), T_COUNT, T_GROSS, T_ARG1(발급사 수), T_ARG2(점유율 bps) + */ +#ifndef ACQ_ISSSTAT_H +#define ACQ_ISSSTAT_H + +#include "ac_svc.h" + +typedef struct { + char biz_date[16]; + char top_issuer[32]; + long top_count; + long top_sum; + long issuer_kinds; + long share_bps; /* 1위 점유율 */ +} ac_issstat_rec_t; + +void ACQ_ISSSTAT(TPSVCINFO *p); + +#endif /* ACQ_ISSSTAT_H */ diff --git a/app/src/ac/svc/ACQ_ISSSTAT.pgc b/app/src/ac/svc/ACQ_ISSSTAT.pgc new file mode 100644 index 0000000..6f3ee90 --- /dev/null +++ b/app/src/ac/svc/ACQ_ISSSTAT.pgc @@ -0,0 +1,44 @@ +/* + * ACQ_ISSSTAT.pgc - ac 모듈 서비스 ACQ_ISSSTAT (one service = one file). + * 매입 발급사별 통계: 영업일 발급사 그룹 집계에서 1위 발급사(건수·총액)와 + * 전체 발급사 수, 1위의 점유율(bps)을 계산해 응답한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_ISSSTAT.h" + +void ACQ_ISSSTAT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long share_bps = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_topcnt = 0, h_topsum = 0, h_kinds = 0, h_total = 0; + char h_top[32], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_top[0] = 0; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + /* 전체 발급사 수 + 총액 */ + EXEC SQL SELECT count(DISTINCT issuer), coalesce(sum(amount), 0) + INTO :h_kinds, :h_total + FROM purchase WHERE biz_date = :h_bizdate AND status <> 'C'; + if (sqlca.sqlcode < 0) FAIL(b); + + /* 1위 발급사 */ + EXEC SQL SELECT issuer, count(*), sum(amount) + INTO :h_top, :h_topcnt, :h_topsum + FROM purchase WHERE biz_date = :h_bizdate AND status <> 'C' + GROUP BY issuer ORDER BY sum(amount) DESC, issuer LIMIT 1; + if (sqlca.sqlcode < 0) { userlog("ACQ_ISSSTAT FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + + if (h_total > 0) share_bps = (h_topsum * 10000) / h_total; + + Bchg(b, T_ISSUER, 0, h_top, 0L); + setl(b, T_COUNT, h_topcnt); + setl(b, T_GROSS, h_topsum); + setl(b, T_ARG1, h_kinds); + setl(b, T_ARG2, share_bps); /* 1위 점유율 bps */ + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_LIMITUSAGE.h b/app/src/ac/svc/ACQ_LIMITUSAGE.h new file mode 100644 index 0000000..d666a1f --- /dev/null +++ b/app/src/ac/svc/ACQ_LIMITUSAGE.h @@ -0,0 +1,23 @@ +/* + * ACQ_LIMITUSAGE.h - ac 서비스 ACQ_LIMITUSAGE 카피북 (copybook / record header). + * 가맹점 한도소진 조회 레코드. + * Request fields: T_MERCHANT, T_BIZDATE + * Response fields: T_AMT1(사용액), T_AMT2(한도), T_AMT3(잔여), T_ARG1(소진율 bps), T_RC + */ +#ifndef ACQ_LIMITUSAGE_H +#define ACQ_LIMITUSAGE_H + +#include "ac_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long used; /* 당일 사용액 */ + long limit; /* 일한도 */ + long remain; /* 잔여 한도 */ + long usage_bps; /* 소진율 (basis points) */ +} ac_limitusage_rec_t; + +void ACQ_LIMITUSAGE(TPSVCINFO *p); + +#endif /* ACQ_LIMITUSAGE_H */ diff --git a/app/src/ac/svc/ACQ_LIMITUSAGE.pgc b/app/src/ac/svc/ACQ_LIMITUSAGE.pgc new file mode 100644 index 0000000..3632591 --- /dev/null +++ b/app/src/ac/svc/ACQ_LIMITUSAGE.pgc @@ -0,0 +1,37 @@ +/* + * ACQ_LIMITUSAGE.pgc - ac 모듈 서비스 ACQ_LIMITUSAGE (one service = one file). + * 가맹점 한도소진 조회: 영업일 유효 매입(취소/무효 제외) 합계를 일한도와 + * 비교해 사용액/잔여액/소진율(bps)을 산출한다. 90% 초과 시 경보 표시. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_LIMITUSAGE.h" + +void ACQ_LIMITUSAGE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long pct_bps; + EXEC SQL BEGIN DECLARE SECTION; + long h_limit = 0, h_used = 0; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL SELECT daily_limit INTO :h_limit FROM merchant WHERE merchant_id = :h_merch; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 5); FAIL(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT coalesce(sum(amount), 0) INTO :h_used FROM purchase + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status NOT IN ('C', 'V'); + if (sqlca.sqlcode < 0) { userlog("ACQ_LIMITUSAGE FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + pct_bps = (h_limit > 0) ? (h_used * 10000) / h_limit : 10000; + + setl(b, T_AMT1, h_used); + setl(b, T_AMT2, h_limit); + setl(b, T_AMT3, h_limit > h_used ? h_limit - h_used : 0); /* 잔여 한도 */ + setl(b, T_ARG1, pct_bps); + setl(b, T_RC, pct_bps > 9000 ? 1 : 0); /* 90% 초과 경보 */ + OK(b); +} diff --git a/app/src/ac/svc/ACQ_MONSTAT.h b/app/src/ac/svc/ACQ_MONSTAT.h new file mode 100644 index 0000000..7277974 --- /dev/null +++ b/app/src/ac/svc/ACQ_MONSTAT.h @@ -0,0 +1,22 @@ +/* + * ACQ_MONSTAT.h - ac 서비스 ACQ_MONSTAT 카피북 (copybook / record header). + * 매입 월간 통계 레코드. + * Request fields: T_BIZDATE(월 기준일) + * Response fields: T_COUNT, T_GROSS, T_AMT1(평균), T_ARG1(영업일수), T_RC + */ +#ifndef ACQ_MONSTAT_H +#define ACQ_MONSTAT_H + +#include "ac_svc.h" + +typedef struct { + char base_date[16]; + long mon_count; + long mon_gross; + long mon_avg; /* 평균 거래액 */ + long biz_days; /* 거래 발생 영업일수 */ +} ac_monstat_rec_t; + +void ACQ_MONSTAT(TPSVCINFO *p); + +#endif /* ACQ_MONSTAT_H */ diff --git a/app/src/ac/svc/ACQ_MONSTAT.pgc b/app/src/ac/svc/ACQ_MONSTAT.pgc new file mode 100644 index 0000000..72ccef1 --- /dev/null +++ b/app/src/ac/svc/ACQ_MONSTAT.pgc @@ -0,0 +1,35 @@ +/* + * ACQ_MONSTAT.pgc - ac 모듈 서비스 ACQ_MONSTAT (one service = one file). + * 매입 월간 통계: 기준일이 속한 달 전체의 건수/총액/평균 거래액과 영업일수 + * (거래 발생일 기준)를 date_trunc 월 경계로 집계한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_MONSTAT.h" + +void ACQ_MONSTAT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0, h_amt = 0, h_avg = 0, h_days = 0; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL SELECT count(*), + coalesce(sum(amount), 0), + coalesce(avg(amount)::bigint, 0), + count(DISTINCT biz_date) + INTO :h_cnt, :h_amt, :h_avg, :h_days + FROM purchase + WHERE date_trunc('month', biz_date) = date_trunc('month', CAST(:h_bizdate AS date)) + AND status NOT IN ('C', 'V'); + if (sqlca.sqlcode < 0) { userlog("ACQ_MONSTAT FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_amt); + setl(b, T_AMT1, h_avg); /* 평균 거래액 */ + setl(b, T_ARG1, h_days); /* 거래 발생 영업일수 */ + setl(b, T_RC, h_cnt > 0 ? 0 : 1); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_NETCALC.h b/app/src/ac/svc/ACQ_NETCALC.h new file mode 100644 index 0000000..e051cd4 --- /dev/null +++ b/app/src/ac/svc/ACQ_NETCALC.h @@ -0,0 +1,21 @@ +/* + * ACQ_NETCALC.h - ac 서비스 ACQ_NETCALC 카피북 (copybook / record header). + * 정산금 재계산 레코드. + * Request fields: T_PURCHASE_ID + * Response fields: T_NET(재계산 정산금), T_TAX(원천세 누계), T_DELTA, T_RC + */ +#ifndef ACQ_NETCALC_H +#define ACQ_NETCALC_H + +#include "ac_svc.h" + +typedef struct { + long purchase_id; + long new_net; /* amount - fee - 원천세 */ + long wht_sum; + long delta; /* 기존 net 대비 변경분 */ +} ac_netcalc_rec_t; + +void ACQ_NETCALC(TPSVCINFO *p); + +#endif /* ACQ_NETCALC_H */ diff --git a/app/src/ac/svc/ACQ_NETCALC.pgc b/app/src/ac/svc/ACQ_NETCALC.pgc new file mode 100644 index 0000000..dba08e7 --- /dev/null +++ b/app/src/ac/svc/ACQ_NETCALC.pgc @@ -0,0 +1,46 @@ +/* + * ACQ_NETCALC.pgc - ac 모듈 서비스 ACQ_NETCALC (one service = one file). + * 정산금 재계산: 매입금액에서 수수료와 원천세 누계를 차감한 실정산금을 + * 다시 구해 purchase.net 을 보정한다. 변경 없으면 T_RC=1. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_NETCALC.h" + +void ACQ_NETCALC(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long newnet; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt = 0, h_fee = 0, h_oldnet = 0, h_wht = 0, h_newnet; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + + EXEC SQL SELECT amount, fee, net INTO :h_amt, :h_fee, :h_oldnet + FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 5); FAIL(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + /* 원천세 누계 반영 */ + EXEC SQL SELECT coalesce(sum(tax_amount), 0) INTO :h_wht + FROM ac_wht WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + + newnet = h_amt - h_fee - h_wht; + if (newnet == h_oldnet) { + setl(b, T_NET, newnet); + setl(b, T_RC, 1); /* 이미 정합 */ + OK(b); + } + + h_newnet = newnet; + EXEC SQL UPDATE purchase SET net = :h_newnet, updated_at = now() + WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) { userlog("ACQ_NETCALC FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_NET, newnet); + setl(b, T_TAX, h_wht); + setl(b, T_DELTA, newnet - h_oldnet); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_NOINTSHARE.h b/app/src/ac/svc/ACQ_NOINTSHARE.h new file mode 100644 index 0000000..68d94ba --- /dev/null +++ b/app/src/ac/svc/ACQ_NOINTSHARE.h @@ -0,0 +1,23 @@ +/* + * ACQ_NOINTSHARE.h - ac 서비스 ACQ_NOINTSHARE 카피북 (copybook / record header). + * 무이자 할부 분담금 레코드. + * Request fields: T_PURCHASE_ID, T_BIZDATE + * Response fields: T_AMT1(분담금), T_INSTALL_N, T_ISSUER, T_ADJ, T_RC + */ +#ifndef ACQ_NOINTSHARE_H +#define ACQ_NOINTSHARE_H + +#include "ac_svc.h" + +typedef struct { + long purchase_id; + char biz_date[16]; + long share; /* 발급사 분담금 (months*15bps) */ + long months; /* 할부 개월수 */ + char issuer[32]; + long adj_id; +} ac_nointshare_rec_t; + +void ACQ_NOINTSHARE(TPSVCINFO *p); + +#endif /* ACQ_NOINTSHARE_H */ diff --git a/app/src/ac/svc/ACQ_NOINTSHARE.pgc b/app/src/ac/svc/ACQ_NOINTSHARE.pgc new file mode 100644 index 0000000..1cbd854 --- /dev/null +++ b/app/src/ac/svc/ACQ_NOINTSHARE.pgc @@ -0,0 +1,44 @@ +/* + * ACQ_NOINTSHARE.pgc - ac 모듈 서비스 ACQ_NOINTSHARE (one service = one file). + * 무이자 할부 분담금 산정: 할부 매입의 개월수에 월당 15bps 를 곱한 발급사 + * 분담률로 분담금을 계산해 수수료 조정(NOINT)으로 반영한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_NOINTSHARE.h" + +#define AC_NOINT_BPS_PER_MONTH 15L /* 발급사 분담률: 월당 15bps */ + +void ACQ_NOINTSHARE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long share, adj; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_months = 0, h_amt = 0; + char h_issuer[32]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + + /* 할부 스케줄에서 개월수 확인 (스케줄 없으면 무이자 대상 아님) */ + EXEC SQL SELECT coalesce(max(months), 0) INTO :h_months + FROM ac_install WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_months < 2) { setl(b, T_RC, 4); FAIL(b); } + + EXEC SQL SELECT amount, issuer INTO :h_amt, :h_issuer + FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + + share = acq_fee(h_amt, h_months * AC_NOINT_BPS_PER_MONTH); + adj = acdb_insert_fee_adj(h_pid, -share, "NOINT", bizdate); + if (adj < 0) { userlog("ACQ_NOINTSHARE adj FAIL pid=%ld", h_pid); FAIL(b); } + + Bchg(b, T_ISSUER, 0, h_issuer, 0L); + setl(b, T_AMT1, share); /* 발급사 분담금 */ + setl(b, T_INSTALL_N, h_months); + setl(b, T_ADJ, adj); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_PARTCXL.h b/app/src/ac/svc/ACQ_PARTCXL.h new file mode 100644 index 0000000..0d1a4d3 --- /dev/null +++ b/app/src/ac/svc/ACQ_PARTCXL.h @@ -0,0 +1,23 @@ +/* + * ACQ_PARTCXL.h - ac 서비스 ACQ_PARTCXL 카피북 (copybook / record header). + * 부분취소 레코드. + * Request fields: T_PURCHASE_ID, T_AMOUNT(취소액), T_BIZDATE + * Response fields: T_CANCEL_ID, T_AMT1(잔여금액), T_FEE(환급 수수료), T_RC + */ +#ifndef ACQ_PARTCXL_H +#define ACQ_PARTCXL_H + +#include "ac_svc.h" + +typedef struct { + long purchase_id; + long cancel_amount; /* 부분취소액 */ + char biz_date[16]; + long cancel_id; + long remain_amount; /* 잔여 매입금액 */ + long fee_back; /* 비례 환급 수수료 */ +} ac_partcxl_rec_t; + +void ACQ_PARTCXL(TPSVCINFO *p); + +#endif /* ACQ_PARTCXL_H */ diff --git a/app/src/ac/svc/ACQ_PARTCXL.pgc b/app/src/ac/svc/ACQ_PARTCXL.pgc new file mode 100644 index 0000000..0fcbfd5 --- /dev/null +++ b/app/src/ac/svc/ACQ_PARTCXL.pgc @@ -0,0 +1,51 @@ +/* + * ACQ_PARTCXL.pgc - ac 모듈 서비스 ACQ_PARTCXL (one service = one file). + * 부분취소: 취소액이 잔여 매입금액 미만인 경우에만 허용. 수수료를 비례 + * 배분해 환급하고 purchase 잔액(amount/fee/net)을 차감, PARTIAL 취소행 생성. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_PARTCXL.h" + +void ACQ_PARTCXL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long feeback; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid, h_pid, h_cxl, h_amt = 0, h_fee = 0, h_fb; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + h_cxl = getl(b, T_AMOUNT); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL SELECT amount, fee INTO :h_amt, :h_fee FROM purchase + WHERE purchase_id = :h_pid AND status IN ('A', 'M'); + if (sqlca.sqlcode == 100) { setl(b, T_RC, 5); FAIL(b); } + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cxl <= 0 || h_cxl >= h_amt) { setl(b, T_RC, 3); FAIL(b); } + + /* 수수료 비례 환급분: fee * 취소액 / 원금액 */ + feeback = (h_fee * h_cxl) / h_amt; + h_fb = feeback; + + EXEC SQL SELECT nextval('ac_cancel_seq') INTO :h_cid; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO ac_cancel (cancel_id, purchase_id, cancel_type, amount, reason, biz_date) + VALUES (:h_cid, :h_pid, 'PARTIAL', :h_cxl, 'PARTCXL', :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL UPDATE purchase + SET amount = amount - :h_cxl, + fee = fee - :h_fb, + net = net - (:h_cxl - :h_fb), + updated_at = now() + WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) { userlog("ACQ_PARTCXL FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_CANCEL_ID, h_cid); + setl(b, T_AMT1, h_amt - h_cxl); /* 잔여 매입금액 */ + setl(b, T_FEE, feeback); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_PARTRFND.h b/app/src/ac/svc/ACQ_PARTRFND.h new file mode 100644 index 0000000..128ec6c --- /dev/null +++ b/app/src/ac/svc/ACQ_PARTRFND.h @@ -0,0 +1,22 @@ +/* + * ACQ_PARTRFND.h - ac 서비스 ACQ_PARTRFND 카피북 (copybook / record header). + * 부분환불 레코드. + * Request fields: T_PURCHASE_ID, T_AMOUNT(환불 요청액), T_BIZDATE + * Response fields: T_CANCEL_ID, T_AMT2(환불 누계), T_RC(6=한도초과) + */ +#ifndef ACQ_PARTRFND_H +#define ACQ_PARTRFND_H + +#include "ac_svc.h" + +typedef struct { + long purchase_id; + long refund_amount; /* 이번 환불 요청액 */ + char biz_date[16]; + long cancel_id; + long refund_accum; /* 환불 누계 */ +} ac_partrfnd_rec_t; + +void ACQ_PARTRFND(TPSVCINFO *p); + +#endif /* ACQ_PARTRFND_H */ diff --git a/app/src/ac/svc/ACQ_PARTRFND.pgc b/app/src/ac/svc/ACQ_PARTRFND.pgc new file mode 100644 index 0000000..947658c --- /dev/null +++ b/app/src/ac/svc/ACQ_PARTRFND.pgc @@ -0,0 +1,44 @@ +/* + * ACQ_PARTRFND.pgc - ac 모듈 서비스 ACQ_PARTRFND (one service = one file). + * 부분환불: 기존 환불 누계 + 요청액이 매입금액을 넘지 않도록 검증하고 + * REFUND 취소행과 음수 원장 분개(PARTRFND)를 함께 기록한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_PARTRFND.h" + +void ACQ_PARTRFND(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid, h_pid, h_req, h_orig = 0, h_prev = 0; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + h_req = getl(b, T_AMOUNT); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT amount INTO :h_orig FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + + /* 환불 누계 검증: 이미 환불된 합계 + 이번 요청액 <= 매입금액 */ + EXEC SQL SELECT coalesce(sum(amount), 0) INTO :h_prev FROM ac_cancel + WHERE purchase_id = :h_pid AND cancel_type = 'REFUND'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_req <= 0 || h_prev + h_req > h_orig) { setl(b, T_RC, 6); FAIL(b); } + + EXEC SQL SELECT nextval('ac_cancel_seq') INTO :h_cid; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO ac_cancel (cancel_id, purchase_id, cancel_type, amount, reason, biz_date) + VALUES (:h_cid, :h_pid, 'REFUND', :h_req, 'PARTRFND', :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + + if (acdb_insert_ledger(h_pid, "PARTRFND", -h_req, bizdate) < 0) FAIL(b); + + setl(b, T_CANCEL_ID, h_cid); + setl(b, T_AMT2, h_prev + h_req); /* 환불 누계 */ + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_PROMO.h b/app/src/ac/svc/ACQ_PROMO.h new file mode 100644 index 0000000..0cd96ad --- /dev/null +++ b/app/src/ac/svc/ACQ_PROMO.h @@ -0,0 +1,22 @@ +/* + * ACQ_PROMO.h - ac 서비스 ACQ_PROMO 카피북 (copybook / record header). + * 프로모션 매입 등록 레코드. + * Request fields: T_MERCHANT, T_AMOUNT, T_STR1(행사코드 PRM-*), T_BIZDATE + * Response fields: T_PURCHASE_ID, T_NET(전액), T_RC(7=행사코드 오류) + */ +#ifndef ACQ_PROMO_H +#define ACQ_PROMO_H + +#include "ac_svc.h" + +typedef struct { + char merchant_id[64]; + long amount; + char promo_code[32]; /* PRM- 접두 행사코드 */ + char biz_date[16]; + long purchase_id; +} ac_promo_rec_t; + +void ACQ_PROMO(TPSVCINFO *p); + +#endif /* ACQ_PROMO_H */ diff --git a/app/src/ac/svc/ACQ_PROMO.pgc b/app/src/ac/svc/ACQ_PROMO.pgc new file mode 100644 index 0000000..67e8e51 --- /dev/null +++ b/app/src/ac/svc/ACQ_PROMO.pgc @@ -0,0 +1,42 @@ +/* + * ACQ_PROMO.pgc - ac 모듈 서비스 ACQ_PROMO (one service = one file). + * 프로모션 매입 등록: 행사 코드가 유효하면 수수료 0 의 프로모션 매입을 + * 생성한다(분류 PROMO). 정산금 전액 지급, 원장에 행사 분개를 남긴다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_PROMO.h" + +void ACQ_PROMO(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char promo[32], bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_amt = getl(b, T_AMOUNT); + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + gets_(b, T_STR1, promo, sizeof(promo)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + /* 행사 코드 형식 검증: PRM- 접두 필수 */ + if (strncmp(promo, "PRM-", 4) != 0) { setl(b, T_RC, 7); FAIL(b); } + if (h_amt <= 0) { setl(b, T_RC, 3); FAIL(b); } + + EXEC SQL SELECT nextval('purchase_seq') INTO :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + + /* 수수료 면제: fee 0, net 전액, 분류 PROMO */ + EXEC SQL INSERT INTO purchase (purchase_id, merchant_id, amount, fee, net, status, biz_date, channel, category) + VALUES (:h_pid, :h_merch, :h_amt, 0, :h_amt, 'A', :h_bizdate, 'POS', 'PROMO'); + if (sqlca.sqlcode < 0) { userlog("ACQ_PROMO FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + if (acdb_insert_ledger(h_pid, "PROMO", h_amt, bizdate) < 0) FAIL(b); + + setl(b, T_PURCHASE_ID, h_pid); + setl(b, T_NET, h_amt); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_REBUY.h b/app/src/ac/svc/ACQ_REBUY.h new file mode 100644 index 0000000..d761146 --- /dev/null +++ b/app/src/ac/svc/ACQ_REBUY.h @@ -0,0 +1,22 @@ +/* + * ACQ_REBUY.h - ac 서비스 ACQ_REBUY 카피북 (copybook / record header). + * 재매입 생성 레코드. + * Request fields: T_PURCHASE_ID(취소된 원매입), T_BIZDATE + * Response fields: T_ID1(신규 매입 ID), T_FEE, T_NET + */ +#ifndef ACQ_REBUY_H +#define ACQ_REBUY_H + +#include "ac_svc.h" + +typedef struct { + long orig_purchase_id; /* 취소 원매입 */ + char biz_date[16]; + long new_purchase_id; /* 재매입 신규 ID */ + long fee; /* 현행 MDR 재산출 수수료 */ + long net; +} ac_rebuy_rec_t; + +void ACQ_REBUY(TPSVCINFO *p); + +#endif /* ACQ_REBUY_H */ diff --git a/app/src/ac/svc/ACQ_REBUY.pgc b/app/src/ac/svc/ACQ_REBUY.pgc new file mode 100644 index 0000000..e395e0d --- /dev/null +++ b/app/src/ac/svc/ACQ_REBUY.pgc @@ -0,0 +1,42 @@ +/* + * ACQ_REBUY.pgc - ac 모듈 서비스 ACQ_REBUY (one service = one file). + * 재매입: 취소(C)된 원매입의 가맹점/금액/채널을 승계해 신규 매입을 생성하고 + * 현재 MDR 로 수수료를 재산출, 원장에 REBUY 연결 분개를 남긴다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_REBUY.h" + +void ACQ_REBUY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long newpid, fee, net, bps = 0; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt = 0; + char h_merch[64], h_chan[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + + /* 취소 상태의 원매입만 재매입 대상 */ + EXEC SQL SELECT merchant_id, amount, channel INTO :h_merch, :h_amt, :h_chan + FROM purchase WHERE purchase_id = :h_pid AND status = 'C'; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 5); FAIL(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + if (acdb_merchant_mdr(h_merch, &bps) < 0) FAIL(b); + fee = acq_fee(h_amt, bps); + net = h_amt - fee; + + newpid = acdb_next_purchase_id(); + if (newpid < 0) FAIL(b); + if (acdb_insert_purchase(newpid, h_merch, h_amt, fee, net, "A", h_chan, bizdate) < 0) FAIL(b); + /* 원매입 → 신매입 연결 분개 */ + if (acdb_insert_ledger(h_pid, "REBUY", h_amt, bizdate) < 0) FAIL(b); + + setl(b, T_ID1, newpid); + setl(b, T_FEE, fee); + setl(b, T_NET, net); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_REMATCH.h b/app/src/ac/svc/ACQ_REMATCH.h new file mode 100644 index 0000000..3f4c2ba --- /dev/null +++ b/app/src/ac/svc/ACQ_REMATCH.h @@ -0,0 +1,20 @@ +/* + * ACQ_REMATCH.h - ac 서비스 ACQ_REMATCH 카피북 (copybook / record header). + * 승인미매칭 재처리 레코드. + * Request fields: T_PURCHASE_ID + * Response fields: T_ARG1(해소 미매입 수), T_ARG2(전이 건수), T_MSG, T_RC + */ +#ifndef ACQ_REMATCH_H +#define ACQ_REMATCH_H + +#include "ac_svc.h" + +typedef struct { + long purchase_id; + long resolved; /* 해소된 미매입 행 수 */ + long promoted; /* A→M 전이 건수 */ +} ac_rematch_rec_t; + +void ACQ_REMATCH(TPSVCINFO *p); + +#endif /* ACQ_REMATCH_H */ diff --git a/app/src/ac/svc/ACQ_REMATCH.pgc b/app/src/ac/svc/ACQ_REMATCH.pgc new file mode 100644 index 0000000..7ad3a4a --- /dev/null +++ b/app/src/ac/svc/ACQ_REMATCH.pgc @@ -0,0 +1,43 @@ +/* + * ACQ_REMATCH.pgc - ac 모듈 서비스 ACQ_REMATCH (one service = one file). + * 승인미매칭 재처리: 미매입 원장에 남은 건에 유효 승인이 도착했는지 다시 + * 확인하고, 승인이 있으면 미매입 행을 걷어내며 매입을 대사완료로 올린다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_REMATCH.h" + +void ACQ_REMATCH(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_appr = 0, h_gone, h_up; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + + /* 유효 승인 존재 여부 재확인 */ + EXEC SQL SELECT count(*) INTO :h_appr FROM approval + WHERE purchase_id = :h_pid AND approved = true; + if (sqlca.sqlcode < 0) FAIL(b); + + if (h_appr == 0) { + Bchg(b, T_MSG, 0, "NO-APPROVAL", 0L); + setl(b, T_RC, 1); + OK(b); /* 승인 미도착: 미매입 유지 */ + } + + EXEC SQL DELETE FROM ac_unmatch + WHERE purchase_id = :h_pid AND reason NOT LIKE 'CLAIM:%'; + if (sqlca.sqlcode < 0) FAIL(b); + h_gone = sqlca.sqlerrd[2]; + + EXEC SQL UPDATE purchase SET status = 'M', updated_at = now() + WHERE purchase_id = :h_pid AND status = 'A'; + if (sqlca.sqlcode < 0) { userlog("ACQ_REMATCH FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + h_up = sqlca.sqlerrd[2]; + + setl(b, T_ARG1, h_gone); /* 해소된 미매입 행 수 */ + setl(b, T_ARG2, h_up); /* 상태 전이 건수 */ + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_RESERVE.h b/app/src/ac/svc/ACQ_RESERVE.h new file mode 100644 index 0000000..b0e3e6a --- /dev/null +++ b/app/src/ac/svc/ACQ_RESERVE.h @@ -0,0 +1,22 @@ +/* + * ACQ_RESERVE.h - ac 서비스 ACQ_RESERVE 카피북 (copybook / record header). + * 매입 예약 레코드. + * Request fields: T_MERCHANT, T_AMOUNT, T_BIZDATE(예약일) + * Response fields: T_PURCHASE_ID(예약 ID), T_FEE, T_RC(6=비활성 가맹점) + */ +#ifndef ACQ_RESERVE_H +#define ACQ_RESERVE_H + +#include "ac_svc.h" + +typedef struct { + char merchant_id[64]; + long amount; + char rsv_date[16]; /* 예약 영업일 */ + long purchase_id; /* 예약 매입 ID (status P) */ + long fee; /* 선산출 수수료 */ +} ac_reserve_rec_t; + +void ACQ_RESERVE(TPSVCINFO *p); + +#endif /* ACQ_RESERVE_H */ diff --git a/app/src/ac/svc/ACQ_RESERVE.pgc b/app/src/ac/svc/ACQ_RESERVE.pgc new file mode 100644 index 0000000..3a35bd9 --- /dev/null +++ b/app/src/ac/svc/ACQ_RESERVE.pgc @@ -0,0 +1,41 @@ +/* + * ACQ_RESERVE.pgc - ac 모듈 서비스 ACQ_RESERVE (one service = one file). + * 매입 예약: 활성(ACTIVE) 가맹점에 대해 미래 영업일 예약 매입(status P)을 + * 만들어 둔다. 수수료는 현행 MDR 로 선산출하고 예약 채널(RSV)로 표시. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_RESERVE.h" + +void ACQ_RESERVE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt, h_fee, h_net, h_bps = 0; + char h_merch[64], h_mst[16], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_amt = getl(b, T_AMOUNT); + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_amt <= 0) { setl(b, T_RC, 3); FAIL(b); } + + /* 활성 가맹점만 예약 허용 + MDR 동시 조회 */ + EXEC SQL SELECT status, mdr_bps INTO :h_mst, :h_bps FROM merchant WHERE merchant_id = :h_merch; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 5); FAIL(b); } + if (sqlca.sqlcode < 0) FAIL(b); + if (strcmp(h_mst, "ACTIVE") != 0) { setl(b, T_RC, 6); FAIL(b); } + + h_fee = acq_fee(h_amt, h_bps); + h_net = h_amt - h_fee; + + EXEC SQL SELECT nextval('purchase_seq') INTO :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO purchase (purchase_id, merchant_id, amount, fee, net, status, biz_date, channel) + VALUES (:h_pid, :h_merch, :h_amt, :h_fee, :h_net, 'P', :h_bizdate, 'RSV'); + if (sqlca.sqlcode < 0) { userlog("ACQ_RESERVE FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_PURCHASE_ID, h_pid); + setl(b, T_FEE, h_fee); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_RESERVECXL.h b/app/src/ac/svc/ACQ_RESERVECXL.h new file mode 100644 index 0000000..a9a1963 --- /dev/null +++ b/app/src/ac/svc/ACQ_RESERVECXL.h @@ -0,0 +1,19 @@ +/* + * ACQ_RESERVECXL.h - ac 서비스 ACQ_RESERVECXL 카피북 (copybook / record header). + * 매입 예약취소 레코드. + * Request fields: T_PURCHASE_ID(예약 ID) + * Response fields: T_AMOUNT(취소된 예약 금액), T_RC(4=예약 아님) + */ +#ifndef ACQ_RESERVECXL_H +#define ACQ_RESERVECXL_H + +#include "ac_svc.h" + +typedef struct { + long purchase_id; /* 예약(P) 매입 ID */ + long amount; /* 취소된 예약 금액 */ +} ac_reservecxl_rec_t; + +void ACQ_RESERVECXL(TPSVCINFO *p); + +#endif /* ACQ_RESERVECXL_H */ diff --git a/app/src/ac/svc/ACQ_RESERVECXL.pgc b/app/src/ac/svc/ACQ_RESERVECXL.pgc new file mode 100644 index 0000000..30c6e29 --- /dev/null +++ b/app/src/ac/svc/ACQ_RESERVECXL.pgc @@ -0,0 +1,31 @@ +/* + * ACQ_RESERVECXL.pgc - ac 모듈 서비스 ACQ_RESERVECXL (one service = one file). + * 매입 예약취소: 아직 접수 전환되지 않은 예약(P) 건만 물리 삭제한다. + * 취소 전 예약 금액을 확보해 응답에 돌려주고 감사 로그를 남긴다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_RESERVECXL.h" + +void ACQ_RESERVECXL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt = 0, h_del; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + + EXEC SQL SELECT amount INTO :h_amt FROM purchase + WHERE purchase_id = :h_pid AND status = 'P'; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 4); FAIL(b); } /* 예약 아님/전환됨 */ + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL DELETE FROM purchase WHERE purchase_id = :h_pid AND status = 'P'; + if (sqlca.sqlcode < 0) { userlog("ACQ_RESERVECXL FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + h_del = sqlca.sqlerrd[2]; + + userlog("ACQ_RESERVECXL: pid=%ld amount=%ld deleted=%ld", h_pid, h_amt, h_del); + setl(b, T_AMOUNT, h_amt); + setl(b, T_RC, h_del > 0 ? 0 : 1); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_RESERVEQRY.h b/app/src/ac/svc/ACQ_RESERVEQRY.h new file mode 100644 index 0000000..b34d232 --- /dev/null +++ b/app/src/ac/svc/ACQ_RESERVEQRY.h @@ -0,0 +1,21 @@ +/* + * ACQ_RESERVEQRY.h - ac 서비스 ACQ_RESERVEQRY 카피북 (copybook / record header). + * 매입 예약 조회 레코드. + * Request fields: T_MERCHANT + * Response fields: T_COUNT, T_GROSS, T_BIZDATE(최근접 예약일), T_RC + */ +#ifndef ACQ_RESERVEQRY_H +#define ACQ_RESERVEQRY_H + +#include "ac_svc.h" + +typedef struct { + char merchant_id[64]; + long rsv_count; + long rsv_total; + char first_date[16]; /* 가장 빠른 예약일 */ +} ac_reserveqry_rec_t; + +void ACQ_RESERVEQRY(TPSVCINFO *p); + +#endif /* ACQ_RESERVEQRY_H */ diff --git a/app/src/ac/svc/ACQ_RESERVEQRY.pgc b/app/src/ac/svc/ACQ_RESERVEQRY.pgc new file mode 100644 index 0000000..221b55e --- /dev/null +++ b/app/src/ac/svc/ACQ_RESERVEQRY.pgc @@ -0,0 +1,42 @@ +/* + * ACQ_RESERVEQRY.pgc - ac 모듈 서비스 ACQ_RESERVEQRY (one service = one file). + * 매입 예약 조회: 가맹점의 대기 중 예약(P)을 예약일 순 커서로 훑어 건수와 + * 총액, 가장 빠른 예약일을 알려준다. 예약이 없으면 T_RC=1. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_RESERVEQRY.h" + +void ACQ_RESERVEQRY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long cnt = 0, total = 0; + char first_date[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_amt; + char h_merch[64], h_date[16]; + EXEC SQL END DECLARE SECTION; + + first_date[0] = 0; + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + + EXEC SQL DECLARE rvq_cur CURSOR FOR + SELECT amount, to_char(biz_date, 'YYYY-MM-DD') FROM purchase + WHERE merchant_id = :h_merch AND status = 'P' + ORDER BY biz_date, purchase_id; + EXEC SQL OPEN rvq_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH rvq_cur INTO :h_amt, :h_date; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE rvq_cur; FAIL(b); } + if (cnt == 0) { strncpy(first_date, h_date, sizeof(first_date)-1); first_date[sizeof(first_date)-1] = 0; } + cnt++; total += h_amt; + } + EXEC SQL CLOSE rvq_cur; + + if (first_date[0]) Bchg(b, T_BIZDATE, 0, first_date, 0L); + setl(b, T_COUNT, cnt); + setl(b, T_GROSS, total); + setl(b, T_RC, cnt > 0 ? 0 : 1); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_RETURN.h b/app/src/ac/svc/ACQ_RETURN.h new file mode 100644 index 0000000..aff971b --- /dev/null +++ b/app/src/ac/svc/ACQ_RETURN.h @@ -0,0 +1,22 @@ +/* + * ACQ_RETURN.h - ac 서비스 ACQ_RETURN 카피북 (copybook / record header). + * 매입 반송 레코드. + * Request fields: T_PURCHASE_ID, T_REASON, T_BIZDATE + * Response fields: T_CANCEL_ID, T_AMOUNT(반송액), T_RC + */ +#ifndef ACQ_RETURN_H +#define ACQ_RETURN_H + +#include "ac_svc.h" + +typedef struct { + long purchase_id; + char reason[110]; /* 반송 사유 */ + char biz_date[16]; + long cancel_id; + long amount; /* 전액 반송 금액 */ +} ac_return_rec_t; + +void ACQ_RETURN(TPSVCINFO *p); + +#endif /* ACQ_RETURN_H */ diff --git a/app/src/ac/svc/ACQ_RETURN.pgc b/app/src/ac/svc/ACQ_RETURN.pgc new file mode 100644 index 0000000..b3381c5 --- /dev/null +++ b/app/src/ac/svc/ACQ_RETURN.pgc @@ -0,0 +1,45 @@ +/* + * ACQ_RETURN.pgc - ac 모듈 서비스 ACQ_RETURN (one service = one file). + * 매입 반송: 접수(A) 상태의 매입을 전액 반송 처리한다. 상태를 취소(C)로 + * 전이하고 RETURN 유형의 취소행에 반송 사유를 기록한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_RETURN.h" + +void ACQ_RETURN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char why[110]; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_cid, h_amt = 0, h_aff; + char h_why[128], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_REASON, why, sizeof(why)); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + snprintf(h_why, sizeof(h_why), "RETURN:%s", why[0] ? why : "FORMAT"); + + EXEC SQL SELECT amount INTO :h_amt FROM purchase + WHERE purchase_id = :h_pid AND status = 'A'; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 5); FAIL(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + /* 접수 상태에서만 반송 전이 허용 */ + EXEC SQL UPDATE purchase SET status = 'C', updated_at = now() + WHERE purchase_id = :h_pid AND status = 'A'; + if (sqlca.sqlcode < 0) FAIL(b); + h_aff = sqlca.sqlerrd[2]; + if (h_aff == 0) { setl(b, T_RC, 1); FAIL(b); } + + EXEC SQL SELECT nextval('ac_cancel_seq') INTO :h_cid; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO ac_cancel (cancel_id, purchase_id, cancel_type, amount, reason, biz_date) + VALUES (:h_cid, :h_pid, 'RETURN', :h_amt, :h_why, :h_bizdate); + if (sqlca.sqlcode < 0) { userlog("ACQ_RETURN FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_CANCEL_ID, h_cid); + setl(b, T_AMOUNT, h_amt); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_SETTLEDUE.h b/app/src/ac/svc/ACQ_SETTLEDUE.h new file mode 100644 index 0000000..00ab670 --- /dev/null +++ b/app/src/ac/svc/ACQ_SETTLEDUE.h @@ -0,0 +1,22 @@ +/* + * ACQ_SETTLEDUE.h - ac 서비스 ACQ_SETTLEDUE 카피북 (copybook / record header). + * 정산예정 조회 레코드. + * Request fields: T_MERCHANT, T_BIZDATE + * Response fields: T_COUNT, T_NET(정산예정액), T_STR1(정산예정일), T_RC + */ +#ifndef ACQ_SETTLEDUE_H +#define ACQ_SETTLEDUE_H + +#include "ac_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long due_count; + long due_net; /* 정산예정액 */ + char due_date[16]; /* 다음 영업일 */ +} ac_settledue_rec_t; + +void ACQ_SETTLEDUE(TPSVCINFO *p); + +#endif /* ACQ_SETTLEDUE_H */ diff --git a/app/src/ac/svc/ACQ_SETTLEDUE.pgc b/app/src/ac/svc/ACQ_SETTLEDUE.pgc new file mode 100644 index 0000000..5e377bb --- /dev/null +++ b/app/src/ac/svc/ACQ_SETTLEDUE.pgc @@ -0,0 +1,36 @@ +/* + * ACQ_SETTLEDUE.pgc - ac 모듈 서비스 ACQ_SETTLEDUE (one service = one file). + * 정산예정 조회: 대사완료(M) 상태이면서 아직 정산행이 만들어지지 않은 + * 매입의 건수와 정산예정액을 NOT EXISTS 부질의로 집계한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_SETTLEDUE.h" + +void ACQ_SETTLEDUE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char nextday[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0, h_due = 0; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL SELECT count(*), coalesce(sum(u.net), 0) + INTO :h_cnt, :h_due + FROM purchase u + WHERE u.merchant_id = :h_merch AND u.biz_date = :h_bizdate AND u.status = 'M' + AND NOT EXISTS (SELECT 1 FROM settlement s WHERE s.purchase_id = u.purchase_id); + if (sqlca.sqlcode < 0) { userlog("ACQ_SETTLEDUE FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + /* 정산예정일 = 다음 영업일 (주말 건너뜀) */ + if (acq_next_bizday(h_bizdate, nextday) == 0) + Bchg(b, T_STR1, 0, nextday, 0L); + + setl(b, T_COUNT, h_cnt); + setl(b, T_NET, h_due); + setl(b, T_RC, h_cnt > 0 ? 0 : 1); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_SPLITPAY.h b/app/src/ac/svc/ACQ_SPLITPAY.h new file mode 100644 index 0000000..f2b36a4 --- /dev/null +++ b/app/src/ac/svc/ACQ_SPLITPAY.h @@ -0,0 +1,22 @@ +/* + * ACQ_SPLITPAY.h - ac 서비스 ACQ_SPLITPAY 카피북 (copybook / record header). + * 분할납부 등록 레코드. + * Request fields: T_PURCHASE_ID, T_INSTALL_N(회차수 2..24), T_BIZDATE + * Response fields: T_COUNT, T_AMT1(회차금액), T_AMT2(마지막 회차), T_RC + */ +#ifndef ACQ_SPLITPAY_H +#define ACQ_SPLITPAY_H + +#include "ac_svc.h" + +typedef struct { + long purchase_id; + long split_n; /* 분할 회차수 */ + char biz_date[16]; + long per_amount; /* 회차 균등금액 */ + long last_amount; /* 잔액 가산된 마지막 회차 */ +} ac_splitpay_rec_t; + +void ACQ_SPLITPAY(TPSVCINFO *p); + +#endif /* ACQ_SPLITPAY_H */ diff --git a/app/src/ac/svc/ACQ_SPLITPAY.pgc b/app/src/ac/svc/ACQ_SPLITPAY.pgc new file mode 100644 index 0000000..73fe24b --- /dev/null +++ b/app/src/ac/svc/ACQ_SPLITPAY.pgc @@ -0,0 +1,43 @@ +/* + * ACQ_SPLITPAY.pgc - ac 모듈 서비스 ACQ_SPLITPAY (one service = one file). + * 매입 분할납부 등록: 매입금액을 요청 회차수로 균등 분할(잔액은 마지막 + * 회차에 가산)해 ac_install 에 회차별 스케줄 행을 루프 삽입한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_SPLITPAY.h" + +void ACQ_SPLITPAY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long n, per, rest, i; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt = 0, h_seq, h_n, h_m; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + n = getl(b, T_INSTALL_N); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (n < 2 || n > 24) { setl(b, T_RC, 7); FAIL(b); } + + EXEC SQL SELECT amount INTO :h_amt FROM purchase WHERE purchase_id = :h_pid AND status <> 'C'; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + + per = h_amt / n; + rest = h_amt - per * n; /* 마지막 회차 가산 잔액 */ + h_n = n; + + for (i = 1; i <= n; i++) { + h_seq = i; + h_m = (i == n) ? per + rest : per; + EXEC SQL INSERT INTO ac_install (install_id, purchase_id, seq_no, months, month_amount, biz_date) + VALUES (nextval('ac_install_seq'), :h_pid, :h_seq, :h_n, :h_m, :h_bizdate); + if (sqlca.sqlcode < 0) { userlog("ACQ_SPLITPAY FAIL seq=%ld [%d]", i, sqlca.sqlcode); FAIL(b); } + } + + setl(b, T_COUNT, n); + setl(b, T_AMT1, per); + setl(b, T_AMT2, per + rest); /* 마지막 회차 금액 */ + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_SPLITQRY.h b/app/src/ac/svc/ACQ_SPLITQRY.h new file mode 100644 index 0000000..93c4ac7 --- /dev/null +++ b/app/src/ac/svc/ACQ_SPLITQRY.h @@ -0,0 +1,22 @@ +/* + * ACQ_SPLITQRY.h - ac 서비스 ACQ_SPLITQRY 카피북 (copybook / record header). + * 분할납부 회차 조회 레코드. + * Request fields: T_PURCHASE_ID + * Response fields: T_COUNT, T_GROSS, T_AMT1(최소), T_AMT2(최대), T_RC(일치=0) + */ +#ifndef ACQ_SPLITQRY_H +#define ACQ_SPLITQRY_H + +#include "ac_svc.h" + +typedef struct { + long purchase_id; + long sched_count; /* 회차수 */ + long sched_sum; /* 스케줄 합계 */ + long min_amount; + long max_amount; +} ac_splitqry_rec_t; + +void ACQ_SPLITQRY(TPSVCINFO *p); + +#endif /* ACQ_SPLITQRY_H */ diff --git a/app/src/ac/svc/ACQ_SPLITQRY.pgc b/app/src/ac/svc/ACQ_SPLITQRY.pgc new file mode 100644 index 0000000..de79b84 --- /dev/null +++ b/app/src/ac/svc/ACQ_SPLITQRY.pgc @@ -0,0 +1,43 @@ +/* + * ACQ_SPLITQRY.pgc - ac 모듈 서비스 ACQ_SPLITQRY (one service = one file). + * 분할납부 회차 조회: 매입 건의 회차 스케줄을 커서로 순회하며 회차수·합계와 + * 최소/최대 회차금액을 산출하고, 원금 대비 스케줄 합계 일치 여부를 점검한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_SPLITQRY.h" + +void ACQ_SPLITQRY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long cnt = 0, sum = 0, mn = 0, mx = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_seq, h_m, h_orig = 0; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + + EXEC SQL DECLARE spq_cur CURSOR FOR + SELECT seq_no, month_amount FROM ac_install + WHERE purchase_id = :h_pid ORDER BY seq_no; + EXEC SQL OPEN spq_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH spq_cur INTO :h_seq, :h_m; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE spq_cur; FAIL(b); } + cnt++; sum += h_m; + if (mn == 0 || h_m < mn) mn = h_m; + if (h_m > mx) mx = h_m; + } + EXEC SQL CLOSE spq_cur; + + EXEC SQL SELECT amount INTO :h_orig FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, cnt); + setl(b, T_GROSS, sum); + setl(b, T_AMT1, mn); + setl(b, T_AMT2, mx); + setl(b, T_RC, (cnt > 0 && sum == h_orig) ? 0 : 1); /* 스케줄-원금 일치 여부 */ + OK(b); +} diff --git a/app/src/ac/svc/ACQ_TAXCXL.h b/app/src/ac/svc/ACQ_TAXCXL.h new file mode 100644 index 0000000..72cd382 --- /dev/null +++ b/app/src/ac/svc/ACQ_TAXCXL.h @@ -0,0 +1,20 @@ +/* + * ACQ_TAXCXL.h - ac 서비스 ACQ_TAXCXL 카피북 (copybook / record header). + * 세금계산서 취소(음수 발행) 레코드. + * Request fields: T_ID1(원본 계산서 ID) + * Response fields: T_ID2(음수 계산서 ID), T_TAX(-부가세), T_RC + */ +#ifndef ACQ_TAXCXL_H +#define ACQ_TAXCXL_H + +#include "ac_svc.h" + +typedef struct { + long orig_inv_id; + long neg_inv_id; /* 상쇄용 음수 계산서 */ + long neg_vat; +} ac_taxcxl_rec_t; + +void ACQ_TAXCXL(TPSVCINFO *p); + +#endif /* ACQ_TAXCXL_H */ diff --git a/app/src/ac/svc/ACQ_TAXCXL.pgc b/app/src/ac/svc/ACQ_TAXCXL.pgc new file mode 100644 index 0000000..f1bbef8 --- /dev/null +++ b/app/src/ac/svc/ACQ_TAXCXL.pgc @@ -0,0 +1,42 @@ +/* + * ACQ_TAXCXL.pgc - ac 모듈 서비스 ACQ_TAXCXL (one service = one file). + * 세금계산서 취소: 원본 계산서를 상쇄하는 음수(공급가/부가세 부호 반전) + * 계산서를 발행한다. 이미 상쇄된 계산서(합계 0)는 재취소를 거부한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_TAXCXL.h" + +void ACQ_TAXCXL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_inv, h_neg, h_pid = 0, h_supply = 0, h_vat = 0, h_bal = 0; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_inv = getl(b, T_ID1); + + EXEC SQL SELECT purchase_id, merchant_id, supply, vat, to_char(biz_date, 'YYYY-MM-DD') + INTO :h_pid, :h_merch, :h_supply, :h_vat, :h_bizdate + FROM ac_tax_invoice WHERE inv_id = :h_inv; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 4); FAIL(b); } + if (sqlca.sqlcode < 0) FAIL(b); + if (h_supply < 0) { setl(b, T_RC, 3); FAIL(b); } /* 음수 계산서는 취소 불가 */ + + /* 동일 매입의 계산서 잔액이 이미 0 이면 상쇄 완료 상태 */ + EXEC SQL SELECT coalesce(sum(supply), 0) INTO :h_bal + FROM ac_tax_invoice WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_bal == 0) { setl(b, T_RC, 2); FAIL(b); } + + EXEC SQL SELECT nextval('ac_taxinv_seq') INTO :h_neg; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO ac_tax_invoice (inv_id, purchase_id, merchant_id, supply, vat, biz_date) + VALUES (:h_neg, :h_pid, :h_merch, -:h_supply, -:h_vat, :h_bizdate); + if (sqlca.sqlcode < 0) { userlog("ACQ_TAXCXL FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_ID2, h_neg); + setl(b, T_TAX, -h_vat); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_TAXREISSUE.h b/app/src/ac/svc/ACQ_TAXREISSUE.h new file mode 100644 index 0000000..6942c40 --- /dev/null +++ b/app/src/ac/svc/ACQ_TAXREISSUE.h @@ -0,0 +1,21 @@ +/* + * ACQ_TAXREISSUE.h - ac 서비스 ACQ_TAXREISSUE 카피북 (copybook / record header). + * 세금계산서 재발행 레코드. + * Request fields: T_ID1(원본 계산서 ID), T_BIZDATE(재발행일) + * Response fields: T_ID2(신규 계산서 ID), T_TAX, T_RC(2=부가세 불일치) + */ +#ifndef ACQ_TAXREISSUE_H +#define ACQ_TAXREISSUE_H + +#include "ac_svc.h" + +typedef struct { + long orig_inv_id; + char biz_date[16]; + long new_inv_id; + long vat; /* 승계 부가세 */ +} ac_taxreissue_rec_t; + +void ACQ_TAXREISSUE(TPSVCINFO *p); + +#endif /* ACQ_TAXREISSUE_H */ diff --git a/app/src/ac/svc/ACQ_TAXREISSUE.pgc b/app/src/ac/svc/ACQ_TAXREISSUE.pgc new file mode 100644 index 0000000..a0b9f9a --- /dev/null +++ b/app/src/ac/svc/ACQ_TAXREISSUE.pgc @@ -0,0 +1,39 @@ +/* + * ACQ_TAXREISSUE.pgc - ac 모듈 서비스 ACQ_TAXREISSUE (one service = one file). + * 세금계산서 재발행: 원본 계산서의 공급가/부가세를 승계한 신규 계산서를 + * 지정 영업일로 다시 발행한다. 부가세는 공급가 기준으로 재검증한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_TAXREISSUE.h" + +void ACQ_TAXREISSUE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_old, h_new, h_pid = 0, h_supply = 0, h_vat = 0; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_old = getl(b, T_ID1); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL SELECT purchase_id, merchant_id, supply, vat + INTO :h_pid, :h_merch, :h_supply, :h_vat + FROM ac_tax_invoice WHERE inv_id = :h_old; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 4); FAIL(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + /* 부가세 정합 재검증: 공급가의 10% 와 다르면 재발행 거부 */ + if (h_vat != acq_vat(h_supply)) { setl(b, T_RC, 2); FAIL(b); } + + EXEC SQL SELECT nextval('ac_taxinv_seq') INTO :h_new; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO ac_tax_invoice (inv_id, purchase_id, merchant_id, supply, vat, biz_date) + VALUES (:h_new, :h_pid, :h_merch, :h_supply, :h_vat, :h_bizdate); + if (sqlca.sqlcode < 0) { userlog("ACQ_TAXREISSUE FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_ID2, h_new); + setl(b, T_TAX, h_vat); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_TOPMERCH.h b/app/src/ac/svc/ACQ_TOPMERCH.h new file mode 100644 index 0000000..617cc47 --- /dev/null +++ b/app/src/ac/svc/ACQ_TOPMERCH.h @@ -0,0 +1,23 @@ +/* + * ACQ_TOPMERCH.h - ac 서비스 ACQ_TOPMERCH 카피북 (copybook / record header). + * 상위 가맹점 조회 레코드. + * Request fields: T_BIZDATE, T_ARG1(상위 N; 1..10, 기본 5) + * Response fields: T_COUNT, T_GROSS, T_KEY1/T_AMT2(occ 0..N-1) + */ +#ifndef ACQ_TOPMERCH_H +#define ACQ_TOPMERCH_H + +#include "ac_svc.h" + +typedef struct { + char biz_date[16]; + long topn; + long got; + long top_sum; /* 상위권 매출 합계 */ + char merchants[10][64]; + long amounts[10]; +} ac_topmerch_rec_t; + +void ACQ_TOPMERCH(TPSVCINFO *p); + +#endif /* ACQ_TOPMERCH_H */ diff --git a/app/src/ac/svc/ACQ_TOPMERCH.pgc b/app/src/ac/svc/ACQ_TOPMERCH.pgc new file mode 100644 index 0000000..9563b4d --- /dev/null +++ b/app/src/ac/svc/ACQ_TOPMERCH.pgc @@ -0,0 +1,44 @@ +/* + * ACQ_TOPMERCH.pgc - ac 모듈 서비스 ACQ_TOPMERCH (one service = one file). + * 상위 가맹점 조회: 영업일 매출 상위 N개 가맹점을 커서로 뽑아 가맹점 ID와 + * 매출액 쌍을 occurrence 로 내려주고 상위권 합계를 계산한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_TOPMERCH.h" + +void ACQ_TOPMERCH(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long want, got = 0, topsum = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_want, h_sum; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + want = getl(b, T_ARG1); + if (want < 1 || want > 10) want = 5L; + h_want = want; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL DECLARE tpm_cur CURSOR FOR + SELECT merchant_id, sum(amount) FROM purchase + WHERE biz_date = :h_bizdate AND status NOT IN ('C', 'V') + GROUP BY merchant_id ORDER BY sum(amount) DESC, merchant_id + LIMIT :h_want; + EXEC SQL OPEN tpm_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH tpm_cur INTO :h_merch, :h_sum; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE tpm_cur; FAIL(b); } + Badd(b, T_KEY1, h_merch, 0L); + Badd(b, T_AMT2, (char *)&h_sum, 0L); + got++; topsum += h_sum; + } + EXEC SQL CLOSE tpm_cur; + + setl(b, T_COUNT, got); + setl(b, T_GROSS, topsum); /* 상위권 매출 합계 */ + setl(b, T_RC, got > 0 ? 0 : 1); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_VELOCITY.h b/app/src/ac/svc/ACQ_VELOCITY.h new file mode 100644 index 0000000..039a446 --- /dev/null +++ b/app/src/ac/svc/ACQ_VELOCITY.h @@ -0,0 +1,21 @@ +/* + * ACQ_VELOCITY.h - ac 서비스 ACQ_VELOCITY 카피북 (copybook / record header). + * 매입 유입속도 점검 레코드. + * Request fields: T_MERCHANT, T_ARG2(10분 허용 건수; 0=기본 100) + * Response fields: T_ARG1(10분 건수), T_ARG3(1시간 건수), T_RC(1=초과) + */ +#ifndef ACQ_VELOCITY_H +#define ACQ_VELOCITY_H + +#include "ac_svc.h" + +typedef struct { + char merchant_id[64]; + long allow_10m; /* 10분 허용 건수 */ + long cnt_10m; + long cnt_1h; +} ac_velocity_rec_t; + +void ACQ_VELOCITY(TPSVCINFO *p); + +#endif /* ACQ_VELOCITY_H */ diff --git a/app/src/ac/svc/ACQ_VELOCITY.pgc b/app/src/ac/svc/ACQ_VELOCITY.pgc new file mode 100644 index 0000000..2181850 --- /dev/null +++ b/app/src/ac/svc/ACQ_VELOCITY.pgc @@ -0,0 +1,32 @@ +/* + * ACQ_VELOCITY.pgc - ac 모듈 서비스 ACQ_VELOCITY (one service = one file). + * 매입 유입속도 점검: 가맹점의 최근 10분간 접수 건수를 재어 허용치 초과 + * 여부를 판정한다. 초과 시 경보와 함께 직전 1시간 건수도 참고로 준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_VELOCITY.h" + +void ACQ_VELOCITY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long allow; + EXEC SQL BEGIN DECLARE SECTION; + long h_10m = 0, h_1h = 0; + char h_merch[64]; + EXEC SQL END DECLARE SECTION; + + allow = getl(b, T_ARG2); /* 10분 허용 건수 */ + if (allow <= 0) allow = 100L; + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + + EXEC SQL SELECT count(*) FILTER (WHERE created_at >= now() - interval '10 minutes'), + count(*) FILTER (WHERE created_at >= now() - interval '1 hour') + INTO :h_10m, :h_1h + FROM purchase WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0) { userlog("ACQ_VELOCITY FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + setl(b, T_ARG1, h_10m); /* 최근 10분 건수 */ + setl(b, T_ARG3, h_1h); /* 최근 1시간 건수 */ + setl(b, T_RC, h_10m > allow ? 1 : 0); + OK(b); +} diff --git a/app/src/ac/svc/ACQ_WEEKSTAT.h b/app/src/ac/svc/ACQ_WEEKSTAT.h new file mode 100644 index 0000000..178c1bb --- /dev/null +++ b/app/src/ac/svc/ACQ_WEEKSTAT.h @@ -0,0 +1,23 @@ +/* + * ACQ_WEEKSTAT.h - ac 서비스 ACQ_WEEKSTAT 카피북 (copybook / record header). + * 매입 주간 통계 레코드. + * Request fields: T_BIZDATE(기준일) + * Response fields: T_COUNT, T_GROSS, T_FEE, T_STR1(최대 거래일), T_AMT1 + */ +#ifndef ACQ_WEEKSTAT_H +#define ACQ_WEEKSTAT_H + +#include "ac_svc.h" + +typedef struct { + char base_date[16]; /* 기준 영업일 */ + long week_count; + long week_gross; + long week_fee; + char peak_day[16]; /* 최대 거래일 */ + long peak_amount; +} ac_weekstat_rec_t; + +void ACQ_WEEKSTAT(TPSVCINFO *p); + +#endif /* ACQ_WEEKSTAT_H */ diff --git a/app/src/ac/svc/ACQ_WEEKSTAT.pgc b/app/src/ac/svc/ACQ_WEEKSTAT.pgc new file mode 100644 index 0000000..9c7d8d3 --- /dev/null +++ b/app/src/ac/svc/ACQ_WEEKSTAT.pgc @@ -0,0 +1,44 @@ +/* + * ACQ_WEEKSTAT.pgc - ac 모듈 서비스 ACQ_WEEKSTAT (one service = one file). + * 매입 주간 통계: 기준일 포함 직전 7일 구간의 건수/총액/수수료 합계와 + * 최대 거래일(거래액 기준)을 산출한다. 구간 경계는 date 연산으로 계산. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr. + */ +#include "ACQ_WEEKSTAT.h" + +void ACQ_WEEKSTAT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0, h_amt = 0, h_fee = 0, h_peak = 0; + char h_bizdate[16], h_peakday[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + h_peakday[0] = 0; + + EXEC SQL SELECT count(*), coalesce(sum(amount), 0), coalesce(sum(fee), 0) + INTO :h_cnt, :h_amt, :h_fee + FROM purchase + WHERE biz_date > CAST(:h_bizdate AS date) - 7 + AND biz_date <= CAST(:h_bizdate AS date) + AND status <> 'C'; + if (sqlca.sqlcode < 0) FAIL(b); + + /* 주간 최대 거래일 */ + EXEC SQL SELECT to_char(biz_date, 'YYYY-MM-DD'), sum(amount) + INTO :h_peakday, :h_peak + FROM purchase + WHERE biz_date > CAST(:h_bizdate AS date) - 7 + AND biz_date <= CAST(:h_bizdate AS date) + AND status <> 'C' + GROUP BY biz_date ORDER BY sum(amount) DESC LIMIT 1; + if (sqlca.sqlcode < 0) { userlog("ACQ_WEEKSTAT FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); FAIL(b); } + + if (h_peakday[0]) Bchg(b, T_STR1, 0, h_peakday, 0L); + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_amt); + setl(b, T_FEE, h_fee); + setl(b, T_AMT1, h_peak); + OK(b); +} diff --git a/app/src/au/au_svr.pgc b/app/src/au/au_svr.pgc index 84a18cd..ab51f18 100644 --- a/app/src/au/au_svr.pgc +++ b/app/src/au/au_svr.pgc @@ -49,6 +49,51 @@ #include "PREAUTH.h" #include "PREAUTH_COMPLETE.h" #include "AUTH_VOID.h" +#include "LIMIT_HIST.h" +#include "LIMIT_RESTORE.h" +#include "LIMIT_REMAIN.h" +#include "LIMIT_USAGE.h" +#include "LIMIT_RAISE_REQ.h" +#include "CARD_SUSPEND.h" +#include "CARD_RESUME.h" +#include "CARD_REISSUE.h" +#include "CARD_STATE_INQ.h" +#include "AUTH_PARTIAL.h" +#include "AUTH_RESEND.h" +#include "TOKEN_VERIFY.h" +#include "BIN_ROUTE_INQ.h" +#include "CANCEL_REPROC.h" +#include "CANCEL_STATUS.h" +#include "NOAUTH_CAPTURE.h" +#include "NIGHT_POLICY.h" +#include "OVERSEAS_POLICY.h" +#include "VELOCITY_HOUR.h" +#include "VELOCITY_WEEK.h" +#include "VELOCITY_MERCH.h" +#include "BLACKLIST_ADD.h" +#include "BLACKLIST_DEL.h" +#include "BLACKLIST_INQ.h" +#include "FRAUD_RULE_AMT.h" +#include "FRAUD_RULE_FREQ.h" +#include "FRAUD_RULE_DUP.h" +#include "FRAUD_CASE_INQ.h" +#include "AUTH_DELAY_MON.h" +#include "STANDIN_SETTLE.h" +#include "STANDIN_INQ.h" +#include "PREAUTH_EXTEND.h" +#include "PREAUTH_CANCEL.h" +#include "PREAUTH_INQ.h" +#include "STAT_HOURLY.h" +#include "STAT_BYTYPE.h" +#include "STAT_ISSUER.h" +#include "DECLINE_ANALYZE.h" +#include "AUTH_AMT_ADJUST.h" +#include "AUTH_EXPIRE_CHK.h" +#include "MERCH_AUTH_RANK.h" +#include "CARD_DAILY_RPT.h" +#include "RISK_SCORE_INQ.h" +#include "APPROVAL_RATE.h" +#include "AUTH_DUP_CHK.h" /* ----- advertise table: {service name, function} (fn is extern from its file) */ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { @@ -67,6 +112,29 @@ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { {"AUTH_ROUTE", AUTH_ROUTE}, {"DCC_QUOTE", DCC_QUOTE}, {"AUTH_CAPTURE", AUTH_CAPTURE}, {"PREAUTH", PREAUTH}, {"PREAUTH_COMPLETE", PREAUTH_COMPLETE}, {"AUTH_VOID", AUTH_VOID}, + {"LIMIT_HIST", LIMIT_HIST}, {"LIMIT_RESTORE", LIMIT_RESTORE}, + {"LIMIT_REMAIN", LIMIT_REMAIN}, {"LIMIT_USAGE", LIMIT_USAGE}, + {"LIMIT_RAISE_REQ", LIMIT_RAISE_REQ}, {"CARD_SUSPEND", CARD_SUSPEND}, + {"CARD_RESUME", CARD_RESUME}, {"CARD_REISSUE", CARD_REISSUE}, + {"CARD_STATE_INQ", CARD_STATE_INQ}, {"AUTH_PARTIAL", AUTH_PARTIAL}, + {"AUTH_RESEND", AUTH_RESEND}, {"TOKEN_VERIFY", TOKEN_VERIFY}, + {"BIN_ROUTE_INQ", BIN_ROUTE_INQ}, {"CANCEL_REPROC", CANCEL_REPROC}, + {"CANCEL_STATUS", CANCEL_STATUS}, {"NOAUTH_CAPTURE", NOAUTH_CAPTURE}, + {"NIGHT_POLICY", NIGHT_POLICY}, {"OVERSEAS_POLICY", OVERSEAS_POLICY}, + {"VELOCITY_HOUR", VELOCITY_HOUR}, {"VELOCITY_WEEK", VELOCITY_WEEK}, + {"VELOCITY_MERCH", VELOCITY_MERCH}, {"BLACKLIST_ADD", BLACKLIST_ADD}, + {"BLACKLIST_DEL", BLACKLIST_DEL}, {"BLACKLIST_INQ", BLACKLIST_INQ}, + {"FRAUD_RULE_AMT", FRAUD_RULE_AMT}, {"FRAUD_RULE_FREQ", FRAUD_RULE_FREQ}, + {"FRAUD_RULE_DUP", FRAUD_RULE_DUP}, {"FRAUD_CASE_INQ", FRAUD_CASE_INQ}, + {"AUTH_DELAY_MON", AUTH_DELAY_MON}, {"STANDIN_SETTLE", STANDIN_SETTLE}, + {"STANDIN_INQ", STANDIN_INQ}, {"PREAUTH_EXTEND", PREAUTH_EXTEND}, + {"PREAUTH_CANCEL", PREAUTH_CANCEL}, {"PREAUTH_INQ", PREAUTH_INQ}, + {"STAT_HOURLY", STAT_HOURLY}, {"STAT_BYTYPE", STAT_BYTYPE}, + {"STAT_ISSUER", STAT_ISSUER}, {"DECLINE_ANALYZE", DECLINE_ANALYZE}, + {"AUTH_AMT_ADJUST", AUTH_AMT_ADJUST}, {"AUTH_EXPIRE_CHK", AUTH_EXPIRE_CHK}, + {"MERCH_AUTH_RANK", MERCH_AUTH_RANK}, {"CARD_DAILY_RPT", CARD_DAILY_RPT}, + {"RISK_SCORE_INQ", RISK_SCORE_INQ}, {"APPROVAL_RATE", APPROVAL_RATE}, + {"AUTH_DUP_CHK", AUTH_DUP_CHK}, {NULL, NULL} }; diff --git a/app/src/au/batch/au_blacklist_hit_batch.pgc b/app/src/au/batch/au_blacklist_hit_batch.pgc new file mode 100644 index 0000000..8dbde32 --- /dev/null +++ b/app/src/au/batch/au_blacklist_hit_batch.pgc @@ -0,0 +1,69 @@ +/* + * au_blacklist_hit_batch.pgc - au 블랙리스트 승인시도 검출 배치 (조인 커서 + 로그, XA). + * + * 영업일 승인원장을 블랙리스트와 조인해, 등재 카드로 들어온 승인 시도를 + * 카드 단위 커서로 적발한다. 카드마다 만점(999) 점수의 BLACKLIST_HIT + * 부정거래 로그를 남기고 시도 건수를 함께 기록한다. + * Usage: au_blacklist_hit_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_blacklist_hit_batch.h" + +long aub_blacklist_hit_score(void) +{ + return 999; +} + +int main(int argc, char **argv) +{ + au_blacklist_hit_ctx_t ctx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_card[24]; + long h_tries, h_fid, h_score; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + h_score = aub_blacklist_hit_score(); + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE cbhb CURSOR FOR + SELECT a.card_no, count(*) + FROM au_authorization a JOIN au_blacklist k ON k.card_no = a.card_no + WHERE a.biz_date = :h_bizdate + GROUP BY a.card_no ORDER BY count(*) DESC; + EXEC SQL OPEN cbhb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH cbhb INTO :h_card, :h_tries; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cbhb; tpabort(0); return 1; } + ctx.hit_cards++; + ctx.hit_auths += h_tries; + EXEC SQL SELECT nextval('au_fraud_seq') INTO :h_fid; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cbhb; tpabort(0); return 1; } + EXEC SQL INSERT INTO au_fraud_log (fraud_id, auth_id, card_no, score, reason, biz_date) + VALUES (:h_fid, 0, :h_card, :h_score, 'BLACKLIST_HIT', :h_bizdate); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cbhb; tpabort(0); return 1; } + ctx.logged++; + printf(" 블랙리스트 히트 card=%.6s** 시도=%ld건\n", h_card, h_tries); + } + EXEC SQL CLOSE cbhb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_blacklist_hit_batch COMMIT: bizdate=%s 적발카드=%ld 시도=%ld건 기록=%ld\n", + ctx.biz_date, ctx.hit_cards, ctx.hit_auths, ctx.logged); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_cancel_reproc_batch.pgc b/app/src/au/batch/au_cancel_reproc_batch.pgc new file mode 100644 index 0000000..5a91a4e --- /dev/null +++ b/app/src/au/batch/au_cancel_reproc_batch.pgc @@ -0,0 +1,81 @@ +/* + * au_cancel_reproc_batch.pgc - au 미반영 승인취소 재처리 배치 (조인 커서 + 분기 UPDATE, XA). + * + * 영업일의 취소 접수(au_cancel) 중 원승인이 아직 취소류 상태가 아닌 건을 + * 조인 커서로 골라, 유형별로 분기 반영한다: FULL->C, VOID->V, + * PARTIAL->원승인 금액 감액(원금액 미만일 때만). 전량 ONE XA transaction. + * Usage: au_cancel_reproc_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_cancel_reproc_batch.h" + +long aub_cancel_reproc_total(const au_cancel_reproc_ctx_t *ctx) +{ + return ctx->reproc_full + ctx->reproc_void + ctx->reproc_partial; +} + +int main(int argc, char **argv) +{ + au_cancel_reproc_ctx_t ctx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_ctype[12], h_astat[4]; + long h_auth, h_camt, h_aamt; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE ccrb CURSOR FOR + SELECT c.auth_id, c.cancel_type, c.amount, a.status, a.amount + FROM au_cancel c JOIN au_authorization a ON a.auth_id = c.auth_id + WHERE c.biz_date = :h_bizdate AND a.status NOT IN ('C','V','R') + ORDER BY c.cancel_id; + EXEC SQL OPEN ccrb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH ccrb INTO :h_auth, :h_ctype, :h_camt, :h_astat, :h_aamt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ccrb; tpabort(0); return 1; } + + if (h_astat[0] != 'A' && h_astat[0] != 'M') { ctx.skipped++; continue; } + + if (strcmp(h_ctype, "PARTIAL") == 0) { + if (h_camt >= h_aamt) { ctx.skipped++; continue; } /* 정합성 오류 건 스킵 */ + EXEC SQL UPDATE au_authorization + SET amount = amount - :h_camt, updated_at = now() + WHERE auth_id = :h_auth; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ccrb; tpabort(0); return 1; } + ctx.reproc_partial++; + } else if (strcmp(h_ctype, "VOID") == 0) { + EXEC SQL UPDATE au_authorization SET status = 'V', updated_at = now() + WHERE auth_id = :h_auth; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ccrb; tpabort(0); return 1; } + ctx.reproc_void++; + } else { + EXEC SQL UPDATE au_authorization SET status = 'C', updated_at = now() + WHERE auth_id = :h_auth; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ccrb; tpabort(0); return 1; } + ctx.reproc_full++; + } + } + EXEC SQL CLOSE ccrb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_cancel_reproc_batch COMMIT: bizdate=%s 재처리=%ld (FULL=%ld VOID=%ld PARTIAL=%ld) skip=%ld\n", + ctx.biz_date, aub_cancel_reproc_total(&ctx), + ctx.reproc_full, ctx.reproc_void, ctx.reproc_partial, ctx.skipped); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_delay_watch_batch.pgc b/app/src/au/batch/au_delay_watch_batch.pgc new file mode 100644 index 0000000..47b6a1f --- /dev/null +++ b/app/src/au/batch/au_delay_watch_batch.pgc @@ -0,0 +1,60 @@ +/* + * au_delay_watch_batch.pgc - au 승인 체류시간 감시 배치 (EPOCH 다중구간 집계, XA). + * + * 미매입 승인(A) 전체의 체류시간을 EPOCH 초로 환산해 평균/최대와 + * 1시간·24시간 초과 구간별 건수를 한 번의 집계로 뽑아, 최악 체류 건의 + * 지연구간 라벨(FRESH/AGING/STALE/CRITICAL)을 판정한다. + * Usage: au_delay_watch_batch + */ +#include +#include +#include +#include +#include "au_delay_watch_batch.h" + +const char *aub_delay_bucket(long age_sec) +{ + if (age_sec >= 86400) return "CRITICAL"; + if (age_sec >= 3600) return "STALE"; + if (age_sec >= 600) return "AGING"; + return "FRESH"; +} + +int main(int argc, char **argv) +{ + au_delay_watch_ctx_t ctx; + + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0, h_avg = 0, h_max = 0, h_o1h = 0, h_o1d = 0; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*), + CAST(coalesce(avg(EXTRACT(EPOCH FROM now() - created_at)),0) AS bigint), + CAST(coalesce(max(EXTRACT(EPOCH FROM now() - created_at)),0) AS bigint), + count(*) FILTER (WHERE created_at < now() - interval '1 hour'), + count(*) FILTER (WHERE created_at < now() - interval '24 hours') + INTO :h_cnt, :h_avg, :h_max, :h_o1h, :h_o1d + FROM au_authorization + WHERE status = 'A' AND captured_at IS NULL; + if (sqlca.sqlcode < 0) { fprintf(stderr, "au_delay_watch_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + ctx.pending = h_cnt; + ctx.avg_age_sec = h_avg; + ctx.max_age_sec = h_max; + ctx.over_1h = h_o1h; + ctx.over_1d = h_o1d; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_delay_watch_batch COMMIT: 미매입=%ld 평균=%lds 최대=%lds(%s) 1h초과=%ld 24h초과=%ld\n", + ctx.pending, ctx.avg_age_sec, ctx.max_age_sec, + aub_delay_bucket(ctx.max_age_sec), ctx.over_1h, ctx.over_1d); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_dup_auth_batch.pgc b/app/src/au/batch/au_dup_auth_batch.pgc new file mode 100644 index 0000000..7122788 --- /dev/null +++ b/app/src/au/batch/au_dup_auth_batch.pgc @@ -0,0 +1,65 @@ +/* + * au_dup_auth_batch.pgc - au 중복승인 검출 배치 (다중키 GROUP 커서, XA). + * + * 영업일의 승인(A/M/S) 건을 카드+가맹점+금액 3중 키로 그룹핑해 2건 이상 + * 반복된 그룹을 커서로 뽑고, 그룹마다 원거래 1건을 제외한 잉여 승인건수와 + * 금액(중복 환불 예상액)을 적산해 보고한다. + * Usage: au_dup_auth_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_dup_auth_batch.h" + +long aub_dup_extra(long group_cnt) +{ + return (group_cnt > 1) ? group_cnt - 1 : 0; +} + +int main(int argc, char **argv) +{ + au_dup_auth_ctx_t ctx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_card[24], h_merch[64]; + long h_amt, h_cnt; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE cdab CURSOR FOR + SELECT card_no, merchant_id, amount, count(*) + FROM au_authorization + WHERE biz_date = :h_bizdate AND status IN ('A','M','S') + GROUP BY card_no, merchant_id, amount + HAVING count(*) >= 2 + ORDER BY count(*) DESC, card_no; + EXEC SQL OPEN cdab; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH cdab INTO :h_card, :h_merch, :h_amt, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cdab; tpabort(0); return 1; } + ctx.dup_groups++; + ctx.extra_auths += aub_dup_extra(h_cnt); + ctx.extra_gross += aub_dup_extra(h_cnt) * h_amt; + printf(" 중복그룹 card=%.6s** merch=%s amt=%ld x%ld\n", h_card, h_merch, h_amt, h_cnt); + } + EXEC SQL CLOSE cdab; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_dup_auth_batch COMMIT: bizdate=%s 중복그룹=%ld 잉여승인=%ld건 %ld원\n", + ctx.biz_date, ctx.dup_groups, ctx.extra_auths, ctx.extra_gross); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_hourly_stat_batch.pgc b/app/src/au/batch/au_hourly_stat_batch.pgc new file mode 100644 index 0000000..415cedf --- /dev/null +++ b/app/src/au/batch/au_hourly_stat_batch.pgc @@ -0,0 +1,72 @@ +/* + * au_hourly_stat_batch.pgc - au 시간대별 승인 히스토그램 배치 (GROUP 커서 + 배열 적재, XA). + * + * 영업일의 승인 접수를 시(0~23) 단위 그룹 커서로 페치해 24칸 히스토그램 + * 배열에 적재한 뒤, C 루프로 피크 시간대를 찾고 오전/오후 분포를 출력한다. + * Usage: au_hourly_stat_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_hourly_stat_batch.h" + +void aub_hourly_find_peak(au_hourly_stat_ctx_t *ctx) +{ + int h; + ctx->peak_hour = 0; + ctx->peak_count = 0; + for (h = 0; h < 24; h++) + if (ctx->by_hour[h] > ctx->peak_count) { + ctx->peak_count = ctx->by_hour[h]; + ctx->peak_hour = h; + } +} + +int main(int argc, char **argv) +{ + au_hourly_stat_ctx_t ctx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long am = 0, pm = 0; + int h; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_hour, h_cnt; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE chsb CURSOR FOR + SELECT CAST(EXTRACT(HOUR FROM created_at) AS bigint), count(*) + FROM au_authorization WHERE biz_date = :h_bizdate + GROUP BY 1 ORDER BY 1; + EXEC SQL OPEN chsb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH chsb INTO :h_hour, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE chsb; tpabort(0); return 1; } + if (h_hour >= 0 && h_hour < 24) ctx.by_hour[h_hour] = h_cnt; + ctx.total += h_cnt; + } + EXEC SQL CLOSE chsb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + + aub_hourly_find_peak(&ctx); + for (h = 0; h < 12; h++) am += ctx.by_hour[h]; + for (h = 12; h < 24; h++) pm += ctx.by_hour[h]; + printf(">>> au_hourly_stat_batch COMMIT: bizdate=%s 총=%ld 피크=%02d시(%ld건) 오전=%ld 오후=%ld\n", + ctx.biz_date, ctx.total, ctx.peak_hour, ctx.peak_count, am, pm); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_issuer_mix_batch.pgc b/app/src/au/batch/au_issuer_mix_batch.pgc new file mode 100644 index 0000000..86ff546 --- /dev/null +++ b/app/src/au/batch/au_issuer_mix_batch.pgc @@ -0,0 +1,76 @@ +/* + * au_issuer_mix_batch.pgc - au 발급사망 구성비 배치 (GROUP BY 커서 + 백분율, XA). + * + * 영업일 승인 접수를 카드번호 선두 자리로 그룹핑한 커서를 순회하며 + * 카드망(AMEX/VISA/MC/LOCAL/ETC)별 건수와 구성비(%)를 출력하고 + * 최다 카드망을 찾아 보고한다. + * Usage: au_issuer_mix_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_issuer_mix_batch.h" + +const char *aub_issuer_network(char lead) +{ + switch (lead) { + case '3': return "AMEX"; + case '4': return "VISA"; + case '5': return "MC"; + case '6': return "LOCAL"; + default: return "ETC"; + } +} + +int main(int argc, char **argv) +{ + au_issuer_mix_ctx_t ctx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_lead[4]; + long h_cnt, h_total = 0; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*) INTO :h_total FROM au_authorization WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "COUNT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + ctx.total = h_total; + + EXEC SQL DECLARE cimb CURSOR FOR + SELECT substr(card_no, 1, 1), count(*) + FROM au_authorization WHERE biz_date = :h_bizdate + GROUP BY substr(card_no, 1, 1) + ORDER BY count(*) DESC; + EXEC SQL OPEN cimb; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH cimb INTO :h_lead, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cimb; tpabort(0); return 1; } + ctx.networks++; + if (h_cnt > ctx.top_count) { + ctx.top_count = h_cnt; + strncpy(ctx.top_network, aub_issuer_network(h_lead[0]), sizeof(ctx.top_network)-1); + } + printf(" 망 %-5s : %6ld건 (%3ld%%)\n", aub_issuer_network(h_lead[0]), h_cnt, + (ctx.total > 0) ? (h_cnt * 100L) / ctx.total : 0); + } + EXEC SQL CLOSE cimb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_issuer_mix_batch COMMIT: bizdate=%s 망수=%ld 총=%ld 최다=%s(%ld건)\n", + ctx.biz_date, ctx.networks, ctx.total, ctx.top_network, ctx.top_count); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_limit_restore_batch.pgc b/app/src/au/batch/au_limit_restore_batch.pgc new file mode 100644 index 0000000..a3dc3c6 --- /dev/null +++ b/app/src/au/batch/au_limit_restore_batch.pgc @@ -0,0 +1,61 @@ +/* + * au_limit_restore_batch.pgc - au 임시한도 일괄 복원 배치 (커서 + UPDATE, XA). + * + * LIMIT_TEMP 로 임시 상향된 채 남아 있는 카드(temp_limit > 0)를 커서로 훑어 + * 카드마다 daily_limit 에서 임시분을 걷어내고 temp_limit 를 0 으로 되돌린다. + * 전체가 ONE global XA transaction; 실패 시 tpabort 로 전량 원복. + * Usage: au_limit_restore_batch + */ +#include +#include +#include +#include +#include "au_limit_restore_batch.h" + +void aub_limit_restore_summary(const au_limit_restore_ctx_t *ctx, char *out, int cap) +{ + snprintf(out, cap, "cards=%ld amount=%ld", ctx->restored_cards, ctx->restored_amount); +} + +int main(int argc, char **argv) +{ + au_limit_restore_ctx_t ctx; + char line[80]; + + EXEC SQL BEGIN DECLARE SECTION; + char h_card[24]; + long h_temp; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE clrb CURSOR FOR + SELECT card_no, temp_limit FROM card_limit + WHERE temp_limit > 0 ORDER BY card_no; + EXEC SQL OPEN clrb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH clrb INTO :h_card, :h_temp; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE clrb; tpabort(0); return 1; } + EXEC SQL UPDATE card_limit + SET daily_limit = daily_limit - :h_temp, temp_limit = 0, updated_at = now() + WHERE card_no = :h_card AND temp_limit = :h_temp; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE clrb; tpabort(0); return 1; } + ctx.restored_cards++; + ctx.restored_amount += h_temp; + } + EXEC SQL CLOSE clrb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + aub_limit_restore_summary(&ctx, line, sizeof(line)); + printf(">>> au_limit_restore_batch COMMIT: 임시한도 복원 %s\n", line); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_merch_rank_batch.pgc b/app/src/au/batch/au_merch_rank_batch.pgc new file mode 100644 index 0000000..e52fb11 --- /dev/null +++ b/app/src/au/batch/au_merch_rank_batch.pgc @@ -0,0 +1,71 @@ +/* + * au_merch_rank_batch.pgc - au 가맹점 승인 순위 배치 (window rank 커서 TOP-N, XA). + * + * 영업일의 가맹점별 유효 승인금액을 rank() 윈도우로 서열화한 서브쿼리를 + * 커서로 열어 상위 5곳만 페치, 1위 대비 점유율(%)과 함께 순위표를 찍는다. + * Usage: au_merch_rank_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_merch_rank_batch.h" + +long aub_rank_share_pct(long gross, long top_gross) +{ + return (top_gross > 0) ? (gross * 100L) / top_gross : 0; +} + +int main(int argc, char **argv) +{ + au_merch_rank_ctx_t ctx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_rnk, h_gross, h_top; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + h_top = AU_MERCH_RANK_TOP; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE cmrb CURSOR FOR + SELECT r.rnk, r.merchant_id, r.gross FROM ( + SELECT merchant_id, sum(amount) AS gross, + rank() OVER (ORDER BY sum(amount) DESC) AS rnk + FROM au_authorization + WHERE biz_date = :h_bizdate AND status IN ('A','M','S') + GROUP BY merchant_id) r + WHERE r.rnk <= :h_top + ORDER BY r.rnk; + EXEC SQL OPEN cmrb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH cmrb INTO :h_rnk, :h_merch, :h_gross; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cmrb; tpabort(0); return 1; } + if (ctx.listed == 0) { + ctx.top_gross = h_gross; + strncpy(ctx.top_merchant, h_merch, sizeof(ctx.top_merchant)-1); + } + ctx.listed++; + ctx.grand_gross += h_gross; + printf(" %ld위 %-10s %12ld원 (1위대비 %ld%%)\n", + h_rnk, h_merch, h_gross, aub_rank_share_pct(h_gross, ctx.top_gross)); + } + EXEC SQL CLOSE cmrb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_merch_rank_batch COMMIT: bizdate=%s TOP%d=%ld곳 1위=%s(%ld원) 합계=%ld원\n", + ctx.biz_date, AU_MERCH_RANK_TOP, ctx.listed, ctx.top_merchant, ctx.top_gross, ctx.grand_gross); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_night_auth_batch.pgc b/app/src/au/batch/au_night_auth_batch.pgc new file mode 100644 index 0000000..4e95ada --- /dev/null +++ b/app/src/au/batch/au_night_auth_batch.pgc @@ -0,0 +1,63 @@ +/* + * au_night_auth_batch.pgc - au 심야 승인 집계 배치 (시간대 조건 집계, XA). + * + * 영업일의 승인 접수를 심야(23시~06시)/주간으로 나눠 한 번의 조건부 FILTER + * 집계로 건수·금액을 뽑고, 심야건수 비중(bps)을 계산해 등급을 판정한다. + * ATMI client: ONE global transaction, tpcommit 이 XA 2PC 를 몬다. + * Usage: au_night_auth_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_night_auth_batch.h" + +const char *aub_night_grade(long bps) +{ + if (bps >= 4000) return "ALERT"; /* 심야비중 40% 이상 */ + if (bps >= 2000) return "ELEVATED"; + return "LOW"; +} + +int main(int argc, char **argv) +{ + au_night_auth_ctx_t ctx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_ncnt = 0, h_namt = 0, h_dcnt = 0; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT + count(*) FILTER (WHERE EXTRACT(HOUR FROM created_at) >= 23 + OR EXTRACT(HOUR FROM created_at) < 6), + coalesce(sum(amount) FILTER (WHERE EXTRACT(HOUR FROM created_at) >= 23 + OR EXTRACT(HOUR FROM created_at) < 6), 0), + count(*) FILTER (WHERE EXTRACT(HOUR FROM created_at) >= 6 + AND EXTRACT(HOUR FROM created_at) < 23) + INTO :h_ncnt, :h_namt, :h_dcnt + FROM au_authorization WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "au_night_auth_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + ctx.night_count = h_ncnt; + ctx.night_gross = h_namt; + ctx.day_count = h_dcnt; + ctx.night_bps = (h_ncnt + h_dcnt > 0) ? (h_ncnt * 10000L) / (h_ncnt + h_dcnt) : 0; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_night_auth_batch COMMIT: bizdate=%s night=%ld(%ld원) day=%ld 심야비중=%ldbps grade=%s\n", + ctx.biz_date, ctx.night_count, ctx.night_gross, ctx.day_count, + ctx.night_bps, aub_night_grade(ctx.night_bps)); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_noauth_detect_batch.pgc b/app/src/au/batch/au_noauth_detect_batch.pgc new file mode 100644 index 0000000..030454d --- /dev/null +++ b/app/src/au/batch/au_noauth_detect_batch.pgc @@ -0,0 +1,67 @@ +/* + * au_noauth_detect_batch.pgc - au 무승인 매입 검출 배치 (NOT EXISTS 안티조인, XA). + * + * 영업일의 매입원장(purchase) 전수와, 같은 가맹점/영업일/금액의 유효 승인이 + * 승인원장에 없는 매입(무승인 매입 의심)을 안티조인 두 번의 집계로 뽑아 + * 의심비중 기반 심각도(CLEAN/MINOR/MAJOR)를 판정한다. + * Usage: au_noauth_detect_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_noauth_detect_batch.h" + +int aub_noauth_severity(long suspects, long purchases) +{ + if (suspects == 0 || purchases == 0) return 0; + if (suspects * 100 >= purchases * 5) return 2; /* 5% 이상이면 MAJOR */ + return 1; +} + +int main(int argc, char **argv) +{ + static const char *sev_name[] = { "CLEAN", "MINOR", "MAJOR" }; + au_noauth_detect_ctx_t ctx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + int sev; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_all = 0, h_bad = 0, h_badamt = 0; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*) INTO :h_all FROM purchase + WHERE biz_date = :h_bizdate AND status NOT IN ('C','V'); + if (sqlca.sqlcode < 0) { fprintf(stderr, "COUNT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + EXEC SQL SELECT count(*), coalesce(sum(p.amount),0) INTO :h_bad, :h_badamt + FROM purchase p + WHERE p.biz_date = :h_bizdate AND p.status NOT IN ('C','V') + AND NOT EXISTS (SELECT 1 FROM au_authorization a + WHERE a.merchant_id = p.merchant_id + AND a.biz_date = p.biz_date + AND a.amount = p.amount + AND a.status IN ('A','M','S')); + if (sqlca.sqlcode < 0) { fprintf(stderr, "DETECT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + ctx.purchases = h_all; + ctx.suspects = h_bad; + ctx.suspect_gross = h_badamt; + sev = aub_noauth_severity(ctx.suspects, ctx.purchases); + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_noauth_detect_batch COMMIT: bizdate=%s 매입=%ld 무승인의심=%ld건 %ld원 심각도=%s\n", + ctx.biz_date, ctx.purchases, ctx.suspects, ctx.suspect_gross, sev_name[sev]); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_preauth_convert_batch.pgc b/app/src/au/batch/au_preauth_convert_batch.pgc new file mode 100644 index 0000000..47638b7 --- /dev/null +++ b/app/src/au/batch/au_preauth_convert_batch.pgc @@ -0,0 +1,71 @@ +/* + * au_preauth_convert_batch.pgc - au 만기 선승인 처분 배치 (커서 + 방침 분기, XA). + * + * 기준일 이전에 잡힌 미확정(P) 선승인을 커서로 훑어 방침에 따라 처분한다: + * 고액(문턱 이상)은 자동확정(C)해 승인 파이프라인으로 넘기고, 나머지는 + * 자동취소(X)로 홀드를 해제하며 풀린 금액을 적산한다. ONE XA transaction. + * Usage: au_preauth_convert_batch [기준일 YYYY-MM-DD] [확정문턱금액] + */ +#include +#include +#include +#include +#include "au_preauth_convert_batch.h" + +int aub_preauth_should_confirm(long amount, long threshold) +{ + return (amount >= threshold) ? 1 : 0; +} + +int main(int argc, char **argv) +{ + au_preauth_convert_ctx_t ctx; + const char *cutoff = (argc > 1) ? argv[1] : "2026-07-12"; + long threshold = (argc > 2) ? atol(argv[2]) : 500000; + + EXEC SQL BEGIN DECLARE SECTION; + char h_cutoff[16]; + long h_pid, h_amt; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(ctx.cutoff_date, cutoff, sizeof(ctx.cutoff_date)-1); + if (threshold <= 0) threshold = 500000; + strncpy(h_cutoff, cutoff, sizeof(h_cutoff)-1); h_cutoff[sizeof(h_cutoff)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE cpcb CURSOR FOR + SELECT preauth_id, amount FROM au_preauth + WHERE status = 'P' AND biz_date < :h_cutoff + ORDER BY preauth_id; + EXEC SQL OPEN cpcb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH cpcb INTO :h_pid, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cpcb; tpabort(0); return 1; } + + if (aub_preauth_should_confirm(h_amt, threshold)) { + EXEC SQL UPDATE au_preauth SET status = 'C' WHERE preauth_id = :h_pid; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cpcb; tpabort(0); return 1; } + ctx.confirmed++; + } else { + EXEC SQL UPDATE au_preauth SET status = 'X' WHERE preauth_id = :h_pid; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cpcb; tpabort(0); return 1; } + ctx.expired++; + ctx.released_amount += h_amt; + } + } + EXEC SQL CLOSE cpcb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_preauth_convert_batch COMMIT: cutoff=%s 자동확정=%ld 자동취소=%ld 해제금액=%ld (문턱=%ld)\n", + ctx.cutoff_date, ctx.confirmed, ctx.expired, ctx.released_amount, threshold); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_standin_settle_batch.pgc b/app/src/au/batch/au_standin_settle_batch.pgc new file mode 100644 index 0000000..db70bdd --- /dev/null +++ b/app/src/au/batch/au_standin_settle_batch.pgc @@ -0,0 +1,64 @@ +/* + * au_standin_settle_batch.pgc - au 대행승인 정산반영 배치 (커서 + 개별 UPDATE, XA). + * + * 이슈어 장애 복구 후 실행: 영업일의 STANDIN 승인(A) 건을 커서로 돌며 + * 건별 검증(금액>0) 후 정산반영(S)으로 전환하고 건수/금액을 적산한다. + * ONE global XA transaction 으로 전량 원자 반영. + * Usage: au_standin_settle_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_standin_settle_batch.h" + +int aub_standin_any(const au_standin_settle_ctx_t *ctx) +{ + return (ctx->settled > 0) ? 1 : 0; +} + +int main(int argc, char **argv) +{ + au_standin_settle_ctx_t ctx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_auth, h_amt; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE cssb CURSOR FOR + SELECT auth_id, amount FROM au_authorization + WHERE biz_date = :h_bizdate AND auth_type = 'STANDIN' AND status = 'A' + ORDER BY auth_id; + EXEC SQL OPEN cssb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH cssb INTO :h_auth, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cssb; tpabort(0); return 1; } + if (h_amt <= 0) { ctx.skipped++; continue; } /* 금액 이상 건은 건너뜀 */ + EXEC SQL UPDATE au_authorization SET status = 'S', updated_at = now() + WHERE auth_id = :h_auth AND status = 'A'; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cssb; tpabort(0); return 1; } + ctx.settled++; + ctx.settled_gross += h_amt; + } + EXEC SQL CLOSE cssb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_standin_settle_batch COMMIT: bizdate=%s 정산반영=%ld건 %ld원 skip=%ld any=%d\n", + ctx.biz_date, ctx.settled, ctx.settled_gross, ctx.skipped, aub_standin_any(&ctx)); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_velocity_flag_batch.pgc b/app/src/au/batch/au_velocity_flag_batch.pgc new file mode 100644 index 0000000..2ea9ad0 --- /dev/null +++ b/app/src/au/batch/au_velocity_flag_batch.pgc @@ -0,0 +1,71 @@ +/* + * au_velocity_flag_batch.pgc - au velocity 위반 카드 일괄 적발 배치 (커서 + INSERT, XA). + * + * 영업일에 임계 건수를 초과 승인한 카드를 HAVING 커서로 골라, 카드별로 + * 부정거래 로그(au_fraud_log)에 RULE_VELOCITY_DAY 사유의 적발 행을 남긴다. + * 모두 ONE global XA transaction 아래에서 수행된다. + * Usage: au_velocity_flag_batch [YYYY-MM-DD] [임계건수] + */ +#include +#include +#include +#include +#include "au_velocity_flag_batch.h" + +long aub_velocity_score(long cnt, long threshold) +{ + long s = 650 + (cnt - threshold) * 15; + return (s > 999) ? 999 : s; +} + +int main(int argc, char **argv) +{ + au_velocity_flag_ctx_t ctx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_card[24]; + long h_thr, h_cnt, h_fid, h_score; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_date)-1); + ctx.threshold = (argc > 2) ? atol(argv[2]) : 10; + if (ctx.threshold <= 0) ctx.threshold = 10; + h_thr = ctx.threshold; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE cvfb CURSOR FOR + SELECT card_no, count(*) FROM au_authorization + WHERE biz_date = :h_bizdate + GROUP BY card_no HAVING count(*) > :h_thr + ORDER BY count(*) DESC; + EXEC SQL OPEN cvfb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH cvfb INTO :h_card, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cvfb; tpabort(0); return 1; } + ctx.flagged_cards++; + h_score = aub_velocity_score(h_cnt, h_thr); + EXEC SQL SELECT nextval('au_fraud_seq') INTO :h_fid; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cvfb; tpabort(0); return 1; } + EXEC SQL INSERT INTO au_fraud_log (fraud_id, auth_id, card_no, score, reason, biz_date) + VALUES (:h_fid, 0, :h_card, :h_score, 'RULE_VELOCITY_DAY', :h_bizdate); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cvfb; tpabort(0); return 1; } + ctx.logged++; + } + EXEC SQL CLOSE cvfb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_velocity_flag_batch COMMIT: bizdate=%s 임계=%ld건 적발카드=%ld 기록=%ld\n", + ctx.biz_date, ctx.threshold, ctx.flagged_cards, ctx.logged); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/dbio/au_blacklist_hit_batch.h b/app/src/au/dbio/au_blacklist_hit_batch.h new file mode 100644 index 0000000..ff5b063 --- /dev/null +++ b/app/src/au/dbio/au_blacklist_hit_batch.h @@ -0,0 +1,18 @@ +/* + * au_blacklist_hit_batch.h - copybook for au_blacklist_hit_batch (XA client). + * 블랙리스트 카드 승인시도 검출 컨텍스트. Kept in dbio/ for -I resolution. + */ +#ifndef AU_BLACKLIST_HIT_BATCH_H +#define AU_BLACKLIST_HIT_BATCH_H + +typedef struct { + char biz_date[16]; + long hit_cards; /* 시도 적발 카드수 */ + long hit_auths; /* 시도 승인건수 */ + long logged; /* fraud_log 신규 기록 */ +} au_blacklist_hit_ctx_t; + +/* 블랙리스트 히트는 무조건 만점(999) 점수. */ +long aub_blacklist_hit_score(void); + +#endif /* AU_BLACKLIST_HIT_BATCH_H */ diff --git a/app/src/au/dbio/au_cancel_reproc_batch.h b/app/src/au/dbio/au_cancel_reproc_batch.h new file mode 100644 index 0000000..b53b7d6 --- /dev/null +++ b/app/src/au/dbio/au_cancel_reproc_batch.h @@ -0,0 +1,19 @@ +/* + * au_cancel_reproc_batch.h - copybook for au_cancel_reproc_batch (XA client). + * 미반영 승인취소 일괄 재처리 컨텍스트. Kept in dbio/ for -I resolution. + */ +#ifndef AU_CANCEL_REPROC_BATCH_H +#define AU_CANCEL_REPROC_BATCH_H + +typedef struct { + char biz_date[16]; + long reproc_full; /* FULL/기타 -> C 전환 */ + long reproc_void; /* VOID -> V 전환 */ + long reproc_partial; /* PARTIAL 감액 반영 */ + long skipped; /* 상태 부적합으로 건너뜀 */ +} au_cancel_reproc_ctx_t; + +/* 재처리 합계. */ +long aub_cancel_reproc_total(const au_cancel_reproc_ctx_t *ctx); + +#endif /* AU_CANCEL_REPROC_BATCH_H */ diff --git a/app/src/au/dbio/au_delay_watch_batch.h b/app/src/au/dbio/au_delay_watch_batch.h new file mode 100644 index 0000000..0fa70b5 --- /dev/null +++ b/app/src/au/dbio/au_delay_watch_batch.h @@ -0,0 +1,19 @@ +/* + * au_delay_watch_batch.h - copybook for au_delay_watch_batch (XA client). + * 승인->매입 지연 감시 컨텍스트 + 지연구간 라벨 헬퍼. Kept in dbio/ for -I resolution. + */ +#ifndef AU_DELAY_WATCH_BATCH_H +#define AU_DELAY_WATCH_BATCH_H + +typedef struct { + long pending; /* 미매입 승인(A) 잔량 */ + long avg_age_sec; /* 평균 경과초 */ + long max_age_sec; /* 최대 경과초 */ + long over_1h; /* 1시간 초과 체류 건수 */ + long over_1d; /* 24시간 초과 체류 건수 */ +} au_delay_watch_ctx_t; + +/* 경과초를 지연구간 라벨(FRESH/AGING/STALE/CRITICAL)로 변환. */ +const char *aub_delay_bucket(long age_sec); + +#endif /* AU_DELAY_WATCH_BATCH_H */ diff --git a/app/src/au/dbio/au_dup_auth_batch.h b/app/src/au/dbio/au_dup_auth_batch.h new file mode 100644 index 0000000..0bd0b54 --- /dev/null +++ b/app/src/au/dbio/au_dup_auth_batch.h @@ -0,0 +1,18 @@ +/* + * au_dup_auth_batch.h - copybook for au_dup_auth_batch (XA client). + * 중복승인 검출 컨텍스트 + 초과분 계산 헬퍼. Kept in dbio/ for -I resolution. + */ +#ifndef AU_DUP_AUTH_BATCH_H +#define AU_DUP_AUTH_BATCH_H + +typedef struct { + char biz_date[16]; + long dup_groups; /* 중복 그룹수 (카드+가맹점+금액) */ + long extra_auths; /* 원거래를 뺀 잉여 승인건수 */ + long extra_gross; /* 잉여 승인 금액합 */ +} au_dup_auth_ctx_t; + +/* 그룹 건수에서 원거래 1건을 뺀 잉여 건수. */ +long aub_dup_extra(long group_cnt); + +#endif /* AU_DUP_AUTH_BATCH_H */ diff --git a/app/src/au/dbio/au_hourly_stat_batch.h b/app/src/au/dbio/au_hourly_stat_batch.h new file mode 100644 index 0000000..f8ac401 --- /dev/null +++ b/app/src/au/dbio/au_hourly_stat_batch.h @@ -0,0 +1,19 @@ +/* + * au_hourly_stat_batch.h - copybook for au_hourly_stat_batch (XA client). + * 시간대별 승인 통계 컨텍스트 (24칸 히스토그램). Kept in dbio/ for -I resolution. + */ +#ifndef AU_HOURLY_STAT_BATCH_H +#define AU_HOURLY_STAT_BATCH_H + +typedef struct { + char biz_date[16]; + long by_hour[24]; /* 시간대별 건수 히스토그램 */ + long total; + int peak_hour; + long peak_count; +} au_hourly_stat_ctx_t; + +/* 히스토그램에서 피크 시간대를 찾아 ctx.peak_* 를 채운다. */ +void aub_hourly_find_peak(au_hourly_stat_ctx_t *ctx); + +#endif /* AU_HOURLY_STAT_BATCH_H */ diff --git a/app/src/au/dbio/au_issuer_mix_batch.h b/app/src/au/dbio/au_issuer_mix_batch.h new file mode 100644 index 0000000..dae220c --- /dev/null +++ b/app/src/au/dbio/au_issuer_mix_batch.h @@ -0,0 +1,20 @@ +/* + * au_issuer_mix_batch.h - copybook for au_issuer_mix_batch (XA client). + * 발급사(카드망)별 구성비 집계 컨텍스트 + BIN 선두자리 -> 망 이름 헬퍼. + * Kept in dbio/ for -I resolution. + */ +#ifndef AU_ISSUER_MIX_BATCH_H +#define AU_ISSUER_MIX_BATCH_H + +typedef struct { + char biz_date[16]; + long networks; /* 관측된 카드망 수 */ + long total; /* 전체 승인건수 */ + char top_network[16]; + long top_count; +} au_issuer_mix_ctx_t; + +/* 카드번호 선두 한 자리를 카드망 이름으로 매핑. */ +const char *aub_issuer_network(char lead); + +#endif /* AU_ISSUER_MIX_BATCH_H */ diff --git a/app/src/au/dbio/au_limit_restore_batch.h b/app/src/au/dbio/au_limit_restore_batch.h new file mode 100644 index 0000000..9cd02c2 --- /dev/null +++ b/app/src/au/dbio/au_limit_restore_batch.h @@ -0,0 +1,16 @@ +/* + * au_limit_restore_batch.h - copybook for au_limit_restore_batch (XA client). + * 임시한도 일괄 복원 컨텍스트. Kept in dbio/ for -I resolution. + */ +#ifndef AU_LIMIT_RESTORE_BATCH_H +#define AU_LIMIT_RESTORE_BATCH_H + +typedef struct { + long restored_cards; /* 복원한 카드수 */ + long restored_amount; /* 걷어낸 임시한도 총액 */ +} au_limit_restore_ctx_t; + +/* 복원 결과 요약 한 줄을 out 에 만든다 (버퍼 cap 바이트). */ +void aub_limit_restore_summary(const au_limit_restore_ctx_t *ctx, char *out, int cap); + +#endif /* AU_LIMIT_RESTORE_BATCH_H */ diff --git a/app/src/au/dbio/au_merch_rank_batch.h b/app/src/au/dbio/au_merch_rank_batch.h new file mode 100644 index 0000000..afb5353 --- /dev/null +++ b/app/src/au/dbio/au_merch_rank_batch.h @@ -0,0 +1,21 @@ +/* + * au_merch_rank_batch.h - copybook for au_merch_rank_batch (XA client). + * 가맹점 승인 순위 TOP-N 컨텍스트. Kept in dbio/ for -I resolution. + */ +#ifndef AU_MERCH_RANK_BATCH_H +#define AU_MERCH_RANK_BATCH_H + +#define AU_MERCH_RANK_TOP 5 + +typedef struct { + char biz_date[16]; + long listed; /* 출력한 순위 줄 수 */ + long top_gross; /* 1위 가맹점 금액 */ + char top_merchant[64]; /* 1위 가맹점 */ + long grand_gross; /* TOP-N 합계 금액 */ +} au_merch_rank_ctx_t; + +/* 1위 대비 점유율(%)을 계산. */ +long aub_rank_share_pct(long gross, long top_gross); + +#endif /* AU_MERCH_RANK_BATCH_H */ diff --git a/app/src/au/dbio/au_night_auth_batch.h b/app/src/au/dbio/au_night_auth_batch.h new file mode 100644 index 0000000..83fae61 --- /dev/null +++ b/app/src/au/dbio/au_night_auth_batch.h @@ -0,0 +1,19 @@ +/* + * au_night_auth_batch.h - copybook for au_night_auth_batch (XA client). + * 심야 승인 집계 컨텍스트 + 심야비중 등급 헬퍼. Kept in dbio/ for -I resolution. + */ +#ifndef AU_NIGHT_AUTH_BATCH_H +#define AU_NIGHT_AUTH_BATCH_H + +typedef struct { + char biz_date[16]; + long night_count; + long night_gross; + long day_count; + long night_bps; /* 전체 대비 심야건수 비중 (bps) */ +} au_night_auth_ctx_t; + +/* 심야비중 bps 를 등급 문자열로 변환 (LOW/ELEVATED/ALERT). */ +const char *aub_night_grade(long bps); + +#endif /* AU_NIGHT_AUTH_BATCH_H */ diff --git a/app/src/au/dbio/au_noauth_detect_batch.h b/app/src/au/dbio/au_noauth_detect_batch.h new file mode 100644 index 0000000..36f0ca5 --- /dev/null +++ b/app/src/au/dbio/au_noauth_detect_batch.h @@ -0,0 +1,18 @@ +/* + * au_noauth_detect_batch.h - copybook for au_noauth_detect_batch (XA client). + * 무승인 매입 검출 컨텍스트 + 심각도 헬퍼. Kept in dbio/ for -I resolution. + */ +#ifndef AU_NOAUTH_DETECT_BATCH_H +#define AU_NOAUTH_DETECT_BATCH_H + +typedef struct { + char biz_date[16]; + long purchases; /* 검사한 매입건수 */ + long suspects; /* 무승인 의심건수 */ + long suspect_gross; /* 의심 금액합 */ +} au_noauth_detect_ctx_t; + +/* 의심비중으로 심각도 판정 (0=CLEAN, 1=MINOR, 2=MAJOR). */ +int aub_noauth_severity(long suspects, long purchases); + +#endif /* AU_NOAUTH_DETECT_BATCH_H */ diff --git a/app/src/au/dbio/au_preauth_convert_batch.h b/app/src/au/dbio/au_preauth_convert_batch.h new file mode 100644 index 0000000..ea13a78 --- /dev/null +++ b/app/src/au/dbio/au_preauth_convert_batch.h @@ -0,0 +1,18 @@ +/* + * au_preauth_convert_batch.h - copybook for au_preauth_convert_batch (XA client). + * 만기 선승인 처분(자동확정/자동취소) 컨텍스트. Kept in dbio/ for -I resolution. + */ +#ifndef AU_PREAUTH_CONVERT_BATCH_H +#define AU_PREAUTH_CONVERT_BATCH_H + +typedef struct { + char cutoff_date[16]; /* 이 날짜 이전 P 건이 처분 대상 */ + long confirmed; /* 고액 -> C 자동확정 */ + long expired; /* 일반 -> X 자동취소 */ + long released_amount; /* 취소로 풀린 홀드 금액 */ +} au_preauth_convert_ctx_t; + +/* 처분 방침: 금액이 문턱 이상이면 확정(1), 아니면 취소(0). */ +int aub_preauth_should_confirm(long amount, long threshold); + +#endif /* AU_PREAUTH_CONVERT_BATCH_H */ diff --git a/app/src/au/dbio/au_standin_settle_batch.h b/app/src/au/dbio/au_standin_settle_batch.h new file mode 100644 index 0000000..427cb22 --- /dev/null +++ b/app/src/au/dbio/au_standin_settle_batch.h @@ -0,0 +1,18 @@ +/* + * au_standin_settle_batch.h - copybook for au_standin_settle_batch (XA client). + * 대행승인(stand-in) 정산반영 컨텍스트. Kept in dbio/ for -I resolution. + */ +#ifndef AU_STANDIN_SETTLE_BATCH_H +#define AU_STANDIN_SETTLE_BATCH_H + +typedef struct { + char biz_date[16]; + long settled; /* A -> S 전환 건수 */ + long settled_gross; /* 전환 금액 합계 */ + long skipped; /* 금액 0 이하로 건너뛴 건수 */ +} au_standin_settle_ctx_t; + +/* 정산반영이 하나라도 있었는지 (1/0). */ +int aub_standin_any(const au_standin_settle_ctx_t *ctx); + +#endif /* AU_STANDIN_SETTLE_BATCH_H */ diff --git a/app/src/au/dbio/au_velocity_flag_batch.h b/app/src/au/dbio/au_velocity_flag_batch.h new file mode 100644 index 0000000..cc90e79 --- /dev/null +++ b/app/src/au/dbio/au_velocity_flag_batch.h @@ -0,0 +1,18 @@ +/* + * au_velocity_flag_batch.h - copybook for au_velocity_flag_batch (XA client). + * velocity 위반 카드 적발 컨텍스트 + 점수 헬퍼. Kept in dbio/ for -I resolution. + */ +#ifndef AU_VELOCITY_FLAG_BATCH_H +#define AU_VELOCITY_FLAG_BATCH_H + +typedef struct { + char biz_date[16]; + long threshold; /* 일일 건수 임계값 */ + long flagged_cards; /* 적발 카드수 */ + long logged; /* 신규 기록한 fraud_log 건수 */ +} au_velocity_flag_ctx_t; + +/* 위반 건수를 fraud score 로 환산 (650 + 초과건수*15, 상한 999). */ +long aub_velocity_score(long cnt, long threshold); + +#endif /* AU_VELOCITY_FLAG_BATCH_H */ diff --git a/app/src/au/svc/APPROVAL_RATE.h b/app/src/au/svc/APPROVAL_RATE.h new file mode 100644 index 0000000..1f8b2bb --- /dev/null +++ b/app/src/au/svc/APPROVAL_RATE.h @@ -0,0 +1,22 @@ +/* + * APPROVAL_RATE.h - au 서비스 APPROVAL_RATE 카피북 (copybook / record header). + * Request fields: T_MERCHANT(가맹점), T_BIZDATE(영업일) + * Response fields: T_ARG1(승인율 bps), T_COUNT(총 접수건수), T_ARG2(승인건수), + * T_RC(0=정상, 1=승인율 80% 미만 경보) + */ +#ifndef APPROVAL_RATE_H +#define APPROVAL_RATE_H + +#include "au_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long rate_bps; + long attempts; + long approved; +} au_approval_rate_rec_t; + +void APPROVAL_RATE(TPSVCINFO *p); + +#endif /* APPROVAL_RATE_H */ diff --git a/app/src/au/svc/APPROVAL_RATE.pgc b/app/src/au/svc/APPROVAL_RATE.pgc new file mode 100644 index 0000000..24289f9 --- /dev/null +++ b/app/src/au/svc/APPROVAL_RATE.pgc @@ -0,0 +1,42 @@ +/* + * APPROVAL_RATE.pgc - au 모듈 서비스 APPROVAL_RATE (one service = one file). + * 가맹점 승인율 조회: 영업일 접수 대비 승인 성공(A/M/S) 비율을 bps 로 + * 환산하고, 표본 10건 이상에서 80% 를 밑돌면 승인율 저조 경보(RC=1)를 낸다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "APPROVAL_RATE.h" + +void APPROVAL_RATE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + long bps = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_all = 0, h_ok = 0; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (merch[0] == 0 || bizdate[0] == 0) FAIL(b); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*), count(*) FILTER (WHERE status IN ('A','M','S')) + INTO :h_all, :h_ok + FROM au_authorization + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + if (h_all > 0) bps = (h_ok * 10000L) / h_all; + setl(b, T_ARG1, bps); + setl(b, T_COUNT, h_all); + setl(b, T_ARG2, h_ok); + if (h_all >= 10 && bps < 8000) { + userlog("APPROVAL_RATE 저조 경보 merch=%s %ldbps (%ld/%ld)", merch, bps, h_ok, h_all); + setl(b, T_RC, 1); + } else { + setl(b, T_RC, 0); + } + OK(b); +} diff --git a/app/src/au/svc/AUTH_AMT_ADJUST.h b/app/src/au/svc/AUTH_AMT_ADJUST.h new file mode 100644 index 0000000..b38b027 --- /dev/null +++ b/app/src/au/svc/AUTH_AMT_ADJUST.h @@ -0,0 +1,19 @@ +/* + * AUTH_AMT_ADJUST.h - au 서비스 AUTH_AMT_ADJUST 카피북 (copybook / record header). + * Request fields: T_ID1(승인번호), T_DELTA(증감액, +증액/-감액) + * Response fields: T_AMOUNT(조정 후 금액), T_RC(0=조정, 1=상태 부적합, 2=한도부족) + */ +#ifndef AUTH_AMT_ADJUST_H +#define AUTH_AMT_ADJUST_H + +#include "au_svc.h" + +typedef struct { + long auth_id; + long delta; + long new_amount; +} au_auth_amt_adjust_rec_t; + +void AUTH_AMT_ADJUST(TPSVCINFO *p); + +#endif /* AUTH_AMT_ADJUST_H */ diff --git a/app/src/au/svc/AUTH_AMT_ADJUST.pgc b/app/src/au/svc/AUTH_AMT_ADJUST.pgc new file mode 100644 index 0000000..2ab794f --- /dev/null +++ b/app/src/au/svc/AUTH_AMT_ADJUST.pgc @@ -0,0 +1,57 @@ +/* + * AUTH_AMT_ADJUST.pgc - au 모듈 서비스 AUTH_AMT_ADJUST (one service = one file). + * 승인금액 조정(incremental/decremental auth): 호텔·주유소처럼 최종 금액이 + * 달라지는 업종의 승인(A) 건 금액을 증감한다. 증액은 잔여한도를 검사해 + * 부족하면 RC=2 로 반려하고, 조정분만큼 카드 사용액도 함께 보정한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "AUTH_AMT_ADJUST.h" + +void AUTH_AMT_ADJUST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long delta, lim = 0, used = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_id, h_delta, h_amt = 0; + char h_card[24], h_status[4]; + EXEC SQL END DECLARE SECTION; + + h_id = getl(b, T_ID1); + delta = getl(b, T_DELTA); + if (h_id <= 0 || delta == 0) { userlog("AUTH_AMT_ADJUST: 인자 오류"); FAIL(b); } + memset(h_card, 0, sizeof(h_card)); + memset(h_status, 0, sizeof(h_status)); + + EXEC SQL SELECT card_no, status, amount INTO :h_card, :h_status, :h_amt + FROM au_authorization WHERE auth_id = :h_id; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + + if (h_status[0] != 'A') { /* 매입확정/취소 후에는 조정 불가 */ + setl(b, T_AMOUNT, h_amt); + setl(b, T_RC, 1); + OK(b); + } + if (h_amt + delta <= 0) FAIL(b); /* 전액 이하로의 감액은 취소로 처리해야 함 */ + + if (delta > 0) { + if (audb_limit_get(h_card, &lim, &used) < 0) FAIL(b); + if (used + delta > lim) { + userlog("AUTH_AMT_ADJUST 한도부족 auth=%ld delta=%ld", h_id, delta); + setl(b, T_AMOUNT, h_amt); + setl(b, T_RC, 2); + OK(b); + } + } + + h_delta = delta; + EXEC SQL UPDATE au_authorization + SET amount = amount + :h_delta, updated_at = now() + WHERE auth_id = :h_id AND status = 'A'; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + if (audb_limit_dec(h_card, delta) < 0) FAIL(b); /* 음수 delta 면 사용액 환원 */ + + userlog("AUTH_AMT_ADJUST auth=%ld %ld -> %ld", h_id, h_amt, h_amt + delta); + setl(b, T_AMOUNT, h_amt + delta); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/au/svc/AUTH_DELAY_MON.h b/app/src/au/svc/AUTH_DELAY_MON.h new file mode 100644 index 0000000..423edf2 --- /dev/null +++ b/app/src/au/svc/AUTH_DELAY_MON.h @@ -0,0 +1,22 @@ +/* + * AUTH_DELAY_MON.h - au 서비스 AUTH_DELAY_MON 카피북 (copybook / record header). + * Request fields: T_BIZDATE(영업일), T_ARG1(지연 기준 분, 기본 30) + * Response fields: T_COUNT(지연 승인건수), T_ARG2(평균 경과초), T_ARG3(최대 경과초), + * T_RC(1=지연건 존재) + */ +#ifndef AUTH_DELAY_MON_H +#define AUTH_DELAY_MON_H + +#include "au_svc.h" + +typedef struct { + char biz_date[16]; + long threshold_min; + long delayed_count; + long avg_age_sec; + long max_age_sec; +} au_auth_delay_rec_t; + +void AUTH_DELAY_MON(TPSVCINFO *p); + +#endif /* AUTH_DELAY_MON_H */ diff --git a/app/src/au/svc/AUTH_DELAY_MON.pgc b/app/src/au/svc/AUTH_DELAY_MON.pgc new file mode 100644 index 0000000..a4fe96d --- /dev/null +++ b/app/src/au/svc/AUTH_DELAY_MON.pgc @@ -0,0 +1,39 @@ +/* + * AUTH_DELAY_MON.pgc - au 모듈 서비스 AUTH_DELAY_MON (one service = one file). + * 승인 지연 모니터: 승인(A) 상태로 기준 시간(분)을 넘겨 매입확정이 안 된 + * 체류 건을 세고, EPOCH 환산으로 평균/최대 경과초를 산출해 관제에 알린다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "AUTH_DELAY_MON.h" + +void AUTH_DELAY_MON(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_min, h_cnt = 0, h_avg = 0, h_max = 0; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) FAIL(b); + h_min = getl(b, T_ARG1); if (h_min <= 0) h_min = 30; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*), + CAST(coalesce(avg(EXTRACT(EPOCH FROM now() - created_at)),0) AS bigint), + CAST(coalesce(max(EXTRACT(EPOCH FROM now() - created_at)),0) AS bigint) + INTO :h_cnt, :h_avg, :h_max + FROM au_authorization + WHERE biz_date = :h_bizdate AND status = 'A' AND captured_at IS NULL + AND created_at < now() - CAST(:h_min AS bigint) * interval '1 minute'; + if (sqlca.sqlcode < 0) FAIL(b); + + if (h_cnt > 0) + userlog("AUTH_DELAY_MON 지연 %ld건 (기준 %ld분, 최대 %ld초)", h_cnt, h_min, h_max); + setl(b, T_COUNT, h_cnt); + setl(b, T_ARG2, h_avg); + setl(b, T_ARG3, h_max); + setl(b, T_RC, (h_cnt > 0) ? 1 : 0); + OK(b); +} diff --git a/app/src/au/svc/AUTH_DUP_CHK.h b/app/src/au/svc/AUTH_DUP_CHK.h new file mode 100644 index 0000000..ed42ffa --- /dev/null +++ b/app/src/au/svc/AUTH_DUP_CHK.h @@ -0,0 +1,22 @@ +/* + * AUTH_DUP_CHK.h - au 서비스 AUTH_DUP_CHK 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_MERCHANT(가맹점), T_AMOUNT(금액) + * Response fields: T_RC(0=중복 없음, 1=중복승인 의심), T_ID1(직전 중복 승인번호), + * T_ARG1(5분내 동일거래 건수) + */ +#ifndef AUTH_DUP_CHK_H +#define AUTH_DUP_CHK_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + char merchant_id[64]; + long amount; + long dup_count; + long last_dup_auth; +} au_auth_dup_chk_rec_t; + +void AUTH_DUP_CHK(TPSVCINFO *p); + +#endif /* AUTH_DUP_CHK_H */ diff --git a/app/src/au/svc/AUTH_DUP_CHK.pgc b/app/src/au/svc/AUTH_DUP_CHK.pgc new file mode 100644 index 0000000..7ee9fe4 --- /dev/null +++ b/app/src/au/svc/AUTH_DUP_CHK.pgc @@ -0,0 +1,43 @@ +/* + * AUTH_DUP_CHK.pgc - au 모듈 서비스 AUTH_DUP_CHK (one service = one file). + * 중복승인 검출: 단말 재시도 등으로 동일 카드+가맹점+금액 조합이 직전 5분내 + * 이미 승인돼 있는지 검사한다. 중복이면 가장 최근 원승인 번호를 돌려줘 + * 단말이 신규 승인 대신 원승인 응답을 재사용하도록 한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "AUTH_DUP_CHK.h" + +void AUTH_DUP_CHK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24], merch[64]; + EXEC SQL BEGIN DECLARE SECTION; + long h_amt, h_cnt = 0, h_last = 0; + char h_card[24], h_merch[64]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + gets_(b, T_MERCHANT, merch, sizeof(merch)); + h_amt = getl(b, T_AMOUNT); + if (card[0] == 0 || merch[0] == 0 || h_amt <= 0) FAIL(b); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT count(*), coalesce(max(auth_id),0) INTO :h_cnt, :h_last + FROM au_authorization + WHERE card_no = :h_card AND merchant_id = :h_merch AND amount = :h_amt + AND status = 'A' + AND created_at >= now() - interval '5 minutes'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_ARG1, h_cnt); + if (h_cnt > 0) { + userlog("AUTH_DUP_CHK 중복 의심 card=%s merch=%s amt=%ld 원승인=%ld", card, merch, h_amt, h_last); + setl(b, T_ID1, h_last); + setl(b, T_RC, 1); + } else { + setl(b, T_ID1, 0); + setl(b, T_RC, 0); + } + OK(b); +} diff --git a/app/src/au/svc/AUTH_EXPIRE_CHK.h b/app/src/au/svc/AUTH_EXPIRE_CHK.h new file mode 100644 index 0000000..5114e05 --- /dev/null +++ b/app/src/au/svc/AUTH_EXPIRE_CHK.h @@ -0,0 +1,22 @@ +/* + * AUTH_EXPIRE_CHK.h - au 서비스 AUTH_EXPIRE_CHK 카피북 (copybook / record header). + * Request fields: T_ARG1(만기 기준일수, 기본 14) + * Response fields: T_COUNT(만기 경과 미매입 건수), T_GROSS(만기 금액합), + * T_ID1(가장 오래된 승인번호), T_STR1(가장 오래된 영업일), T_RC(1=만기건 존재) + */ +#ifndef AUTH_EXPIRE_CHK_H +#define AUTH_EXPIRE_CHK_H + +#include "au_svc.h" + +typedef struct { + long grace_days; + long expired_count; + long expired_gross; + long oldest_auth_id; + char oldest_date[16]; +} au_auth_expire_rec_t; + +void AUTH_EXPIRE_CHK(TPSVCINFO *p); + +#endif /* AUTH_EXPIRE_CHK_H */ diff --git a/app/src/au/svc/AUTH_EXPIRE_CHK.pgc b/app/src/au/svc/AUTH_EXPIRE_CHK.pgc new file mode 100644 index 0000000..6c745a1 --- /dev/null +++ b/app/src/au/svc/AUTH_EXPIRE_CHK.pgc @@ -0,0 +1,44 @@ +/* + * AUTH_EXPIRE_CHK.pgc - au 모듈 서비스 AUTH_EXPIRE_CHK (one service = one file). + * 미매입 승인 만기 점검: 기준일수를 넘겨도 매입확정되지 않은 승인(A) 건의 + * 규모(건수/금액)와 가장 오래된 건(승인번호/영업일)을 min 서브쿼리 조회로 + * 찾아 만기 스윕 배치의 대상 규모를 미리 알려준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "AUTH_EXPIRE_CHK.h" + +void AUTH_EXPIRE_CHK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_days, h_cnt = 0, h_amt = 0, h_oldest = 0; + char h_odate[16]; + EXEC SQL END DECLARE SECTION; + + h_days = getl(b, T_ARG1); if (h_days <= 0) h_days = 14; + memset(h_odate, 0, sizeof(h_odate)); + + EXEC SQL SELECT count(*), coalesce(sum(amount),0) + INTO :h_cnt, :h_amt + FROM au_authorization + WHERE status = 'A' AND captured_at IS NULL + AND biz_date < current_date - CAST(:h_days AS integer); + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_amt); + if (h_cnt == 0) { setl(b, T_RC, 0); setl(b, T_ID1, 0); OK(b); } + + EXEC SQL SELECT auth_id, CAST(biz_date AS text) INTO :h_oldest, :h_odate + FROM au_authorization + WHERE status = 'A' AND captured_at IS NULL + AND biz_date < current_date - CAST(:h_days AS integer) + ORDER BY biz_date, auth_id LIMIT 1; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + + userlog("AUTH_EXPIRE_CHK 만기 %ld건 %ld원 (최고 오래된 auth=%ld %s)", h_cnt, h_amt, h_oldest, h_odate); + setl(b, T_ID1, h_oldest); + Bchg(b, T_STR1, 0, h_odate, 0L); + setl(b, T_RC, 1); + OK(b); +} diff --git a/app/src/au/svc/AUTH_PARTIAL.h b/app/src/au/svc/AUTH_PARTIAL.h new file mode 100644 index 0000000..8a99c8c --- /dev/null +++ b/app/src/au/svc/AUTH_PARTIAL.h @@ -0,0 +1,22 @@ +/* + * AUTH_PARTIAL.h - au 서비스 AUTH_PARTIAL 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_MERCHANT(가맹점), T_AMOUNT(요청금액), T_BIZDATE(영업일) + * Response fields: T_ID1(승인번호), T_AMT1(승인금액), T_RC(0=전액승인, 2=부분승인, 1=거절), + * T_STR2(승인코드) + */ +#ifndef AUTH_PARTIAL_H +#define AUTH_PARTIAL_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + char merchant_id[64]; + long req_amount; + long approved_amount; + char biz_date[16]; +} au_auth_partial_rec_t; + +void AUTH_PARTIAL(TPSVCINFO *p); + +#endif /* AUTH_PARTIAL_H */ diff --git a/app/src/au/svc/AUTH_PARTIAL.pgc b/app/src/au/svc/AUTH_PARTIAL.pgc new file mode 100644 index 0000000..d9d97e6 --- /dev/null +++ b/app/src/au/svc/AUTH_PARTIAL.pgc @@ -0,0 +1,47 @@ +/* + * AUTH_PARTIAL.pgc - au 모듈 서비스 AUTH_PARTIAL (one service = one file). + * 부분승인: 잔여한도가 요청금액에 못 미치면 잔여한도만큼만 승인(RC=2)하고, + * 잔여한도가 0 이면 거절(D)로 기록한다(RC=1). 승인분만 한도를 차감한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "AUTH_PARTIAL.h" + +void AUTH_PARTIAL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24], merch[64], bizdate[16], appr[12]; + long amount, remain, grant, auth_id, lim = 0, used = 0; + + gets_(b, T_KEY1, card, sizeof(card)); + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + amount = getl(b, T_AMOUNT); + if (card[0] == 0 || amount <= 0) { userlog("AUTH_PARTIAL: 인자 오류"); FAIL(b); } + + if (audb_limit_get(card, &lim, &used) < 0) FAIL(b); + remain = lim - used; + if (remain < 0) remain = 0; + + auth_id = audb_next_auth_id(); + if (auth_id < 0) FAIL(b); + + if (remain == 0) { /* 잔여한도 전무 - 거절 기록 */ + if (audb_insert_auth(auth_id, card, merch, amount, "000000", "AUTH", "D", bizdate) < 0) FAIL(b); + setl(b, T_ID1, auth_id); + setl(b, T_AMT1, 0); + setl(b, T_RC, 1); + OK(b); + } + + grant = (amount <= remain) ? amount : remain; + sprintf(appr, "P%05ld", auth_id % 100000); + if (audb_insert_auth(auth_id, card, merch, grant, appr, "AUTH", "A", bizdate) < 0) FAIL(b); + if (audb_limit_dec(card, grant) < 0) FAIL(b); + + userlog("AUTH_PARTIAL card=%s 요청=%ld 승인=%ld (잔여한도 %ld)", card, amount, grant, remain); + setl(b, T_ID1, auth_id); + setl(b, T_AMT1, grant); + Bchg(b, T_STR2, 0, appr, 0L); + setl(b, T_RC, (grant < amount) ? 2 : 0); + OK(b); +} diff --git a/app/src/au/svc/AUTH_RESEND.h b/app/src/au/svc/AUTH_RESEND.h new file mode 100644 index 0000000..1d01f36 --- /dev/null +++ b/app/src/au/svc/AUTH_RESEND.h @@ -0,0 +1,21 @@ +/* + * AUTH_RESEND.h - au 서비스 AUTH_RESEND 카피북 (copybook / record header). + * Request fields: T_ID1(승인번호) + * Response fields: T_STR2(승인코드), T_AMOUNT(승인금액), T_STATUS(상태), + * T_RC(0=재전송, 1=재전송 불가 상태) + */ +#ifndef AUTH_RESEND_H +#define AUTH_RESEND_H + +#include "au_svc.h" + +typedef struct { + long auth_id; + char approval_code[16]; + long amount; + char status[4]; +} au_auth_resend_rec_t; + +void AUTH_RESEND(TPSVCINFO *p); + +#endif /* AUTH_RESEND_H */ diff --git a/app/src/au/svc/AUTH_RESEND.pgc b/app/src/au/svc/AUTH_RESEND.pgc new file mode 100644 index 0000000..f61b052 --- /dev/null +++ b/app/src/au/svc/AUTH_RESEND.pgc @@ -0,0 +1,42 @@ +/* + * AUTH_RESEND.pgc - au 모듈 서비스 AUTH_RESEND (one service = one file). + * 승인 재전송: 망 단절 등으로 응답을 못 받은 단말이 원승인의 승인코드를 + * 다시 받아가는 멱등 조회. 승인(A)/매입(M) 상태만 재전송을 허용하고 + * updated_at 을 갱신해 재전송 시각을 남긴다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "AUTH_RESEND.h" + +void AUTH_RESEND(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_id, h_amt = 0; + char h_appr[16], h_status[4]; + EXEC SQL END DECLARE SECTION; + + h_id = getl(b, T_ID1); + if (h_id <= 0) { userlog("AUTH_RESEND: 승인번호 누락"); FAIL(b); } + memset(h_appr, 0, sizeof(h_appr)); + memset(h_status, 0, sizeof(h_status)); + + EXEC SQL SELECT approval_code, amount, status INTO :h_appr, :h_amt, :h_status + FROM au_authorization WHERE auth_id = :h_id; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + + Bchg(b, T_STATUS, 0, h_status, 0L); + setl(b, T_AMOUNT, h_amt); + + if (h_status[0] != 'A' && h_status[0] != 'M') { /* 취소/역전/거절 건은 재전송 불가 */ + setl(b, T_RC, 1); + OK(b); + } + + EXEC SQL UPDATE au_authorization SET updated_at = now() WHERE auth_id = :h_id; + if (sqlca.sqlcode < 0) FAIL(b); + + userlog("AUTH_RESEND auth_id=%ld appr=%s 재전송", h_id, h_appr); + Bchg(b, T_STR2, 0, h_appr, 0L); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/au/svc/BIN_ROUTE_INQ.h b/app/src/au/svc/BIN_ROUTE_INQ.h new file mode 100644 index 0000000..90739bc --- /dev/null +++ b/app/src/au/svc/BIN_ROUTE_INQ.h @@ -0,0 +1,21 @@ +/* + * BIN_ROUTE_INQ.h - au 서비스 BIN_ROUTE_INQ 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호 또는 BIN 6자리) + * Response fields: T_STR1(라우팅 게이트웨이), T_STR2(BIN), T_COUNT(당일 동일 BIN 승인건수), + * T_RC(0=라우팅 확정, 1=미지원 BIN) + */ +#ifndef BIN_ROUTE_INQ_H +#define BIN_ROUTE_INQ_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + char bin[8]; + char gateway[16]; + long bin_today; +} au_bin_route_rec_t; + +void BIN_ROUTE_INQ(TPSVCINFO *p); + +#endif /* BIN_ROUTE_INQ_H */ diff --git a/app/src/au/svc/BIN_ROUTE_INQ.pgc b/app/src/au/svc/BIN_ROUTE_INQ.pgc new file mode 100644 index 0000000..5f1602d --- /dev/null +++ b/app/src/au/svc/BIN_ROUTE_INQ.pgc @@ -0,0 +1,48 @@ +/* + * BIN_ROUTE_INQ.pgc - au 모듈 서비스 BIN_ROUTE_INQ (one service = one file). + * BIN별 라우팅 조회: 카드번호 선두 6자리(BIN)의 첫 자리로 매입 게이트웨이를 + * 결정하고(3=AMEX, 4=VISA, 5=MC, 6=LOCAL, 9=TESTNET), 당일 동일 BIN 대역의 + * 승인 트래픽 건수를 LIKE 프리픽스 집계로 함께 내려준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "BIN_ROUTE_INQ.h" + +void BIN_ROUTE_INQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24], bin[8]; + const char *gw; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0; + char h_pat[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + if (strlen(card) < 6) { userlog("BIN_ROUTE_INQ: BIN 6자리 미만"); FAIL(b); } + memcpy(bin, card, 6); bin[6] = 0; + + switch (bin[0]) { + case '3': gw = "AMEX-GW"; break; + case '4': gw = "VISA-GW"; break; + case '5': gw = "MC-GW"; break; + case '6': gw = "LOCAL-GW"; break; + case '9': gw = "TESTNET"; break; + default: gw = ""; break; + } + + sprintf(h_pat, "%s%%", bin); + EXEC SQL SELECT count(*) INTO :h_cnt FROM au_authorization + WHERE card_no LIKE :h_pat AND biz_date = current_date; + if (sqlca.sqlcode < 0) FAIL(b); + + Bchg(b, T_STR2, 0, bin, 0L); + setl(b, T_COUNT, h_cnt); + if (gw[0] == 0) { /* 미지원 BIN 대역 */ + Bchg(b, T_STR1, 0, "UNROUTED", 0L); + setl(b, T_RC, 1); + OK(b); + } + Bchg(b, T_STR1, 0, (char *)gw, 0L); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/au/svc/BLACKLIST_ADD.h b/app/src/au/svc/BLACKLIST_ADD.h new file mode 100644 index 0000000..a7e1a30 --- /dev/null +++ b/app/src/au/svc/BLACKLIST_ADD.h @@ -0,0 +1,19 @@ +/* + * BLACKLIST_ADD.h - au 서비스 BLACKLIST_ADD 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_REASON(등재사유) + * Response fields: T_RC(0=신규등재, 1=사유 갱신), T_COUNT(등재 후 총 블랙리스트 건수) + */ +#ifndef BLACKLIST_ADD_H +#define BLACKLIST_ADD_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + char reason[128]; + long total_after; +} au_blacklist_add_rec_t; + +void BLACKLIST_ADD(TPSVCINFO *p); + +#endif /* BLACKLIST_ADD_H */ diff --git a/app/src/au/svc/BLACKLIST_ADD.pgc b/app/src/au/svc/BLACKLIST_ADD.pgc new file mode 100644 index 0000000..dfdb505 --- /dev/null +++ b/app/src/au/svc/BLACKLIST_ADD.pgc @@ -0,0 +1,46 @@ +/* + * BLACKLIST_ADD.pgc - au 모듈 서비스 BLACKLIST_ADD (one service = one file). + * 블랙리스트 등재: 카드를 블랙리스트에 올리고(기등재 건은 사유만 갱신), + * 해당 카드의 한도원장이 ACTIVE 면 동시에 SUSPEND 로 정지시킨다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "BLACKLIST_ADD.h" + +void BLACKLIST_ADD(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24], reason[128]; + long fresh; + EXEC SQL BEGIN DECLARE SECTION; + long h_before = 0, h_after = 0; + char h_card[24], h_reason[128]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + gets_(b, T_REASON, reason, sizeof(reason)); + if (card[0] == 0) { userlog("BLACKLIST_ADD: 카드번호 누락"); FAIL(b); } + if (reason[0] == 0) strcpy(reason, "MANUAL"); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + strncpy(h_reason, reason, sizeof(h_reason)-1); h_reason[sizeof(h_reason)-1] = 0; + + EXEC SQL SELECT count(*) INTO :h_before FROM au_blacklist WHERE card_no = :h_card; + if (sqlca.sqlcode < 0) FAIL(b); + fresh = (h_before == 0); + + EXEC SQL INSERT INTO au_blacklist (card_no, reason) VALUES (:h_card, :h_reason) + ON CONFLICT (card_no) DO UPDATE SET reason = EXCLUDED.reason; + if (sqlca.sqlcode < 0) FAIL(b); + + /* 연계 정지: 한도원장이 살아 있으면 같이 잠근다 (원장 없으면 무시) */ + EXEC SQL UPDATE card_limit SET status = 'SUSPEND', updated_at = now() + WHERE card_no = :h_card AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT count(*) INTO :h_after FROM au_blacklist; + if (sqlca.sqlcode < 0) FAIL(b); + + userlog("BLACKLIST_ADD card=%s reason=%s %s", card, reason, fresh ? "신규" : "갱신"); + setl(b, T_COUNT, h_after); + setl(b, T_RC, fresh ? 0 : 1); + OK(b); +} diff --git a/app/src/au/svc/BLACKLIST_DEL.h b/app/src/au/svc/BLACKLIST_DEL.h new file mode 100644 index 0000000..7131c66 --- /dev/null +++ b/app/src/au/svc/BLACKLIST_DEL.h @@ -0,0 +1,18 @@ +/* + * BLACKLIST_DEL.h - au 서비스 BLACKLIST_DEL 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_ARG1(1이면 한도원장도 ACTIVE 복원) + * Response fields: T_RC(0=해제완료, 1=미등재) + */ +#ifndef BLACKLIST_DEL_H +#define BLACKLIST_DEL_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long reactivate; +} au_blacklist_del_rec_t; + +void BLACKLIST_DEL(TPSVCINFO *p); + +#endif /* BLACKLIST_DEL_H */ diff --git a/app/src/au/svc/BLACKLIST_DEL.pgc b/app/src/au/svc/BLACKLIST_DEL.pgc new file mode 100644 index 0000000..2daf25a --- /dev/null +++ b/app/src/au/svc/BLACKLIST_DEL.pgc @@ -0,0 +1,39 @@ +/* + * BLACKLIST_DEL.pgc - au 모듈 서비스 BLACKLIST_DEL (one service = one file). + * 블랙리스트 해제: 등재 행을 삭제하고, 옵션(T_ARG1=1)에 따라 함께 정지됐던 + * 한도원장을 ACTIVE 로 복원한다. 미등재 카드는 RC=1 로 멱등 응답. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "BLACKLIST_DEL.h" + +void BLACKLIST_DEL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24]; + long reactivate; + EXEC SQL BEGIN DECLARE SECTION; + char h_card[24]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + reactivate = getl(b, T_ARG1); + if (card[0] == 0) FAIL(b); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + + EXEC SQL DELETE FROM au_blacklist WHERE card_no = :h_card; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { /* 등재된 적 없음 - 멱등 */ + setl(b, T_RC, 1); + OK(b); + } + + if (reactivate == 1) { + EXEC SQL UPDATE card_limit SET status = 'ACTIVE', updated_at = now() + WHERE card_no = :h_card AND status = 'SUSPEND'; + if (sqlca.sqlcode < 0) FAIL(b); + } + + userlog("BLACKLIST_DEL card=%s 해제 (복원=%ld)", card, reactivate); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/au/svc/BLACKLIST_INQ.h b/app/src/au/svc/BLACKLIST_INQ.h new file mode 100644 index 0000000..8bb183a --- /dev/null +++ b/app/src/au/svc/BLACKLIST_INQ.h @@ -0,0 +1,21 @@ +/* + * BLACKLIST_INQ.h - au 서비스 BLACKLIST_INQ 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호) + * Response fields: T_RC(0=미등재, 1=등재), T_REASON(등재사유), T_STR1(등재일시), + * T_COUNT(전체 블랙리스트 건수) + */ +#ifndef BLACKLIST_INQ_H +#define BLACKLIST_INQ_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + char reason[128]; + char listed_at[32]; + long total; +} au_blacklist_inq_rec_t; + +void BLACKLIST_INQ(TPSVCINFO *p); + +#endif /* BLACKLIST_INQ_H */ diff --git a/app/src/au/svc/BLACKLIST_INQ.pgc b/app/src/au/svc/BLACKLIST_INQ.pgc new file mode 100644 index 0000000..3e54b9f --- /dev/null +++ b/app/src/au/svc/BLACKLIST_INQ.pgc @@ -0,0 +1,41 @@ +/* + * BLACKLIST_INQ.pgc - au 모듈 서비스 BLACKLIST_INQ (one service = one file). + * 블랙리스트 조회: 특정 카드의 등재 여부/사유/등재일시(문자열 포맷)를 + * 조회하고 전체 등재 건수도 함께 내려준다. 미등재는 RC=0 정상 응답. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "BLACKLIST_INQ.h" + +void BLACKLIST_INQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24]; + EXEC SQL BEGIN DECLARE SECTION; + long h_total = 0; + char h_card[24], h_reason[128], h_when[32]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + if (card[0] == 0) FAIL(b); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + memset(h_reason, 0, sizeof(h_reason)); + memset(h_when, 0, sizeof(h_when)); + + EXEC SQL SELECT count(*) INTO :h_total FROM au_blacklist; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, h_total); + + EXEC SQL SELECT reason, to_char(created_at, 'YYYY-MM-DD HH24:MI:SS') + INTO :h_reason, :h_when + FROM au_blacklist WHERE card_no = :h_card; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { + setl(b, T_RC, 0); /* 미등재 */ + OK(b); + } + + Bchg(b, T_REASON, 0, h_reason, 0L); + Bchg(b, T_STR1, 0, h_when, 0L); + setl(b, T_RC, 1); + OK(b); +} diff --git a/app/src/au/svc/CANCEL_REPROC.h b/app/src/au/svc/CANCEL_REPROC.h new file mode 100644 index 0000000..3618a50 --- /dev/null +++ b/app/src/au/svc/CANCEL_REPROC.h @@ -0,0 +1,20 @@ +/* + * CANCEL_REPROC.h - au 서비스 CANCEL_REPROC 카피북 (copybook / record header). + * Request fields: T_CANCEL_ID(취소 ID) + * Response fields: T_ID1(원승인번호), T_RC(0=재처리 완료, 1=이미 반영됨, 2=원승인 상태 부적합) + */ +#ifndef CANCEL_REPROC_H +#define CANCEL_REPROC_H + +#include "au_svc.h" + +typedef struct { + long cancel_id; + long auth_id; + char cancel_type[12]; + long amount; +} au_cancel_reproc_rec_t; + +void CANCEL_REPROC(TPSVCINFO *p); + +#endif /* CANCEL_REPROC_H */ diff --git a/app/src/au/svc/CANCEL_REPROC.pgc b/app/src/au/svc/CANCEL_REPROC.pgc new file mode 100644 index 0000000..11e1f08 --- /dev/null +++ b/app/src/au/svc/CANCEL_REPROC.pgc @@ -0,0 +1,56 @@ +/* + * CANCEL_REPROC.pgc - au 모듈 서비스 CANCEL_REPROC (one service = one file). + * 승인취소 재처리: au_cancel 에 접수됐지만 원승인 원장에 반영되지 못한 건을 + * 다시 반영한다. FULL/VOID 는 원승인 상태를 C/V 로 전환하고, + * PARTIAL 은 원승인 금액을 취소분만큼 감액한다. 이미 반영된 건은 RC=1. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "CANCEL_REPROC.h" + +void CANCEL_REPROC(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_cxl, h_auth = 0, h_camt = 0, h_aamt = 0; + char h_ctype[12], h_astat[4]; + EXEC SQL END DECLARE SECTION; + + h_cxl = getl(b, T_CANCEL_ID); + if (h_cxl <= 0) { userlog("CANCEL_REPROC: 취소 ID 누락"); FAIL(b); } + memset(h_ctype, 0, sizeof(h_ctype)); + memset(h_astat, 0, sizeof(h_astat)); + + EXEC SQL SELECT c.auth_id, c.cancel_type, c.amount, a.status, a.amount + INTO :h_auth, :h_ctype, :h_camt, :h_astat, :h_aamt + FROM au_cancel c JOIN au_authorization a ON a.auth_id = c.auth_id + WHERE c.cancel_id = :h_cxl; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + + setl(b, T_ID1, h_auth); + if (h_astat[0] == 'C' || h_astat[0] == 'V' || h_astat[0] == 'R') { + setl(b, T_RC, 1); /* 이미 취소류 상태 - 재처리 불필요 */ + OK(b); + } + if (h_astat[0] != 'A' && h_astat[0] != 'M') { + setl(b, T_RC, 2); /* 보류/거절 등 - 재처리 부적합 */ + OK(b); + } + + if (strcmp(h_ctype, "PARTIAL") == 0) { + if (h_camt >= h_aamt) FAIL(b); /* 부분취소가 원금액 이상이면 정합성 오류 */ + EXEC SQL UPDATE au_authorization + SET amount = amount - :h_camt, updated_at = now() + WHERE auth_id = :h_auth; + } else if (strcmp(h_ctype, "VOID") == 0) { + EXEC SQL UPDATE au_authorization SET status = 'V', updated_at = now() + WHERE auth_id = :h_auth; + } else { + EXEC SQL UPDATE au_authorization SET status = 'C', updated_at = now() + WHERE auth_id = :h_auth; + } + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + + userlog("CANCEL_REPROC cancel_id=%ld auth_id=%ld type=%s 재처리 완료", h_cxl, h_auth, h_ctype); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/au/svc/CANCEL_STATUS.h b/app/src/au/svc/CANCEL_STATUS.h new file mode 100644 index 0000000..ce01e04 --- /dev/null +++ b/app/src/au/svc/CANCEL_STATUS.h @@ -0,0 +1,23 @@ +/* + * CANCEL_STATUS.h - au 서비스 CANCEL_STATUS 카피북 (copybook / record header). + * Request fields: T_BIZDATE(영업일) + * Response fields: T_COUNT(총 취소건수), T_ARG1(FULL), T_ARG2(PARTIAL), T_ARG3(VOID), + * T_GROSS(총 취소금액) + */ +#ifndef CANCEL_STATUS_H +#define CANCEL_STATUS_H + +#include "au_svc.h" + +typedef struct { + char biz_date[16]; + long total; + long full_cnt; + long partial_cnt; + long void_cnt; + long gross; +} au_cancel_status_rec_t; + +void CANCEL_STATUS(TPSVCINFO *p); + +#endif /* CANCEL_STATUS_H */ diff --git a/app/src/au/svc/CANCEL_STATUS.pgc b/app/src/au/svc/CANCEL_STATUS.pgc new file mode 100644 index 0000000..0e398df --- /dev/null +++ b/app/src/au/svc/CANCEL_STATUS.pgc @@ -0,0 +1,37 @@ +/* + * CANCEL_STATUS.pgc - au 모듈 서비스 CANCEL_STATUS (one service = one file). + * 취소 처리현황 조회: 영업일 기준 취소 접수를 유형(FULL/PARTIAL/VOID)별로 + * FILTER 집계해 건수 분포와 총 취소금액을 한 번의 SELECT 로 산출한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "CANCEL_STATUS.h" + +void CANCEL_STATUS(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_tot = 0, h_full = 0, h_part = 0, h_void = 0, h_amt = 0; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) { userlog("CANCEL_STATUS: 영업일 누락"); FAIL(b); } + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*), + count(*) FILTER (WHERE cancel_type = 'FULL'), + count(*) FILTER (WHERE cancel_type = 'PARTIAL'), + count(*) FILTER (WHERE cancel_type = 'VOID'), + coalesce(sum(amount),0) + INTO :h_tot, :h_full, :h_part, :h_void, :h_amt + FROM au_cancel WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_tot); + setl(b, T_ARG1, h_full); + setl(b, T_ARG2, h_part); + setl(b, T_ARG3, h_void); + setl(b, T_GROSS, h_amt); + OK(b); +} diff --git a/app/src/au/svc/CARD_DAILY_RPT.h b/app/src/au/svc/CARD_DAILY_RPT.h new file mode 100644 index 0000000..0f31f10 --- /dev/null +++ b/app/src/au/svc/CARD_DAILY_RPT.h @@ -0,0 +1,26 @@ +/* + * CARD_DAILY_RPT.h - au 서비스 CARD_DAILY_RPT 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_BIZDATE(영업일) + * Response fields: T_ARG1(승인), T_ARG2(거절), T_ARG3(취소류), T_ARG4(매입확정), + * T_GROSS(승인금액), T_AMT1(최대 단건금액), T_ARG5(승인율 bps) + */ +#ifndef CARD_DAILY_RPT_H +#define CARD_DAILY_RPT_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + char biz_date[16]; + long approved; + long declined; + long canceled; + long captured; + long gross; + long max_one; + long approve_bps; +} au_card_daily_rpt_rec_t; + +void CARD_DAILY_RPT(TPSVCINFO *p); + +#endif /* CARD_DAILY_RPT_H */ diff --git a/app/src/au/svc/CARD_DAILY_RPT.pgc b/app/src/au/svc/CARD_DAILY_RPT.pgc new file mode 100644 index 0000000..41699ba --- /dev/null +++ b/app/src/au/svc/CARD_DAILY_RPT.pgc @@ -0,0 +1,46 @@ +/* + * CARD_DAILY_RPT.pgc - au 모듈 서비스 CARD_DAILY_RPT (one service = one file). + * 카드 일일 승인 리포트: 카드+영업일 기준 상태별(승인/거절/취소류/매입) 건수, + * 유효 승인금액 합, 최대 단건금액을 FILTER 집계 한 방으로 뽑고 + * 승인율(bps)을 계산해 종합 리포트로 내려준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "CARD_DAILY_RPT.h" + +void CARD_DAILY_RPT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24], bizdate[16]; + long attempts; + EXEC SQL BEGIN DECLARE SECTION; + long h_ap = 0, h_de = 0, h_cx = 0, h_cap = 0, h_sum = 0, h_max = 0; + char h_card[24], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (card[0] == 0 || bizdate[0] == 0) FAIL(b); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*) FILTER (WHERE status = 'A'), + count(*) FILTER (WHERE status = 'D'), + count(*) FILTER (WHERE status IN ('C','R','V')), + count(*) FILTER (WHERE status IN ('M','S')), + coalesce(sum(amount) FILTER (WHERE status IN ('A','M','S')), 0), + coalesce(max(amount) FILTER (WHERE status IN ('A','M','S')), 0) + INTO :h_ap, :h_de, :h_cx, :h_cap, :h_sum, :h_max + FROM au_authorization + WHERE card_no = :h_card AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + attempts = h_ap + h_de + h_cap; + setl(b, T_ARG1, h_ap); + setl(b, T_ARG2, h_de); + setl(b, T_ARG3, h_cx); + setl(b, T_ARG4, h_cap); + setl(b, T_GROSS, h_sum); + setl(b, T_AMT1, h_max); + setl(b, T_ARG5, (attempts > 0) ? ((h_ap + h_cap) * 10000L) / attempts : 0); + OK(b); +} diff --git a/app/src/au/svc/CARD_REISSUE.h b/app/src/au/svc/CARD_REISSUE.h new file mode 100644 index 0000000..21b7487 --- /dev/null +++ b/app/src/au/svc/CARD_REISSUE.h @@ -0,0 +1,19 @@ +/* + * CARD_REISSUE.h - au 서비스 CARD_REISSUE 카피북 (copybook / record header). + * Request fields: T_KEY1(구 카드번호), T_KEY2(신 카드번호) + * Response fields: T_AMT1(이관된 기본한도), T_RC(0=이관완료, 1=신카드 이미 존재) + */ +#ifndef CARD_REISSUE_H +#define CARD_REISSUE_H + +#include "au_svc.h" + +typedef struct { + char old_card[24]; + char new_card[24]; + long moved_limit; +} au_card_reissue_rec_t; + +void CARD_REISSUE(TPSVCINFO *p); + +#endif /* CARD_REISSUE_H */ diff --git a/app/src/au/svc/CARD_REISSUE.pgc b/app/src/au/svc/CARD_REISSUE.pgc new file mode 100644 index 0000000..3974028 --- /dev/null +++ b/app/src/au/svc/CARD_REISSUE.pgc @@ -0,0 +1,48 @@ +/* + * CARD_REISSUE.pgc - au 모듈 서비스 CARD_REISSUE (one service = one file). + * 카드 재발급 연계: 구 카드의 한도원장을 신 카드번호로 이관 생성하고 + * (사용액은 승계, 임시한도는 소멸) 구 카드는 CLOSED 처리한다. + * 신 카드 원장이 이미 있으면 RC=1 로 응답하고 이관하지 않는다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "CARD_REISSUE.h" + +void CARD_REISSUE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char oldc[24], newc[24]; + EXEC SQL BEGIN DECLARE SECTION; + long h_daily = 0, h_used = 0, h_exists = 0; + char h_old[24], h_new[24]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, oldc, sizeof(oldc)); + gets_(b, T_KEY2, newc, sizeof(newc)); + if (oldc[0] == 0 || newc[0] == 0 || strcmp(oldc, newc) == 0) { + userlog("CARD_REISSUE: 카드번호 인자 오류"); + FAIL(b); + } + strncpy(h_old, oldc, sizeof(h_old)-1); h_old[sizeof(h_old)-1] = 0; + strncpy(h_new, newc, sizeof(h_new)-1); h_new[sizeof(h_new)-1] = 0; + + EXEC SQL SELECT count(*) INTO :h_exists FROM card_limit WHERE card_no = :h_new; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_exists > 0) { setl(b, T_RC, 1); setl(b, T_AMT1, 0); OK(b); } + + EXEC SQL SELECT daily_limit, used_amount INTO :h_daily, :h_used + FROM card_limit WHERE card_no = :h_old; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + + EXEC SQL INSERT INTO card_limit (card_no, daily_limit, used_amount, temp_limit, status) + VALUES (:h_new, :h_daily, :h_used, 0, 'ACTIVE'); + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL UPDATE card_limit SET status = 'CLOSED', temp_limit = 0, updated_at = now() + WHERE card_no = :h_old; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + + userlog("CARD_REISSUE %s -> %s 한도 %ld 이관", oldc, newc, h_daily); + setl(b, T_AMT1, h_daily); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/au/svc/CARD_RESUME.h b/app/src/au/svc/CARD_RESUME.h new file mode 100644 index 0000000..2518382 --- /dev/null +++ b/app/src/au/svc/CARD_RESUME.h @@ -0,0 +1,18 @@ +/* + * CARD_RESUME.h - au 서비스 CARD_RESUME 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호) + * Response fields: T_RC(0=해제완료, 1=정지상태 아님), T_STATUS(해제 후 상태) + */ +#ifndef CARD_RESUME_H +#define CARD_RESUME_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + char new_status[16]; +} au_card_resume_rec_t; + +void CARD_RESUME(TPSVCINFO *p); + +#endif /* CARD_RESUME_H */ diff --git a/app/src/au/svc/CARD_RESUME.pgc b/app/src/au/svc/CARD_RESUME.pgc new file mode 100644 index 0000000..68144a1 --- /dev/null +++ b/app/src/au/svc/CARD_RESUME.pgc @@ -0,0 +1,42 @@ +/* + * CARD_RESUME.pgc - au 모듈 서비스 CARD_RESUME (one service = one file). + * 카드 정지 해제: 블랙리스트에 남아 있으면 해제를 거부하고, + * SUSPEND 상태의 카드만 ACTIVE 로 되돌린다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "CARD_RESUME.h" + +void CARD_RESUME(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24]; + EXEC SQL BEGIN DECLARE SECTION; + long h_black = 0; + char h_card[24], h_status[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + if (card[0] == 0) FAIL(b); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + memset(h_status, 0, sizeof(h_status)); + + EXEC SQL SELECT count(*) INTO :h_black FROM au_blacklist WHERE card_no = :h_card; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_black > 0) { userlog("CARD_RESUME 거부: 블랙리스트 등재 card=%s", card); FAIL(b); } + + EXEC SQL UPDATE card_limit SET status = 'ACTIVE', updated_at = now() + WHERE card_no = :h_card AND status = 'SUSPEND'; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { + EXEC SQL SELECT status INTO :h_status FROM card_limit WHERE card_no = :h_card; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + Bchg(b, T_STATUS, 0, h_status, 0L); + setl(b, T_RC, 1); /* 정지상태가 아니었음 */ + OK(b); + } + + userlog("CARD_RESUME 카드=%s 정지 해제", card); + Bchg(b, T_STATUS, 0, "ACTIVE", 0L); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/au/svc/CARD_STATE_INQ.h b/app/src/au/svc/CARD_STATE_INQ.h new file mode 100644 index 0000000..e7e7552 --- /dev/null +++ b/app/src/au/svc/CARD_STATE_INQ.h @@ -0,0 +1,24 @@ +/* + * CARD_STATE_INQ.h - au 서비스 CARD_STATE_INQ 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호) + * Response fields: T_STATUS(카드상태), T_AMT1(기본한도), T_AMT2(사용액), T_AMT3(임시한도), + * T_COUNT(당일 승인건수), T_RC(1=블랙리스트 등재) + */ +#ifndef CARD_STATE_INQ_H +#define CARD_STATE_INQ_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + char status[16]; + long daily_limit; + long used_amount; + long temp_limit; + long today_count; + long black_flag; +} au_card_state_rec_t; + +void CARD_STATE_INQ(TPSVCINFO *p); + +#endif /* CARD_STATE_INQ_H */ diff --git a/app/src/au/svc/CARD_STATE_INQ.pgc b/app/src/au/svc/CARD_STATE_INQ.pgc new file mode 100644 index 0000000..435f6ec --- /dev/null +++ b/app/src/au/svc/CARD_STATE_INQ.pgc @@ -0,0 +1,39 @@ +/* + * CARD_STATE_INQ.pgc - au 모듈 서비스 CARD_STATE_INQ (one service = one file). + * 카드 상태 종합조회: 한도원장(상태/기본·임시한도/사용액)에 + * 당일 승인건수와 블랙리스트 등재 여부를 스칼라 서브쿼리로 묶어 한 번에 조회. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "CARD_STATE_INQ.h" + +void CARD_STATE_INQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24]; + EXEC SQL BEGIN DECLARE SECTION; + long h_daily = 0, h_used = 0, h_temp = 0, h_cnt = 0, h_black = 0; + char h_card[24], h_status[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + if (card[0] == 0) FAIL(b); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + memset(h_status, 0, sizeof(h_status)); + + EXEC SQL SELECT l.status, l.daily_limit, l.used_amount, l.temp_limit, + (SELECT count(*) FROM au_authorization a + WHERE a.card_no = l.card_no AND a.biz_date = current_date), + (SELECT count(*) FROM au_blacklist k WHERE k.card_no = l.card_no) + INTO :h_status, :h_daily, :h_used, :h_temp, :h_cnt, :h_black + FROM card_limit l WHERE l.card_no = :h_card; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { userlog("CARD_STATE_INQ: 원장 없음 card=%s", card); FAIL(b); } + + Bchg(b, T_STATUS, 0, h_status, 0L); + setl(b, T_AMT1, h_daily); + setl(b, T_AMT2, h_used); + setl(b, T_AMT3, h_temp); + setl(b, T_COUNT, h_cnt); + setl(b, T_RC, (h_black > 0) ? 1 : 0); + OK(b); +} diff --git a/app/src/au/svc/CARD_SUSPEND.h b/app/src/au/svc/CARD_SUSPEND.h new file mode 100644 index 0000000..b85067d --- /dev/null +++ b/app/src/au/svc/CARD_SUSPEND.h @@ -0,0 +1,19 @@ +/* + * CARD_SUSPEND.h - au 서비스 CARD_SUSPEND 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_REASON(정지사유) + * Response fields: T_RC(0=정지완료, 1=이미 정지/폐기 상태), T_COUNT(당일 승인건수) + */ +#ifndef CARD_SUSPEND_H +#define CARD_SUSPEND_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + char reason[128]; + long today_auths; +} au_card_suspend_rec_t; + +void CARD_SUSPEND(TPSVCINFO *p); + +#endif /* CARD_SUSPEND_H */ diff --git a/app/src/au/svc/CARD_SUSPEND.pgc b/app/src/au/svc/CARD_SUSPEND.pgc new file mode 100644 index 0000000..83d5a70 --- /dev/null +++ b/app/src/au/svc/CARD_SUSPEND.pgc @@ -0,0 +1,41 @@ +/* + * CARD_SUSPEND.pgc - au 모듈 서비스 CARD_SUSPEND (one service = one file). + * 카드 정지: ACTIVE 상태의 카드만 SUSPEND 로 전환하고, 참고용으로 + * 당일 승인건수를 함께 내려준다. 이미 정지/폐기된 카드는 RC=1. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "CARD_SUSPEND.h" + +void CARD_SUSPEND(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24], reason[128]; + EXEC SQL BEGIN DECLARE SECTION; + long h_today = 0; + char h_card[24]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + gets_(b, T_REASON, reason, sizeof(reason)); + if (card[0] == 0) { userlog("CARD_SUSPEND: 카드번호 누락"); FAIL(b); } + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + + EXEC SQL SELECT count(*) INTO :h_today FROM au_authorization + WHERE card_no = :h_card AND biz_date = current_date; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, h_today); + + EXEC SQL UPDATE card_limit SET status = 'SUSPEND', updated_at = now() + WHERE card_no = :h_card AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { /* ACTIVE 아님: 존재만 하면 RC=1 */ + EXEC SQL SELECT count(*) INTO :h_today FROM card_limit WHERE card_no = :h_card; + if (sqlca.sqlcode < 0 || h_today == 0) FAIL(b); + setl(b, T_RC, 1); + OK(b); + } + + userlog("CARD_SUSPEND 카드=%s 정지 (사유=%s)", card, reason); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/au/svc/DECLINE_ANALYZE.h b/app/src/au/svc/DECLINE_ANALYZE.h new file mode 100644 index 0000000..8c363db --- /dev/null +++ b/app/src/au/svc/DECLINE_ANALYZE.h @@ -0,0 +1,22 @@ +/* + * DECLINE_ANALYZE.h - au 서비스 DECLINE_ANALYZE 카피북 (copybook / record header). + * Request fields: T_BIZDATE(영업일) + * Response fields: T_STR1(상위 거절사유 "사유:건수;" 최대 3개), T_REASON(1위 사유), + * T_COUNT(총 거절건수), T_ARG1(거절률 bps) + */ +#ifndef DECLINE_ANALYZE_H +#define DECLINE_ANALYZE_H + +#include "au_svc.h" + +typedef struct { + char biz_date[16]; + char top3[256]; + char top_reason[64]; + long decline_total; + long decline_bps; +} au_decline_analyze_rec_t; + +void DECLINE_ANALYZE(TPSVCINFO *p); + +#endif /* DECLINE_ANALYZE_H */ diff --git a/app/src/au/svc/DECLINE_ANALYZE.pgc b/app/src/au/svc/DECLINE_ANALYZE.pgc new file mode 100644 index 0000000..62c3a63 --- /dev/null +++ b/app/src/au/svc/DECLINE_ANALYZE.pgc @@ -0,0 +1,54 @@ +/* + * DECLINE_ANALYZE.pgc - au 모듈 서비스 DECLINE_ANALYZE (one service = one file). + * 승인 거절사유 분석: 영업일 거절(D) 건을 사유별로 그룹핑해 상위 3개 사유를 + * 커서로 뽑아 "사유:건수;" 문자열로 조립하고, 전체 접수 대비 거절률(bps)을 + * 계산해 내려준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "DECLINE_ANALYZE.h" + +void DECLINE_ANALYZE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], top3[256] = "", top_reason[64] = ""; + char item[96]; + int rank = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_dec = 0, h_all = 0; + char h_bizdate[16], h_reason[64]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) FAIL(b); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*), count(*) FILTER (WHERE status = 'D') + INTO :h_all, :h_dec + FROM au_authorization WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL DECLARE cur_dean CURSOR FOR + SELECT CASE WHEN decline_reason = '' THEN 'UNSPECIFIED' ELSE decline_reason END, + count(*) + FROM au_authorization + WHERE biz_date = :h_bizdate AND status = 'D' + GROUP BY 1 ORDER BY 2 DESC LIMIT 3; + EXEC SQL OPEN cur_dean; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cur_dean INTO :h_reason, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_dean; FAIL(b); } + rank++; + if (rank == 1) strncpy(top_reason, h_reason, sizeof(top_reason)-1); + snprintf(item, sizeof(item), "%s:%ld;", h_reason, h_cnt); + if (strlen(top3) + strlen(item) < sizeof(top3) - 1) strcat(top3, item); + } + EXEC SQL CLOSE cur_dean; + + Bchg(b, T_STR1, 0, top3, 0L); + Bchg(b, T_REASON, 0, top_reason, 0L); + setl(b, T_COUNT, h_dec); + setl(b, T_ARG1, (h_all > 0) ? (h_dec * 10000L) / h_all : 0); + OK(b); +} diff --git a/app/src/au/svc/FRAUD_CASE_INQ.h b/app/src/au/svc/FRAUD_CASE_INQ.h new file mode 100644 index 0000000..354e880 --- /dev/null +++ b/app/src/au/svc/FRAUD_CASE_INQ.h @@ -0,0 +1,23 @@ +/* + * FRAUD_CASE_INQ.h - au 서비스 FRAUD_CASE_INQ 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호) + * Response fields: T_COUNT(적발 건수), T_ARG1(최고점수), T_ARG2(평균점수), + * T_REASON(최근 적발사유), T_STR1(최근 적발일) + */ +#ifndef FRAUD_CASE_INQ_H +#define FRAUD_CASE_INQ_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long case_count; + long max_score; + long avg_score; + char last_reason[128]; + char last_date[16]; +} au_fraud_case_inq_rec_t; + +void FRAUD_CASE_INQ(TPSVCINFO *p); + +#endif /* FRAUD_CASE_INQ_H */ diff --git a/app/src/au/svc/FRAUD_CASE_INQ.pgc b/app/src/au/svc/FRAUD_CASE_INQ.pgc new file mode 100644 index 0000000..2d5ffee --- /dev/null +++ b/app/src/au/svc/FRAUD_CASE_INQ.pgc @@ -0,0 +1,42 @@ +/* + * FRAUD_CASE_INQ.pgc - au 모듈 서비스 FRAUD_CASE_INQ (one service = one file). + * 이상거래 케이스 조회: 카드의 부정거래 적발 이력을 집계(건수/최고/평균점수) + * 하고, 가장 최근 적발 건의 사유와 발생일을 LIMIT 1 정렬 조회로 내려준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "FRAUD_CASE_INQ.h" + +void FRAUD_CASE_INQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0, h_max = 0, h_avg = 0; + char h_card[24], h_reason[128], h_date[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + if (card[0] == 0) FAIL(b); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + memset(h_reason, 0, sizeof(h_reason)); + memset(h_date, 0, sizeof(h_date)); + + EXEC SQL SELECT count(*), coalesce(max(score),0), CAST(coalesce(avg(score),0) AS bigint) + INTO :h_cnt, :h_max, :h_avg + FROM au_fraud_log WHERE card_no = :h_card; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + setl(b, T_ARG1, h_max); + setl(b, T_ARG2, h_avg); + if (h_cnt == 0) OK(b); /* 깨끗한 카드 */ + + EXEC SQL SELECT reason, CAST(biz_date AS text) INTO :h_reason, :h_date + FROM au_fraud_log WHERE card_no = :h_card + ORDER BY fraud_id DESC LIMIT 1; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + + Bchg(b, T_REASON, 0, h_reason, 0L); + Bchg(b, T_STR1, 0, h_date, 0L); + OK(b); +} diff --git a/app/src/au/svc/FRAUD_RULE_AMT.h b/app/src/au/svc/FRAUD_RULE_AMT.h new file mode 100644 index 0000000..3279eca --- /dev/null +++ b/app/src/au/svc/FRAUD_RULE_AMT.h @@ -0,0 +1,22 @@ +/* + * FRAUD_RULE_AMT.h - au 서비스 FRAUD_RULE_AMT 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_AMOUNT(거래금액), T_BIZDATE(영업일) + * Response fields: T_RC(0=정상, 1=고액 이상거래 탐지), T_ID1(적발 로그 ID), + * T_AMT1(30일 평균금액), T_ARG1(탐지점수) + */ +#ifndef FRAUD_RULE_AMT_H +#define FRAUD_RULE_AMT_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long amount; + long avg30; + long score; + long fraud_id; +} au_fraud_rule_amt_rec_t; + +void FRAUD_RULE_AMT(TPSVCINFO *p); + +#endif /* FRAUD_RULE_AMT_H */ diff --git a/app/src/au/svc/FRAUD_RULE_AMT.pgc b/app/src/au/svc/FRAUD_RULE_AMT.pgc new file mode 100644 index 0000000..c44ce9f --- /dev/null +++ b/app/src/au/svc/FRAUD_RULE_AMT.pgc @@ -0,0 +1,51 @@ +/* + * FRAUD_RULE_AMT.pgc - au 모듈 서비스 FRAUD_RULE_AMT (one service = one file). + * 이상거래 고액 룰: 거래금액이 카드의 30일 평균 사용액의 3배를 넘고 + * 절대금액 100만원 이상이면 배수 기반 점수를 산출해 부정거래 로그에 + * 적발 기록을 남긴다. 평균 이력이 없는 신규 카드는 룰을 건너뛴다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "FRAUD_RULE_AMT.h" + +void FRAUD_RULE_AMT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24], bizdate[16]; + long amount, score, fid; + EXEC SQL BEGIN DECLARE SECTION; + long h_avg = 0, h_hist = 0; + char h_card[24]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + amount = getl(b, T_AMOUNT); + if (card[0] == 0 || amount <= 0) FAIL(b); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + + EXEC SQL SELECT count(*), CAST(coalesce(avg(amount),0) AS bigint) + INTO :h_hist, :h_avg + FROM au_authorization + WHERE card_no = :h_card AND status IN ('A','M','S') + AND biz_date >= current_date - 30; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_AMT1, h_avg); + if (h_hist < 3 || h_avg <= 0) { /* 이력 부족 - 룰 미적용 */ + setl(b, T_RC, 0); setl(b, T_ARG1, 0); OK(b); + } + if (amount <= h_avg * 3 || amount < 1000000) { + setl(b, T_RC, 0); setl(b, T_ARG1, 0); OK(b); + } + + score = 500 + (amount / h_avg) * 50; /* 평균 대비 배수 가중 점수 */ + if (score > 999) score = 999; + fid = audb_insert_fraud(0, card, score, "RULE_AMT_SPIKE", bizdate); + if (fid < 0) FAIL(b); + + userlog("FRAUD_RULE_AMT 적발 card=%s amt=%ld avg=%ld score=%ld", card, amount, h_avg, score); + setl(b, T_ID1, fid); + setl(b, T_ARG1, score); + setl(b, T_RC, 1); + OK(b); +} diff --git a/app/src/au/svc/FRAUD_RULE_DUP.h b/app/src/au/svc/FRAUD_RULE_DUP.h new file mode 100644 index 0000000..62afa3d --- /dev/null +++ b/app/src/au/svc/FRAUD_RULE_DUP.h @@ -0,0 +1,21 @@ +/* + * FRAUD_RULE_DUP.h - au 서비스 FRAUD_RULE_DUP 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_BIZDATE(영업일) + * Response fields: T_RC(0=정상, 1=동일금액 반복 탐지), T_COUNT(반복 금액 그룹수), + * T_AMT1(최다 반복 금액), T_ID1(적발 로그 ID) + */ +#ifndef FRAUD_RULE_DUP_H +#define FRAUD_RULE_DUP_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long dup_groups; + long worst_amount; + long fraud_id; +} au_fraud_rule_dup_rec_t; + +void FRAUD_RULE_DUP(TPSVCINFO *p); + +#endif /* FRAUD_RULE_DUP_H */ diff --git a/app/src/au/svc/FRAUD_RULE_DUP.pgc b/app/src/au/svc/FRAUD_RULE_DUP.pgc new file mode 100644 index 0000000..72405e6 --- /dev/null +++ b/app/src/au/svc/FRAUD_RULE_DUP.pgc @@ -0,0 +1,55 @@ +/* + * FRAUD_RULE_DUP.pgc - au 모듈 서비스 FRAUD_RULE_DUP (one service = one file). + * 이상거래 동일금액 반복 룰: 당일 카드 승인 가운데 같은 금액이 3회 이상 + * 반복된 금액 그룹을 HAVING 커서로 훑어, 가장 반복이 심한 금액을 찾아 + * 반복횟수 비례 점수로 부정거래 로그에 적발 기록한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "FRAUD_RULE_DUP.h" + +void FRAUD_RULE_DUP(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24], bizdate[16]; + long groups = 0, worst_amt = 0, worst_rep = 0, score, fid; + EXEC SQL BEGIN DECLARE SECTION; + long h_amt, h_rep; + char h_card[24], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (card[0] == 0 || bizdate[0] == 0) FAIL(b); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL DECLARE cur_fdup CURSOR FOR + SELECT amount, count(*) FROM au_authorization + WHERE card_no = :h_card AND biz_date = :h_bizdate + GROUP BY amount HAVING count(*) >= 3 + ORDER BY count(*) DESC; + EXEC SQL OPEN cur_fdup; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cur_fdup INTO :h_amt, :h_rep; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_fdup; FAIL(b); } + groups++; + if (h_rep > worst_rep) { worst_rep = h_rep; worst_amt = h_amt; } + } + EXEC SQL CLOSE cur_fdup; + + setl(b, T_COUNT, groups); + setl(b, T_AMT1, worst_amt); + if (groups == 0) { setl(b, T_RC, 0); OK(b); } + + score = 600 + worst_rep * 30; + if (score > 999) score = 999; + fid = audb_insert_fraud(0, card, score, "RULE_DUP_AMOUNT", bizdate); + if (fid < 0) FAIL(b); + + userlog("FRAUD_RULE_DUP 적발 card=%s 반복금액=%ld x%ld score=%ld", card, worst_amt, worst_rep, score); + setl(b, T_ID1, fid); + setl(b, T_RC, 1); + OK(b); +} diff --git a/app/src/au/svc/FRAUD_RULE_FREQ.h b/app/src/au/svc/FRAUD_RULE_FREQ.h new file mode 100644 index 0000000..9eb88e1 --- /dev/null +++ b/app/src/au/svc/FRAUD_RULE_FREQ.h @@ -0,0 +1,19 @@ +/* + * FRAUD_RULE_FREQ.h - au 서비스 FRAUD_RULE_FREQ 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_BIZDATE(영업일) + * Response fields: T_RC(0=정상, 1=다빈도 탐지), T_COUNT(10분 시도건수), T_ID1(적발 로그 ID) + */ +#ifndef FRAUD_RULE_FREQ_H +#define FRAUD_RULE_FREQ_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long tries10m; + long fraud_id; +} au_fraud_rule_freq_rec_t; + +void FRAUD_RULE_FREQ(TPSVCINFO *p); + +#endif /* FRAUD_RULE_FREQ_H */ diff --git a/app/src/au/svc/FRAUD_RULE_FREQ.pgc b/app/src/au/svc/FRAUD_RULE_FREQ.pgc new file mode 100644 index 0000000..58cb3b6 --- /dev/null +++ b/app/src/au/svc/FRAUD_RULE_FREQ.pgc @@ -0,0 +1,49 @@ +/* + * FRAUD_RULE_FREQ.pgc - au 모듈 서비스 FRAUD_RULE_FREQ (one service = one file). + * 이상거래 다빈도 룰: 직전 10분간 승인 시도(거절 포함)가 5건 이상이면 + * 건수 비례 점수(700 + 초과건수*20)를 매겨 부정거래 로그에 적발한다. + * 같은 카드가 이미 당일 동일 룰로 적발돼 있으면 중복 적발을 생략한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "FRAUD_RULE_FREQ.h" + +void FRAUD_RULE_FREQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24], bizdate[16]; + long score, fid; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0, h_dup = 0; + char h_card[24], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (card[0] == 0 || bizdate[0] == 0) FAIL(b); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*) INTO :h_cnt + FROM au_authorization + WHERE card_no = :h_card AND created_at >= now() - interval '10 minutes'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + if (h_cnt < 5) { setl(b, T_RC, 0); OK(b); } + + EXEC SQL SELECT count(*) INTO :h_dup + FROM au_fraud_log + WHERE card_no = :h_card AND biz_date = :h_bizdate AND reason = 'RULE_HIGH_FREQ'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_dup > 0) { setl(b, T_RC, 1); setl(b, T_ID1, 0); OK(b); } /* 기적발 */ + + score = 700 + (h_cnt - 5) * 20; + if (score > 999) score = 999; + fid = audb_insert_fraud(0, card, score, "RULE_HIGH_FREQ", bizdate); + if (fid < 0) FAIL(b); + + userlog("FRAUD_RULE_FREQ 적발 card=%s 10분 %ld건 score=%ld", card, h_cnt, score); + setl(b, T_ID1, fid); + setl(b, T_RC, 1); + OK(b); +} diff --git a/app/src/au/svc/LIMIT_HIST.h b/app/src/au/svc/LIMIT_HIST.h new file mode 100644 index 0000000..5e5d8b1 --- /dev/null +++ b/app/src/au/svc/LIMIT_HIST.h @@ -0,0 +1,22 @@ +/* + * LIMIT_HIST.h - au 서비스 LIMIT_HIST 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_ARG1(조회일수) + * Response fields: T_COUNT(사용일수), T_GROSS(기간 총사용액), T_AMT1(최대 일사용액), T_STR1(최대 사용일) + */ +#ifndef LIMIT_HIST_H +#define LIMIT_HIST_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long days; + long used_days; + long total_used; + long peak_amount; + char peak_date[16]; +} au_limit_hist_rec_t; + +void LIMIT_HIST(TPSVCINFO *p); + +#endif /* LIMIT_HIST_H */ diff --git a/app/src/au/svc/LIMIT_HIST.pgc b/app/src/au/svc/LIMIT_HIST.pgc new file mode 100644 index 0000000..27fb485 --- /dev/null +++ b/app/src/au/svc/LIMIT_HIST.pgc @@ -0,0 +1,49 @@ +/* + * LIMIT_HIST.pgc - au 모듈 서비스 LIMIT_HIST (one service = one file). + * 한도 사용 이력조회: 최근 N일 일자별 승인합계를 커서로 훑어 + * 사용일수 / 기간 총사용액 / 최대 일사용액(및 해당 일자)을 산출한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "LIMIT_HIST.h" + +void LIMIT_HIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24]; + long days_used = 0, total = 0, peak = 0; + char peak_date[16] = ""; + EXEC SQL BEGIN DECLARE SECTION; + long h_days, h_daysum; + char h_card[24], h_date[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + if (card[0] == 0) { userlog("LIMIT_HIST: 카드번호 누락"); FAIL(b); } + h_days = getl(b, T_ARG1); if (h_days <= 0 || h_days > 90) h_days = 30; + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + + EXEC SQL DECLARE cur_lhist CURSOR FOR + SELECT CAST(biz_date AS text), coalesce(sum(amount),0) + FROM au_authorization + WHERE card_no = :h_card + AND status IN ('A','M','S') + AND biz_date >= current_date - CAST(:h_days AS integer) + GROUP BY biz_date ORDER BY biz_date; + EXEC SQL OPEN cur_lhist; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cur_lhist INTO :h_date, :h_daysum; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_lhist; FAIL(b); } + days_used++; + total += h_daysum; + if (h_daysum > peak) { peak = h_daysum; strncpy(peak_date, h_date, sizeof(peak_date)-1); } + } + EXEC SQL CLOSE cur_lhist; + + setl(b, T_COUNT, days_used); + setl(b, T_GROSS, total); + setl(b, T_AMT1, peak); + Bchg(b, T_STR1, 0, peak_date, 0L); + OK(b); +} diff --git a/app/src/au/svc/LIMIT_RAISE_REQ.h b/app/src/au/svc/LIMIT_RAISE_REQ.h new file mode 100644 index 0000000..20d0558 --- /dev/null +++ b/app/src/au/svc/LIMIT_RAISE_REQ.h @@ -0,0 +1,21 @@ +/* + * LIMIT_RAISE_REQ.h - au 서비스 LIMIT_RAISE_REQ 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_AMT1(희망 한도) + * Response fields: T_RC(0=승인, 1=실적부족 거절, 2=거절이력 거절), T_AMT2(확정 한도), + * T_GROSS(최근 30일 사용실적) + */ +#ifndef LIMIT_RAISE_REQ_H +#define LIMIT_RAISE_REQ_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long want_limit; + long approved_limit; + long spend30; +} au_limit_raise_rec_t; + +void LIMIT_RAISE_REQ(TPSVCINFO *p); + +#endif /* LIMIT_RAISE_REQ_H */ diff --git a/app/src/au/svc/LIMIT_RAISE_REQ.pgc b/app/src/au/svc/LIMIT_RAISE_REQ.pgc new file mode 100644 index 0000000..073cd92 --- /dev/null +++ b/app/src/au/svc/LIMIT_RAISE_REQ.pgc @@ -0,0 +1,51 @@ +/* + * LIMIT_RAISE_REQ.pgc - au 모듈 서비스 LIMIT_RAISE_REQ (one service = one file). + * 한도 영구상향 심사: 최근 30일 승인실적 합계와 거절 건수를 심사해 + * 거절이력이 있으면 반려(RC=2), 실적이 희망한도의 절반 미만이면 반려(RC=1), + * 통과 시 daily_limit 를 희망한도로 확정한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "LIMIT_RAISE_REQ.h" + +void LIMIT_RAISE_REQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24]; + EXEC SQL BEGIN DECLARE SECTION; + long h_want, h_spend = 0, h_decl = 0, h_cur = 0; + char h_card[24]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + h_want = getl(b, T_AMT1); + if (card[0] == 0 || h_want <= 0) FAIL(b); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + + EXEC SQL SELECT daily_limit INTO :h_cur FROM card_limit WHERE card_no = :h_card; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + if (h_want <= h_cur) { userlog("LIMIT_RAISE_REQ: 현행한도 이하 요청 card=%s", card); FAIL(b); } + + EXEC SQL SELECT coalesce(sum(amount) FILTER (WHERE status IN ('A','M','S')), 0), + count(*) FILTER (WHERE status = 'D') + INTO :h_spend, :h_decl + FROM au_authorization + WHERE card_no = :h_card AND biz_date >= current_date - 30; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_GROSS, h_spend); + if (h_decl > 0) { /* 최근 거절이력 보유 - 심사 반려 */ + setl(b, T_RC, 2); setl(b, T_AMT2, h_cur); OK(b); + } + if (h_spend * 2 < h_want) { /* 사용실적 부족 - 심사 반려 */ + setl(b, T_RC, 1); setl(b, T_AMT2, h_cur); OK(b); + } + + EXEC SQL UPDATE card_limit SET daily_limit = :h_want, updated_at = now() + WHERE card_no = :h_card; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + + userlog("LIMIT_RAISE_REQ 승인 card=%s %ld -> %ld (30일실적 %ld)", card, h_cur, h_want, h_spend); + setl(b, T_RC, 0); + setl(b, T_AMT2, h_want); + OK(b); +} diff --git a/app/src/au/svc/LIMIT_REMAIN.h b/app/src/au/svc/LIMIT_REMAIN.h new file mode 100644 index 0000000..6ae7116 --- /dev/null +++ b/app/src/au/svc/LIMIT_REMAIN.h @@ -0,0 +1,21 @@ +/* + * LIMIT_REMAIN.h - au 서비스 LIMIT_REMAIN 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호) + * Response fields: T_AMT1(잔여한도), T_AMT2(총한도=기본+임시), T_STATUS(카드상태), + * T_RC(0=사용가능, 1=비활성 카드) + */ +#ifndef LIMIT_REMAIN_H +#define LIMIT_REMAIN_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long remain; + long ceiling; + char status[16]; +} au_limit_remain_rec_t; + +void LIMIT_REMAIN(TPSVCINFO *p); + +#endif /* LIMIT_REMAIN_H */ diff --git a/app/src/au/svc/LIMIT_REMAIN.pgc b/app/src/au/svc/LIMIT_REMAIN.pgc new file mode 100644 index 0000000..61ea46a --- /dev/null +++ b/app/src/au/svc/LIMIT_REMAIN.pgc @@ -0,0 +1,38 @@ +/* + * LIMIT_REMAIN.pgc - au 모듈 서비스 LIMIT_REMAIN (one service = one file). + * 잔여한도 조회: (기본한도 + 임시한도 - 사용액) 을 계산해 내려준다. + * 음수면 0 으로 절사하고, 카드 상태가 ACTIVE 가 아니면 RC=1 로 표시. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "LIMIT_REMAIN.h" + +void LIMIT_REMAIN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24]; + long remain; + EXEC SQL BEGIN DECLARE SECTION; + long h_daily = 0, h_used = 0, h_temp = 0; + char h_card[24], h_status[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + if (card[0] == 0) FAIL(b); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + memset(h_status, 0, sizeof(h_status)); + + EXEC SQL SELECT daily_limit, used_amount, temp_limit, status + INTO :h_daily, :h_used, :h_temp, :h_status + FROM card_limit WHERE card_no = :h_card; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { userlog("LIMIT_REMAIN: 한도원장 없음 card=%s", card); FAIL(b); } + + remain = h_daily + h_temp - h_used; + if (remain < 0) remain = 0; + + setl(b, T_AMT1, remain); + setl(b, T_AMT2, h_daily + h_temp); + Bchg(b, T_STATUS, 0, h_status, 0L); + setl(b, T_RC, (strcmp(h_status, "ACTIVE") == 0) ? 0 : 1); + OK(b); +} diff --git a/app/src/au/svc/LIMIT_RESTORE.h b/app/src/au/svc/LIMIT_RESTORE.h new file mode 100644 index 0000000..dea0d63 --- /dev/null +++ b/app/src/au/svc/LIMIT_RESTORE.h @@ -0,0 +1,18 @@ +/* + * LIMIT_RESTORE.h - au 서비스 LIMIT_RESTORE 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호) + * Response fields: T_AMT1(복원된 임시한도), T_RC(0=복원, 1=복원대상 없음) + */ +#ifndef LIMIT_RESTORE_H +#define LIMIT_RESTORE_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long restored_amount; +} au_limit_restore_rec_t; + +void LIMIT_RESTORE(TPSVCINFO *p); + +#endif /* LIMIT_RESTORE_H */ diff --git a/app/src/au/svc/LIMIT_RESTORE.pgc b/app/src/au/svc/LIMIT_RESTORE.pgc new file mode 100644 index 0000000..925f86f --- /dev/null +++ b/app/src/au/svc/LIMIT_RESTORE.pgc @@ -0,0 +1,41 @@ +/* + * LIMIT_RESTORE.pgc - au 모듈 서비스 LIMIT_RESTORE (one service = one file). + * 임시한도 자동복원: LIMIT_TEMP 로 상향됐던 temp_limit 를 걷어내고 + * daily_limit 를 원상 복구한다. temp_limit 가 0 이면 멱등하게 RC=1 로 응답. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "LIMIT_RESTORE.h" + +void LIMIT_RESTORE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24]; + EXEC SQL BEGIN DECLARE SECTION; + long h_temp = 0; + char h_card[24]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + if (card[0] == 0) { userlog("LIMIT_RESTORE: 카드번호 누락"); FAIL(b); } + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + + EXEC SQL SELECT temp_limit INTO :h_temp FROM card_limit WHERE card_no = :h_card; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { userlog("LIMIT_RESTORE: 미등록 카드 %s", card); FAIL(b); } + + if (h_temp <= 0) { /* 복원할 임시한도 없음 - 멱등 처리 */ + setl(b, T_AMT1, 0); + setl(b, T_RC, 1); + OK(b); + } + + EXEC SQL UPDATE card_limit + SET daily_limit = daily_limit - :h_temp, temp_limit = 0, updated_at = now() + WHERE card_no = :h_card AND temp_limit = :h_temp; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + + userlog("LIMIT_RESTORE 카드=%s 임시한도 %ld 복원", card, h_temp); + setl(b, T_AMT1, h_temp); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/au/svc/LIMIT_USAGE.h b/app/src/au/svc/LIMIT_USAGE.h new file mode 100644 index 0000000..d3a8022 --- /dev/null +++ b/app/src/au/svc/LIMIT_USAGE.h @@ -0,0 +1,20 @@ +/* + * LIMIT_USAGE.h - au 서비스 LIMIT_USAGE 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호) + * Response fields: T_ARG1(사용률 bps, 10000=100%), T_STR1(등급 NORMAL/WARN/CRIT/OVER), + * T_AMT1(사용액), T_AMT2(기본한도) + */ +#ifndef LIMIT_USAGE_H +#define LIMIT_USAGE_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long usage_bps; + char grade[8]; +} au_limit_usage_rec_t; + +void LIMIT_USAGE(TPSVCINFO *p); + +#endif /* LIMIT_USAGE_H */ diff --git a/app/src/au/svc/LIMIT_USAGE.pgc b/app/src/au/svc/LIMIT_USAGE.pgc new file mode 100644 index 0000000..fb69810 --- /dev/null +++ b/app/src/au/svc/LIMIT_USAGE.pgc @@ -0,0 +1,40 @@ +/* + * LIMIT_USAGE.pgc - au 모듈 서비스 LIMIT_USAGE (one service = one file). + * 한도 사용률 조회: 사용액/기본한도를 bps(만분율)로 환산하고 + * 70% 미만 NORMAL, 70~90% WARN, 90~100% CRIT, 100% 초과 OVER 등급을 매긴다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "LIMIT_USAGE.h" + +void LIMIT_USAGE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24]; + long bps = 0; + const char *grade; + EXEC SQL BEGIN DECLARE SECTION; + long h_daily = 0, h_used = 0; + char h_card[24]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + if (card[0] == 0) FAIL(b); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + + EXEC SQL SELECT daily_limit, used_amount INTO :h_daily, :h_used + FROM card_limit WHERE card_no = :h_card; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + + if (h_daily > 0) bps = (h_used * 10000L) / h_daily; + + if (bps < 7000) grade = "NORMAL"; + else if (bps < 9000) grade = "WARN"; + else if (bps <= 10000) grade = "CRIT"; + else grade = "OVER"; + + setl(b, T_ARG1, bps); + Bchg(b, T_STR1, 0, (char *)grade, 0L); + setl(b, T_AMT1, h_used); + setl(b, T_AMT2, h_daily); + OK(b); +} diff --git a/app/src/au/svc/MERCH_AUTH_RANK.h b/app/src/au/svc/MERCH_AUTH_RANK.h new file mode 100644 index 0000000..8e3fd72 --- /dev/null +++ b/app/src/au/svc/MERCH_AUTH_RANK.h @@ -0,0 +1,22 @@ +/* + * MERCH_AUTH_RANK.h - au 서비스 MERCH_AUTH_RANK 카피북 (copybook / record header). + * Request fields: T_MERCHANT(가맹점), T_BIZDATE(영업일) + * Response fields: T_ARG1(승인금액 순위), T_GROSS(가맹점 승인금액), T_COUNT(전체 가맹점수), + * T_RC(1=당일 승인실적 없음) + */ +#ifndef MERCH_AUTH_RANK_H +#define MERCH_AUTH_RANK_H + +#include "au_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long rank; + long gross; + long merchant_total; +} au_merch_auth_rank_rec_t; + +void MERCH_AUTH_RANK(TPSVCINFO *p); + +#endif /* MERCH_AUTH_RANK_H */ diff --git a/app/src/au/svc/MERCH_AUTH_RANK.pgc b/app/src/au/svc/MERCH_AUTH_RANK.pgc new file mode 100644 index 0000000..0f7c053 --- /dev/null +++ b/app/src/au/svc/MERCH_AUTH_RANK.pgc @@ -0,0 +1,49 @@ +/* + * MERCH_AUTH_RANK.pgc - au 모듈 서비스 MERCH_AUTH_RANK (one service = one file). + * 가맹점 승인 순위: 영업일의 가맹점별 승인금액을 rank() 윈도우로 서열화한 + * 서브쿼리에서 요청 가맹점의 순위와 금액을 찾아준다. 실적이 없으면 RC=1. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "MERCH_AUTH_RANK.h" + +void MERCH_AUTH_RANK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_rank = 0, h_gross = 0, h_total = 0; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (merch[0] == 0 || bizdate[0] == 0) FAIL(b); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(DISTINCT merchant_id) INTO :h_total + FROM au_authorization + WHERE biz_date = :h_bizdate AND status IN ('A','M','S'); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, h_total); + + EXEC SQL SELECT r.rnk, r.gross INTO :h_rank, :h_gross FROM ( + SELECT merchant_id, sum(amount) AS gross, + rank() OVER (ORDER BY sum(amount) DESC) AS rnk + FROM au_authorization + WHERE biz_date = :h_bizdate AND status IN ('A','M','S') + GROUP BY merchant_id) r + WHERE r.merchant_id = :h_merch; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { /* 당일 승인실적 없음 */ + setl(b, T_ARG1, 0); + setl(b, T_GROSS, 0); + setl(b, T_RC, 1); + OK(b); + } + + setl(b, T_ARG1, h_rank); + setl(b, T_GROSS, h_gross); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/au/svc/NIGHT_POLICY.h b/app/src/au/svc/NIGHT_POLICY.h new file mode 100644 index 0000000..276d1f7 --- /dev/null +++ b/app/src/au/svc/NIGHT_POLICY.h @@ -0,0 +1,21 @@ +/* + * NIGHT_POLICY.h - au 서비스 NIGHT_POLICY 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_AMOUNT(승인 요청금액) + * Response fields: T_RC(0=허용, 1=심야 건수제한 초과, 2=심야 금액제한 초과), + * T_COUNT(최근 7일 심야건수), T_GROSS(최근 7일 심야금액) + */ +#ifndef NIGHT_POLICY_H +#define NIGHT_POLICY_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long req_amount; + long night_count; + long night_gross; +} au_night_policy_rec_t; + +void NIGHT_POLICY(TPSVCINFO *p); + +#endif /* NIGHT_POLICY_H */ diff --git a/app/src/au/svc/NIGHT_POLICY.pgc b/app/src/au/svc/NIGHT_POLICY.pgc new file mode 100644 index 0000000..d82d2cd --- /dev/null +++ b/app/src/au/svc/NIGHT_POLICY.pgc @@ -0,0 +1,45 @@ +/* + * NIGHT_POLICY.pgc - au 모듈 서비스 NIGHT_POLICY (one service = one file). + * 심야 승인정책 점검: 최근 7일간 심야시간대(23시~06시) 승인 이력을 집계해 + * 심야건수 8건 초과면 차단(RC=1), 심야누적 + 요청금액이 500만원을 넘으면 + * 금액제한 차단(RC=2)을 판정한다. 실제 차단은 호출자(AUTH 흐름)가 수행. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "NIGHT_POLICY.h" + +void NIGHT_POLICY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24]; + long amount; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0, h_sum = 0; + char h_card[24]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + amount = getl(b, T_AMOUNT); + if (card[0] == 0) FAIL(b); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_sum + FROM au_authorization + WHERE card_no = :h_card + AND status IN ('A','M','S') + AND created_at >= now() - interval '7 days' + AND (EXTRACT(HOUR FROM created_at) >= 23 OR EXTRACT(HOUR FROM created_at) < 6); + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + if (h_cnt > 8) { + userlog("NIGHT_POLICY 심야 건수제한 card=%s cnt=%ld", card, h_cnt); + setl(b, T_RC, 1); + } else if (h_sum + amount > 5000000) { + userlog("NIGHT_POLICY 심야 금액제한 card=%s sum=%ld req=%ld", card, h_sum, amount); + setl(b, T_RC, 2); + } else { + setl(b, T_RC, 0); + } + OK(b); +} diff --git a/app/src/au/svc/NOAUTH_CAPTURE.h b/app/src/au/svc/NOAUTH_CAPTURE.h new file mode 100644 index 0000000..2f9e302 --- /dev/null +++ b/app/src/au/svc/NOAUTH_CAPTURE.h @@ -0,0 +1,20 @@ +/* + * NOAUTH_CAPTURE.h - au 서비스 NOAUTH_CAPTURE 카피북 (copybook / record header). + * Request fields: T_MERCHANT(가맹점), T_BIZDATE(영업일) + * Response fields: T_COUNT(무승인 매입 의심건수), T_GROSS(의심 금액합), T_RC(1=의심건 존재) + */ +#ifndef NOAUTH_CAPTURE_H +#define NOAUTH_CAPTURE_H + +#include "au_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long suspect_count; + long suspect_gross; +} au_noauth_capture_rec_t; + +void NOAUTH_CAPTURE(TPSVCINFO *p); + +#endif /* NOAUTH_CAPTURE_H */ diff --git a/app/src/au/svc/NOAUTH_CAPTURE.pgc b/app/src/au/svc/NOAUTH_CAPTURE.pgc new file mode 100644 index 0000000..d45f5fb --- /dev/null +++ b/app/src/au/svc/NOAUTH_CAPTURE.pgc @@ -0,0 +1,43 @@ +/* + * NOAUTH_CAPTURE.pgc - au 모듈 서비스 NOAUTH_CAPTURE (one service = one file). + * 무승인 매입 검출: 매입원장(purchase)에는 있는데 같은 가맹점/영업일/금액의 + * 유효 승인(A/M/S)이 승인원장에 존재하지 않는 매입 건을 NOT EXISTS + * 안티조인으로 골라 건수와 금액을 보고한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "NOAUTH_CAPTURE.h" + +void NOAUTH_CAPTURE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0, h_amt = 0; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (merch[0] == 0 || bizdate[0] == 0) { userlog("NOAUTH_CAPTURE: 인자 누락"); FAIL(b); } + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*), coalesce(sum(p.amount),0) + INTO :h_cnt, :h_amt + FROM purchase p + WHERE p.merchant_id = :h_merch AND p.biz_date = :h_bizdate + AND p.status NOT IN ('C','V') + AND NOT EXISTS (SELECT 1 FROM au_authorization a + WHERE a.merchant_id = p.merchant_id + AND a.biz_date = p.biz_date + AND a.amount = p.amount + AND a.status IN ('A','M','S')); + if (sqlca.sqlcode < 0) FAIL(b); + + if (h_cnt > 0) + userlog("NOAUTH_CAPTURE 의심건 merch=%s date=%s cnt=%ld amt=%ld", merch, bizdate, h_cnt, h_amt); + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_amt); + setl(b, T_RC, (h_cnt > 0) ? 1 : 0); + OK(b); +} diff --git a/app/src/au/svc/OVERSEAS_POLICY.h b/app/src/au/svc/OVERSEAS_POLICY.h new file mode 100644 index 0000000..8b8c49b --- /dev/null +++ b/app/src/au/svc/OVERSEAS_POLICY.h @@ -0,0 +1,22 @@ +/* + * OVERSEAS_POLICY.h - au 서비스 OVERSEAS_POLICY 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호), T_MERCHANT(가맹점), T_AMOUNT(금액), + * T_STR1(국가코드), T_BIZDATE(영업일) + * Response fields: T_ID1(승인번호), T_RC(0=승인, 1=일일 해외한도 초과, 9=제재국 차단) + */ +#ifndef OVERSEAS_POLICY_H +#define OVERSEAS_POLICY_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + char merchant_id[64]; + long amount; + char country[4]; + char biz_date[16]; +} au_overseas_policy_rec_t; + +void OVERSEAS_POLICY(TPSVCINFO *p); + +#endif /* OVERSEAS_POLICY_H */ diff --git a/app/src/au/svc/OVERSEAS_POLICY.pgc b/app/src/au/svc/OVERSEAS_POLICY.pgc new file mode 100644 index 0000000..8668ddf --- /dev/null +++ b/app/src/au/svc/OVERSEAS_POLICY.pgc @@ -0,0 +1,61 @@ +/* + * OVERSEAS_POLICY.pgc - au 모듈 서비스 OVERSEAS_POLICY (one service = one file). + * 해외 승인정책: 제재국(간이 목록) 거래는 즉시 차단(RC=9)하고, + * 당일 해외(auth_type=OVERSEA) 누적 + 요청금액이 300만원 한도를 넘으면 + * 거절(D) 기록 후 RC=1, 통과 시 OVERSEA 유형으로 승인 원장을 기록한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "OVERSEAS_POLICY.h" + +static const char *AU_BLOCKED_CTRY[] = { "KP", "IR", "SY", "CU", 0 }; + +void OVERSEAS_POLICY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24], merch[64], ctry[4], bizdate[16], appr[12]; + long amount, auth_id; + int i; + EXEC SQL BEGIN DECLARE SECTION; + long h_daysum = 0; + char h_card[24], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_STR1, ctry, sizeof(ctry)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + amount = getl(b, T_AMOUNT); + if (card[0] == 0 || amount <= 0) FAIL(b); + + for (i = 0; AU_BLOCKED_CTRY[i]; i++) + if (strcmp(ctry, AU_BLOCKED_CTRY[i]) == 0) { + userlog("OVERSEAS_POLICY 제재국 차단 card=%s ctry=%s", card, ctry); + setl(b, T_RC, 9); + OK(b); + } + + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_daysum + FROM au_authorization + WHERE card_no = :h_card AND biz_date = :h_bizdate + AND auth_type = 'OVERSEA' AND status = 'A'; + if (sqlca.sqlcode < 0) FAIL(b); + + auth_id = audb_next_auth_id(); + if (auth_id < 0) FAIL(b); + + if (h_daysum + amount > 3000000) { /* 일일 해외한도 300만원 */ + if (audb_insert_auth(auth_id, card, merch, amount, "000000", "OVERSEA", "D", bizdate) < 0) FAIL(b); + setl(b, T_ID1, auth_id); + setl(b, T_RC, 1); + OK(b); + } + + sprintf(appr, "F%05ld", auth_id % 100000); + if (audb_insert_auth(auth_id, card, merch, amount, appr, "OVERSEA", "A", bizdate) < 0) FAIL(b); + userlog("OVERSEAS_POLICY 승인 card=%s ctry=%s amt=%ld 누적=%ld", card, ctry, amount, h_daysum + amount); + setl(b, T_ID1, auth_id); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/au/svc/PREAUTH_CANCEL.h b/app/src/au/svc/PREAUTH_CANCEL.h new file mode 100644 index 0000000..ee7f7c1 --- /dev/null +++ b/app/src/au/svc/PREAUTH_CANCEL.h @@ -0,0 +1,18 @@ +/* + * PREAUTH_CANCEL.h - au 서비스 PREAUTH_CANCEL 카피북 (copybook / record header). + * Request fields: T_ID2(선승인 ID) + * Response fields: T_AMT1(해제된 선승인 금액), T_RC(0=취소, 1=없음, 2=이미 확정됨, 3=이미 취소됨) + */ +#ifndef PREAUTH_CANCEL_H +#define PREAUTH_CANCEL_H + +#include "au_svc.h" + +typedef struct { + long preauth_id; + long released_amount; +} au_preauth_cancel_rec_t; + +void PREAUTH_CANCEL(TPSVCINFO *p); + +#endif /* PREAUTH_CANCEL_H */ diff --git a/app/src/au/svc/PREAUTH_CANCEL.pgc b/app/src/au/svc/PREAUTH_CANCEL.pgc new file mode 100644 index 0000000..ff3d366 --- /dev/null +++ b/app/src/au/svc/PREAUTH_CANCEL.pgc @@ -0,0 +1,37 @@ +/* + * PREAUTH_CANCEL.pgc - au 모듈 서비스 PREAUTH_CANCEL (one service = one file). + * 선승인 취소: 미확정(P) 선승인을 X 로 전환해 홀드 금액을 해제한다. + * 상태별로 세분화된 RC(없음/이미 확정/이미 취소)를 돌려줘 콜센터 응대에 쓴다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "PREAUTH_CANCEL.h" + +void PREAUTH_CANCEL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_id, h_amt = 0; + char h_status[4]; + EXEC SQL END DECLARE SECTION; + + h_id = getl(b, T_ID2); + if (h_id <= 0) FAIL(b); + memset(h_status, 0, sizeof(h_status)); + + EXEC SQL SELECT status, amount INTO :h_status, :h_amt + FROM au_preauth WHERE preauth_id = :h_id; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); setl(b, T_AMT1, 0); OK(b); } + + if (h_status[0] == 'C') { setl(b, T_RC, 2); setl(b, T_AMT1, h_amt); OK(b); } + if (h_status[0] == 'X') { setl(b, T_RC, 3); setl(b, T_AMT1, h_amt); OK(b); } + + EXEC SQL UPDATE au_preauth SET status = 'X' + WHERE preauth_id = :h_id AND status = 'P'; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + + userlog("PREAUTH_CANCEL id=%ld 금액 %ld 홀드 해제", h_id, h_amt); + setl(b, T_AMT1, h_amt); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/au/svc/PREAUTH_EXTEND.h b/app/src/au/svc/PREAUTH_EXTEND.h new file mode 100644 index 0000000..f16d840 --- /dev/null +++ b/app/src/au/svc/PREAUTH_EXTEND.h @@ -0,0 +1,19 @@ +/* + * PREAUTH_EXTEND.h - au 서비스 PREAUTH_EXTEND 카피북 (copybook / record header). + * Request fields: T_ID2(선승인 ID), T_ARG1(연장일수, 기본 7) + * Response fields: T_STR1(연장 후 만기일), T_RC(0=연장, 1=미확정 상태 아님) + */ +#ifndef PREAUTH_EXTEND_H +#define PREAUTH_EXTEND_H + +#include "au_svc.h" + +typedef struct { + long preauth_id; + long extend_days; + char new_expiry[16]; +} au_preauth_extend_rec_t; + +void PREAUTH_EXTEND(TPSVCINFO *p); + +#endif /* PREAUTH_EXTEND_H */ diff --git a/app/src/au/svc/PREAUTH_EXTEND.pgc b/app/src/au/svc/PREAUTH_EXTEND.pgc new file mode 100644 index 0000000..8be1717 --- /dev/null +++ b/app/src/au/svc/PREAUTH_EXTEND.pgc @@ -0,0 +1,44 @@ +/* + * PREAUTH_EXTEND.pgc - au 모듈 서비스 PREAUTH_EXTEND (one service = one file). + * 선승인 연장: 아직 확정되지 않은(P) 선승인의 기준일(biz_date)을 지정 일수만큼 + * 미뤄 만기를 연장하고, 연장 후 만기일을 문자열로 돌려준다. + * 확정(C)/취소(X) 상태는 연장 불가로 RC=1 응답한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "PREAUTH_EXTEND.h" + +void PREAUTH_EXTEND(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_id, h_days; + char h_newdate[16], h_status[4]; + EXEC SQL END DECLARE SECTION; + + h_id = getl(b, T_ID2); + h_days = getl(b, T_ARG1); if (h_days <= 0 || h_days > 60) h_days = 7; + if (h_id <= 0) { userlog("PREAUTH_EXTEND: 선승인 ID 누락"); FAIL(b); } + memset(h_newdate, 0, sizeof(h_newdate)); + memset(h_status, 0, sizeof(h_status)); + + EXEC SQL UPDATE au_preauth + SET biz_date = biz_date + CAST(:h_days AS integer) + WHERE preauth_id = :h_id AND status = 'P'; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { + EXEC SQL SELECT status INTO :h_status FROM au_preauth WHERE preauth_id = :h_id; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + userlog("PREAUTH_EXTEND 연장불가 id=%ld status=%s", h_id, h_status); + setl(b, T_RC, 1); + OK(b); + } + + EXEC SQL SELECT CAST(biz_date AS text) INTO :h_newdate + FROM au_preauth WHERE preauth_id = :h_id; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + + userlog("PREAUTH_EXTEND id=%ld +%ld일 -> %s", h_id, h_days, h_newdate); + Bchg(b, T_STR1, 0, h_newdate, 0L); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/au/svc/PREAUTH_INQ.h b/app/src/au/svc/PREAUTH_INQ.h new file mode 100644 index 0000000..4c9c501 --- /dev/null +++ b/app/src/au/svc/PREAUTH_INQ.h @@ -0,0 +1,22 @@ +/* + * PREAUTH_INQ.h - au 서비스 PREAUTH_INQ 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호) + * Response fields: T_ARG1(미확정 P건수), T_ARG2(확정 C건수), T_ARG3(취소 X건수), + * T_AMT1(미확정 홀드금액), T_RC(1=홀드 잔존) + */ +#ifndef PREAUTH_INQ_H +#define PREAUTH_INQ_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long pending_cnt; + long confirmed_cnt; + long canceled_cnt; + long hold_amount; +} au_preauth_inq_rec_t; + +void PREAUTH_INQ(TPSVCINFO *p); + +#endif /* PREAUTH_INQ_H */ diff --git a/app/src/au/svc/PREAUTH_INQ.pgc b/app/src/au/svc/PREAUTH_INQ.pgc new file mode 100644 index 0000000..ecd021c --- /dev/null +++ b/app/src/au/svc/PREAUTH_INQ.pgc @@ -0,0 +1,36 @@ +/* + * PREAUTH_INQ.pgc - au 모듈 서비스 PREAUTH_INQ (one service = one file). + * 선승인 조회: 카드의 선승인 현황을 상태(P/C/X)별 FILTER 집계로 나눠 주고 + * 미확정 홀드금액 합계를 함께 내려준다. 홀드가 남아 있으면 RC=1. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "PREAUTH_INQ.h" + +void PREAUTH_INQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24]; + EXEC SQL BEGIN DECLARE SECTION; + long h_p = 0, h_c = 0, h_x = 0, h_hold = 0; + char h_card[24]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + if (card[0] == 0) FAIL(b); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + + EXEC SQL SELECT count(*) FILTER (WHERE status = 'P'), + count(*) FILTER (WHERE status = 'C'), + count(*) FILTER (WHERE status = 'X'), + coalesce(sum(amount) FILTER (WHERE status = 'P'), 0) + INTO :h_p, :h_c, :h_x, :h_hold + FROM au_preauth WHERE card_no = :h_card; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_ARG1, h_p); + setl(b, T_ARG2, h_c); + setl(b, T_ARG3, h_x); + setl(b, T_AMT1, h_hold); + setl(b, T_RC, (h_p > 0) ? 1 : 0); + OK(b); +} diff --git a/app/src/au/svc/RISK_SCORE_INQ.h b/app/src/au/svc/RISK_SCORE_INQ.h new file mode 100644 index 0000000..367b628 --- /dev/null +++ b/app/src/au/svc/RISK_SCORE_INQ.h @@ -0,0 +1,22 @@ +/* + * RISK_SCORE_INQ.h - au 서비스 RISK_SCORE_INQ 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호) + * Response fields: T_ARG1(종합 리스크점수 0~1000), T_STR1(등급 LOW/MID/HIGH/SEVERE), + * T_ARG2(부정적발 건수), T_ARG3(30일 거절 건수) + */ +#ifndef RISK_SCORE_INQ_H +#define RISK_SCORE_INQ_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long risk_score; + char grade[8]; + long fraud_cases; + long declines30; +} au_risk_score_rec_t; + +void RISK_SCORE_INQ(TPSVCINFO *p); + +#endif /* RISK_SCORE_INQ_H */ diff --git a/app/src/au/svc/RISK_SCORE_INQ.pgc b/app/src/au/svc/RISK_SCORE_INQ.pgc new file mode 100644 index 0000000..b006373 --- /dev/null +++ b/app/src/au/svc/RISK_SCORE_INQ.pgc @@ -0,0 +1,54 @@ +/* + * RISK_SCORE_INQ.pgc - au 모듈 서비스 RISK_SCORE_INQ (one service = one file). + * 카드 리스크 점수 산출: 부정거래 적발 이력(평균점수/건수), 30일 거절률, + * 블랙리스트 등재 여부를 가중 합산해 0~1000 종합 리스크 점수와 + * 4단계 등급(LOW/MID/HIGH/SEVERE)을 매긴다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "RISK_SCORE_INQ.h" + +void RISK_SCORE_INQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24]; + long score; + const char *grade; + EXEC SQL BEGIN DECLARE SECTION; + long h_fcnt = 0, h_favg = 0, h_dec = 0, h_all = 0, h_black = 0; + char h_card[24]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + if (card[0] == 0) FAIL(b); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + + EXEC SQL SELECT count(*), CAST(coalesce(avg(score),0) AS bigint) + INTO :h_fcnt, :h_favg FROM au_fraud_log WHERE card_no = :h_card; + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT count(*), count(*) FILTER (WHERE status = 'D') + INTO :h_all, :h_dec + FROM au_authorization + WHERE card_no = :h_card AND biz_date >= current_date - 30; + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT count(*) INTO :h_black FROM au_blacklist WHERE card_no = :h_card; + if (sqlca.sqlcode < 0) FAIL(b); + + /* 가중 합산: 적발평균 40% + 적발건수(건당 60점) + 거절률(최대 300점) + 블랙리스트 300점 */ + score = (h_favg * 4) / 10 + h_fcnt * 60; + if (h_all > 0) score += (h_dec * 300L) / h_all; + if (h_black > 0) score += 300; + if (score > 1000) score = 1000; + + if (score < 200) grade = "LOW"; + else if (score < 500) grade = "MID"; + else if (score < 800) grade = "HIGH"; + else grade = "SEVERE"; + + setl(b, T_ARG1, score); + Bchg(b, T_STR1, 0, (char *)grade, 0L); + setl(b, T_ARG2, h_fcnt); + setl(b, T_ARG3, h_dec); + OK(b); +} diff --git a/app/src/au/svc/STANDIN_INQ.h b/app/src/au/svc/STANDIN_INQ.h new file mode 100644 index 0000000..45810b5 --- /dev/null +++ b/app/src/au/svc/STANDIN_INQ.h @@ -0,0 +1,22 @@ +/* + * STANDIN_INQ.h - au 서비스 STANDIN_INQ 카피북 (copybook / record header). + * Request fields: T_BIZDATE(영업일) + * Response fields: T_ARG1(미정산 A건수), T_ARG2(정산완료 S건수), T_AMT1(미정산 금액), + * T_AMT2(정산완료 금액), T_RC(1=미정산 잔존) + */ +#ifndef STANDIN_INQ_H +#define STANDIN_INQ_H + +#include "au_svc.h" + +typedef struct { + char biz_date[16]; + long pending_cnt; + long settled_cnt; + long pending_amt; + long settled_amt; +} au_standin_inq_rec_t; + +void STANDIN_INQ(TPSVCINFO *p); + +#endif /* STANDIN_INQ_H */ diff --git a/app/src/au/svc/STANDIN_INQ.pgc b/app/src/au/svc/STANDIN_INQ.pgc new file mode 100644 index 0000000..f345680 --- /dev/null +++ b/app/src/au/svc/STANDIN_INQ.pgc @@ -0,0 +1,37 @@ +/* + * STANDIN_INQ.pgc - au 모듈 서비스 STANDIN_INQ (one service = one file). + * 대행승인 현황 조회: 영업일의 STANDIN 승인 건을 미정산(A)/정산완료(S)로 + * 나눠 FILTER 집계하고 미정산 잔존 여부를 RC 로 알린다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "STANDIN_INQ.h" + +void STANDIN_INQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_pc = 0, h_sc = 0, h_pa = 0, h_sa = 0; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) FAIL(b); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*) FILTER (WHERE status = 'A'), + count(*) FILTER (WHERE status = 'S'), + coalesce(sum(amount) FILTER (WHERE status = 'A'), 0), + coalesce(sum(amount) FILTER (WHERE status = 'S'), 0) + INTO :h_pc, :h_sc, :h_pa, :h_sa + FROM au_authorization + WHERE biz_date = :h_bizdate AND auth_type = 'STANDIN'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_ARG1, h_pc); + setl(b, T_ARG2, h_sc); + setl(b, T_AMT1, h_pa); + setl(b, T_AMT2, h_sa); + setl(b, T_RC, (h_pc > 0) ? 1 : 0); + OK(b); +} diff --git a/app/src/au/svc/STANDIN_SETTLE.h b/app/src/au/svc/STANDIN_SETTLE.h new file mode 100644 index 0000000..7b9ccd3 --- /dev/null +++ b/app/src/au/svc/STANDIN_SETTLE.h @@ -0,0 +1,19 @@ +/* + * STANDIN_SETTLE.h - au 서비스 STANDIN_SETTLE 카피북 (copybook / record header). + * Request fields: T_BIZDATE(영업일) + * Response fields: T_COUNT(정산반영 건수), T_GROSS(정산반영 금액), T_RC(1=대상 없음) + */ +#ifndef STANDIN_SETTLE_H +#define STANDIN_SETTLE_H + +#include "au_svc.h" + +typedef struct { + char biz_date[16]; + long settled_count; + long settled_gross; +} au_standin_settle_rec_t; + +void STANDIN_SETTLE(TPSVCINFO *p); + +#endif /* STANDIN_SETTLE_H */ diff --git a/app/src/au/svc/STANDIN_SETTLE.pgc b/app/src/au/svc/STANDIN_SETTLE.pgc new file mode 100644 index 0000000..c023c90 --- /dev/null +++ b/app/src/au/svc/STANDIN_SETTLE.pgc @@ -0,0 +1,39 @@ +/* + * STANDIN_SETTLE.pgc - au 모듈 서비스 STANDIN_SETTLE (one service = one file). + * 대행승인(stand-in) 정산: 이슈어 복구 후, 대행으로 승인(A)해 둔 STANDIN + * 건들을 일괄 정산반영(S) 상태로 전환한다. 전환 전 건수/금액을 미리 집계해 + * 응답으로 돌려주며 대상이 없으면 RC=1 멱등 응답한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "STANDIN_SETTLE.h" + +void STANDIN_SETTLE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0, h_amt = 0; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) { userlog("STANDIN_SETTLE: 영업일 누락"); FAIL(b); } + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_amt + FROM au_authorization + WHERE biz_date = :h_bizdate AND auth_type = 'STANDIN' AND status = 'A'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_amt); + if (h_cnt == 0) { setl(b, T_RC, 1); OK(b); } + + EXEC SQL UPDATE au_authorization SET status = 'S', updated_at = now() + WHERE biz_date = :h_bizdate AND auth_type = 'STANDIN' AND status = 'A'; + if (sqlca.sqlcode < 0) FAIL(b); + + userlog("STANDIN_SETTLE %s 대행승인 %ld건 %ld원 정산반영", bizdate, h_cnt, h_amt); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/au/svc/STAT_BYTYPE.h b/app/src/au/svc/STAT_BYTYPE.h new file mode 100644 index 0000000..962c7f0 --- /dev/null +++ b/app/src/au/svc/STAT_BYTYPE.h @@ -0,0 +1,24 @@ +/* + * STAT_BYTYPE.h - au 서비스 STAT_BYTYPE 카피북 (copybook / record header). + * Request fields: T_BIZDATE(영업일) + * Response fields: T_ARG1(AUTH), T_ARG2(STANDIN), T_ARG3(INSTALL), T_ARG4(PREAUTH), + * T_ARG5(OVERSEA), T_ARG6(기타), T_COUNT(총건수) + */ +#ifndef STAT_BYTYPE_H +#define STAT_BYTYPE_H + +#include "au_svc.h" + +typedef struct { + char biz_date[16]; + long cnt_auth; + long cnt_standin; + long cnt_install; + long cnt_preauth; + long cnt_oversea; + long cnt_other; +} au_stat_bytype_rec_t; + +void STAT_BYTYPE(TPSVCINFO *p); + +#endif /* STAT_BYTYPE_H */ diff --git a/app/src/au/svc/STAT_BYTYPE.pgc b/app/src/au/svc/STAT_BYTYPE.pgc new file mode 100644 index 0000000..b48bbe5 --- /dev/null +++ b/app/src/au/svc/STAT_BYTYPE.pgc @@ -0,0 +1,41 @@ +/* + * STAT_BYTYPE.pgc - au 모듈 서비스 STAT_BYTYPE (one service = one file). + * 승인유형(채널)별 통계: 영업일 승인 접수를 auth_type + * (AUTH/STANDIN/INSTALL/PREAUTH/OVERSEA/기타)별 FILTER 집계로 나눠 + * 채널 구성 현황을 한 번에 내려준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "STAT_BYTYPE.h" + +void STAT_BYTYPE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_a = 0, h_s = 0, h_i = 0, h_p = 0, h_o = 0, h_tot = 0; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) FAIL(b); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*), + count(*) FILTER (WHERE auth_type = 'AUTH'), + count(*) FILTER (WHERE auth_type = 'STANDIN'), + count(*) FILTER (WHERE auth_type = 'INSTALL'), + count(*) FILTER (WHERE auth_type = 'PREAUTH'), + count(*) FILTER (WHERE auth_type = 'OVERSEA') + INTO :h_tot, :h_a, :h_s, :h_i, :h_p, :h_o + FROM au_authorization WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_ARG1, h_a); + setl(b, T_ARG2, h_s); + setl(b, T_ARG3, h_i); + setl(b, T_ARG4, h_p); + setl(b, T_ARG5, h_o); + setl(b, T_ARG6, h_tot - h_a - h_s - h_i - h_p - h_o); + setl(b, T_COUNT, h_tot); + OK(b); +} diff --git a/app/src/au/svc/STAT_HOURLY.h b/app/src/au/svc/STAT_HOURLY.h new file mode 100644 index 0000000..1708366 --- /dev/null +++ b/app/src/au/svc/STAT_HOURLY.h @@ -0,0 +1,22 @@ +/* + * STAT_HOURLY.h - au 서비스 STAT_HOURLY 카피북 (copybook / record header). + * Request fields: T_BIZDATE(영업일) + * Response fields: T_STR1(피크 시간대 HH), T_ARG1(피크 시간대 건수), T_COUNT(총건수), + * T_ARG2(활동 시간대 수) + */ +#ifndef STAT_HOURLY_H +#define STAT_HOURLY_H + +#include "au_svc.h" + +typedef struct { + char biz_date[16]; + char peak_hour[4]; + long peak_count; + long total_count; + long active_hours; +} au_stat_hourly_rec_t; + +void STAT_HOURLY(TPSVCINFO *p); + +#endif /* STAT_HOURLY_H */ diff --git a/app/src/au/svc/STAT_HOURLY.pgc b/app/src/au/svc/STAT_HOURLY.pgc new file mode 100644 index 0000000..7f7eba4 --- /dev/null +++ b/app/src/au/svc/STAT_HOURLY.pgc @@ -0,0 +1,45 @@ +/* + * STAT_HOURLY.pgc - au 모듈 서비스 STAT_HOURLY (one service = one file). + * 시간대별 승인 통계: 영업일의 승인 접수를 시(HH24) 단위로 그룹핑한 커서를 + * 순회하며 총건수·활동 시간대 수를 세고 피크 시간대와 그 건수를 찾아낸다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "STAT_HOURLY.h" + +void STAT_HOURLY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], peak_hour[4] = ""; + long total = 0, hours = 0, peak = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; + char h_bizdate[16], h_hour[4]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) FAIL(b); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL DECLARE cur_shour CURSOR FOR + SELECT to_char(created_at, 'HH24'), count(*) + FROM au_authorization WHERE biz_date = :h_bizdate + GROUP BY to_char(created_at, 'HH24') + ORDER BY to_char(created_at, 'HH24'); + EXEC SQL OPEN cur_shour; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cur_shour INTO :h_hour, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_shour; FAIL(b); } + hours++; + total += h_cnt; + if (h_cnt > peak) { peak = h_cnt; strncpy(peak_hour, h_hour, sizeof(peak_hour)-1); } + } + EXEC SQL CLOSE cur_shour; + + Bchg(b, T_STR1, 0, peak_hour, 0L); + setl(b, T_ARG1, peak); + setl(b, T_COUNT, total); + setl(b, T_ARG2, hours); + OK(b); +} diff --git a/app/src/au/svc/STAT_ISSUER.h b/app/src/au/svc/STAT_ISSUER.h new file mode 100644 index 0000000..f5d1453 --- /dev/null +++ b/app/src/au/svc/STAT_ISSUER.h @@ -0,0 +1,22 @@ +/* + * STAT_ISSUER.h - au 서비스 STAT_ISSUER 카피북 (copybook / record header). + * Request fields: T_BIZDATE(영업일) + * Response fields: T_STR1(발급사망 분포 문자열 "VISA=n;MC=n;..."), T_ISSUER(최다 발급사망), + * T_ARG1(최다 발급사망 건수), T_COUNT(총건수) + */ +#ifndef STAT_ISSUER_H +#define STAT_ISSUER_H + +#include "au_svc.h" + +typedef struct { + char biz_date[16]; + char mix[256]; + char top_issuer[16]; + long top_count; + long total; +} au_stat_issuer_rec_t; + +void STAT_ISSUER(TPSVCINFO *p); + +#endif /* STAT_ISSUER_H */ diff --git a/app/src/au/svc/STAT_ISSUER.pgc b/app/src/au/svc/STAT_ISSUER.pgc new file mode 100644 index 0000000..6417e3c --- /dev/null +++ b/app/src/au/svc/STAT_ISSUER.pgc @@ -0,0 +1,59 @@ +/* + * STAT_ISSUER.pgc - au 모듈 서비스 STAT_ISSUER (one service = one file). + * 발급사(카드망)별 승인 통계: 카드번호 선두 자리로 발급사망을 판별 + * (3=AMEX, 4=VISA, 5=MC, 6=LOCAL, 그외 ETC)하는 커서를 돌며 분포 문자열을 + * 조립하고 최다 발급사망을 찾아낸다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "STAT_ISSUER.h" + +static const char *au_issuer_net_name(const char *lead) +{ + switch (lead[0]) { + case '3': return "AMEX"; + case '4': return "VISA"; + case '5': return "MC"; + case '6': return "LOCAL"; + default: return "ETC"; + } +} + +void STAT_ISSUER(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], mix[256] = "", top_name[16] = ""; + char item[40]; + long total = 0, top = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; + char h_bizdate[16], h_lead[4]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) FAIL(b); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL DECLARE cur_siss CURSOR FOR + SELECT substr(card_no, 1, 1), count(*) + FROM au_authorization WHERE biz_date = :h_bizdate + GROUP BY substr(card_no, 1, 1) + ORDER BY count(*) DESC; + EXEC SQL OPEN cur_siss; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cur_siss INTO :h_lead, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_siss; FAIL(b); } + total += h_cnt; + if (h_cnt > top) { top = h_cnt; strncpy(top_name, au_issuer_net_name(h_lead), sizeof(top_name)-1); } + snprintf(item, sizeof(item), "%s=%ld;", au_issuer_net_name(h_lead), h_cnt); + if (strlen(mix) + strlen(item) < sizeof(mix) - 1) strcat(mix, item); + } + EXEC SQL CLOSE cur_siss; + + Bchg(b, T_STR1, 0, mix, 0L); + Bchg(b, T_ISSUER, 0, top_name, 0L); + setl(b, T_ARG1, top); + setl(b, T_COUNT, total); + OK(b); +} diff --git a/app/src/au/svc/TOKEN_VERIFY.h b/app/src/au/svc/TOKEN_VERIFY.h new file mode 100644 index 0000000..5e7c098 --- /dev/null +++ b/app/src/au/svc/TOKEN_VERIFY.h @@ -0,0 +1,20 @@ +/* + * TOKEN_VERIFY.h - au 서비스 TOKEN_VERIFY 카피북 (copybook / record header). + * Request fields: T_KEY1(카드 토큰/PAN) + * Response fields: T_RC(0=유효, 비트: 1=형식오류, 2=Luhn실패, 4=블랙리스트, 8=원장없음), + * T_STR1(판정 요약) + */ +#ifndef TOKEN_VERIFY_H +#define TOKEN_VERIFY_H + +#include "au_svc.h" + +typedef struct { + char token[24]; + long verdict_bits; + char verdict[32]; +} au_token_verify_rec_t; + +void TOKEN_VERIFY(TPSVCINFO *p); + +#endif /* TOKEN_VERIFY_H */ diff --git a/app/src/au/svc/TOKEN_VERIFY.pgc b/app/src/au/svc/TOKEN_VERIFY.pgc new file mode 100644 index 0000000..1182ce9 --- /dev/null +++ b/app/src/au/svc/TOKEN_VERIFY.pgc @@ -0,0 +1,43 @@ +/* + * TOKEN_VERIFY.pgc - au 모듈 서비스 TOKEN_VERIFY (one service = one file). + * 토큰 검증: 결제 토큰(PAN)을 4단계로 검증해 비트마스크 RC 로 응답한다. + * 1) 길이/숫자 형식, 2) Luhn 체크섬, 3) 블랙리스트 등재, 4) 한도원장 존재. + * 모든 비트가 0 이면 승인 파이프라인 진입 가능. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "TOKEN_VERIFY.h" + +void TOKEN_VERIFY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char tok[24]; + long bits = 0; + int i, len, digits_ok = 1; + EXEC SQL BEGIN DECLARE SECTION; + long h_black = 0, h_reg = 0; + char h_tok[24]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, tok, sizeof(tok)); + len = (int)strlen(tok); + for (i = 0; i < len; i++) + if (tok[i] < '0' || tok[i] > '9') { digits_ok = 0; break; } + + if (len < 13 || len > 19 || !digits_ok) bits |= 1; /* 형식 오류 */ + else if (!acq_luhn_ok(tok)) bits |= 2; /* Luhn 실패 */ + + if ((bits & 1) == 0) { + strncpy(h_tok, tok, sizeof(h_tok)-1); h_tok[sizeof(h_tok)-1] = 0; + EXEC SQL SELECT (SELECT count(*) FROM au_blacklist WHERE card_no = :h_tok), + (SELECT count(*) FROM card_limit WHERE card_no = :h_tok) + INTO :h_black, :h_reg; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_black > 0) bits |= 4; /* 블랙리스트 */ + if (h_reg == 0) bits |= 8; /* 원장 미등록 */ + } + + setl(b, T_RC, bits); + Bchg(b, T_STR1, 0, (bits == 0) ? "VALID" : "REJECT", 0L); + userlog("TOKEN_VERIFY len=%d bits=%ld", len, bits); + OK(b); +} diff --git a/app/src/au/svc/VELOCITY_HOUR.h b/app/src/au/svc/VELOCITY_HOUR.h new file mode 100644 index 0000000..110311c --- /dev/null +++ b/app/src/au/svc/VELOCITY_HOUR.h @@ -0,0 +1,20 @@ +/* + * VELOCITY_HOUR.h - au 서비스 VELOCITY_HOUR 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호) + * Response fields: T_COUNT(최근 1시간 건수), T_GROSS(최근 1시간 금액), + * T_RC(1=시간당 속도위반) + */ +#ifndef VELOCITY_HOUR_H +#define VELOCITY_HOUR_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long hour_count; + long hour_gross; +} au_velocity_hour_rec_t; + +void VELOCITY_HOUR(TPSVCINFO *p); + +#endif /* VELOCITY_HOUR_H */ diff --git a/app/src/au/svc/VELOCITY_HOUR.pgc b/app/src/au/svc/VELOCITY_HOUR.pgc new file mode 100644 index 0000000..1fa8655 --- /dev/null +++ b/app/src/au/svc/VELOCITY_HOUR.pgc @@ -0,0 +1,32 @@ +/* + * VELOCITY_HOUR.pgc - au 모듈 서비스 VELOCITY_HOUR (one service = one file). + * 시간 단위 velocity 점검: 직전 60분 슬라이딩 윈도우의 카드 승인 + * 건수/금액을 집계해 3건 초과 또는 200만원 초과면 속도위반(RC=1)으로 판정. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "VELOCITY_HOUR.h" + +void VELOCITY_HOUR(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0, h_sum = 0; + char h_card[24]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + if (card[0] == 0) FAIL(b); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_sum + FROM au_authorization + WHERE card_no = :h_card AND status = 'A' + AND created_at >= now() - interval '1 hour'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + setl(b, T_RC, (h_cnt > 3 || h_sum > 2000000) ? 1 : 0); + OK(b); +} diff --git a/app/src/au/svc/VELOCITY_MERCH.h b/app/src/au/svc/VELOCITY_MERCH.h new file mode 100644 index 0000000..9408f26 --- /dev/null +++ b/app/src/au/svc/VELOCITY_MERCH.h @@ -0,0 +1,20 @@ +/* + * VELOCITY_MERCH.h - au 서비스 VELOCITY_MERCH 카피북 (copybook / record header). + * Request fields: T_MERCHANT(가맹점) + * Response fields: T_COUNT(최근 10분 건수), T_ARG1(최근 10분 distinct 카드수), + * T_RC(1=단말 폭주 의심, 2=동일카드 반복 의심) + */ +#ifndef VELOCITY_MERCH_H +#define VELOCITY_MERCH_H + +#include "au_svc.h" + +typedef struct { + char merchant_id[64]; + long burst_count; + long card_cnt; +} au_velocity_merch_rec_t; + +void VELOCITY_MERCH(TPSVCINFO *p); + +#endif /* VELOCITY_MERCH_H */ diff --git a/app/src/au/svc/VELOCITY_MERCH.pgc b/app/src/au/svc/VELOCITY_MERCH.pgc new file mode 100644 index 0000000..400cbdd --- /dev/null +++ b/app/src/au/svc/VELOCITY_MERCH.pgc @@ -0,0 +1,41 @@ +/* + * VELOCITY_MERCH.pgc - au 모듈 서비스 VELOCITY_MERCH (one service = one file). + * 가맹점 velocity 점검: 직전 10분간 가맹점 승인 요청 폭주를 감시한다. + * 30건 초과면 단말 폭주 의심(RC=1), 건수 대비 distinct 카드수가 1/3 미만이면 + * 동일카드 반복시도 의심(RC=2)으로 판정한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "VELOCITY_MERCH.h" + +void VELOCITY_MERCH(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0, h_cards = 0; + char h_merch[64]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + if (merch[0] == 0) FAIL(b); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT count(*), count(DISTINCT card_no) INTO :h_cnt, :h_cards + FROM au_authorization + WHERE merchant_id = :h_merch + AND created_at >= now() - interval '10 minutes'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + setl(b, T_ARG1, h_cards); + if (h_cnt > 30) { + userlog("VELOCITY_MERCH 폭주 의심 merch=%s cnt=%ld", merch, h_cnt); + setl(b, T_RC, 1); + } else if (h_cnt >= 6 && h_cards * 3 < h_cnt) { + userlog("VELOCITY_MERCH 동일카드 반복 의심 merch=%s cnt=%ld cards=%ld", merch, h_cnt, h_cards); + setl(b, T_RC, 2); + } else { + setl(b, T_RC, 0); + } + OK(b); +} diff --git a/app/src/au/svc/VELOCITY_WEEK.h b/app/src/au/svc/VELOCITY_WEEK.h new file mode 100644 index 0000000..35150f5 --- /dev/null +++ b/app/src/au/svc/VELOCITY_WEEK.h @@ -0,0 +1,21 @@ +/* + * VELOCITY_WEEK.h - au 서비스 VELOCITY_WEEK 카피북 (copybook / record header). + * Request fields: T_KEY1(카드번호) + * Response fields: T_COUNT(주간 건수), T_GROSS(주간 금액), T_ARG1(방문 가맹점수), + * T_RC(1=주간 속도위반) + */ +#ifndef VELOCITY_WEEK_H +#define VELOCITY_WEEK_H + +#include "au_svc.h" + +typedef struct { + char card_no[24]; + long week_count; + long week_gross; + long merchant_cnt; +} au_velocity_week_rec_t; + +void VELOCITY_WEEK(TPSVCINFO *p); + +#endif /* VELOCITY_WEEK_H */ diff --git a/app/src/au/svc/VELOCITY_WEEK.pgc b/app/src/au/svc/VELOCITY_WEEK.pgc new file mode 100644 index 0000000..9c4cd96 --- /dev/null +++ b/app/src/au/svc/VELOCITY_WEEK.pgc @@ -0,0 +1,40 @@ +/* + * VELOCITY_WEEK.pgc - au 모듈 서비스 VELOCITY_WEEK (one service = one file). + * 주간 velocity 점검: 최근 7일 카드 승인 건수/금액에 더해 방문 가맹점의 + * distinct 수를 집계, 50건 초과·거래 가맹점 15곳 초과·주간 2천만원 초과 + * 중 하나라도 걸리면 위반(RC=1)으로 판정한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr. + */ +#include "VELOCITY_WEEK.h" + +void VELOCITY_WEEK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char card[24]; + int viol; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt = 0, h_sum = 0, h_merch = 0; + char h_card[24]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, card, sizeof(card)); + if (card[0] == 0) FAIL(b); + strncpy(h_card, card, sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + + EXEC SQL SELECT count(*), coalesce(sum(amount),0), count(DISTINCT merchant_id) + INTO :h_cnt, :h_sum, :h_merch + FROM au_authorization + WHERE card_no = :h_card AND status IN ('A','M','S') + AND biz_date >= current_date - 7; + if (sqlca.sqlcode < 0) FAIL(b); + + viol = (h_cnt > 50) || (h_merch > 15) || (h_sum > 20000000); + if (viol) + userlog("VELOCITY_WEEK 위반 card=%s cnt=%ld merch=%ld sum=%ld", card, h_cnt, h_merch, h_sum); + + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + setl(b, T_ARG1, h_merch); + setl(b, T_RC, viol ? 1 : 0); + OK(b); +} diff --git a/app/src/cl/batch/cl_apprflow_batch.pgc b/app/src/cl/batch/cl_apprflow_batch.pgc new file mode 100644 index 0000000..07dbe59 --- /dev/null +++ b/app/src/cl/batch/cl_apprflow_batch.pgc @@ -0,0 +1,62 @@ +/* + * cl_apprflow_batch.pgc - cl 마감 승인 워크플로 일괄 배치 (검증부 승인 + XA). + * + * 승인요청(APPR_REQ)된 완료(C) 마감을 커서로 순회, 저장 건수와 실매입 건수가 + * 일치하는 건만 승인(A)으로 전환하고 불일치 건은 HOLD 표시 후 건너뛴다. + * 승인 게이트가 검증을 겸하는 워크플로. 전 구간 ONE global XA. + * host 변수는 전용 카피북 cl_apprflow_cpy.h 를 EXEC SQL INCLUDE 로 가져온다. + * Usage: cl_apprflow_batch [YYYY-MM] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *prefix = (argc > 1) ? argv[1] : "2026-07"; + long ok = 0, held = 0; + + EXEC SQL INCLUDE cl_apprflow_cpy; + + strncpy(af_prefix, prefix, sizeof(af_prefix)-1); af_prefix[sizeof(af_prefix)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(90, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE bapf CURSOR FOR + SELECT close_id, to_char(biz_date,'YYYY-MM-DD'), txn_count FROM cl_close_log + WHERE status = 'C' AND memo LIKE '%APPR_REQ%' + AND period_key LIKE :af_prefix || '%' + ORDER BY close_id; + EXEC SQL OPEN bapf; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH bapf INTO :af_cid, :af_day, :af_stored; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bapf; tpabort(0); return 1; } + EXEC SQL SELECT count(*) INTO :af_actual FROM purchase WHERE biz_date = :af_day; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bapf; tpabort(0); return 1; } + if (af_stored == af_actual) { + EXEC SQL UPDATE cl_close_log + SET status = 'A', updated_at = now() WHERE close_id = :af_cid; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bapf; tpabort(0); return 1; } + ok++; + } else { + EXEC SQL UPDATE cl_close_log + SET memo = memo || '|HOLD', updated_at = now() WHERE close_id = :af_cid; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bapf; tpabort(0); return 1; } + printf(" 승인보류 cid=%ld (%ld != %ld)\n", af_cid, af_stored, af_actual); + held++; + } + } + EXEC SQL CLOSE bapf; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_apprflow_batch COMMIT: prefix=%s 승인=%ld 보류=%ld\n", prefix, ok, held); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_apprflow_cpy.h b/app/src/cl/batch/cl_apprflow_cpy.h new file mode 100644 index 0000000..0bbe984 --- /dev/null +++ b/app/src/cl/batch/cl_apprflow_cpy.h @@ -0,0 +1,8 @@ +/* cl_apprflow_cpy.h - cl_apprflow_batch 전용 카피북 (host 변수 선언, EXEC SQL INCLUDE 대상). */ +EXEC SQL BEGIN DECLARE SECTION; +char af_prefix[16]; +char af_day[16]; +long af_cid; +long af_stored; +long af_actual; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/cl/batch/cl_audit_batch.pgc b/app/src/cl/batch/cl_audit_batch.pgc new file mode 100644 index 0000000..320f59b --- /dev/null +++ b/app/src/cl/batch/cl_audit_batch.pgc @@ -0,0 +1,64 @@ +/* + * cl_audit_batch.pgc - cl 마감 이력 감사 배치 (4개 감사항목 집계 + XA). + * + * 기간 프리픽스에 대해 (1)재개시 흔적(REOPEN memo) (2)승인/잠금인데 잠금행 + * 없음 (3)잠금시각 이후 수정 흔적 (4)사유 없는 취소(X) 4개 감사항목을 + * 각각 단건 집계 SELECT 로 산출해 감사 리포트를 출력한다 (조회 전용). + * host 변수는 전용 카피북 cl_audit_cpy.h 를 EXEC SQL INCLUDE 로 가져온다. + * Usage: cl_audit_batch [YYYY-MM] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *prefix = (argc > 1) ? argv[1] : "2026-07"; + long findings = 0; + + EXEC SQL INCLUDE cl_audit_cpy; + + strncpy(au_prefix, prefix, sizeof(au_prefix)-1); au_prefix[sizeof(au_prefix)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + printf(">>> cl_audit_batch %s 마감 이력 감사\n", prefix); + + EXEC SQL SELECT count(*) INTO :au_reopened FROM cl_close_log + WHERE period_key LIKE :au_prefix || '%' AND memo LIKE 'REOPEN:%'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + printf(" [감사1] 재개시 이력 : %ld건\n", au_reopened); + + EXEC SQL SELECT count(*) INTO :au_nolockrow FROM cl_close_log c + WHERE c.period_key LIKE :au_prefix || '%' AND c.status IN ('A','L') + AND NOT EXISTS (SELECT 1 FROM cl_period_lock l + WHERE l.period_key = c.period_key AND l.locked = true); + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + printf(" [감사2] 승인/잠금인데 잠금행 없음: %ld건\n", au_nolockrow); + + EXEC SQL SELECT count(*) INTO :au_tampered + FROM cl_close_log c, cl_period_lock l + WHERE c.period_key LIKE :au_prefix || '%' + AND l.period_key = c.period_key AND l.locked = true + AND c.updated_at > l.locked_at; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + printf(" [감사3] 잠금 후 수정 흔적 : %ld건\n", au_tampered); + + EXEC SQL SELECT count(*) INTO :au_emptycxl FROM cl_close_log + WHERE period_key LIKE :au_prefix || '%' AND status = 'X' + AND (memo = '' OR memo IS NULL); + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + printf(" [감사4] 사유 없는 취소 : %ld건\n", au_emptycxl); + + findings = au_nolockrow + au_tampered + au_emptycxl; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_audit_batch 결과: 이상항목 합계 %ld건 -> %s\n", + findings, findings ? "감사 소명 필요" : "적합"); + + tpclose(); tpterm(); + return findings ? 2 : 0; +} diff --git a/app/src/cl/batch/cl_audit_cpy.h b/app/src/cl/batch/cl_audit_cpy.h new file mode 100644 index 0000000..6d200ac --- /dev/null +++ b/app/src/cl/batch/cl_audit_cpy.h @@ -0,0 +1,8 @@ +/* cl_audit_cpy.h - cl_audit_batch 전용 카피북 (host 변수 선언, EXEC SQL INCLUDE 대상). */ +EXEC SQL BEGIN DECLARE SECTION; +char au_prefix[16]; +long au_reopened; +long au_nolockrow; +long au_tampered; +long au_emptycxl; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/cl/batch/cl_chnclose_batch.pgc b/app/src/cl/batch/cl_chnclose_batch.pgc new file mode 100644 index 0000000..53b5005 --- /dev/null +++ b/app/src/cl/batch/cl_chnclose_batch.pgc @@ -0,0 +1,54 @@ +/* + * cl_chnclose_batch.pgc - cl 채널별 일괄 마감 배치 (그룹 커서 + XA). + * + * 영업일의 매입을 채널(POS/EDC/EDI/FOREIGN) 그룹 커서로 순회하며 채널마다 + * CHN 마감이력을 한 건씩 생성한다. 전 구간 ONE global XA (tpcommit = 2PC). + * host 변수는 전용 카피북 cl_chnclose_cpy.h 를 EXEC SQL INCLUDE 로 가져온다. + * Usage: cl_chnclose_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long made = 0; + + EXEC SQL INCLUDE cl_chnclose_cpy; + + strncpy(cc_bizdate, bizdate, sizeof(cc_bizdate)-1); cc_bizdate[sizeof(cc_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE bchn CURSOR FOR + SELECT channel, count(*), coalesce(sum(amount),0) FROM purchase + WHERE biz_date = :cc_bizdate GROUP BY channel ORDER BY channel; + EXEC SQL OPEN bchn; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH bchn INTO :cc_chn, :cc_cnt, :cc_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE bchn; tpabort(0); return 1; } + EXEC SQL SELECT nextval('cl_close_seq') INTO :cc_cid; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bchn; tpabort(0); return 1; } + snprintf(cc_memo, sizeof(cc_memo), "채널마감:%s", cc_chn); + EXEC SQL INSERT INTO cl_close_log + (close_id, close_type, period_key, biz_date, status, txn_count, total_amount, memo) + VALUES (:cc_cid, 'CHN', :cc_bizdate, :cc_bizdate, 'C', :cc_cnt, :cc_amt, :cc_memo); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bchn; tpabort(0); return 1; } + printf(" 채널 %-8s cid=%ld cnt=%ld amt=%ld\n", cc_chn, cc_cid, cc_cnt, cc_amt); + made++; + } + EXEC SQL CLOSE bchn; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_chnclose_batch COMMIT: bizdate=%s 채널마감 %ld건 생성\n", bizdate, made); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_chnclose_cpy.h b/app/src/cl/batch/cl_chnclose_cpy.h new file mode 100644 index 0000000..413debd --- /dev/null +++ b/app/src/cl/batch/cl_chnclose_cpy.h @@ -0,0 +1,9 @@ +/* cl_chnclose_cpy.h - cl_chnclose_batch 전용 카피북 (host 변수 선언, EXEC SQL INCLUDE 대상). */ +EXEC SQL BEGIN DECLARE SECTION; +char cc_bizdate[16]; +char cc_chn[16]; +char cc_memo[48]; +long cc_cid; +long cc_cnt; +long cc_amt; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/cl/batch/cl_dupchk_batch.pgc b/app/src/cl/batch/cl_dupchk_batch.pgc new file mode 100644 index 0000000..ef9faa4 --- /dev/null +++ b/app/src/cl/batch/cl_dupchk_batch.pgc @@ -0,0 +1,57 @@ +/* + * cl_dupchk_batch.pgc - cl 중복 마감 정리 배치 (HAVING 그룹 커서 + 정리 + XA). + * + * 취소(X) 제외 동일 (close_type, period_key) 마감이 2건 이상인 그룹을 찾아 + * 최소 close_id 한 건만 남기고 나머지를 취소(X, memo 'DUP') 처리한다. + * 그룹별 잔존/정리 건수를 리포트. 전 구간 ONE global XA. + * host 변수는 전용 카피북 cl_dupchk_cpy.h 를 EXEC SQL INCLUDE 로 가져온다. + * Usage: cl_dupchk_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long groups = 0, cleaned = 0; + + EXEC SQL INCLUDE cl_dupchk_cpy; + + (void)argc; (void)argv; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(90, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE bdup CURSOR FOR + SELECT close_type, period_key, count(*), min(close_id) FROM cl_close_log + WHERE status <> 'X' + GROUP BY close_type, period_key HAVING count(*) > 1 + ORDER BY close_type, period_key; + EXEC SQL OPEN bdup; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH bdup INTO :dp_type, :dp_period, :dp_n, :dp_keep; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bdup; tpabort(0); return 1; } + EXEC SQL UPDATE cl_close_log + SET status = 'X', memo = 'DUP', updated_at = now() + WHERE close_type = :dp_type AND period_key = :dp_period + AND status <> 'X' AND close_id <> :dp_keep; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bdup; tpabort(0); return 1; } + dp_gone = sqlca.sqlerrd[2]; + printf(" 중복그룹 %s/%s %ld건 -> keep cid=%ld, 취소 %ld건\n", + dp_type, dp_period, dp_n, dp_keep, dp_gone); + cleaned += dp_gone; + groups++; + } + EXEC SQL CLOSE bdup; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_dupchk_batch COMMIT: 중복그룹=%ld 정리=%ld건\n", groups, cleaned); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_dupchk_cpy.h b/app/src/cl/batch/cl_dupchk_cpy.h new file mode 100644 index 0000000..f978e3c --- /dev/null +++ b/app/src/cl/batch/cl_dupchk_cpy.h @@ -0,0 +1,8 @@ +/* cl_dupchk_cpy.h - cl_dupchk_batch 전용 카피북 (host 변수 선언, EXEC SQL INCLUDE 대상). */ +EXEC SQL BEGIN DECLARE SECTION; +char dp_type[16]; +char dp_period[24]; +long dp_n; +long dp_keep; +long dp_gone; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/cl/batch/cl_finalize_batch.pgc b/app/src/cl/batch/cl_finalize_batch.pgc new file mode 100644 index 0000000..1734135 --- /dev/null +++ b/app/src/cl/batch/cl_finalize_batch.pgc @@ -0,0 +1,60 @@ +/* + * cl_finalize_batch.pgc - cl 가마감 -> 본마감 일괄 확정 배치 (커서 + 재집계 + XA). + * + * 월 내 가마감(PROV, status O)을 커서로 순회하며 각 일자의 매입을 재집계해 + * 본마감(C, memo FINAL)으로 확정한다. 가마감 대비 건수 변동은 리포트로 출력. + * 전 구간 ONE global XA. host 변수는 전용 카피북 cl_finalize_cpy.h 사용. + * Usage: cl_finalize_batch [YYYY-MM] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *month = (argc > 1) ? argv[1] : "2026-07"; + long fixed = 0, drift = 0; + + EXEC SQL INCLUDE cl_finalize_cpy; + + strncpy(fz_month, month, sizeof(fz_month)-1); fz_month[sizeof(fz_month)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(90, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE bfin CURSOR FOR + SELECT close_id, period_key, txn_count FROM cl_close_log + WHERE close_type = 'DAILY' AND status = 'O' AND memo = 'PROV' + AND period_key LIKE :fz_month || '%' + ORDER BY period_key; + EXEC SQL OPEN bfin; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH bfin INTO :fz_cid, :fz_day, :fz_oldcnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bfin; tpabort(0); return 1; } + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :fz_cnt, :fz_amt + FROM purchase WHERE biz_date = :fz_day AND status IN ('M','S'); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bfin; tpabort(0); return 1; } + EXEC SQL UPDATE cl_close_log + SET status = 'C', txn_count = :fz_cnt, total_amount = :fz_amt, + memo = 'FINAL', updated_at = now() + WHERE close_id = :fz_cid AND status = 'O'; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bfin; tpabort(0); return 1; } + if (fz_cnt != fz_oldcnt) { + printf(" 본마감 %s cid=%ld 건수변동 %ld -> %ld\n", fz_day, fz_cid, fz_oldcnt, fz_cnt); + drift++; + } + fixed++; + } + EXEC SQL CLOSE bfin; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_finalize_batch COMMIT: %s 본마감 확정=%ld건 (변동 %ld건)\n", month, fixed, drift); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_finalize_cpy.h b/app/src/cl/batch/cl_finalize_cpy.h new file mode 100644 index 0000000..531699f --- /dev/null +++ b/app/src/cl/batch/cl_finalize_cpy.h @@ -0,0 +1,9 @@ +/* cl_finalize_cpy.h - cl_finalize_batch 전용 카피북 (host 변수 선언, EXEC SQL INCLUDE 대상). */ +EXEC SQL BEGIN DECLARE SECTION; +char fz_month[16]; +char fz_day[16]; +long fz_cid; +long fz_oldcnt; +long fz_cnt; +long fz_amt; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/cl/batch/cl_gapdays_batch.pgc b/app/src/cl/batch/cl_gapdays_batch.pgc new file mode 100644 index 0000000..101c7b1 --- /dev/null +++ b/app/src/cl/batch/cl_gapdays_batch.pgc @@ -0,0 +1,58 @@ +/* + * cl_gapdays_batch.pgc - cl 마감 누락일 탐지/보정 배치 (NOT EXISTS 커서 + XA). + * + * 월 내 매입은 있으나 일마감이 전혀 없는 누락일을 커서로 찾아 리포트하고, + * 각 누락일에 개시(O) placeholder 마감(memo 'GAP')을 만들어 마감 담당자의 + * 후속 처리 대기열에 올린다. 전 구간 ONE global XA. + * host 변수는 전용 카피북 cl_gapdays_cpy.h 를 EXEC SQL INCLUDE 로 가져온다. + * Usage: cl_gapdays_batch [YYYY-MM] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *month = (argc > 1) ? argv[1] : "2026-07"; + long gaps = 0; + + EXEC SQL INCLUDE cl_gapdays_cpy; + + strncpy(gd_month, month, sizeof(gd_month)-1); gd_month[sizeof(gd_month)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE bgapd CURSOR FOR + SELECT to_char(p.biz_date,'YYYY-MM-DD'), count(*) FROM purchase p + WHERE to_char(p.biz_date,'YYYY-MM') = :gd_month + AND NOT EXISTS (SELECT 1 FROM cl_close_log c + WHERE c.close_type = 'DAILY' + AND c.period_key = to_char(p.biz_date,'YYYY-MM-DD')) + GROUP BY p.biz_date ORDER BY p.biz_date; + EXEC SQL OPEN bgapd; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH bgapd INTO :gd_day, :gd_txn; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bgapd; tpabort(0); return 1; } + EXEC SQL SELECT nextval('cl_close_seq') INTO :gd_cid; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bgapd; tpabort(0); return 1; } + EXEC SQL INSERT INTO cl_close_log + (close_id, close_type, period_key, biz_date, status, txn_count, total_amount, memo) + VALUES (:gd_cid, 'DAILY', :gd_day, :gd_day, 'O', 0, 0, 'GAP'); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bgapd; tpabort(0); return 1; } + printf(" 누락일 %s (매입 %ld건) -> placeholder cid=%ld 개시\n", gd_day, gd_txn, gd_cid); + gaps++; + } + EXEC SQL CLOSE bgapd; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_gapdays_batch COMMIT: %s 누락일 %ld건 등록\n", month, gaps); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_gapdays_cpy.h b/app/src/cl/batch/cl_gapdays_cpy.h new file mode 100644 index 0000000..e71ff99 --- /dev/null +++ b/app/src/cl/batch/cl_gapdays_cpy.h @@ -0,0 +1,7 @@ +/* cl_gapdays_cpy.h - cl_gapdays_batch 전용 카피북 (host 변수 선언, EXEC SQL INCLUDE 대상). */ +EXEC SQL BEGIN DECLARE SECTION; +char gd_month[16]; +char gd_day[16]; +long gd_cid; +long gd_txn; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/cl/batch/cl_halfclose_batch.pgc b/app/src/cl/batch/cl_halfclose_batch.pgc new file mode 100644 index 0000000..e1ccb65 --- /dev/null +++ b/app/src/cl/batch/cl_halfclose_batch.pgc @@ -0,0 +1,77 @@ +/* + * cl_halfclose_batch.pgc - cl 반기 결산 마감 배치 (월별 소계 + 반기 마감 + XA). + * + * 연도/반기(1|2)를 받아 반기 구간의 월별 소계를 커서로 출력한 뒤 반기 전체를 + * BETWEEN 집계해 HALF 마감이력(YYYY-Hn, status C)을 생성한다. + * 동일 반기 기존 마감이 있으면 거절. 전 구간 ONE global XA. + * host 변수는 전용 카피북 cl_halfclose_cpy.h 를 EXEC SQL INCLUDE 로 가져온다. + * Usage: cl_halfclose_batch [YYYY] [1|2] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *year = (argc > 1) ? argv[1] : "2026"; + int half = (argc > 2) ? atoi(argv[2]) : 1; + long months = 0; + + EXEC SQL INCLUDE cl_halfclose_cpy; + + if (half != 1 && half != 2) { fprintf(stderr, "반기는 1|2 만 허용\n"); return 1; } + if (half == 1) { + snprintf(hf_from, sizeof(hf_from), "%s-01-01", year); + snprintf(hf_to, sizeof(hf_to), "%s-06-30", year); + } else { + snprintf(hf_from, sizeof(hf_from), "%s-07-01", year); + snprintf(hf_to, sizeof(hf_to), "%s-12-31", year); + } + snprintf(hf_period, sizeof(hf_period), "%s-H%d", year, half); + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(90, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*) INTO :hf_cnt FROM cl_close_log + WHERE close_type = 'HALF' AND period_key = :hf_period AND status <> 'X'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + if (hf_cnt > 0) { + fprintf(stderr, "cl_halfclose_batch: %s 반기마감 기존재 - 중복 불가\n", hf_period); + tpabort(0); tpclose(); tpterm(); + return 2; + } + + EXEC SQL DECLARE bhalf CURSOR FOR + SELECT to_char(biz_date,'YYYY-MM'), count(*), coalesce(sum(amount),0) + FROM purchase WHERE biz_date BETWEEN :hf_from AND :hf_to + GROUP BY 1 ORDER BY 1; + EXEC SQL OPEN bhalf; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH bhalf INTO :hf_mon, :hf_mcnt, :hf_mamt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bhalf; tpabort(0); return 1; } + printf(" 월소계 %s cnt=%ld amt=%ld\n", hf_mon, hf_mcnt, hf_mamt); + months++; + } + EXEC SQL CLOSE bhalf; + + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :hf_cnt, :hf_amt + FROM purchase WHERE biz_date BETWEEN :hf_from AND :hf_to; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + EXEC SQL SELECT nextval('cl_close_seq') INTO :hf_cid; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + EXEC SQL INSERT INTO cl_close_log + (close_id, close_type, period_key, biz_date, status, txn_count, total_amount, memo) + VALUES (:hf_cid, 'HALF', :hf_period, :hf_to, 'C', :hf_cnt, :hf_amt, '반기결산'); + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_halfclose_batch COMMIT: %s cid=%ld 월=%ld개 cnt=%ld amt=%ld\n", + hf_period, hf_cid, months, hf_cnt, hf_amt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_halfclose_cpy.h b/app/src/cl/batch/cl_halfclose_cpy.h new file mode 100644 index 0000000..cf30127 --- /dev/null +++ b/app/src/cl/batch/cl_halfclose_cpy.h @@ -0,0 +1,12 @@ +/* cl_halfclose_cpy.h - cl_halfclose_batch 전용 카피북 (host 변수 선언, EXEC SQL INCLUDE 대상). */ +EXEC SQL BEGIN DECLARE SECTION; +char hf_from[16]; +char hf_to[16]; +char hf_period[16]; +char hf_mon[16]; +long hf_cid; +long hf_cnt; +long hf_amt; +long hf_mcnt; +long hf_mamt; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/cl/batch/cl_holiday_batch.pgc b/app/src/cl/batch/cl_holiday_batch.pgc new file mode 100644 index 0000000..d7265c3 --- /dev/null +++ b/app/src/cl/batch/cl_holiday_batch.pgc @@ -0,0 +1,60 @@ +/* + * cl_holiday_batch.pgc - cl 휴일 마감 정책 배치 (주말 판정 + 태깅 + XA). + * + * 월 내 매입 발생 일자를 커서로 순회하며 요일을 판정, 주말(토/일) 거래일을 + * 리포트하고 해당 일자의 일마감에는 memo '|HOL' 태그를 남긴다. + * 휴일 마감은 익영업일 이월 대상이라는 정책 표시. 전 구간 ONE global XA. + * host 변수는 전용 카피북 cl_holiday_cpy.h 를 EXEC SQL INCLUDE 로 가져온다. + * Usage: cl_holiday_batch [YYYY-MM] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *month = (argc > 1) ? argv[1] : "2026-07"; + long weekend = 0, tagged = 0; + + EXEC SQL INCLUDE cl_holiday_cpy; + + strncpy(hd_month, month, sizeof(hd_month)-1); hd_month[sizeof(hd_month)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE bhol CURSOR FOR + SELECT to_char(biz_date,'YYYY-MM-DD'), to_char(biz_date,'D'), count(*) + FROM purchase + WHERE to_char(biz_date,'YYYY-MM') = :hd_month + GROUP BY biz_date ORDER BY biz_date; + EXEC SQL OPEN bhol; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH bhol INTO :hd_day, :hd_dow, :hd_txn; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bhol; tpabort(0); return 1; } + if (hd_dow[0] != '1' && hd_dow[0] != '7') continue; /* 평일은 정책 대상 아님 */ + weekend++; + printf(" 휴일거래 %s (요일코드 %s) %ld건 -> 익영업일 이월 대상\n", hd_day, hd_dow, hd_txn); + EXEC SQL UPDATE cl_close_log + SET memo = memo || '|HOL', updated_at = now() + WHERE close_type = 'DAILY' AND period_key = :hd_day + AND status <> 'X' AND memo NOT LIKE '%|HOL%'; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bhol; tpabort(0); return 1; } + EXEC SQL SELECT count(*) INTO :hd_marked FROM cl_close_log + WHERE close_type = 'DAILY' AND period_key = :hd_day AND memo LIKE '%|HOL%'; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bhol; tpabort(0); return 1; } + tagged += hd_marked; + } + EXEC SQL CLOSE bhol; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_holiday_batch COMMIT: %s 휴일거래일=%ld HOL태그 마감=%ld\n", month, weekend, tagged); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_holiday_cpy.h b/app/src/cl/batch/cl_holiday_cpy.h new file mode 100644 index 0000000..6aba414 --- /dev/null +++ b/app/src/cl/batch/cl_holiday_cpy.h @@ -0,0 +1,8 @@ +/* cl_holiday_cpy.h - cl_holiday_batch 전용 카피북 (host 변수 선언, EXEC SQL INCLUDE 대상). */ +EXEC SQL BEGIN DECLARE SECTION; +char hd_month[16]; +char hd_day[16]; +char hd_dow[4]; +long hd_txn; +long hd_marked; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/cl/batch/cl_posttrig_batch.pgc b/app/src/cl/batch/cl_posttrig_batch.pgc new file mode 100644 index 0000000..5f76825 --- /dev/null +++ b/app/src/cl/batch/cl_posttrig_batch.pgc @@ -0,0 +1,57 @@ +/* + * cl_posttrig_batch.pgc - cl 마감 후속작업 트리거 배치 (INSERT..SELECT + XA). + * + * 마감 완료(C/A/L)된 영업일의 정산건 중 아직 ledger 에 XFER 전기가 없는 + * 건을 NOT EXISTS 조건 INSERT..SELECT 한 방으로 일괄 전기하고, 전기 전/후 + * 건수를 리포트한다. 마감 없는 일자는 거절. 전 구간 ONE global XA. + * host 변수는 전용 카피북 cl_posttrig_cpy.h 를 EXEC SQL INCLUDE 로 가져온다. + * Usage: cl_posttrig_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long closed = 0; + + EXEC SQL INCLUDE cl_posttrig_cpy; + + strncpy(pt_bizdate, bizdate, sizeof(pt_bizdate)-1); pt_bizdate[sizeof(pt_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*) INTO :pt_ready FROM cl_close_log + WHERE close_type = 'DAILY' AND period_key = :pt_bizdate AND status IN ('C','A','L'); + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + if (pt_ready == 0) { + fprintf(stderr, "cl_posttrig_batch: %s 완료된 일마감 없음 - 후속작업 불가\n", bizdate); + tpabort(0); tpclose(); tpterm(); + return 2; + } + closed = pt_ready; + + EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date) + SELECT nextval('ledger_seq'), s.purchase_id, 'XFER', s.net, s.biz_date + FROM settlement s + WHERE s.biz_date = :pt_bizdate + AND NOT EXISTS (SELECT 1 FROM ledger l + WHERE l.purchase_id = s.purchase_id AND l.entry_type = 'XFER'); + if (sqlca.sqlcode < 0) { fprintf(stderr, "INSERT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + pt_ins = sqlca.sqlerrd[2]; + + EXEC SQL SELECT count(*) INTO :pt_total FROM ledger + WHERE biz_date = :pt_bizdate AND entry_type = 'XFER'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_posttrig_batch COMMIT: %s (마감 %ld건 확인) 신규 XFER=%ld 누적=%ld\n", + bizdate, closed, pt_ins, pt_total); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_posttrig_cpy.h b/app/src/cl/batch/cl_posttrig_cpy.h new file mode 100644 index 0000000..eec9f4b --- /dev/null +++ b/app/src/cl/batch/cl_posttrig_cpy.h @@ -0,0 +1,7 @@ +/* cl_posttrig_cpy.h - cl_posttrig_batch 전용 카피북 (host 변수 선언, EXEC SQL INCLUDE 대상). */ +EXEC SQL BEGIN DECLARE SECTION; +char pt_bizdate[16]; +long pt_ready; +long pt_ins; +long pt_total; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/cl/batch/cl_precheck_batch.pgc b/app/src/cl/batch/cl_precheck_batch.pgc new file mode 100644 index 0000000..bbf992a --- /dev/null +++ b/app/src/cl/batch/cl_precheck_batch.pgc @@ -0,0 +1,64 @@ +/* + * cl_precheck_batch.pgc - cl 마감 전 체크리스트 일괄 검증 배치 (5항목 + XA). + * + * 일마감 직전에 돌려 (1)미마감 M 매입 (2)미매칭 (3)기간잠금 (4)기존 마감 + * (5)보류(H) 매입 5개 항목을 점검하고 체크리스트 리포트를 출력한다. + * 하나라도 실패면 rc=2 로 종료해 후속 마감 잡을 차단한다 (조회 전용, XA 조회 브랜치). + * host 변수는 전용 카피북 cl_precheck_cpy.h 를 EXEC SQL INCLUDE 로 가져온다. + * Usage: cl_precheck_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + int fails = 0; + + EXEC SQL INCLUDE cl_precheck_cpy; + + strncpy(pc_bizdate, bizdate, sizeof(pc_bizdate)-1); pc_bizdate[sizeof(pc_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(30, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + printf(">>> cl_precheck_batch %s 마감 전 체크리스트\n", bizdate); + + EXEC SQL SELECT count(*) INTO :pc_unclosed FROM purchase + WHERE biz_date = :pc_bizdate AND status = 'M'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + printf(" [1] 미마감(M) 매입 : %ld건 %s\n", pc_unclosed, pc_unclosed ? "FAIL" : "ok"); + if (pc_unclosed) fails++; + + EXEC SQL SELECT count(*) INTO :pc_unmatch FROM ac_unmatch WHERE biz_date = :pc_bizdate; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + printf(" [2] 미매칭 건 : %ld건 %s\n", pc_unmatch, pc_unmatch ? "FAIL" : "ok"); + if (pc_unmatch) fails++; + + EXEC SQL SELECT count(*) INTO :pc_locked FROM cl_period_lock + WHERE period_key = :pc_bizdate AND locked = true; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + printf(" [3] 기간 잠금 : %s\n", pc_locked ? "잠금됨 FAIL" : "ok"); + if (pc_locked) fails++; + + EXEC SQL SELECT count(*) INTO :pc_already FROM cl_close_log + WHERE close_type = 'DAILY' AND period_key = :pc_bizdate AND status <> 'X'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + printf(" [4] 기존 일마감 : %ld건 %s\n", pc_already, pc_already ? "FAIL" : "ok"); + if (pc_already) fails++; + + EXEC SQL SELECT count(*) INTO :pc_hold FROM purchase + WHERE biz_date = :pc_bizdate AND status = 'H'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + printf(" [5] 보류(H) 매입 : %ld건 %s\n", pc_hold, pc_hold ? "FAIL" : "ok"); + if (pc_hold) fails++; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_precheck_batch 결과: %d항목 실패 -> %s\n", fails, fails ? "마감 차단" : "마감 진행 가능"); + + tpclose(); tpterm(); + return fails ? 2 : 0; +} diff --git a/app/src/cl/batch/cl_precheck_cpy.h b/app/src/cl/batch/cl_precheck_cpy.h new file mode 100644 index 0000000..dbae655 --- /dev/null +++ b/app/src/cl/batch/cl_precheck_cpy.h @@ -0,0 +1,9 @@ +/* cl_precheck_cpy.h - cl_precheck_batch 전용 카피북 (host 변수 선언, EXEC SQL INCLUDE 대상). */ +EXEC SQL BEGIN DECLARE SECTION; +char pc_bizdate[16]; +long pc_unclosed; +long pc_unmatch; +long pc_locked; +long pc_already; +long pc_hold; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/cl/batch/cl_provclose_batch.pgc b/app/src/cl/batch/cl_provclose_batch.pgc new file mode 100644 index 0000000..df7d0bf --- /dev/null +++ b/app/src/cl/batch/cl_provclose_batch.pgc @@ -0,0 +1,62 @@ +/* + * cl_provclose_batch.pgc - cl 가마감 일괄 생성 배치 (누락일 커서 + XA). + * + * 월(YYYY-MM) 내 매입은 있으나 어떤 일마감도 없는 일자를 커서로 찾아 + * 일자마다 status O + memo 'PROV' 가마감을 집계와 함께 생성한다. + * 본마감 전 임시 스냅샷 성격. 전 구간 ONE global XA. + * host 변수는 전용 카피북 cl_provclose_cpy.h 를 EXEC SQL INCLUDE 로 가져온다. + * Usage: cl_provclose_batch [YYYY-MM] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *month = (argc > 1) ? argv[1] : "2026-07"; + long made = 0; + + EXEC SQL INCLUDE cl_provclose_cpy; + + strncpy(pv_month, month, sizeof(pv_month)-1); pv_month[sizeof(pv_month)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(90, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE bprov CURSOR FOR + SELECT DISTINCT to_char(p.biz_date,'YYYY-MM-DD') FROM purchase p + WHERE to_char(p.biz_date,'YYYY-MM') = :pv_month + AND NOT EXISTS (SELECT 1 FROM cl_close_log c + WHERE c.close_type = 'DAILY' + AND c.period_key = to_char(p.biz_date,'YYYY-MM-DD') + AND c.status <> 'X') + ORDER BY 1; + EXEC SQL OPEN bprov; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH bprov INTO :pv_day; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bprov; tpabort(0); return 1; } + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :pv_cnt, :pv_amt + FROM purchase WHERE biz_date = :pv_day AND status IN ('M','S'); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bprov; tpabort(0); return 1; } + EXEC SQL SELECT nextval('cl_close_seq') INTO :pv_cid; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bprov; tpabort(0); return 1; } + EXEC SQL INSERT INTO cl_close_log + (close_id, close_type, period_key, biz_date, status, txn_count, total_amount, memo) + VALUES (:pv_cid, 'DAILY', :pv_day, :pv_day, 'O', :pv_cnt, :pv_amt, 'PROV'); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bprov; tpabort(0); return 1; } + printf(" 가마감 %s cid=%ld cnt=%ld\n", pv_day, pv_cid, pv_cnt); + made++; + } + EXEC SQL CLOSE bprov; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_provclose_batch COMMIT: %s 가마감 %ld건 생성\n", month, made); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_provclose_cpy.h b/app/src/cl/batch/cl_provclose_cpy.h new file mode 100644 index 0000000..428b9b5 --- /dev/null +++ b/app/src/cl/batch/cl_provclose_cpy.h @@ -0,0 +1,8 @@ +/* cl_provclose_cpy.h - cl_provclose_batch 전용 카피북 (host 변수 선언, EXEC SQL INCLUDE 대상). */ +EXEC SQL BEGIN DECLARE SECTION; +char pv_month[16]; +char pv_day[16]; +long pv_cid; +long pv_cnt; +long pv_amt; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/cl/batch/cl_slamon_batch.pgc b/app/src/cl/batch/cl_slamon_batch.pgc new file mode 100644 index 0000000..cfeb830 --- /dev/null +++ b/app/src/cl/batch/cl_slamon_batch.pgc @@ -0,0 +1,62 @@ +/* + * cl_slamon_batch.pgc - cl 마감 SLA 모니터 배치 (지연 마감 태깅 + XA). + * + * 기간 내 일마감 중 생성시각이 기준시각(HHMI, 기본 1000)을 넘긴 지연 마감을 + * 커서로 찾아 memo 에 '|SLA' 태그를 남기고 지연 목록을 출력한다. + * 이미 태깅된 건은 제외. 전 구간 ONE global XA. + * host 변수는 전용 카피북 cl_slamon_cpy.h 를 EXEC SQL INCLUDE 로 가져온다. + * Usage: cl_slamon_batch [YYYY-MM] [HHMI] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *prefix = (argc > 1) ? argv[1] : "2026-07"; + const char *cutoff = (argc > 2) ? argv[2] : "1000"; + long tagged = 0; + + EXEC SQL INCLUDE cl_slamon_cpy; + + strncpy(sl_prefix, prefix, sizeof(sl_prefix)-1); sl_prefix[sizeof(sl_prefix)-1] = 0; + strncpy(sl_cut, cutoff, sizeof(sl_cut)-1); sl_cut[sizeof(sl_cut)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE bsla CURSOR FOR + SELECT close_id, period_key, to_char(created_at,'HH24MI') FROM cl_close_log + WHERE close_type = 'DAILY' AND status <> 'X' + AND period_key LIKE :sl_prefix || '%' + AND to_char(created_at,'HH24MI') > :sl_cut + AND memo NOT LIKE '%|SLA%' + ORDER BY created_at; + EXEC SQL OPEN bsla; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH bsla INTO :sl_cid, :sl_period, :sl_hhmi; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bsla; tpabort(0); return 1; } + EXEC SQL UPDATE cl_close_log + SET memo = memo || '|SLA', updated_at = now() WHERE close_id = :sl_cid; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bsla; tpabort(0); return 1; } + printf(" SLA위반 %s cid=%ld 생성 %s (기준 %s)\n", sl_period, sl_cid, sl_hhmi, sl_cut); + tagged++; + } + EXEC SQL CLOSE bsla; + + EXEC SQL SELECT count(*) INTO :sl_late FROM cl_close_log + WHERE close_type = 'DAILY' AND period_key LIKE :sl_prefix || '%' + AND memo LIKE '%|SLA%'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_slamon_batch COMMIT: prefix=%s 신규태깅=%ld 누적 SLA위반=%ld\n", prefix, tagged, sl_late); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_slamon_cpy.h b/app/src/cl/batch/cl_slamon_cpy.h new file mode 100644 index 0000000..c7a3794 --- /dev/null +++ b/app/src/cl/batch/cl_slamon_cpy.h @@ -0,0 +1,9 @@ +/* cl_slamon_cpy.h - cl_slamon_batch 전용 카피북 (host 변수 선언, EXEC SQL INCLUDE 대상). */ +EXEC SQL BEGIN DECLARE SECTION; +char sl_prefix[16]; +char sl_cut[8]; +char sl_hhmi[8]; +char sl_period[24]; +long sl_cid; +long sl_late; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/cl/batch/cl_xcheck_batch.pgc b/app/src/cl/batch/cl_xcheck_batch.pgc new file mode 100644 index 0000000..f6e174d --- /dev/null +++ b/app/src/cl/batch/cl_xcheck_batch.pgc @@ -0,0 +1,62 @@ +/* + * cl_xcheck_batch.pgc - cl 마감 데이터 건수/금액 크로스체크 배치 (이중 대사 + XA). + * + * 기간 내 일마감을 커서로 순회하며 마감이력의 (txn_count, total_amount) 와 + * 실매입의 (count, sum) 을 동시에 대사해 건수/금액 불일치를 각각 집계, + * 불일치 상세를 표로 출력한다 (조회 전용). host 변수는 cl_xcheck_cpy.h 사용. + * Usage: cl_xcheck_batch [YYYY-MM] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *prefix = (argc > 1) ? argv[1] : "2026-07"; + long seen = 0, cntbad = 0, amtbad = 0; + + EXEC SQL INCLUDE cl_xcheck_cpy; + + strncpy(xc_prefix, prefix, sizeof(xc_prefix)-1); xc_prefix[sizeof(xc_prefix)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE bxch CURSOR FOR + SELECT close_id, to_char(biz_date,'YYYY-MM-DD'), txn_count, total_amount + FROM cl_close_log + WHERE close_type = 'DAILY' AND status IN ('C','A','L') + AND period_key LIKE :xc_prefix || '%' + ORDER BY biz_date; + EXEC SQL OPEN bxch; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH bxch INTO :xc_cid, :xc_day, :xc_logcnt, :xc_logamt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bxch; tpabort(0); return 1; } + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :xc_realcnt, :xc_realamt + FROM purchase WHERE biz_date = :xc_day; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bxch; tpabort(0); return 1; } + seen++; + if (xc_logcnt != xc_realcnt) { + printf(" [건수] %s cid=%ld 이력=%ld 실제=%ld\n", xc_day, xc_cid, xc_logcnt, xc_realcnt); + cntbad++; + } + if (xc_logamt != xc_realamt) { + printf(" [금액] %s cid=%ld 이력=%ld 실제=%ld (편차 %+ld)\n", + xc_day, xc_cid, xc_logamt, xc_realamt, xc_logamt - xc_realamt); + amtbad++; + } + } + EXEC SQL CLOSE bxch; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_xcheck_batch COMMIT: prefix=%s 대사=%ld 건수불일치=%ld 금액불일치=%ld\n", + prefix, seen, cntbad, amtbad); + + tpclose(); tpterm(); + return (cntbad + amtbad) ? 2 : 0; +} diff --git a/app/src/cl/batch/cl_xcheck_cpy.h b/app/src/cl/batch/cl_xcheck_cpy.h new file mode 100644 index 0000000..a4147a5 --- /dev/null +++ b/app/src/cl/batch/cl_xcheck_cpy.h @@ -0,0 +1,10 @@ +/* cl_xcheck_cpy.h - cl_xcheck_batch 전용 카피북 (host 변수 선언, EXEC SQL INCLUDE 대상). */ +EXEC SQL BEGIN DECLARE SECTION; +char xc_prefix[16]; +char xc_day[16]; +long xc_cid; +long xc_logcnt; +long xc_logamt; +long xc_realcnt; +long xc_realamt; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/cl/cl_svr.pgc b/app/src/cl/cl_svr.pgc index d3b156f..35c040f 100644 --- a/app/src/cl/cl_svr.pgc +++ b/app/src/cl/cl_svr.pgc @@ -2,7 +2,7 @@ * cl_svr.pgc - cl (마감/closing) MODULE SERVER (thin dispatcher). * * The legacy "one module server, many services" pattern: this binary owns NO - * business logic. Each of cl's 30 online services now lives in its own file + * business logic. Each of cl's 75 online services now lives in its own file * (app/src/cl/svc/.pgc) with its own copybook header * (app/src/cl/svc/.h); those objects are archived into libclsvc.a and * linked into this server. Here we only: @@ -53,6 +53,51 @@ #include "CL_PURGE.h" #include "CL_RESTORE.h" #include "CL_INQUIRY.h" +#include "CL_CHNCLOSE.h" +#include "CL_DIVCLOSE.h" +#include "CL_CORPCLOSE.h" +#include "CL_PRECHECK.h" +#include "CL_DELAYREG.h" +#include "CL_PROVCLOSE.h" +#include "CL_FINALIZE.h" +#include "CL_APPRREQ.h" +#include "CL_APPRREJECT.h" +#include "CL_APPRHIST.h" +#include "CL_CXLREASON.h" +#include "CL_CNTXCHECK.h" +#include "CL_AMTXCHECK.h" +#include "CL_SNAPXCHECK.h" +#include "CL_RPTCHN.h" +#include "CL_RPTISSUER.h" +#include "CL_RPTCAT.h" +#include "CL_RPTTOPMER.h" +#include "CL_SCHEDULE.h" +#include "CL_HOLIDAY.h" +#include "CL_POSTTRIG.h" +#include "CL_AUDITTRAIL.h" +#include "CL_HALFCLOSE.h" +#include "CL_YEARPREP.h" +#include "CL_SLAMON.h" +#include "CL_DELAYLIST.h" +#include "CL_LOCKSWEEP.h" +#include "CL_LOCKAUDIT.h" +#include "CL_SEQAUDIT.h" +#include "CL_MEMOSEARCH.h" +#include "CL_REOPENREQ.h" +#include "CL_CLOSERATE.h" +#include "CL_GAPDAYS.h" +#include "CL_DUPCHK.h" +#include "CL_TREND.h" +#include "CL_CHNVERIFY.h" +#include "CL_MERCLOSE.h" +#include "CL_FEEVERIFY.h" +#include "CL_NETVERIFY.h" +#include "CL_PENDLIST.h" +#include "CL_ESCALATE.h" +#include "CL_CALENDAR.h" +#include "CL_TRIGSTAT.h" +#include "CL_FORCECLOSE.h" +#include "CL_SNAPDIFF.h" /* ----- advertise table: {service name, function} (fn is extern from its file) */ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { @@ -71,6 +116,29 @@ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { {"CL_MIDCLOSE", CL_MIDCLOSE}, {"CL_RECON", CL_RECON}, {"CL_ARCHIVE", CL_ARCHIVE}, {"CL_PURGE", CL_PURGE}, {"CL_RESTORE", CL_RESTORE}, {"CL_INQUIRY", CL_INQUIRY}, + {"CL_CHNCLOSE", CL_CHNCLOSE}, {"CL_DIVCLOSE", CL_DIVCLOSE}, + {"CL_CORPCLOSE", CL_CORPCLOSE}, {"CL_PRECHECK", CL_PRECHECK}, + {"CL_DELAYREG", CL_DELAYREG}, {"CL_PROVCLOSE", CL_PROVCLOSE}, + {"CL_FINALIZE", CL_FINALIZE}, {"CL_APPRREQ", CL_APPRREQ}, + {"CL_APPRREJECT", CL_APPRREJECT}, {"CL_APPRHIST", CL_APPRHIST}, + {"CL_CXLREASON", CL_CXLREASON}, {"CL_CNTXCHECK", CL_CNTXCHECK}, + {"CL_AMTXCHECK", CL_AMTXCHECK}, {"CL_SNAPXCHECK", CL_SNAPXCHECK}, + {"CL_RPTCHN", CL_RPTCHN}, {"CL_RPTISSUER", CL_RPTISSUER}, + {"CL_RPTCAT", CL_RPTCAT}, {"CL_RPTTOPMER", CL_RPTTOPMER}, + {"CL_SCHEDULE", CL_SCHEDULE}, {"CL_HOLIDAY", CL_HOLIDAY}, + {"CL_POSTTRIG", CL_POSTTRIG}, {"CL_AUDITTRAIL", CL_AUDITTRAIL}, + {"CL_HALFCLOSE", CL_HALFCLOSE}, {"CL_YEARPREP", CL_YEARPREP}, + {"CL_SLAMON", CL_SLAMON}, {"CL_DELAYLIST", CL_DELAYLIST}, + {"CL_LOCKSWEEP", CL_LOCKSWEEP}, {"CL_LOCKAUDIT", CL_LOCKAUDIT}, + {"CL_SEQAUDIT", CL_SEQAUDIT}, {"CL_MEMOSEARCH", CL_MEMOSEARCH}, + {"CL_REOPENREQ", CL_REOPENREQ}, {"CL_CLOSERATE", CL_CLOSERATE}, + {"CL_GAPDAYS", CL_GAPDAYS}, {"CL_DUPCHK", CL_DUPCHK}, + {"CL_TREND", CL_TREND}, {"CL_CHNVERIFY", CL_CHNVERIFY}, + {"CL_MERCLOSE", CL_MERCLOSE}, {"CL_FEEVERIFY", CL_FEEVERIFY}, + {"CL_NETVERIFY", CL_NETVERIFY}, {"CL_PENDLIST", CL_PENDLIST}, + {"CL_ESCALATE", CL_ESCALATE}, {"CL_CALENDAR", CL_CALENDAR}, + {"CL_TRIGSTAT", CL_TRIGSTAT}, {"CL_FORCECLOSE", CL_FORCECLOSE}, + {"CL_SNAPDIFF", CL_SNAPDIFF}, {NULL, NULL} }; diff --git a/app/src/cl/svc/CL_AMTXCHECK.h b/app/src/cl/svc/CL_AMTXCHECK.h new file mode 100644 index 0000000..b3cf935 --- /dev/null +++ b/app/src/cl/svc/CL_AMTXCHECK.h @@ -0,0 +1,21 @@ +/* + * CL_AMTXCHECK.h - cl 서비스 CL_AMTXCHECK 카피북 (copybook / record header). + * 마감 금액 크로스체크: 마감총액 vs 매입합계 vs 스냅샷 gross 편차 산출. + */ +#ifndef CL_AMTXCHECK_H +#define CL_AMTXCHECK_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_amtxcheck_rec_t; + +void CL_AMTXCHECK(TPSVCINFO *p); + +#endif /* CL_AMTXCHECK_H */ diff --git a/app/src/cl/svc/CL_AMTXCHECK.pgc b/app/src/cl/svc/CL_AMTXCHECK.pgc new file mode 100644 index 0000000..53a9b59 --- /dev/null +++ b/app/src/cl/svc/CL_AMTXCHECK.pgc @@ -0,0 +1,39 @@ +/* + * CL_AMTXCHECK.pgc - cl 모듈 서비스 CL_AMTXCHECK (one service = one file). + * 마감 금액 크로스체크: 마감이력 total_amount vs 매입 amount 합계 vs 스냅샷 + * gross 합계를 대사하고 최대 절대 편차(T_DELTA)를 산출한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_AMTXCHECK.h" + +void CL_AMTXCHECK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long d1, d2, worst; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid, h_log, h_real, h_snap; + char h_date[16]; + EXEC SQL END DECLARE SECTION; + h_cid = getl(b, T_ID1); + EXEC SQL SELECT total_amount, to_char(biz_date,'YYYY-MM-DD') + INTO :h_log, :h_date FROM cl_close_log WHERE close_id = :h_cid AND status <> 'X'; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_real + FROM purchase WHERE biz_date = :h_date; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(gross_amount),0) INTO :h_snap + FROM cl_snapshot WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0) FAIL(b); + d1 = h_log - h_real; if (d1 < 0) d1 = -d1; + d2 = (h_snap == 0) ? 0 : (h_log - h_snap); + if (d2 < 0) d2 = -d2; + worst = (d1 > d2) ? d1 : d2; + setl(b, T_AMT1, h_log); + setl(b, T_AMT2, h_real); + setl(b, T_AMT3, h_snap); + setl(b, T_DELTA, worst); + setl(b, T_RC, (worst == 0) ? 0 : 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_APPRHIST.h b/app/src/cl/svc/CL_APPRHIST.h new file mode 100644 index 0000000..4e5136a --- /dev/null +++ b/app/src/cl/svc/CL_APPRHIST.h @@ -0,0 +1,21 @@ +/* + * CL_APPRHIST.h - cl 서비스 CL_APPRHIST 카피북 (copybook / record header). + * 마감 승인 이력: 기간 프리픽스의 승인/완료/취소 건수 CASE 집계. + */ +#ifndef CL_APPRHIST_H +#define CL_APPRHIST_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_apprhist_rec_t; + +void CL_APPRHIST(TPSVCINFO *p); + +#endif /* CL_APPRHIST_H */ diff --git a/app/src/cl/svc/CL_APPRHIST.pgc b/app/src/cl/svc/CL_APPRHIST.pgc new file mode 100644 index 0000000..a80dc3e --- /dev/null +++ b/app/src/cl/svc/CL_APPRHIST.pgc @@ -0,0 +1,32 @@ +/* + * CL_APPRHIST.pgc - cl 모듈 서비스 CL_APPRHIST (one service = one file). + * 마감 승인 이력: 기간 프리픽스(YYYY-MM 등)의 마감을 승인(A/L)/완료(C)/취소(X) + * 로 CASE 집계하여 승인 진행률 현황을 한 번의 SELECT 로 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_APPRHIST.h" + +void CL_APPRHIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_appr, h_done, h_cxl; + char h_prefix[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_STR1, h_prefix, sizeof(h_prefix)); + if (h_prefix[0] == 0) { Bchg(b, T_MSG, 0, "기간 프리픽스 필요", 0L); FAIL(b); } + EXEC SQL SELECT + coalesce(sum(CASE WHEN status IN ('A','L') THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN status = 'C' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN status = 'X' THEN 1 ELSE 0 END),0) + INTO :h_appr, :h_done, :h_cxl + FROM cl_close_log WHERE period_key LIKE :h_prefix || '%'; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_ARG1, h_appr); + setl(b, T_ARG2, h_done); + setl(b, T_ARG3, h_cxl); + setl(b, T_COUNT, h_appr + h_done + h_cxl); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_APPRREJECT.h b/app/src/cl/svc/CL_APPRREJECT.h new file mode 100644 index 0000000..17fd704 --- /dev/null +++ b/app/src/cl/svc/CL_APPRREJECT.h @@ -0,0 +1,21 @@ +/* + * CL_APPRREJECT.h - cl 서비스 CL_APPRREJECT 카피북 (copybook / record header). + * 마감 승인 반려: 잠금기간 아닌 A 상태 마감을 C 로 반려하고 사유 기록. + */ +#ifndef CL_APPRREJECT_H +#define CL_APPRREJECT_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_apprreject_rec_t; + +void CL_APPRREJECT(TPSVCINFO *p); + +#endif /* CL_APPRREJECT_H */ diff --git a/app/src/cl/svc/CL_APPRREJECT.pgc b/app/src/cl/svc/CL_APPRREJECT.pgc new file mode 100644 index 0000000..7297951 --- /dev/null +++ b/app/src/cl/svc/CL_APPRREJECT.pgc @@ -0,0 +1,38 @@ +/* + * CL_APPRREJECT.pgc - cl 모듈 서비스 CL_APPRREJECT (one service = one file). + * 마감 승인 반려: 승인(A) 상태 마감을 완료(C)로 되돌리며 반려 사유를 기록. + * 해당 기간이 이미 잠금(cl_period_lock)되어 있으면 반려 불가. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_APPRREJECT.h" + +void CL_APPRREJECT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char reason[64]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid; + char h_memo[80]; + EXEC SQL END DECLARE SECTION; + h_cid = getl(b, T_ID1); + gets_(b, T_REASON, reason, sizeof(reason)); + if (reason[0] == 0) strcpy(reason, "사유미기재"); + snprintf(h_memo, sizeof(h_memo), "REJ:%s", reason); + EXEC SQL UPDATE cl_close_log c + SET status = 'C', memo = :h_memo, updated_at = now() + WHERE c.close_id = :h_cid AND c.status = 'A' + AND NOT EXISTS (SELECT 1 FROM cl_period_lock l + WHERE l.period_key = c.period_key AND l.locked = true); + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlerrd[2] == 0) { + setl(b, T_RC, 2); + Bchg(b, T_MSG, 0, "반려 불가(비승인 상태 또는 잠금 기간)", 0L); + FAIL(b); + } + setl(b, T_RC, 0); + Bchg(b, T_STATUS, 0, "C", 0L); + userlog("CL_APPRREJECT cid=%ld 승인 반려: %s", h_cid, reason); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_APPRREQ.h b/app/src/cl/svc/CL_APPRREQ.h new file mode 100644 index 0000000..5ed7eee --- /dev/null +++ b/app/src/cl/svc/CL_APPRREQ.h @@ -0,0 +1,21 @@ +/* + * CL_APPRREQ.h - cl 서비스 CL_APPRREQ 카피북 (copybook / record header). + * 마감 승인 요청: C 상태 검증 후 승인요청 표시, 승인 대기 순번 반환. + */ +#ifndef CL_APPRREQ_H +#define CL_APPRREQ_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_apprreq_rec_t; + +void CL_APPRREQ(TPSVCINFO *p); + +#endif /* CL_APPRREQ_H */ diff --git a/app/src/cl/svc/CL_APPRREQ.pgc b/app/src/cl/svc/CL_APPRREQ.pgc new file mode 100644 index 0000000..a3ad876 --- /dev/null +++ b/app/src/cl/svc/CL_APPRREQ.pgc @@ -0,0 +1,35 @@ +/* + * CL_APPRREQ.pgc - cl 모듈 서비스 CL_APPRREQ (one service = one file). + * 마감 승인 요청: C 상태이며 집계건수>0 인 마감에 승인요청(APPR_REQ) 표시를 + * 남기고, 자기보다 먼저 요청된 승인 대기(C) 건수 기준 대기 순번을 반환. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_APPRREQ.h" + +void CL_APPRREQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid, h_txn, h_ahead; + char h_st[4]; + EXEC SQL END DECLARE SECTION; + h_cid = getl(b, T_ID1); + EXEC SQL SELECT status, txn_count INTO :h_st, :h_txn + FROM cl_close_log WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + if (h_st[0] != 'C') { Bchg(b, T_MSG, 0, "완료(C) 상태만 승인 요청 가능", 0L); FAIL(b); } + if (h_txn <= 0) { Bchg(b, T_MSG, 0, "집계 0건 마감은 승인 불가", 0L); FAIL(b); } + EXEC SQL UPDATE cl_close_log + SET memo = memo || '|APPR_REQ', updated_at = now() + WHERE close_id = :h_cid AND memo NOT LIKE '%APPR_REQ%'; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_ahead FROM cl_close_log + WHERE status = 'C' AND memo LIKE '%APPR_REQ%' AND close_id < :h_cid; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_ARG1, h_ahead + 1); + setl(b, T_RC, 0); + userlog("CL_APPRREQ cid=%ld 승인요청 대기순번=%ld", h_cid, h_ahead + 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_AUDITTRAIL.h b/app/src/cl/svc/CL_AUDITTRAIL.h new file mode 100644 index 0000000..26a9851 --- /dev/null +++ b/app/src/cl/svc/CL_AUDITTRAIL.h @@ -0,0 +1,21 @@ +/* + * CL_AUDITTRAIL.h - cl 서비스 CL_AUDITTRAIL 카피북 (copybook / record header). + * 마감 이력 감사: 생성/수정 시각 차이와 잠금 후 수정 여부 감사. + */ +#ifndef CL_AUDITTRAIL_H +#define CL_AUDITTRAIL_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_audittrail_rec_t; + +void CL_AUDITTRAIL(TPSVCINFO *p); + +#endif /* CL_AUDITTRAIL_H */ diff --git a/app/src/cl/svc/CL_AUDITTRAIL.pgc b/app/src/cl/svc/CL_AUDITTRAIL.pgc new file mode 100644 index 0000000..7881437 --- /dev/null +++ b/app/src/cl/svc/CL_AUDITTRAIL.pgc @@ -0,0 +1,37 @@ +/* + * CL_AUDITTRAIL.pgc - cl 모듈 서비스 CL_AUDITTRAIL (one service = one file). + * 마감 이력 감사: 마감건의 생성-수정 시각 간격(초)을 산출하고, 잠금(L) + * 상태인데 잠금 후에도 수정된 흔적(수정시각>잠금기록시각)을 감사 플래그로 반환. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_AUDITTRAIL.h" + +void CL_AUDITTRAIL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long flag = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid, h_gap, h_tampered; + char h_st[4]; + EXEC SQL END DECLARE SECTION; + h_cid = getl(b, T_ID1); + EXEC SQL SELECT status, + cast(extract(epoch from (updated_at - created_at)) as bigint) + INTO :h_st, :h_gap FROM cl_close_log WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + if (h_st[0] == 'L') { + EXEC SQL SELECT count(*) INTO :h_tampered + FROM cl_close_log c, cl_period_lock l + WHERE c.close_id = :h_cid AND l.period_key = c.period_key + AND c.updated_at > l.locked_at; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_tampered > 0) flag = 1; + } + Bchg(b, T_STATUS, 0, h_st, 0L); + setl(b, T_ARG1, h_gap); + setl(b, T_RC, flag); + if (flag) userlog("CL_AUDITTRAIL cid=%ld 잠금 후 수정 흔적 감지", h_cid); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_CALENDAR.h b/app/src/cl/svc/CL_CALENDAR.h new file mode 100644 index 0000000..5ec47a0 --- /dev/null +++ b/app/src/cl/svc/CL_CALENDAR.h @@ -0,0 +1,21 @@ +/* + * CL_CALENDAR.h - cl 서비스 CL_CALENDAR 카피북 (copybook / record header). + * 마감 캘린더: 월 일자별 마감 상태 비트맵 문자열 생성. + */ +#ifndef CL_CALENDAR_H +#define CL_CALENDAR_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_calendar_rec_t; + +void CL_CALENDAR(TPSVCINFO *p); + +#endif /* CL_CALENDAR_H */ diff --git a/app/src/cl/svc/CL_CALENDAR.pgc b/app/src/cl/svc/CL_CALENDAR.pgc new file mode 100644 index 0000000..2bfcc54 --- /dev/null +++ b/app/src/cl/svc/CL_CALENDAR.pgc @@ -0,0 +1,45 @@ +/* + * CL_CALENDAR.pgc - cl 모듈 서비스 CL_CALENDAR (one service = one file). + * 마감 캘린더: 월(YYYY-MM)의 1~31일 각 일자 마감 상태를 한 글자씩 표현한 + * 비트맵 문자열을 만들어 반환한다. '.'=마감없음, 그 외 상태문자(O/C/A/L/R/X). + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_CALENDAR.h" + +void CL_CALENDAR(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char map[33]; + int i; + long done = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_day; + char h_month[16], h_st[4]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_STR1, h_month, sizeof(h_month)); + if (h_month[0] == 0) { Bchg(b, T_MSG, 0, "월(YYYY-MM) 입력 필요", 0L); FAIL(b); } + for (i = 0; i < 31; i++) map[i] = '.'; + map[31] = 0; + EXEC SQL DECLARE ccal CURSOR FOR + SELECT cast(substr(period_key, 9, 2) as int), max(status) + FROM cl_close_log + WHERE close_type = 'DAILY' AND period_key LIKE :h_month || '-%' + GROUP BY 1 ORDER BY 1; + EXEC SQL OPEN ccal; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH ccal INTO :h_day, :h_st; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ccal; FAIL(b); } + if (h_day >= 1 && h_day <= 31) { + map[h_day - 1] = h_st[0]; + if (h_st[0] != 'O' && h_st[0] != 'X') done++; + } + } + EXEC SQL CLOSE ccal; + Bchg(b, T_STR1, 0, map, 0L); + setl(b, T_COUNT, done); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_CHNCLOSE.h b/app/src/cl/svc/CL_CHNCLOSE.h new file mode 100644 index 0000000..593603e --- /dev/null +++ b/app/src/cl/svc/CL_CHNCLOSE.h @@ -0,0 +1,21 @@ +/* + * CL_CHNCLOSE.h - cl 서비스 CL_CHNCLOSE 카피북 (copybook / record header). + * 채널별 마감: 채널(POS/EDC/EDI/FOREIGN) 단위 매입 집계 후 CHN 마감이력 생성. + */ +#ifndef CL_CHNCLOSE_H +#define CL_CHNCLOSE_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_chnclose_rec_t; + +void CL_CHNCLOSE(TPSVCINFO *p); + +#endif /* CL_CHNCLOSE_H */ diff --git a/app/src/cl/svc/CL_CHNCLOSE.pgc b/app/src/cl/svc/CL_CHNCLOSE.pgc new file mode 100644 index 0000000..c2024a1 --- /dev/null +++ b/app/src/cl/svc/CL_CHNCLOSE.pgc @@ -0,0 +1,35 @@ +/* + * CL_CHNCLOSE.pgc - cl 모듈 서비스 CL_CHNCLOSE (one service = one file). + * 채널별 마감: 채널(POS/EDC/EDI/FOREIGN) 단위로 당일 매입을 집계하고 + * CHN 마감이력을 생성한다. memo 에 채널명을 남긴다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_CHNCLOSE.h" + +void CL_CHNCLOSE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char memo[48]; + long cid; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_amt; + char h_bizdate[16], h_chn[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + gets_(b, T_CHANNEL, h_chn, sizeof(h_chn)); + if (h_chn[0] == 0) strcpy(h_chn, "POS"); + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_amt + FROM purchase WHERE biz_date = :h_bizdate AND channel = :h_chn; + if (sqlca.sqlcode < 0) FAIL(b); + cid = cldb_next_close_id(); + if (cid < 0) FAIL(b); + snprintf(memo, sizeof(memo), "채널마감:%s", h_chn); + if (cldb_insert_close(cid, "CHN", h_bizdate, h_bizdate, "C", h_cnt, h_amt, memo) < 0) FAIL(b); + setl(b, T_ID1, cid); + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_amt); + userlog("CL_CHNCLOSE cid=%ld chn=%s cnt=%ld 채널마감", cid, h_chn, h_cnt); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_CHNVERIFY.h b/app/src/cl/svc/CL_CHNVERIFY.h new file mode 100644 index 0000000..49e7ce6 --- /dev/null +++ b/app/src/cl/svc/CL_CHNVERIFY.h @@ -0,0 +1,21 @@ +/* + * CL_CHNVERIFY.h - cl 서비스 CL_CHNVERIFY 카피북 (copybook / record header). + * 채널별 마감 검증: 채널 매입건수 vs ledger RECON 조인 대사. + */ +#ifndef CL_CHNVERIFY_H +#define CL_CHNVERIFY_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_chnverify_rec_t; + +void CL_CHNVERIFY(TPSVCINFO *p); + +#endif /* CL_CHNVERIFY_H */ diff --git a/app/src/cl/svc/CL_CHNVERIFY.pgc b/app/src/cl/svc/CL_CHNVERIFY.pgc new file mode 100644 index 0000000..12bcbb2 --- /dev/null +++ b/app/src/cl/svc/CL_CHNVERIFY.pgc @@ -0,0 +1,34 @@ +/* + * CL_CHNVERIFY.pgc - cl 모듈 서비스 CL_CHNVERIFY (one service = one file). + * 채널별 마감 검증: 영업일+채널의 매입건수와 해당 매입에 연결된 ledger + * RECON 전기건수를 조인 대사한다. 미전기 잔여건수를 반환. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_CHNVERIFY.h" + +void CL_CHNVERIFY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_pur, h_led; + char h_bizdate[16], h_chn[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + gets_(b, T_CHANNEL, h_chn, sizeof(h_chn)); + if (h_chn[0] == 0) strcpy(h_chn, "POS"); + EXEC SQL SELECT count(*) INTO :h_pur FROM purchase + WHERE biz_date = :h_bizdate AND channel = :h_chn AND status <> 'C'; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(DISTINCT l.purchase_id) INTO :h_led + FROM ledger l JOIN purchase pu ON pu.purchase_id = l.purchase_id + WHERE l.entry_type = 'RECON' AND pu.biz_date = :h_bizdate + AND pu.channel = :h_chn; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_ARG1, h_pur); + setl(b, T_ARG2, h_led); + setl(b, T_DELTA, h_pur - h_led); + setl(b, T_RC, (h_pur == h_led) ? 0 : 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_CLOSERATE.h b/app/src/cl/svc/CL_CLOSERATE.h new file mode 100644 index 0000000..06a7e8b --- /dev/null +++ b/app/src/cl/svc/CL_CLOSERATE.h @@ -0,0 +1,21 @@ +/* + * CL_CLOSERATE.h - cl 서비스 CL_CLOSERATE 카피북 (copybook / record header). + * 마감율 산출: 월 영업일 대비 일마감 완료율(bps) 계산. + */ +#ifndef CL_CLOSERATE_H +#define CL_CLOSERATE_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_closerate_rec_t; + +void CL_CLOSERATE(TPSVCINFO *p); + +#endif /* CL_CLOSERATE_H */ diff --git a/app/src/cl/svc/CL_CLOSERATE.pgc b/app/src/cl/svc/CL_CLOSERATE.pgc new file mode 100644 index 0000000..5c90dc1 --- /dev/null +++ b/app/src/cl/svc/CL_CLOSERATE.pgc @@ -0,0 +1,36 @@ +/* + * CL_CLOSERATE.pgc - cl 모듈 서비스 CL_CLOSERATE (one service = one file). + * 마감율 산출: 월 프리픽스(YYYY-MM)에 대해 매입이 발생한 영업일수 대비 + * 일마감 완료(C/A/L)된 일수의 비율을 bps(만분율)로 계산해 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_CLOSERATE.h" + +void CL_CLOSERATE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rate; + EXEC SQL BEGIN DECLARE SECTION; + long h_bizdays, h_closed; + char h_month[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_STR1, h_month, sizeof(h_month)); + if (h_month[0] == 0) { Bchg(b, T_MSG, 0, "월(YYYY-MM) 입력 필요", 0L); FAIL(b); } + EXEC SQL SELECT count(DISTINCT biz_date) INTO :h_bizdays + FROM purchase WHERE to_char(biz_date,'YYYY-MM') = :h_month; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(DISTINCT period_key) INTO :h_closed + FROM cl_close_log + WHERE close_type = 'DAILY' AND status IN ('C','A','L') + AND period_key LIKE :h_month || '%'; + if (sqlca.sqlcode < 0) FAIL(b); + rate = (h_bizdays > 0) ? (h_closed * 10000) / h_bizdays : 0; + if (rate > 10000) rate = 10000; + setl(b, T_ARG1, h_bizdays); + setl(b, T_ARG2, h_closed); + setl(b, T_ARG3, rate); + setl(b, T_RC, (rate >= 10000) ? 0 : 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_CNTXCHECK.h b/app/src/cl/svc/CL_CNTXCHECK.h new file mode 100644 index 0000000..784ca13 --- /dev/null +++ b/app/src/cl/svc/CL_CNTXCHECK.h @@ -0,0 +1,21 @@ +/* + * CL_CNTXCHECK.h - cl 서비스 CL_CNTXCHECK 카피북 (copybook / record header). + * 마감 건수 크로스체크: 마감이력 vs 실매입 vs 스냅샷 3원 건수 대사. + */ +#ifndef CL_CNTXCHECK_H +#define CL_CNTXCHECK_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_cntxcheck_rec_t; + +void CL_CNTXCHECK(TPSVCINFO *p); + +#endif /* CL_CNTXCHECK_H */ diff --git a/app/src/cl/svc/CL_CNTXCHECK.pgc b/app/src/cl/svc/CL_CNTXCHECK.pgc new file mode 100644 index 0000000..8cb8739 --- /dev/null +++ b/app/src/cl/svc/CL_CNTXCHECK.pgc @@ -0,0 +1,37 @@ +/* + * CL_CNTXCHECK.pgc - cl 모듈 서비스 CL_CNTXCHECK (one service = one file). + * 마감 건수 크로스체크: 마감이력 txn_count vs 실매입 count vs 스냅샷 + * txn_count 합계의 3원 대사. RC 비트: 1=이력/실매입 불일치, 2=이력/스냅샷 불일치. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_CNTXCHECK.h" + +void CL_CNTXCHECK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid, h_log, h_real, h_snap; + char h_date[16]; + EXEC SQL END DECLARE SECTION; + h_cid = getl(b, T_ID1); + EXEC SQL SELECT txn_count, to_char(biz_date,'YYYY-MM-DD') INTO :h_log, :h_date + FROM cl_close_log WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_real FROM purchase WHERE biz_date = :h_date; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(txn_count),0) INTO :h_snap + FROM cl_snapshot WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_log != h_real) rc |= 1; + if (h_snap != 0 && h_log != h_snap) rc |= 2; + setl(b, T_ARG1, h_log); + setl(b, T_ARG2, h_real); + setl(b, T_ARG3, h_snap); + setl(b, T_RC, rc); + if (rc) userlog("CL_CNTXCHECK cid=%ld 건수 불일치 rc=%ld (%ld/%ld/%ld)", + h_cid, rc, h_log, h_real, h_snap); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_CORPCLOSE.h b/app/src/cl/svc/CL_CORPCLOSE.h new file mode 100644 index 0000000..43f7eac --- /dev/null +++ b/app/src/cl/svc/CL_CORPCLOSE.h @@ -0,0 +1,21 @@ +/* + * CL_CORPCLOSE.h - cl 서비스 CL_CORPCLOSE 카피북 (copybook / record header). + * 법인별 마감: 발급사(issuer) 법인 단위 커서 순회로 CORP 마감이력 일괄 생성. + */ +#ifndef CL_CORPCLOSE_H +#define CL_CORPCLOSE_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_corpclose_rec_t; + +void CL_CORPCLOSE(TPSVCINFO *p); + +#endif /* CL_CORPCLOSE_H */ diff --git a/app/src/cl/svc/CL_CORPCLOSE.pgc b/app/src/cl/svc/CL_CORPCLOSE.pgc new file mode 100644 index 0000000..e9d15ae --- /dev/null +++ b/app/src/cl/svc/CL_CORPCLOSE.pgc @@ -0,0 +1,43 @@ +/* + * CL_CORPCLOSE.pgc - cl 모듈 서비스 CL_CORPCLOSE (one service = one file). + * 법인별 마감: 당일 매입을 발급사(issuer) 법인 단위로 그룹 커서 순회하며 + * 법인마다 CORP 마감이력을 한 건씩 생성한다. 생성 건수를 반환. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_CORPCLOSE.h" + +void CL_CORPCLOSE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long made = 0, lastcid = 0, cid; + char memo[64]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_amt; + char h_bizdate[16], h_issuer[24]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL DECLARE ccorp CURSOR FOR + SELECT issuer, count(*), coalesce(sum(amount),0) FROM purchase + WHERE biz_date = :h_bizdate GROUP BY issuer ORDER BY issuer; + EXEC SQL OPEN ccorp; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH ccorp INTO :h_issuer, :h_cnt, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ccorp; FAIL(b); } + cid = cldb_next_close_id(); + if (cid < 0) { EXEC SQL CLOSE ccorp; FAIL(b); } + snprintf(memo, sizeof(memo), "법인마감:%s", h_issuer); + if (cldb_insert_close(cid, "CORP", h_bizdate, h_bizdate, "C", + h_cnt, h_amt, memo) < 0) { EXEC SQL CLOSE ccorp; FAIL(b); } + lastcid = cid; + made++; + } + EXEC SQL CLOSE ccorp; + setl(b, T_COUNT, made); + setl(b, T_ID1, lastcid); + userlog("CL_CORPCLOSE %s 법인마감 %ld건 생성", h_bizdate, made); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_CXLREASON.h b/app/src/cl/svc/CL_CXLREASON.h new file mode 100644 index 0000000..39c0b57 --- /dev/null +++ b/app/src/cl/svc/CL_CXLREASON.h @@ -0,0 +1,21 @@ +/* + * CL_CXLREASON.h - cl 서비스 CL_CXLREASON 카피북 (copybook / record header). + * 마감 취소 사유별 집계: X 상태 memo 그룹 커서 → occurrence 반환. + */ +#ifndef CL_CXLREASON_H +#define CL_CXLREASON_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_cxlreason_rec_t; + +void CL_CXLREASON(TPSVCINFO *p); + +#endif /* CL_CXLREASON_H */ diff --git a/app/src/cl/svc/CL_CXLREASON.pgc b/app/src/cl/svc/CL_CXLREASON.pgc new file mode 100644 index 0000000..7f2bfda --- /dev/null +++ b/app/src/cl/svc/CL_CXLREASON.pgc @@ -0,0 +1,39 @@ +/* + * CL_CXLREASON.pgc - cl 모듈 서비스 CL_CXLREASON (one service = one file). + * 마감 취소 사유별 집계: 기간 프리픽스의 취소(X) 마감을 memo(사유)별로 그룹 + * 커서 순회하여 UBF occurrence (T_STR1[i]=사유, T_ARG1[i]=건수) 로 반환. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_CXLREASON.h" + +void CL_CXLREASON(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long occ = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_n; + char h_prefix[16], h_memo[80]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_STR1, h_prefix, sizeof(h_prefix)); + if (h_prefix[0] == 0) strcpy(h_prefix, "2026"); + EXEC SQL DECLARE ccxl CURSOR FOR + SELECT memo, count(*) FROM cl_close_log + WHERE status = 'X' AND period_key LIKE :h_prefix || '%' + GROUP BY memo ORDER BY count(*) DESC, memo; + EXEC SQL OPEN ccxl; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH ccxl INTO :h_memo, :h_n; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ccxl; FAIL(b); } + if (occ >= 20) break; /* occurrence 상한 */ + Bchg(b, T_STR1, (BFLDOCC)occ, h_memo, 0L); + Bchg(b, T_ARG1, (BFLDOCC)occ, (char *)&h_n, 0L); + occ++; + } + EXEC SQL CLOSE ccxl; + setl(b, T_COUNT, occ); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_DELAYLIST.h b/app/src/cl/svc/CL_DELAYLIST.h new file mode 100644 index 0000000..4a277d2 --- /dev/null +++ b/app/src/cl/svc/CL_DELAYLIST.h @@ -0,0 +1,21 @@ +/* + * CL_DELAYLIST.h - cl 서비스 CL_DELAYLIST 카피북 (copybook / record header). + * 지연 마감 목록: 24시간 경과 미완료(O) 마감 커서 목록 반환. + */ +#ifndef CL_DELAYLIST_H +#define CL_DELAYLIST_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_delaylist_rec_t; + +void CL_DELAYLIST(TPSVCINFO *p); + +#endif /* CL_DELAYLIST_H */ diff --git a/app/src/cl/svc/CL_DELAYLIST.pgc b/app/src/cl/svc/CL_DELAYLIST.pgc new file mode 100644 index 0000000..03b1a48 --- /dev/null +++ b/app/src/cl/svc/CL_DELAYLIST.pgc @@ -0,0 +1,38 @@ +/* + * CL_DELAYLIST.pgc - cl 모듈 서비스 CL_DELAYLIST (one service = one file). + * 지연 마감 목록: 생성 후 24시간이 지나도록 개시(O) 상태로 남은 마감을 + * 커서로 수집해 occurrence (T_ID1[i], T_STR1[i]=기간) 목록으로 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_DELAYLIST.h" + +void CL_DELAYLIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long occ = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid; + char h_period[24]; + EXEC SQL END DECLARE SECTION; + EXEC SQL DECLARE cdly CURSOR FOR + SELECT close_id, period_key FROM cl_close_log + WHERE status = 'O' AND created_at < now() - interval '24 hours' + ORDER BY created_at; + EXEC SQL OPEN cdly; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cdly INTO :h_cid, :h_period; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cdly; FAIL(b); } + if (occ >= 40) break; + Bchg(b, T_ID1, (BFLDOCC)occ, (char *)&h_cid, 0L); + Bchg(b, T_STR1, (BFLDOCC)occ, h_period, 0L); + occ++; + } + EXEC SQL CLOSE cdly; + setl(b, T_COUNT, occ); + setl(b, T_RC, (occ > 0) ? 1 : 0); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_DELAYREG.h b/app/src/cl/svc/CL_DELAYREG.h new file mode 100644 index 0000000..f01c2ca --- /dev/null +++ b/app/src/cl/svc/CL_DELAYREG.h @@ -0,0 +1,21 @@ +/* + * CL_DELAYREG.h - cl 서비스 CL_DELAYREG 카피북 (copybook / record header). + * 마감 지연 사유 등록: 개시(O) 상태 마감건 memo 에 지연 사유 기록. + */ +#ifndef CL_DELAYREG_H +#define CL_DELAYREG_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_delayreg_rec_t; + +void CL_DELAYREG(TPSVCINFO *p); + +#endif /* CL_DELAYREG_H */ diff --git a/app/src/cl/svc/CL_DELAYREG.pgc b/app/src/cl/svc/CL_DELAYREG.pgc new file mode 100644 index 0000000..c4c1593 --- /dev/null +++ b/app/src/cl/svc/CL_DELAYREG.pgc @@ -0,0 +1,36 @@ +/* + * CL_DELAYREG.pgc - cl 모듈 서비스 CL_DELAYREG (one service = one file). + * 마감 지연 사유 등록: 개시(O) 상태의 마감건에만 지연 사유를 memo 로 + * 기록한다 (기존 memo 에 '지연:' 프리픽스로 덧붙임). 상태가 O 가 아니면 거절. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_DELAYREG.h" + +void CL_DELAYREG(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char st[4], reason[64]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid; + char h_reason[80]; + EXEC SQL END DECLARE SECTION; + h_cid = getl(b, T_ID1); + gets_(b, T_REASON, reason, sizeof(reason)); + if (reason[0] == 0) { Bchg(b, T_MSG, 0, "지연 사유 누락", 0L); FAIL(b); } + if (cldb_get_close_status(h_cid, st) < 0) FAIL(b); + if (st[0] != 'O') { + setl(b, T_RC, 9); + Bchg(b, T_MSG, 0, "개시(O) 상태만 지연 등록 가능", 0L); + FAIL(b); + } + snprintf(h_reason, sizeof(h_reason), "지연:%s", reason); + EXEC SQL UPDATE cl_close_log + SET memo = :h_reason, updated_at = now() + WHERE close_id = :h_cid AND status = 'O'; + if (sqlca.sqlcode < 0 || sqlca.sqlerrd[2] == 0) FAIL(b); + setl(b, T_RC, 0); + userlog("CL_DELAYREG cid=%ld 지연사유 등록: %s", h_cid, reason); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_DIVCLOSE.h b/app/src/cl/svc/CL_DIVCLOSE.h new file mode 100644 index 0000000..631d4a1 --- /dev/null +++ b/app/src/cl/svc/CL_DIVCLOSE.h @@ -0,0 +1,21 @@ +/* + * CL_DIVCLOSE.h - cl 서비스 CL_DIVCLOSE 카피북 (copybook / record header). + * 부문별 마감: 업종(category) 부문 단위 정산액 집계 후 DIV 마감이력 직접 생성. + */ +#ifndef CL_DIVCLOSE_H +#define CL_DIVCLOSE_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_divclose_rec_t; + +void CL_DIVCLOSE(TPSVCINFO *p); + +#endif /* CL_DIVCLOSE_H */ diff --git a/app/src/cl/svc/CL_DIVCLOSE.pgc b/app/src/cl/svc/CL_DIVCLOSE.pgc new file mode 100644 index 0000000..2acb3d9 --- /dev/null +++ b/app/src/cl/svc/CL_DIVCLOSE.pgc @@ -0,0 +1,36 @@ +/* + * CL_DIVCLOSE.pgc - cl 모듈 서비스 CL_DIVCLOSE (one service = one file). + * 부문별 마감: 업종(category) 부문 단위로 당일 매입 건수/정산액(net)을 집계하고 + * 마감이력을 nextval 로 직접 INSERT (DIV, status C, memo=부문코드). + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_DIVCLOSE.h" + +void CL_DIVCLOSE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid, h_cnt, h_net; + char h_bizdate[16], h_cat[16], h_memo[48]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + gets_(b, T_CATEGORY, h_cat, sizeof(h_cat)); + if (h_cat[0] == 0) strcpy(h_cat, "GEN"); + EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_cnt, :h_net + FROM purchase + WHERE biz_date = :h_bizdate AND category = :h_cat AND status <> 'C'; + if (sqlca.sqlcode < 0) FAIL(b); + snprintf(h_memo, sizeof(h_memo), "부문마감:%s", h_cat); + EXEC SQL SELECT nextval('cl_close_seq') INTO :h_cid; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO cl_close_log + (close_id, close_type, period_key, biz_date, status, txn_count, total_amount, memo) + VALUES (:h_cid, 'DIV', :h_bizdate, :h_bizdate, 'C', :h_cnt, :h_net, :h_memo); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_ID1, h_cid); + setl(b, T_COUNT, h_cnt); + setl(b, T_NET, h_net); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_DUPCHK.h b/app/src/cl/svc/CL_DUPCHK.h new file mode 100644 index 0000000..d235985 --- /dev/null +++ b/app/src/cl/svc/CL_DUPCHK.h @@ -0,0 +1,21 @@ +/* + * CL_DUPCHK.h - cl 서비스 CL_DUPCHK 카피북 (copybook / record header). + * 중복 마감 탐지: (유형,기간) 중복 마감 그룹 탐지 집계. + */ +#ifndef CL_DUPCHK_H +#define CL_DUPCHK_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_dupchk_rec_t; + +void CL_DUPCHK(TPSVCINFO *p); + +#endif /* CL_DUPCHK_H */ diff --git a/app/src/cl/svc/CL_DUPCHK.pgc b/app/src/cl/svc/CL_DUPCHK.pgc new file mode 100644 index 0000000..b04579c --- /dev/null +++ b/app/src/cl/svc/CL_DUPCHK.pgc @@ -0,0 +1,43 @@ +/* + * CL_DUPCHK.pgc - cl 모듈 서비스 CL_DUPCHK (one service = one file). + * 중복 마감 탐지: 취소(X) 제외 동일 (close_type, period_key) 에 마감이 2건 + * 이상인 그룹을 HAVING 으로 찾아 그룹 수와 잉여 건수 합계를 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_DUPCHK.h" + +void CL_DUPCHK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long groups = 0, extras = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_n; + char h_type[16], h_period[24]; + EXEC SQL END DECLARE SECTION; + EXEC SQL DECLARE cdup CURSOR FOR + SELECT close_type, period_key, count(*) FROM cl_close_log + WHERE status <> 'X' + GROUP BY close_type, period_key HAVING count(*) > 1 + ORDER BY count(*) DESC; + EXEC SQL OPEN cdup; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cdup INTO :h_type, :h_period, :h_n; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cdup; FAIL(b); } + if (groups < 20) { + Bchg(b, T_STR2, (BFLDOCC)groups, h_type, 0L); + Bchg(b, T_KEY2, (BFLDOCC)groups, h_period, 0L); + } + extras += h_n - 1; + groups++; + } + EXEC SQL CLOSE cdup; + setl(b, T_COUNT, groups); + setl(b, T_ARG1, extras); + setl(b, T_RC, (groups == 0) ? 0 : 1); + if (groups) userlog("CL_DUPCHK 중복그룹 %ld개 잉여 %ld건", groups, extras); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_ESCALATE.h b/app/src/cl/svc/CL_ESCALATE.h new file mode 100644 index 0000000..e34dd9c --- /dev/null +++ b/app/src/cl/svc/CL_ESCALATE.h @@ -0,0 +1,21 @@ +/* + * CL_ESCALATE.h - cl 서비스 CL_ESCALATE 카피북 (copybook / record header). + * 승인 지연 에스컬레이션: 기준일 초과 대기 마감 ESC 표시. + */ +#ifndef CL_ESCALATE_H +#define CL_ESCALATE_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_escalate_rec_t; + +void CL_ESCALATE(TPSVCINFO *p); + +#endif /* CL_ESCALATE_H */ diff --git a/app/src/cl/svc/CL_ESCALATE.pgc b/app/src/cl/svc/CL_ESCALATE.pgc new file mode 100644 index 0000000..0c2095b --- /dev/null +++ b/app/src/cl/svc/CL_ESCALATE.pgc @@ -0,0 +1,35 @@ +/* + * CL_ESCALATE.pgc - cl 모듈 서비스 CL_ESCALATE (one service = one file). + * 승인 지연 에스컬레이션: 기준일수(T_ARG1, 기본 3일) 초과 승인 대기(C) + * 마감에 ESC 표시를 일괄 갱신하고 대상 건수를 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_ESCALATE.h" + +void CL_ESCALATE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long marked; + EXEC SQL BEGIN DECLARE SECTION; + long h_days, h_left; + EXEC SQL END DECLARE SECTION; + h_days = getl(b, T_ARG1); + if (h_days <= 0) h_days = 3; + EXEC SQL UPDATE cl_close_log + SET memo = memo || '|ESC', updated_at = now() + WHERE status = 'C' AND memo LIKE '%APPR_REQ%' + AND memo NOT LIKE '%|ESC%' + AND created_at < now() - make_interval(days => cast(:h_days as int)); + if (sqlca.sqlcode < 0) FAIL(b); + marked = sqlca.sqlerrd[2]; + EXEC SQL SELECT count(*) INTO :h_left FROM cl_close_log + WHERE status = 'C' AND memo LIKE '%APPR_REQ%' AND memo NOT LIKE '%|ESC%'; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, marked); + setl(b, T_ARG2, h_left); + setl(b, T_RC, (marked > 0) ? 1 : 0); + if (marked) userlog("CL_ESCALATE %ld일 초과 %ld건 에스컬레이션", h_days, marked); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_FEEVERIFY.h b/app/src/cl/svc/CL_FEEVERIFY.h new file mode 100644 index 0000000..1677ed0 --- /dev/null +++ b/app/src/cl/svc/CL_FEEVERIFY.h @@ -0,0 +1,21 @@ +/* + * CL_FEEVERIFY.h - cl 서비스 CL_FEEVERIFY 카피북 (copybook / record header). + * 수수료 검증: 스냅샷 fee 합계 vs 매입 fee 합계 대사. + */ +#ifndef CL_FEEVERIFY_H +#define CL_FEEVERIFY_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_feeverify_rec_t; + +void CL_FEEVERIFY(TPSVCINFO *p); + +#endif /* CL_FEEVERIFY_H */ diff --git a/app/src/cl/svc/CL_FEEVERIFY.pgc b/app/src/cl/svc/CL_FEEVERIFY.pgc new file mode 100644 index 0000000..12e2f3e --- /dev/null +++ b/app/src/cl/svc/CL_FEEVERIFY.pgc @@ -0,0 +1,39 @@ +/* + * CL_FEEVERIFY.pgc - cl 모듈 서비스 CL_FEEVERIFY (one service = one file). + * 수수료 검증: 마감 스냅샷의 fee 합계와 해당 영업일 매입 fee 합계에 + * 수수료 조정(ac_fee_adj delta)을 가산한 기대치를 대사한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_FEEVERIFY.h" + +void CL_FEEVERIFY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long expect; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid, h_snapfee, h_purfee, h_adj; + char h_date[16]; + EXEC SQL END DECLARE SECTION; + h_cid = getl(b, T_ID1); + EXEC SQL SELECT to_char(biz_date,'YYYY-MM-DD') INTO :h_date + FROM cl_close_log WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + EXEC SQL SELECT coalesce(sum(fee_amount),0) INTO :h_snapfee + FROM cl_snapshot WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(fee),0) INTO :h_purfee + FROM purchase WHERE biz_date = :h_date; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(delta),0) INTO :h_adj + FROM ac_fee_adj WHERE biz_date = :h_date; + if (sqlca.sqlcode < 0) FAIL(b); + expect = h_purfee + h_adj; + setl(b, T_AMT1, h_snapfee); + setl(b, T_AMT2, h_purfee); + setl(b, T_AMT3, h_adj); + setl(b, T_DELTA, h_snapfee - expect); + setl(b, T_RC, (h_snapfee == expect) ? 0 : 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_FINALIZE.h b/app/src/cl/svc/CL_FINALIZE.h new file mode 100644 index 0000000..2317646 --- /dev/null +++ b/app/src/cl/svc/CL_FINALIZE.h @@ -0,0 +1,21 @@ +/* + * CL_FINALIZE.h - cl 서비스 CL_FINALIZE 카피북 (copybook / record header). + * 본마감: 가마감(PROV) 대상 재집계 후 본마감(C) 확정 전환. + */ +#ifndef CL_FINALIZE_H +#define CL_FINALIZE_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_finalize_rec_t; + +void CL_FINALIZE(TPSVCINFO *p); + +#endif /* CL_FINALIZE_H */ diff --git a/app/src/cl/svc/CL_FINALIZE.pgc b/app/src/cl/svc/CL_FINALIZE.pgc new file mode 100644 index 0000000..edb4cb1 --- /dev/null +++ b/app/src/cl/svc/CL_FINALIZE.pgc @@ -0,0 +1,39 @@ +/* + * CL_FINALIZE.pgc - cl 모듈 서비스 CL_FINALIZE (one service = one file). + * 본마감: 해당 영업일의 가마감(PROV, status O)을 찾아 매입을 재집계한 뒤 + * 본마감(C)으로 확정 전환한다. 가마감 시점과 건수가 달라졌으면 새 집계로 갱신. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_FINALIZE.h" + +void CL_FINALIZE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid, h_oldcnt, h_cnt, h_amt; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL SELECT close_id, txn_count INTO :h_cid, :h_oldcnt FROM cl_close_log + WHERE close_type = 'DAILY' AND period_key = :h_bizdate + AND status = 'O' AND memo = 'PROV' + ORDER BY close_id DESC LIMIT 1; + if (sqlca.sqlcode == 100) { Bchg(b, T_MSG, 0, "확정할 가마감 없음", 0L); FAIL(b); } + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_amt + FROM purchase WHERE biz_date = :h_bizdate AND status IN ('M','S'); + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL UPDATE cl_close_log + SET status = 'C', txn_count = :h_cnt, total_amount = :h_amt, + memo = 'FINAL', updated_at = now() + WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0 || sqlca.sqlerrd[2] == 0) FAIL(b); + setl(b, T_ID1, h_cid); + setl(b, T_COUNT, h_cnt); + setl(b, T_DELTA, h_cnt - h_oldcnt); + Bchg(b, T_STATUS, 0, "C", 0L); + userlog("CL_FINALIZE cid=%ld 본마감 확정 (가마감 대비 %+ld건)", h_cid, h_cnt - h_oldcnt); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_FORCECLOSE.h b/app/src/cl/svc/CL_FORCECLOSE.h new file mode 100644 index 0000000..e1b122e --- /dev/null +++ b/app/src/cl/svc/CL_FORCECLOSE.h @@ -0,0 +1,21 @@ +/* + * CL_FORCECLOSE.h - cl 서비스 CL_FORCECLOSE 카피북 (copybook / record header). + * 강제 마감: 기존 개시(O)건 제거 후 FORCE 마감 생성. + */ +#ifndef CL_FORCECLOSE_H +#define CL_FORCECLOSE_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_forceclose_rec_t; + +void CL_FORCECLOSE(TPSVCINFO *p); + +#endif /* CL_FORCECLOSE_H */ diff --git a/app/src/cl/svc/CL_FORCECLOSE.pgc b/app/src/cl/svc/CL_FORCECLOSE.pgc new file mode 100644 index 0000000..9f2def4 --- /dev/null +++ b/app/src/cl/svc/CL_FORCECLOSE.pgc @@ -0,0 +1,37 @@ +/* + * CL_FORCECLOSE.pgc - cl 모듈 서비스 CL_FORCECLOSE (one service = one file). + * 강제 마감: 체크리스트를 우회해 해당 영업일의 미확정 개시(O) 마감을 제거한 + * 뒤 FORCE memo 로 즉시 완료(C) 마감을 생성한다. 운영 비상용. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_FORCECLOSE.h" + +void CL_FORCECLOSE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long cid, wiped; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_amt; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL DELETE FROM cl_close_log + WHERE close_type = 'DAILY' AND period_key = :h_bizdate AND status = 'O'; + if (sqlca.sqlcode < 0) FAIL(b); + wiped = sqlca.sqlerrd[2]; + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_amt + FROM purchase WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + cid = cldb_next_close_id(); + if (cid < 0) FAIL(b); + if (cldb_insert_close(cid, "DAILY", h_bizdate, h_bizdate, "C", + h_cnt, h_amt, "FORCE") < 0) FAIL(b); + setl(b, T_ID1, cid); + setl(b, T_ARG1, wiped); + setl(b, T_COUNT, h_cnt); + Bchg(b, T_STATUS, 0, "C", 0L); + userlog("CL_FORCECLOSE %s 강제마감 cid=%ld (개시건 %ld건 제거)", h_bizdate, cid, wiped); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_GAPDAYS.h b/app/src/cl/svc/CL_GAPDAYS.h new file mode 100644 index 0000000..9e8f738 --- /dev/null +++ b/app/src/cl/svc/CL_GAPDAYS.h @@ -0,0 +1,21 @@ +/* + * CL_GAPDAYS.h - cl 서비스 CL_GAPDAYS 카피북 (copybook / record header). + * 마감 누락일 탐지: 매입 존재하나 일마감 없는 일자 커서 수집. + */ +#ifndef CL_GAPDAYS_H +#define CL_GAPDAYS_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_gapdays_rec_t; + +void CL_GAPDAYS(TPSVCINFO *p); + +#endif /* CL_GAPDAYS_H */ diff --git a/app/src/cl/svc/CL_GAPDAYS.pgc b/app/src/cl/svc/CL_GAPDAYS.pgc new file mode 100644 index 0000000..9e66898 --- /dev/null +++ b/app/src/cl/svc/CL_GAPDAYS.pgc @@ -0,0 +1,42 @@ +/* + * CL_GAPDAYS.pgc - cl 모듈 서비스 CL_GAPDAYS (one service = one file). + * 마감 누락일 탐지: 월 프리픽스 내 매입은 존재하나 일마감이 없는 일자를 + * NOT EXISTS 커서로 수집해 occurrence (T_STR4[i]=일자) 로 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_GAPDAYS.h" + +void CL_GAPDAYS(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long occ = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_month[16], h_day[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_STR1, h_month, sizeof(h_month)); + if (h_month[0] == 0) { Bchg(b, T_MSG, 0, "월(YYYY-MM) 입력 필요", 0L); FAIL(b); } + EXEC SQL DECLARE cgapd CURSOR FOR + SELECT DISTINCT to_char(p.biz_date,'YYYY-MM-DD') FROM purchase p + WHERE to_char(p.biz_date,'YYYY-MM') = :h_month + AND NOT EXISTS (SELECT 1 FROM cl_close_log c + WHERE c.close_type = 'DAILY' + AND c.period_key = to_char(p.biz_date,'YYYY-MM-DD') + AND c.status <> 'X') + ORDER BY 1; + EXEC SQL OPEN cgapd; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cgapd INTO :h_day; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cgapd; FAIL(b); } + if (occ >= 31) break; + Bchg(b, T_STR4, (BFLDOCC)occ, h_day, 0L); + occ++; + } + EXEC SQL CLOSE cgapd; + setl(b, T_COUNT, occ); + setl(b, T_RC, (occ == 0) ? 0 : 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_HALFCLOSE.h b/app/src/cl/svc/CL_HALFCLOSE.h new file mode 100644 index 0000000..f39fd4d --- /dev/null +++ b/app/src/cl/svc/CL_HALFCLOSE.h @@ -0,0 +1,21 @@ +/* + * CL_HALFCLOSE.h - cl 서비스 CL_HALFCLOSE 카피북 (copybook / record header). + * 반기 결산 마감: 상/하반기 기간 집계 후 HALF 마감이력 생성. + */ +#ifndef CL_HALFCLOSE_H +#define CL_HALFCLOSE_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_halfclose_rec_t; + +void CL_HALFCLOSE(TPSVCINFO *p); + +#endif /* CL_HALFCLOSE_H */ diff --git a/app/src/cl/svc/CL_HALFCLOSE.pgc b/app/src/cl/svc/CL_HALFCLOSE.pgc new file mode 100644 index 0000000..0c3eb4b --- /dev/null +++ b/app/src/cl/svc/CL_HALFCLOSE.pgc @@ -0,0 +1,45 @@ +/* + * CL_HALFCLOSE.pgc - cl 모듈 서비스 CL_HALFCLOSE (one service = one file). + * 반기 결산 마감: 연도(T_STR1=YYYY)와 반기(T_ARG1=1|2)를 받아 상/하반기 + * 날짜구간을 만들어 매입을 BETWEEN 집계하고 HALF 마감이력(YYYY-Hn)을 생성. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_HALFCLOSE.h" + +void CL_HALFCLOSE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long half, cid; + char year[8], period[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_amt; + char h_from[16], h_to[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_STR1, year, sizeof(year)); + half = getl(b, T_ARG1); + if (year[0] == 0 || (half != 1 && half != 2)) { + Bchg(b, T_MSG, 0, "연도(YYYY)/반기(1|2) 입력 필요", 0L); + FAIL(b); + } + if (half == 1) { + snprintf(h_from, sizeof(h_from), "%s-01-01", year); + snprintf(h_to, sizeof(h_to), "%s-06-30", year); + } else { + snprintf(h_from, sizeof(h_from), "%s-07-01", year); + snprintf(h_to, sizeof(h_to), "%s-12-31", year); + } + snprintf(period, sizeof(period), "%s-H%ld", year, half); + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_amt + FROM purchase WHERE biz_date BETWEEN :h_from AND :h_to; + if (sqlca.sqlcode < 0) FAIL(b); + cid = cldb_next_close_id(); + if (cid < 0) FAIL(b); + if (cldb_insert_close(cid, "HALF", period, h_to, "C", h_cnt, h_amt, "반기결산") < 0) FAIL(b); + setl(b, T_ID1, cid); + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_amt); + userlog("CL_HALFCLOSE %s 반기결산 cid=%ld cnt=%ld", period, cid, h_cnt); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_HOLIDAY.h b/app/src/cl/svc/CL_HOLIDAY.h new file mode 100644 index 0000000..4b5a5e6 --- /dev/null +++ b/app/src/cl/svc/CL_HOLIDAY.h @@ -0,0 +1,21 @@ +/* + * CL_HOLIDAY.h - cl 서비스 CL_HOLIDAY 카피북 (copybook / record header). + * 휴일 마감 정책: 요일 판정으로 휴일이면 마감 SKIP 정책 응답. + */ +#ifndef CL_HOLIDAY_H +#define CL_HOLIDAY_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_holiday_rec_t; + +void CL_HOLIDAY(TPSVCINFO *p); + +#endif /* CL_HOLIDAY_H */ diff --git a/app/src/cl/svc/CL_HOLIDAY.pgc b/app/src/cl/svc/CL_HOLIDAY.pgc new file mode 100644 index 0000000..6f2894e --- /dev/null +++ b/app/src/cl/svc/CL_HOLIDAY.pgc @@ -0,0 +1,38 @@ +/* + * CL_HOLIDAY.pgc - cl 모듈 서비스 CL_HOLIDAY (one service = one file). + * 휴일 마감 정책: 영업일의 요일을 판정해 주말이면 마감 SKIP(RC=1) 정책을, + * 평일이면 매입 유무에 따라 진행(0)/무거래(2) 를 응답한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_HOLIDAY.h" + +void CL_HOLIDAY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_txn; + char h_bizdate[16], h_dow[4]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL SELECT to_char(to_date(:h_bizdate,'YYYY-MM-DD'),'D') INTO :h_dow; + if (sqlca.sqlcode < 0) FAIL(b); + Bchg(b, T_STR3, 0, h_dow, 0L); + if (h_dow[0] == '1' || h_dow[0] == '7') { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "휴일 정책: 주말 마감 SKIP, 익영업일로 이월", 0L); + OK(b); + } + EXEC SQL SELECT count(*) INTO :h_txn FROM purchase WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, h_txn); + if (h_txn == 0) { + setl(b, T_RC, 2); + Bchg(b, T_MSG, 0, "평일 무거래: 0건 마감 여부 운영 판단", 0L); + } else { + setl(b, T_RC, 0); + Bchg(b, T_MSG, 0, "평일 정상: 마감 진행 가능", 0L); + } + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_LOCKAUDIT.h b/app/src/cl/svc/CL_LOCKAUDIT.h new file mode 100644 index 0000000..627ffa7 --- /dev/null +++ b/app/src/cl/svc/CL_LOCKAUDIT.h @@ -0,0 +1,21 @@ +/* + * CL_LOCKAUDIT.h - cl 서비스 CL_LOCKAUDIT 카피북 (copybook / record header). + * 잠금 감사: 승인(A/L) 마감 없는 잠금 기간 탐지. + */ +#ifndef CL_LOCKAUDIT_H +#define CL_LOCKAUDIT_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_lockaudit_rec_t; + +void CL_LOCKAUDIT(TPSVCINFO *p); + +#endif /* CL_LOCKAUDIT_H */ diff --git a/app/src/cl/svc/CL_LOCKAUDIT.pgc b/app/src/cl/svc/CL_LOCKAUDIT.pgc new file mode 100644 index 0000000..2a4960e --- /dev/null +++ b/app/src/cl/svc/CL_LOCKAUDIT.pgc @@ -0,0 +1,33 @@ +/* + * CL_LOCKAUDIT.pgc - cl 모듈 서비스 CL_LOCKAUDIT (one service = one file). + * 잠금 감사: 잠금(locked=true) 기간 중 승인(A) 또는 잠금(L) 마감이 하나도 + * 없는 비정상 잠금을 탐지해 건수와 첫 위반 기간키를 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_LOCKAUDIT.h" + +void CL_LOCKAUDIT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_bad; + char h_first[24]; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*), coalesce(min(l.period_key),'') + INTO :h_bad, :h_first + FROM cl_period_lock l + WHERE l.locked = true + AND NOT EXISTS (SELECT 1 FROM cl_close_log c + WHERE c.period_key = l.period_key + AND c.status IN ('A','L')); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, h_bad); + setl(b, T_RC, (h_bad == 0) ? 0 : 1); + if (h_bad > 0) { + Bchg(b, T_KEY1, 0, h_first, 0L); + userlog("CL_LOCKAUDIT 승인없는 잠금 %ld건 (최초 %s)", h_bad, h_first); + } + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_LOCKSWEEP.h b/app/src/cl/svc/CL_LOCKSWEEP.h new file mode 100644 index 0000000..6190e9b --- /dev/null +++ b/app/src/cl/svc/CL_LOCKSWEEP.h @@ -0,0 +1,21 @@ +/* + * CL_LOCKSWEEP.h - cl 서비스 CL_LOCKSWEEP 카피북 (copybook / record header). + * 기간 잠금 일괄: 프리픽스 잠금 갱신 + 누락 잠금 보충 삽입. + */ +#ifndef CL_LOCKSWEEP_H +#define CL_LOCKSWEEP_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_locksweep_rec_t; + +void CL_LOCKSWEEP(TPSVCINFO *p); + +#endif /* CL_LOCKSWEEP_H */ diff --git a/app/src/cl/svc/CL_LOCKSWEEP.pgc b/app/src/cl/svc/CL_LOCKSWEEP.pgc new file mode 100644 index 0000000..041f7e7 --- /dev/null +++ b/app/src/cl/svc/CL_LOCKSWEEP.pgc @@ -0,0 +1,37 @@ +/* + * CL_LOCKSWEEP.pgc - cl 모듈 서비스 CL_LOCKSWEEP (one service = one file). + * 기간 잠금 일괄: 프리픽스 구간의 기존 잠금 레코드를 locked=true 로 재잠금하고 + * 승인(A/L) 마감이 있으나 잠금 레코드가 없는 기간은 보충 INSERT 한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_LOCKSWEEP.h" + +void CL_LOCKSWEEP(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long relocked, added; + EXEC SQL BEGIN DECLARE SECTION; + char h_prefix[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_STR1, h_prefix, sizeof(h_prefix)); + if (h_prefix[0] == 0) { Bchg(b, T_MSG, 0, "잠금 프리픽스 필요", 0L); FAIL(b); } + EXEC SQL UPDATE cl_period_lock + SET locked = true, locked_at = now() + WHERE period_key LIKE :h_prefix || '%' AND locked = false; + if (sqlca.sqlcode < 0) FAIL(b); + relocked = sqlca.sqlerrd[2]; + EXEC SQL INSERT INTO cl_period_lock (period_key, locked) + SELECT DISTINCT c.period_key, true FROM cl_close_log c + WHERE c.period_key LIKE :h_prefix || '%' AND c.status IN ('A','L') + AND NOT EXISTS (SELECT 1 FROM cl_period_lock l + WHERE l.period_key = c.period_key); + if (sqlca.sqlcode < 0) FAIL(b); + added = sqlca.sqlerrd[2]; + setl(b, T_ARG1, relocked); + setl(b, T_ARG2, added); + setl(b, T_COUNT, relocked + added); + userlog("CL_LOCKSWEEP %s 재잠금=%ld 보충=%ld", h_prefix, relocked, added); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_MEMOSEARCH.h b/app/src/cl/svc/CL_MEMOSEARCH.h new file mode 100644 index 0000000..d2f923a --- /dev/null +++ b/app/src/cl/svc/CL_MEMOSEARCH.h @@ -0,0 +1,21 @@ +/* + * CL_MEMOSEARCH.h - cl 서비스 CL_MEMOSEARCH 카피북 (copybook / record header). + * 마감 사유 검색: memo 키워드 검색으로 최신 마감 반환. + */ +#ifndef CL_MEMOSEARCH_H +#define CL_MEMOSEARCH_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_memosearch_rec_t; + +void CL_MEMOSEARCH(TPSVCINFO *p); + +#endif /* CL_MEMOSEARCH_H */ diff --git a/app/src/cl/svc/CL_MEMOSEARCH.pgc b/app/src/cl/svc/CL_MEMOSEARCH.pgc new file mode 100644 index 0000000..be63c84 --- /dev/null +++ b/app/src/cl/svc/CL_MEMOSEARCH.pgc @@ -0,0 +1,35 @@ +/* + * CL_MEMOSEARCH.pgc - cl 모듈 서비스 CL_MEMOSEARCH (one service = one file). + * 마감 사유 검색: memo 에 키워드(T_STR1)가 포함된 마감을 검색해 총 건수와 + * 가장 최근 마감의 id/상태/기간을 반환한다. 키워드 없으면 거절. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_MEMOSEARCH.h" + +void CL_MEMOSEARCH(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_hits, h_cid; + char h_kw[40], h_st[4], h_period[24]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_STR1, h_kw, sizeof(h_kw)); + if (h_kw[0] == 0) { Bchg(b, T_MSG, 0, "검색 키워드 필요", 0L); FAIL(b); } + EXEC SQL SELECT count(*) INTO :h_hits FROM cl_close_log + WHERE memo LIKE '%' || :h_kw || '%'; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, h_hits); + if (h_hits == 0) { setl(b, T_RC, 1); OK(b); } + EXEC SQL SELECT close_id, status, period_key INTO :h_cid, :h_st, :h_period + FROM cl_close_log + WHERE memo LIKE '%' || :h_kw || '%' + ORDER BY created_at DESC, close_id DESC LIMIT 1; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_ID1, h_cid); + Bchg(b, T_STATUS, 0, h_st, 0L); + Bchg(b, T_KEY1, 0, h_period, 0L); + setl(b, T_RC, 0); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_MERCLOSE.h b/app/src/cl/svc/CL_MERCLOSE.h new file mode 100644 index 0000000..3d7295a --- /dev/null +++ b/app/src/cl/svc/CL_MERCLOSE.h @@ -0,0 +1,21 @@ +/* + * CL_MERCLOSE.h - cl 서비스 CL_MERCLOSE 카피북 (copybook / record header). + * 가맹점별 마감: 단일 가맹점 당일 집계 스냅샷 확정. + */ +#ifndef CL_MERCLOSE_H +#define CL_MERCLOSE_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_merclose_rec_t; + +void CL_MERCLOSE(TPSVCINFO *p); + +#endif /* CL_MERCLOSE_H */ diff --git a/app/src/cl/svc/CL_MERCLOSE.pgc b/app/src/cl/svc/CL_MERCLOSE.pgc new file mode 100644 index 0000000..59cb66e --- /dev/null +++ b/app/src/cl/svc/CL_MERCLOSE.pgc @@ -0,0 +1,43 @@ +/* + * CL_MERCLOSE.pgc - cl 모듈 서비스 CL_MERCLOSE (one service = one file). + * 가맹점별 마감: 단일 가맹점의 당일 매입(gross/fee/net/건수)을 집계해 해당 + * 영업일 최신 DAILY 마감에 연결된 스냅샷으로 확정 기록한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_MERCLOSE.h" + +void CL_MERCLOSE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long sid; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid, h_gross, h_fee, h_net, h_cnt; + char h_bizdate[16], h_merch[24]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + if (h_merch[0] == 0) { Bchg(b, T_MSG, 0, "가맹점 ID 필요", 0L); FAIL(b); } + EXEC SQL SELECT close_id INTO :h_cid FROM cl_close_log + WHERE close_type = 'DAILY' AND period_key = :h_bizdate AND status <> 'X' + ORDER BY close_id DESC LIMIT 1; + if (sqlca.sqlcode == 100) { Bchg(b, T_MSG, 0, "당일 일마감 선행 필요", 0L); FAIL(b); } + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(amount),0), coalesce(sum(fee),0), + coalesce(sum(net),0), count(*) + INTO :h_gross, :h_fee, :h_net, :h_cnt + FROM purchase WHERE biz_date = :h_bizdate AND merchant_id = :h_merch; + if (sqlca.sqlcode < 0) FAIL(b); + sid = cldb_next_snapshot_id(); + if (sid < 0) FAIL(b); + if (cldb_insert_snapshot(sid, h_cid, h_bizdate, h_bizdate, h_merch, + h_gross, h_fee, h_net, h_cnt) < 0) FAIL(b); + setl(b, T_ID1, h_cid); + setl(b, T_ID2, sid); + setl(b, T_GROSS, h_gross); + setl(b, T_NET, h_net); + setl(b, T_COUNT, h_cnt); + userlog("CL_MERCLOSE %s/%s sid=%ld 가맹점 마감", h_merch, h_bizdate, sid); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_NETVERIFY.h b/app/src/cl/svc/CL_NETVERIFY.h new file mode 100644 index 0000000..7fc477b --- /dev/null +++ b/app/src/cl/svc/CL_NETVERIFY.h @@ -0,0 +1,21 @@ +/* + * CL_NETVERIFY.h - cl 서비스 CL_NETVERIFY 카피북 (copybook / record header). + * 정산액 검증: 스냅샷 gross-fee=net 위반행 탐지. + */ +#ifndef CL_NETVERIFY_H +#define CL_NETVERIFY_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_netverify_rec_t; + +void CL_NETVERIFY(TPSVCINFO *p); + +#endif /* CL_NETVERIFY_H */ diff --git a/app/src/cl/svc/CL_NETVERIFY.pgc b/app/src/cl/svc/CL_NETVERIFY.pgc new file mode 100644 index 0000000..c852967 --- /dev/null +++ b/app/src/cl/svc/CL_NETVERIFY.pgc @@ -0,0 +1,35 @@ +/* + * CL_NETVERIFY.pgc - cl 모듈 서비스 CL_NETVERIFY (one service = one file). + * 정산액 검증: 마감 스냅샷에서 gross - fee <> net 인 내부 모순 행을 찾아 + * 위반 행수와 첫 위반 가맹점/편차를 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_NETVERIFY.h" + +void CL_NETVERIFY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid, h_bad, h_gap; + char h_merch[24]; + EXEC SQL END DECLARE SECTION; + h_cid = getl(b, T_ID1); + EXEC SQL SELECT count(*) INTO :h_bad FROM cl_snapshot + WHERE close_id = :h_cid AND gross_amount - fee_amount <> net_amount; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, h_bad); + if (h_bad == 0) { setl(b, T_RC, 0); OK(b); } + EXEC SQL SELECT merchant_id, gross_amount - fee_amount - net_amount + INTO :h_merch, :h_gap + FROM cl_snapshot + WHERE close_id = :h_cid AND gross_amount - fee_amount <> net_amount + ORDER BY abs(gross_amount - fee_amount - net_amount) DESC LIMIT 1; + if (sqlca.sqlcode < 0) FAIL(b); + Bchg(b, T_MERCHANT, 0, h_merch, 0L); + setl(b, T_DELTA, h_gap); + setl(b, T_RC, 1); + userlog("CL_NETVERIFY cid=%ld net 모순 %ld행 (최대 %s %+ld)", h_cid, h_bad, h_merch, h_gap); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_PENDLIST.h b/app/src/cl/svc/CL_PENDLIST.h new file mode 100644 index 0000000..ad5fe8a --- /dev/null +++ b/app/src/cl/svc/CL_PENDLIST.h @@ -0,0 +1,21 @@ +/* + * CL_PENDLIST.h - cl 서비스 CL_PENDLIST 카피북 (copybook / record header). + * 승인 대기 목록: 승인 대기(C) 건수와 최장 대기 마감 조회. + */ +#ifndef CL_PENDLIST_H +#define CL_PENDLIST_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_pendlist_rec_t; + +void CL_PENDLIST(TPSVCINFO *p); + +#endif /* CL_PENDLIST_H */ diff --git a/app/src/cl/svc/CL_PENDLIST.pgc b/app/src/cl/svc/CL_PENDLIST.pgc new file mode 100644 index 0000000..f04832b --- /dev/null +++ b/app/src/cl/svc/CL_PENDLIST.pgc @@ -0,0 +1,36 @@ +/* + * CL_PENDLIST.pgc - cl 모듈 서비스 CL_PENDLIST (one service = one file). + * 승인 대기 목록: 승인요청(memo APPR_REQ) 상태로 대기 중인 완료(C) 마감의 + * 건수, 최장 대기 건의 id 와 대기 일수를 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_PENDLIST.h" + +void CL_PENDLIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_pend, h_oldest, h_waitdays; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_pend FROM cl_close_log + WHERE status = 'C' AND memo LIKE '%APPR_REQ%'; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, h_pend); + if (h_pend == 0) { + setl(b, T_RC, 0); + Bchg(b, T_MSG, 0, "승인 대기 없음", 0L); + OK(b); + } + EXEC SQL SELECT close_id, current_date - cast(updated_at as date) + INTO :h_oldest, :h_waitdays + FROM cl_close_log + WHERE status = 'C' AND memo LIKE '%APPR_REQ%' + ORDER BY updated_at ASC LIMIT 1; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_ID1, h_oldest); + setl(b, T_ARG1, h_waitdays); + setl(b, T_RC, (h_waitdays >= 3) ? 2 : 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_POSTTRIG.h b/app/src/cl/svc/CL_POSTTRIG.h new file mode 100644 index 0000000..b3c5d89 --- /dev/null +++ b/app/src/cl/svc/CL_POSTTRIG.h @@ -0,0 +1,21 @@ +/* + * CL_POSTTRIG.h - cl 서비스 CL_POSTTRIG 카피북 (copybook / record header). + * 마감 후속작업 트리거: 마감 완료 후 정산건을 ledger XFER 로 전기. + */ +#ifndef CL_POSTTRIG_H +#define CL_POSTTRIG_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_posttrig_rec_t; + +void CL_POSTTRIG(TPSVCINFO *p); + +#endif /* CL_POSTTRIG_H */ diff --git a/app/src/cl/svc/CL_POSTTRIG.pgc b/app/src/cl/svc/CL_POSTTRIG.pgc new file mode 100644 index 0000000..7eebf80 --- /dev/null +++ b/app/src/cl/svc/CL_POSTTRIG.pgc @@ -0,0 +1,38 @@ +/* + * CL_POSTTRIG.pgc - cl 모듈 서비스 CL_POSTTRIG (one service = one file). + * 마감 후속작업 트리거: 마감(C/A) 확인 후 해당 영업일의 정산건을 ledger 에 + * XFER 로 일괄 전기한다(이미 전기된 건은 NOT EXISTS 로 제외). INSERT..SELECT. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_POSTTRIG.h" + +void CL_POSTTRIG(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char st[4]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid; + char h_date[16]; + EXEC SQL END DECLARE SECTION; + h_cid = getl(b, T_ID1); + if (cldb_get_close_status(h_cid, st) < 0) FAIL(b); + if (st[0] != 'C' && st[0] != 'A') { + Bchg(b, T_MSG, 0, "마감완료(C)/승인(A) 후에만 후속작업 가능", 0L); + FAIL(b); + } + EXEC SQL SELECT to_char(biz_date,'YYYY-MM-DD') INTO :h_date + FROM cl_close_log WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date) + SELECT nextval('ledger_seq'), s.purchase_id, 'XFER', s.net, s.biz_date + FROM settlement s + WHERE s.biz_date = :h_date + AND NOT EXISTS (SELECT 1 FROM ledger l + WHERE l.purchase_id = s.purchase_id AND l.entry_type = 'XFER'); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, sqlca.sqlerrd[2]); + userlog("CL_POSTTRIG cid=%ld XFER %ld건 전기", h_cid, (long)sqlca.sqlerrd[2]); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_PRECHECK.h b/app/src/cl/svc/CL_PRECHECK.h new file mode 100644 index 0000000..a89f900 --- /dev/null +++ b/app/src/cl/svc/CL_PRECHECK.h @@ -0,0 +1,21 @@ +/* + * CL_PRECHECK.h - cl 서비스 CL_PRECHECK 카피북 (copybook / record header). + * 마감 전 체크리스트 검증: 미마감/미매칭/기잠금/기마감 4항목 비트마스크 점검. + */ +#ifndef CL_PRECHECK_H +#define CL_PRECHECK_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_precheck_rec_t; + +void CL_PRECHECK(TPSVCINFO *p); + +#endif /* CL_PRECHECK_H */ diff --git a/app/src/cl/svc/CL_PRECHECK.pgc b/app/src/cl/svc/CL_PRECHECK.pgc new file mode 100644 index 0000000..889f2f9 --- /dev/null +++ b/app/src/cl/svc/CL_PRECHECK.pgc @@ -0,0 +1,47 @@ +/* + * CL_PRECHECK.pgc - cl 모듈 서비스 CL_PRECHECK (one service = one file). + * 마감 전 체크리스트 검증: (1)미마감 M 매입 (2)미매칭 ac_unmatch (3)기간 기잠금 + * (4)당일 DAILY 기마감 존재 - 4개 항목을 비트마스크(T_RC)로 반환. + * 0 이면 마감 진행 가능. T_MSG 에 실패 항목 요약. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_PRECHECK.h" + +void CL_PRECHECK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long mask = 0; + char msg[128]; + EXEC SQL BEGIN DECLARE SECTION; + long h_m, h_um, h_lk, h_dc; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL SELECT count(*) INTO :h_m FROM purchase + WHERE biz_date = :h_bizdate AND status = 'M'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_m > 0) mask |= 1; + EXEC SQL SELECT count(*) INTO :h_um FROM ac_unmatch WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_um > 0) mask |= 2; + EXEC SQL SELECT count(*) INTO :h_lk FROM cl_period_lock + WHERE period_key = :h_bizdate AND locked = true; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_lk > 0) mask |= 4; + EXEC SQL SELECT count(*) INTO :h_dc FROM cl_close_log + WHERE close_type = 'DAILY' AND period_key = :h_bizdate AND status <> 'X'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_dc > 0) mask |= 8; + msg[0] = 0; + if (mask & 1) strcat(msg, "[미마감M]"); + if (mask & 2) strcat(msg, "[미매칭]"); + if (mask & 4) strcat(msg, "[기간잠금]"); + if (mask & 8) strcat(msg, "[기마감]"); + if (mask == 0) strcpy(msg, "체크리스트 전항목 통과"); + setl(b, T_RC, mask); + setl(b, T_COUNT, h_m + h_um); + Bchg(b, T_MSG, 0, msg, 0L); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_PROVCLOSE.h b/app/src/cl/svc/CL_PROVCLOSE.h new file mode 100644 index 0000000..de7a7dd --- /dev/null +++ b/app/src/cl/svc/CL_PROVCLOSE.h @@ -0,0 +1,21 @@ +/* + * CL_PROVCLOSE.h - cl 서비스 CL_PROVCLOSE 카피북 (copybook / record header). + * 가마감: 기존 가마감 중복 확인 후 status O + memo PROV 가마감 생성. + */ +#ifndef CL_PROVCLOSE_H +#define CL_PROVCLOSE_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_provclose_rec_t; + +void CL_PROVCLOSE(TPSVCINFO *p); + +#endif /* CL_PROVCLOSE_H */ diff --git a/app/src/cl/svc/CL_PROVCLOSE.pgc b/app/src/cl/svc/CL_PROVCLOSE.pgc new file mode 100644 index 0000000..1596f1a --- /dev/null +++ b/app/src/cl/svc/CL_PROVCLOSE.pgc @@ -0,0 +1,42 @@ +/* + * CL_PROVCLOSE.pgc - cl 모듈 서비스 CL_PROVCLOSE (one service = one file). + * 가마감: 당일 매입 집계 후 status O + memo 'PROV' 가마감을 생성한다. + * 동일 영업일에 미확정 가마감이 이미 있으면 중복 생성하지 않고 거절. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_PROVCLOSE.h" + +void CL_PROVCLOSE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long cid; + EXEC SQL BEGIN DECLARE SECTION; + long h_dup, h_cnt, h_amt; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL SELECT count(*) INTO :h_dup FROM cl_close_log + WHERE close_type = 'DAILY' AND period_key = :h_bizdate + AND status = 'O' AND memo = 'PROV'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_dup > 0) { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "가마감 중복: 미확정 가마감 존재", 0L); + FAIL(b); + } + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_amt + FROM purchase WHERE biz_date = :h_bizdate AND status IN ('M','S'); + if (sqlca.sqlcode < 0) FAIL(b); + cid = cldb_next_close_id(); + if (cid < 0) FAIL(b); + if (cldb_insert_close(cid, "DAILY", h_bizdate, h_bizdate, "O", + h_cnt, h_amt, "PROV") < 0) FAIL(b); + setl(b, T_ID1, cid); + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_amt); + Bchg(b, T_STATUS, 0, "O", 0L); + userlog("CL_PROVCLOSE cid=%ld %s 가마감 생성", cid, h_bizdate); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_REOPENREQ.h b/app/src/cl/svc/CL_REOPENREQ.h new file mode 100644 index 0000000..6d436ab --- /dev/null +++ b/app/src/cl/svc/CL_REOPENREQ.h @@ -0,0 +1,21 @@ +/* + * CL_REOPENREQ.h - cl 서비스 CL_REOPENREQ 카피북 (copybook / record header). + * 마감 재개시: 잠금 아닌 완료(C) 마감을 O 로 재개시. + */ +#ifndef CL_REOPENREQ_H +#define CL_REOPENREQ_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_reopenreq_rec_t; + +void CL_REOPENREQ(TPSVCINFO *p); + +#endif /* CL_REOPENREQ_H */ diff --git a/app/src/cl/svc/CL_REOPENREQ.pgc b/app/src/cl/svc/CL_REOPENREQ.pgc new file mode 100644 index 0000000..ccb96e1 --- /dev/null +++ b/app/src/cl/svc/CL_REOPENREQ.pgc @@ -0,0 +1,44 @@ +/* + * CL_REOPENREQ.pgc - cl 모듈 서비스 CL_REOPENREQ (one service = one file). + * 마감 재개시: 완료(C) 상태이고 기간이 잠금되지 않은 마감을 개시(O)로 + * 되돌린다. memo 에 REOPEN 사유를 남기며, 잠금 기간이면 RC=3 거절. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_REOPENREQ.h" + +void CL_REOPENREQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char reason[48]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid, h_locked; + char h_memo[64]; + EXEC SQL END DECLARE SECTION; + h_cid = getl(b, T_ID1); + gets_(b, T_REASON, reason, sizeof(reason)); + EXEC SQL SELECT count(*) INTO :h_locked + FROM cl_period_lock l, cl_close_log c + WHERE c.close_id = :h_cid AND l.period_key = c.period_key AND l.locked = true; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_locked > 0) { + setl(b, T_RC, 3); + Bchg(b, T_MSG, 0, "잠금 기간: 재개시 불가 (선 잠금해제 필요)", 0L); + FAIL(b); + } + snprintf(h_memo, sizeof(h_memo), "REOPEN:%s", reason[0] ? reason : "무사유"); + EXEC SQL UPDATE cl_close_log + SET status = 'O', memo = :h_memo, updated_at = now() + WHERE close_id = :h_cid AND status = 'C'; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlerrd[2] != 1) { + setl(b, T_RC, 4); + Bchg(b, T_MSG, 0, "완료(C) 상태가 아니어서 재개시 실패", 0L); + FAIL(b); + } + setl(b, T_RC, 0); + Bchg(b, T_STATUS, 0, "O", 0L); + userlog("CL_REOPENREQ cid=%ld 재개시", h_cid); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_RPTCAT.h b/app/src/cl/svc/CL_RPTCAT.h new file mode 100644 index 0000000..26d8a4d --- /dev/null +++ b/app/src/cl/svc/CL_RPTCAT.h @@ -0,0 +1,21 @@ +/* + * CL_RPTCAT.h - cl 서비스 CL_RPTCAT 카피북 (copybook / record header). + * 업종별 마감 리포트: 업종별 수수료율(bps) 산출 리포트. + */ +#ifndef CL_RPTCAT_H +#define CL_RPTCAT_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_rptcat_rec_t; + +void CL_RPTCAT(TPSVCINFO *p); + +#endif /* CL_RPTCAT_H */ diff --git a/app/src/cl/svc/CL_RPTCAT.pgc b/app/src/cl/svc/CL_RPTCAT.pgc new file mode 100644 index 0000000..1eec36f --- /dev/null +++ b/app/src/cl/svc/CL_RPTCAT.pgc @@ -0,0 +1,41 @@ +/* + * CL_RPTCAT.pgc - cl 모듈 서비스 CL_RPTCAT (one service = one file). + * 업종별 마감 리포트: 영업일 매입을 업종(category) 그룹으로 집계하며 업종별 + * 실효 수수료율(bps = fee 합계 * 10000 / amount 합계)을 SQL 에서 산출해 반환. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_RPTCAT.h" + +void CL_RPTCAT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long occ = 0, maxbps = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_amt, h_bps; + char h_bizdate[16], h_cat[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL DECLARE crcat CURSOR FOR + SELECT category, coalesce(sum(amount),0), + coalesce(sum(fee) * 10000 / nullif(sum(amount),0), 0) + FROM purchase WHERE biz_date = :h_bizdate + GROUP BY category ORDER BY category; + EXEC SQL OPEN crcat; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH crcat INTO :h_cat, :h_amt, :h_bps; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE crcat; FAIL(b); } + Bchg(b, T_CATEGORY, (BFLDOCC)occ, h_cat, 0L); + Bchg(b, T_AMT3, (BFLDOCC)occ, (char *)&h_amt, 0L); + Bchg(b, T_ARG3, (BFLDOCC)occ, (char *)&h_bps, 0L); + if (h_bps > maxbps) maxbps = h_bps; + occ++; + } + EXEC SQL CLOSE crcat; + setl(b, T_COUNT, occ); + setl(b, T_ARG4, maxbps); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_RPTCHN.h b/app/src/cl/svc/CL_RPTCHN.h new file mode 100644 index 0000000..17f31f8 --- /dev/null +++ b/app/src/cl/svc/CL_RPTCHN.h @@ -0,0 +1,21 @@ +/* + * CL_RPTCHN.h - cl 서비스 CL_RPTCHN 카피북 (copybook / record header). + * 채널별 마감 리포트: 영업일 채널 그룹 집계 occurrence 리포트. + */ +#ifndef CL_RPTCHN_H +#define CL_RPTCHN_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_rptchn_rec_t; + +void CL_RPTCHN(TPSVCINFO *p); + +#endif /* CL_RPTCHN_H */ diff --git a/app/src/cl/svc/CL_RPTCHN.pgc b/app/src/cl/svc/CL_RPTCHN.pgc new file mode 100644 index 0000000..7b7b007 --- /dev/null +++ b/app/src/cl/svc/CL_RPTCHN.pgc @@ -0,0 +1,40 @@ +/* + * CL_RPTCHN.pgc - cl 모듈 서비스 CL_RPTCHN (one service = one file). + * 채널별 마감 리포트: 영업일의 매입을 채널 그룹으로 집계해 occurrence + * (T_CHANNEL[i], T_ARG1[i]=건수, T_AMT1[i]=금액) 리포트로 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_RPTCHN.h" + +void CL_RPTCHN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long occ = 0, totamt = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_amt; + char h_bizdate[16], h_chn[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL DECLARE crchn CURSOR FOR + SELECT channel, count(*), coalesce(sum(amount),0) + FROM purchase WHERE biz_date = :h_bizdate AND status <> 'C' + GROUP BY channel ORDER BY 3 DESC; + EXEC SQL OPEN crchn; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH crchn INTO :h_chn, :h_cnt, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE crchn; FAIL(b); } + Bchg(b, T_CHANNEL, (BFLDOCC)occ, h_chn, 0L); + Bchg(b, T_ARG1, (BFLDOCC)occ, (char *)&h_cnt, 0L); + Bchg(b, T_AMT1, (BFLDOCC)occ, (char *)&h_amt, 0L); + totamt += h_amt; + occ++; + } + EXEC SQL CLOSE crchn; + setl(b, T_COUNT, occ); + setl(b, T_GROSS, totamt); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_RPTISSUER.h b/app/src/cl/svc/CL_RPTISSUER.h new file mode 100644 index 0000000..d77050a --- /dev/null +++ b/app/src/cl/svc/CL_RPTISSUER.h @@ -0,0 +1,21 @@ +/* + * CL_RPTISSUER.h - cl 서비스 CL_RPTISSUER 카피북 (copybook / record header). + * 발급사별 마감 리포트: 임계금액 이상 발급사 HAVING 리포트. + */ +#ifndef CL_RPTISSUER_H +#define CL_RPTISSUER_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_rptissuer_rec_t; + +void CL_RPTISSUER(TPSVCINFO *p); + +#endif /* CL_RPTISSUER_H */ diff --git a/app/src/cl/svc/CL_RPTISSUER.pgc b/app/src/cl/svc/CL_RPTISSUER.pgc new file mode 100644 index 0000000..7ff43de --- /dev/null +++ b/app/src/cl/svc/CL_RPTISSUER.pgc @@ -0,0 +1,41 @@ +/* + * CL_RPTISSUER.pgc - cl 모듈 서비스 CL_RPTISSUER (one service = one file). + * 발급사별 마감 리포트: 영업일 매입을 발급사 그룹으로 집계하되 임계금액 + * (T_AMT1 입력, 기본 0) 이상인 발급사만 HAVING 으로 걸러 occurrence 반환. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_RPTISSUER.h" + +void CL_RPTISSUER(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long occ = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_min, h_cnt, h_amt; + char h_bizdate[16], h_iss[24]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + h_min = getl(b, T_AMT1); + if (h_min < 0) h_min = 0; + EXEC SQL DECLARE criss CURSOR FOR + SELECT issuer, count(*), sum(amount) FROM purchase + WHERE biz_date = :h_bizdate + GROUP BY issuer HAVING sum(amount) >= :h_min + ORDER BY sum(amount) DESC; + EXEC SQL OPEN criss; + if (sqlca.sqlcode < 0) FAIL(b); + while (occ < 30) { + EXEC SQL FETCH criss INTO :h_iss, :h_cnt, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE criss; FAIL(b); } + Bchg(b, T_ISSUER, (BFLDOCC)occ, h_iss, 0L); + Bchg(b, T_ARG2, (BFLDOCC)occ, (char *)&h_cnt, 0L); + Bchg(b, T_AMT2, (BFLDOCC)occ, (char *)&h_amt, 0L); + occ++; + } + EXEC SQL CLOSE criss; + setl(b, T_COUNT, occ); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_RPTTOPMER.h b/app/src/cl/svc/CL_RPTTOPMER.h new file mode 100644 index 0000000..127a65b --- /dev/null +++ b/app/src/cl/svc/CL_RPTTOPMER.h @@ -0,0 +1,21 @@ +/* + * CL_RPTTOPMER.h - cl 서비스 CL_RPTTOPMER 카피북 (copybook / record header). + * 상위 가맹점 마감 리포트: 스냅샷 net 상위 N 가맹점 리포트. + */ +#ifndef CL_RPTTOPMER_H +#define CL_RPTTOPMER_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_rpttopmer_rec_t; + +void CL_RPTTOPMER(TPSVCINFO *p); + +#endif /* CL_RPTTOPMER_H */ diff --git a/app/src/cl/svc/CL_RPTTOPMER.pgc b/app/src/cl/svc/CL_RPTTOPMER.pgc new file mode 100644 index 0000000..f0b7e21 --- /dev/null +++ b/app/src/cl/svc/CL_RPTTOPMER.pgc @@ -0,0 +1,38 @@ +/* + * CL_RPTTOPMER.pgc - cl 모듈 서비스 CL_RPTTOPMER (one service = one file). + * 상위 가맹점 마감 리포트: 마감 스냅샷에서 net 상위 N(T_COUNT 입력, 기본 5) + * 가맹점을 occurrence (T_MERCHANT[i], T_NET[i]) 로 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_RPTTOPMER.h" + +void CL_RPTTOPMER(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long topn, occ = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid, h_net; + char h_merch[24]; + EXEC SQL END DECLARE SECTION; + h_cid = getl(b, T_ID1); + topn = getl(b, T_COUNT); + if (topn <= 0 || topn > 50) topn = 5; + EXEC SQL DECLARE crtop CURSOR FOR + SELECT merchant_id, net_amount FROM cl_snapshot + WHERE close_id = :h_cid ORDER BY net_amount DESC, merchant_id; + EXEC SQL OPEN crtop; + if (sqlca.sqlcode < 0) FAIL(b); + while (occ < topn) { + EXEC SQL FETCH crtop INTO :h_merch, :h_net; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE crtop; FAIL(b); } + Bchg(b, T_MERCHANT, (BFLDOCC)occ, h_merch, 0L); + Bchg(b, T_NET, (BFLDOCC)occ, (char *)&h_net, 0L); + occ++; + } + EXEC SQL CLOSE crtop; + setl(b, T_COUNT, occ); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_SCHEDULE.h b/app/src/cl/svc/CL_SCHEDULE.h new file mode 100644 index 0000000..01b0eb0 --- /dev/null +++ b/app/src/cl/svc/CL_SCHEDULE.h @@ -0,0 +1,21 @@ +/* + * CL_SCHEDULE.h - cl 서비스 CL_SCHEDULE 카피북 (copybook / record header). + * 마감 일정 관리: 최종 일마감 기준 차기 마감 예정일/경과일 산출. + */ +#ifndef CL_SCHEDULE_H +#define CL_SCHEDULE_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_schedule_rec_t; + +void CL_SCHEDULE(TPSVCINFO *p); + +#endif /* CL_SCHEDULE_H */ diff --git a/app/src/cl/svc/CL_SCHEDULE.pgc b/app/src/cl/svc/CL_SCHEDULE.pgc new file mode 100644 index 0000000..74dea36 --- /dev/null +++ b/app/src/cl/svc/CL_SCHEDULE.pgc @@ -0,0 +1,37 @@ +/* + * CL_SCHEDULE.pgc - cl 모듈 서비스 CL_SCHEDULE (one service = one file). + * 마감 일정 관리: 최종 일마감(biz_date 최대) 기준으로 차기 마감 예정일과 + * 오늘까지의 경과 일수를 산출한다. 일마감 이력이 없으면 RC=1. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_SCHEDULE.h" + +void CL_SCHEDULE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_have, h_behind; + char h_last[16], h_next[16]; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_have FROM cl_close_log + WHERE close_type = 'DAILY' AND status IN ('C','A','L'); + if (sqlca.sqlcode < 0) FAIL(b); + if (h_have == 0) { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "일마감 이력 없음: 초기 일정 수립 필요", 0L); + OK(b); + } + EXEC SQL SELECT to_char(max(biz_date),'YYYY-MM-DD'), + to_char(max(biz_date) + 1,'YYYY-MM-DD'), + current_date - max(biz_date) + INTO :h_last, :h_next, :h_behind + FROM cl_close_log WHERE close_type = 'DAILY' AND status IN ('C','A','L'); + if (sqlca.sqlcode < 0) FAIL(b); + Bchg(b, T_STR1, 0, h_last, 0L); + Bchg(b, T_STR2, 0, h_next, 0L); + setl(b, T_ARG1, h_behind); + setl(b, T_RC, (h_behind > 1) ? 2 : 0); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_SEQAUDIT.h b/app/src/cl/svc/CL_SEQAUDIT.h new file mode 100644 index 0000000..e9cbdf7 --- /dev/null +++ b/app/src/cl/svc/CL_SEQAUDIT.h @@ -0,0 +1,21 @@ +/* + * CL_SEQAUDIT.h - cl 서비스 CL_SEQAUDIT 카피북 (copybook / record header). + * 마감 순번 감사: seq_reg last_seq vs 실제 마감 건수 대사. + */ +#ifndef CL_SEQAUDIT_H +#define CL_SEQAUDIT_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_seqaudit_rec_t; + +void CL_SEQAUDIT(TPSVCINFO *p); + +#endif /* CL_SEQAUDIT_H */ diff --git a/app/src/cl/svc/CL_SEQAUDIT.pgc b/app/src/cl/svc/CL_SEQAUDIT.pgc new file mode 100644 index 0000000..551d538 --- /dev/null +++ b/app/src/cl/svc/CL_SEQAUDIT.pgc @@ -0,0 +1,40 @@ +/* + * CL_SEQAUDIT.pgc - cl 모듈 서비스 CL_SEQAUDIT (one service = one file). + * 마감 순번 감사: cl_seq_reg 의 유형별 last_seq 와 실제 cl_close_log 의 + * 유형별 건수를 커서 대사하여 순번 역전/누락 유형 수를 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_SEQAUDIT.h" + +void CL_SEQAUDIT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long types = 0, drift = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_seq, h_actual; + char h_type[16]; + EXEC SQL END DECLARE SECTION; + EXEC SQL DECLARE cseqa CURSOR FOR + SELECT r.close_type, r.last_seq, + (SELECT count(*) FROM cl_close_log c WHERE c.close_type = r.close_type) + FROM cl_seq_reg r ORDER BY r.close_type; + EXEC SQL OPEN cseqa; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cseqa INTO :h_type, :h_seq, :h_actual; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cseqa; FAIL(b); } + types++; + if (h_seq < h_actual) { + Bchg(b, T_STR2, (BFLDOCC)drift, h_type, 0L); + drift++; + } + } + EXEC SQL CLOSE cseqa; + setl(b, T_COUNT, types); + setl(b, T_ARG1, drift); + setl(b, T_RC, (drift == 0) ? 0 : 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_SLAMON.h b/app/src/cl/svc/CL_SLAMON.h new file mode 100644 index 0000000..1ff9573 --- /dev/null +++ b/app/src/cl/svc/CL_SLAMON.h @@ -0,0 +1,21 @@ +/* + * CL_SLAMON.h - cl 서비스 CL_SLAMON 카피북 (copybook / record header). + * 마감 SLA 모니터: 기준시각 초과 지연 마감 건수/최대 지연시각 산출. + */ +#ifndef CL_SLAMON_H +#define CL_SLAMON_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_slamon_rec_t; + +void CL_SLAMON(TPSVCINFO *p); + +#endif /* CL_SLAMON_H */ diff --git a/app/src/cl/svc/CL_SLAMON.pgc b/app/src/cl/svc/CL_SLAMON.pgc new file mode 100644 index 0000000..8afac4a --- /dev/null +++ b/app/src/cl/svc/CL_SLAMON.pgc @@ -0,0 +1,38 @@ +/* + * CL_SLAMON.pgc - cl 모듈 서비스 CL_SLAMON (one service = one file). + * 마감 SLA 모니터: 기간 프리픽스의 일마감 중 기준시각(T_STR2=HHMI, 기본 1000) + * 을 초과해 생성된 지연 마감 건수와 가장 늦은 생성시각을 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_SLAMON.h" + +void CL_SLAMON(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_late, h_all; + char h_prefix[16], h_cut[8], h_worst[8]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_STR1, h_prefix, sizeof(h_prefix)); + gets_(b, T_STR2, h_cut, sizeof(h_cut)); + if (h_cut[0] == 0) strcpy(h_cut, "1000"); + if (h_prefix[0] == 0) { Bchg(b, T_MSG, 0, "기간 프리픽스 필요", 0L); FAIL(b); } + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN to_char(created_at,'HH24MI') > :h_cut + THEN 1 ELSE 0 END),0), + coalesce(max(to_char(created_at,'HH24MI')),'0000') + INTO :h_all, :h_late, :h_worst + FROM cl_close_log + WHERE close_type = 'DAILY' AND period_key LIKE :h_prefix || '%' + AND status <> 'X'; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, h_all); + setl(b, T_ARG1, h_late); + Bchg(b, T_STR3, 0, h_worst, 0L); + setl(b, T_RC, (h_late > 0) ? 1 : 0); + if (h_late > 0) userlog("CL_SLAMON %s SLA 위반 %ld/%ld건 (최지연 %s)", + h_prefix, h_late, h_all, h_worst); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_SNAPDIFF.h b/app/src/cl/svc/CL_SNAPDIFF.h new file mode 100644 index 0000000..96f626a --- /dev/null +++ b/app/src/cl/svc/CL_SNAPDIFF.h @@ -0,0 +1,21 @@ +/* + * CL_SNAPDIFF.h - cl 서비스 CL_SNAPDIFF 카피북 (copybook / record header). + * 스냅샷 비교: 두 마감의 스냅샷 합계 차이 산출. + */ +#ifndef CL_SNAPDIFF_H +#define CL_SNAPDIFF_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_snapdiff_rec_t; + +void CL_SNAPDIFF(TPSVCINFO *p); + +#endif /* CL_SNAPDIFF_H */ diff --git a/app/src/cl/svc/CL_SNAPDIFF.pgc b/app/src/cl/svc/CL_SNAPDIFF.pgc new file mode 100644 index 0000000..03a391e --- /dev/null +++ b/app/src/cl/svc/CL_SNAPDIFF.pgc @@ -0,0 +1,33 @@ +/* + * CL_SNAPDIFF.pgc - cl 모듈 서비스 CL_SNAPDIFF (one service = one file). + * 스냅샷 비교: 두 마감(T_ID1, T_ID2)의 스냅샷 gross/net 합계와 가맹점 수를 + * 각각 집계해 차이를 산출한다. 재마감 전후 비교 용도. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_SNAPDIFF.h" + +void CL_SNAPDIFF(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_a, h_b, h_ga, h_gb, h_na, h_nb, h_ma, h_mb; + EXEC SQL END DECLARE SECTION; + h_a = getl(b, T_ID1); + h_b = getl(b, T_ID2); + if (h_a == h_b) { Bchg(b, T_MSG, 0, "서로 다른 두 마감 필요", 0L); FAIL(b); } + EXEC SQL SELECT coalesce(sum(gross_amount),0), coalesce(sum(net_amount),0), + count(DISTINCT merchant_id) + INTO :h_ga, :h_na, :h_ma FROM cl_snapshot WHERE close_id = :h_a; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(gross_amount),0), coalesce(sum(net_amount),0), + count(DISTINCT merchant_id) + INTO :h_gb, :h_nb, :h_mb FROM cl_snapshot WHERE close_id = :h_b; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_AMT1, h_ga - h_gb); + setl(b, T_AMT2, h_na - h_nb); + setl(b, T_ARG1, h_ma - h_mb); + setl(b, T_RC, (h_ga == h_gb && h_na == h_nb && h_ma == h_mb) ? 0 : 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_SNAPXCHECK.h b/app/src/cl/svc/CL_SNAPXCHECK.h new file mode 100644 index 0000000..6fb8122 --- /dev/null +++ b/app/src/cl/svc/CL_SNAPXCHECK.h @@ -0,0 +1,21 @@ +/* + * CL_SNAPXCHECK.h - cl 서비스 CL_SNAPXCHECK 카피북 (copybook / record header). + * 스냅샷-원장 크로스체크: 스냅샷 net 합계 vs ledger SETTLE 합계 bps 편차. + */ +#ifndef CL_SNAPXCHECK_H +#define CL_SNAPXCHECK_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_snapxcheck_rec_t; + +void CL_SNAPXCHECK(TPSVCINFO *p); + +#endif /* CL_SNAPXCHECK_H */ diff --git a/app/src/cl/svc/CL_SNAPXCHECK.pgc b/app/src/cl/svc/CL_SNAPXCHECK.pgc new file mode 100644 index 0000000..bcc3e45 --- /dev/null +++ b/app/src/cl/svc/CL_SNAPXCHECK.pgc @@ -0,0 +1,42 @@ +/* + * CL_SNAPXCHECK.pgc - cl 모듈 서비스 CL_SNAPXCHECK (one service = one file). + * 스냅샷-원장 크로스체크: 마감 스냅샷 net 합계 vs 같은 영업일 ledger SETTLE + * 합계를 대사하고 편차를 bps(만분율)로 환산해 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_SNAPXCHECK.h" + +void CL_SNAPXCHECK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long diff, bps = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_cid, h_snap, h_led; + char h_date[16]; + EXEC SQL END DECLARE SECTION; + h_cid = getl(b, T_ID1); + EXEC SQL SELECT to_char(biz_date,'YYYY-MM-DD') INTO :h_date + FROM cl_close_log WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + EXEC SQL SELECT coalesce(sum(net_amount),0) INTO :h_snap + FROM cl_snapshot WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_led + FROM ledger WHERE biz_date = :h_date AND entry_type = 'SETTLE'; + if (sqlca.sqlcode < 0) FAIL(b); + diff = h_snap - h_led; + if (h_snap != 0) { + bps = (diff * 10000) / h_snap; + if (bps < 0) bps = -bps; + } else if (h_led != 0) { + bps = 10000; + } + setl(b, T_AMT1, h_snap); + setl(b, T_AMT2, h_led); + setl(b, T_DELTA, diff); + setl(b, T_ARG1, bps); + setl(b, T_RC, (bps > 0) ? 1 : 0); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_TREND.h b/app/src/cl/svc/CL_TREND.h new file mode 100644 index 0000000..52627f3 --- /dev/null +++ b/app/src/cl/svc/CL_TREND.h @@ -0,0 +1,21 @@ +/* + * CL_TREND.h - cl 서비스 CL_TREND 카피북 (copybook / record header). + * 마감 금액 추이: 최근 N 회 일마감 총액 평균/최소/최대. + */ +#ifndef CL_TREND_H +#define CL_TREND_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_trend_rec_t; + +void CL_TREND(TPSVCINFO *p); + +#endif /* CL_TREND_H */ diff --git a/app/src/cl/svc/CL_TREND.pgc b/app/src/cl/svc/CL_TREND.pgc new file mode 100644 index 0000000..7410ea6 --- /dev/null +++ b/app/src/cl/svc/CL_TREND.pgc @@ -0,0 +1,33 @@ +/* + * CL_TREND.pgc - cl 모듈 서비스 CL_TREND (one service = one file). + * 마감 금액 추이: 최근 N회(T_ARG1, 기본 7) 일마감의 총액 평균/최소/최대를 + * 서브쿼리(LIMIT) 로 산출한다. 표본이 없으면 RC=1. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_TREND.h" + +void CL_TREND(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_n, h_cnt, h_avg, h_min, h_max; + EXEC SQL END DECLARE SECTION; + h_n = getl(b, T_ARG1); + if (h_n <= 0 || h_n > 90) h_n = 7; + EXEC SQL SELECT count(*), coalesce(cast(avg(t.total_amount) as bigint),0), + coalesce(min(t.total_amount),0), coalesce(max(t.total_amount),0) + INTO :h_cnt, :h_avg, :h_min, :h_max + FROM (SELECT total_amount FROM cl_close_log + WHERE close_type = 'DAILY' AND status IN ('C','A','L') + ORDER BY biz_date DESC LIMIT :h_n) t; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt == 0) { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "추이 표본 없음", 0L); OK(b); } + setl(b, T_COUNT, h_cnt); + setl(b, T_AMT1, h_avg); + setl(b, T_AMT2, h_min); + setl(b, T_AMT3, h_max); + setl(b, T_RC, 0); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_TRIGSTAT.h b/app/src/cl/svc/CL_TRIGSTAT.h new file mode 100644 index 0000000..1247dc8 --- /dev/null +++ b/app/src/cl/svc/CL_TRIGSTAT.h @@ -0,0 +1,21 @@ +/* + * CL_TRIGSTAT.h - cl 서비스 CL_TRIGSTAT 카피북 (copybook / record header). + * 후속작업 상태: 정산 건수 대비 ledger XFER 전기율 산출. + */ +#ifndef CL_TRIGSTAT_H +#define CL_TRIGSTAT_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_trigstat_rec_t; + +void CL_TRIGSTAT(TPSVCINFO *p); + +#endif /* CL_TRIGSTAT_H */ diff --git a/app/src/cl/svc/CL_TRIGSTAT.pgc b/app/src/cl/svc/CL_TRIGSTAT.pgc new file mode 100644 index 0000000..8f4cc96 --- /dev/null +++ b/app/src/cl/svc/CL_TRIGSTAT.pgc @@ -0,0 +1,35 @@ +/* + * CL_TRIGSTAT.pgc - cl 모듈 서비스 CL_TRIGSTAT (one service = one file). + * 후속작업 상태: 영업일 정산 건수 대비 ledger XFER 전기 건수를 비교해 + * 후속작업(전기) 진행률 bps 를 산출한다. 100% 미만이면 잔여건수 리포트. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_TRIGSTAT.h" + +void CL_TRIGSTAT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long pct = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_settle, h_xfer; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL SELECT count(*) INTO :h_settle FROM settlement WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_xfer FROM ledger + WHERE biz_date = :h_bizdate AND entry_type = 'XFER'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_settle > 0) { + pct = (h_xfer * 10000) / h_settle; + if (pct > 10000) pct = 10000; + } + setl(b, T_ARG1, h_settle); + setl(b, T_ARG2, h_xfer); + setl(b, T_ARG3, pct); + setl(b, T_DELTA, h_settle - h_xfer); + setl(b, T_RC, (h_settle <= h_xfer) ? 0 : 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cl/svc/CL_YEARPREP.h b/app/src/cl/svc/CL_YEARPREP.h new file mode 100644 index 0000000..eaf3832 --- /dev/null +++ b/app/src/cl/svc/CL_YEARPREP.h @@ -0,0 +1,21 @@ +/* + * CL_YEARPREP.h - cl 서비스 CL_YEARPREP 카피북 (copybook / record header). + * 연말 결산 사전점검: 연도 내 월마감 완료 개수/누락 월수 점검. + */ +#ifndef CL_YEARPREP_H +#define CL_YEARPREP_H + +#include "cl_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} cl_cl_yearprep_rec_t; + +void CL_YEARPREP(TPSVCINFO *p); + +#endif /* CL_YEARPREP_H */ diff --git a/app/src/cl/svc/CL_YEARPREP.pgc b/app/src/cl/svc/CL_YEARPREP.pgc new file mode 100644 index 0000000..d309273 --- /dev/null +++ b/app/src/cl/svc/CL_YEARPREP.pgc @@ -0,0 +1,37 @@ +/* + * CL_YEARPREP.pgc - cl 모듈 서비스 CL_YEARPREP (one service = one file). + * 연말 결산 사전점검: 연도 내 완료된 월마감(MONTHLY, C/A/L)의 distinct + * 월 수를 세어 12개월 대비 누락 월수를 반환. 누락 0 이어야 연말결산 가능. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); cl_svr 가 광고한다. + */ +#include "CL_YEARPREP.h" + +void CL_YEARPREP(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long missing; + EXEC SQL BEGIN DECLARE SECTION; + long h_months, h_ycnt; + char h_year[8]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_STR1, h_year, sizeof(h_year)); + if (h_year[0] == 0) strcpy(h_year, "2026"); + EXEC SQL SELECT count(DISTINCT period_key) INTO :h_months FROM cl_close_log + WHERE close_type = 'MONTHLY' AND status IN ('C','A','L') + AND period_key LIKE :h_year || '-%'; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_ycnt FROM cl_close_log + WHERE close_type = 'YEAREND' AND period_key = :h_year AND status <> 'X'; + if (sqlca.sqlcode < 0) FAIL(b); + missing = 12 - h_months; + if (missing < 0) missing = 0; + setl(b, T_ARG1, h_months); + setl(b, T_ARG2, missing); + setl(b, T_ARG3, h_ycnt); + if (h_ycnt > 0) setl(b, T_RC, 2); /* 이미 연말결산 존재 */ + else if (missing) setl(b, T_RC, 1); /* 월마감 누락 */ + else setl(b, T_RC, 0); /* 진행 가능 */ + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/cm/batch/cm_codeaudit_batch.pgc b/app/src/cm/batch/cm_codeaudit_batch.pgc new file mode 100644 index 0000000..c6766ec --- /dev/null +++ b/app/src/cm/batch/cm_codeaudit_batch.pgc @@ -0,0 +1,52 @@ +/* + * cm_codeaudit_batch.pgc - cm 폐기 코드 감사 배치 (XA 단일 글로벌 트랜잭션). + * + * 공통코드 전체/폐기(use_yn='N') 건수를 집계해 폐기 비율(bps)을 계산하고, + * 감사 결과 한 건을 cm_log (svc=CODEAUD) 에 적재한다. 폐기 비율이 절반을 + * 넘으면 W 레벨로 남겨 운영자가 코드 정비를 하도록 알린다. + * Usage: cm_codeaudit_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_codeaudit_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + cm_codeaudit_rec_t rec; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_msg[128], h_lvl[4]; + long h_total = 0, h_dis = 0; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN use_yn = 'N' THEN 1 ELSE 0 END), 0) + INTO :h_total, :h_dis FROM cm_code; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + rec.total = h_total; + rec.disabled = h_dis; + rec.ratio_bps = (h_total > 0) ? h_dis * 10000L / h_total : 0; + strcpy(h_lvl, rec.ratio_bps > 5000 ? "W" : "I"); + snprintf(h_msg, sizeof(h_msg), "CODE AUDIT total=%ld disabled=%ld ratio=%ldbps", + rec.total, rec.disabled, rec.ratio_bps); + + EXEC SQL INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date) + VALUES (nextval('cm_log_seq'), 'CODEAUD', :h_lvl, :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_codeaudit_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_codeaudit_batch COMMIT: bizdate=%s 전체=%ld 폐기=%ld (%ldbps)\n", + bizdate, rec.total, rec.disabled, rec.ratio_bps); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_fxfill_batch.pgc b/app/src/cm/batch/cm_fxfill_batch.pgc new file mode 100644 index 0000000..96022cf --- /dev/null +++ b/app/src/cm/batch/cm_fxfill_batch.pgc @@ -0,0 +1,57 @@ +/* + * cm_fxfill_batch.pgc - cm 환율 결측일 백필 배치 (INSERT..SELECT, XA). + * + * 기준일에 고시가 없는 통화를 찾아, 통화별 직전(기준일 미만 최신) 고시가를 + * 그대로 기준일 행으로 복제(캐리포워드)한다. 휴일/장애로 고시가 빠진 날에도 + * 환산 서비스가 끊기지 않게 한다. 백필 결과는 cm_log (svc=FXFILL) 로 남긴다. + * Usage: cm_fxfill_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_fxfill_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + cm_fxfill_rec_t rec = { 0, 0 }; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_msg[96]; + long h_before = 0; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*) INTO :h_before + FROM cm_fx_rate WHERE base_date = :h_bizdate; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + rec.before = h_before; + + /* 통화별 직전 고시가를 기준일 행으로 캐리포워드 (기고시 통화는 제외) */ + EXEC SQL INSERT INTO cm_fx_rate (ccy, base_date, rate_bps) + SELECT f.ccy, :h_bizdate::date, f.rate_bps + FROM cm_fx_rate f + WHERE f.base_date = (SELECT max(x.base_date) FROM cm_fx_rate x + WHERE x.ccy = f.ccy AND x.base_date < :h_bizdate) + AND NOT EXISTS (SELECT 1 FROM cm_fx_rate e + WHERE e.ccy = f.ccy AND e.base_date = :h_bizdate); + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_fxfill_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.filled = (long)sqlca.sqlerrd[2]; + + snprintf(h_msg, sizeof(h_msg), "FXFILL before=%ld filled=%ld", rec.before, rec.filled); + EXEC SQL INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date) + VALUES (nextval('cm_log_seq'), 'FXFILL', 'I', :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_fxfill_batch COMMIT: bizdate=%s 기고시=%ld 백필=%ld\n", + bizdate, rec.before, rec.filled); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_fxstale_batch.pgc b/app/src/cm/batch/cm_fxstale_batch.pgc new file mode 100644 index 0000000..acc9ec7 --- /dev/null +++ b/app/src/cm/batch/cm_fxstale_batch.pgc @@ -0,0 +1,56 @@ +/* + * cm_fxstale_batch.pgc - cm 환율 노후(미고시) 점검 배치 (커서, XA). + * + * 통화별 마지막 고시일 경과일수를 커서로 훑어 허용 한도(7일)를 넘은 통화를 + * STALE 로 판정하고, 통화별로 W/I 레벨 로그(svc=FXSTALE)를 적재한다. + * STALE 통화가 하나라도 있으면 종료코드 2 로 스케줄러에 알린다. + * Usage: cm_fxstale_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_fxstale_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + cm_fxstale_rec_t rec = { 0, 0, 0 }; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_ccy[8], h_msg[96], h_lvl[4]; + long h_age = 0; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE cur_fxstale CURSOR FOR + SELECT ccy, (:h_bizdate::date - max(base_date))::int + FROM cm_fx_rate GROUP BY ccy ORDER BY ccy; + EXEC SQL OPEN cur_fxstale; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + while (1) { + EXEC SQL FETCH cur_fxstale INTO :h_ccy, :h_age; + if (sqlca.sqlcode != 0) break; + rec.ccys++; + if (h_age > rec.worst_age) rec.worst_age = h_age; + if (h_age > CM_FXSTALE_LIMIT_DAYS) { rec.stale++; strcpy(h_lvl, "W"); } + else strcpy(h_lvl, "I"); + snprintf(h_msg, sizeof(h_msg), "FXSTALE %s age=%ldd", h_ccy, h_age); + EXEC SQL INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date) + VALUES (nextval('cm_log_seq'), 'FXSTALE', :h_lvl, :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_fxstale; tpabort(0); return 1; } + } + EXEC SQL CLOSE cur_fxstale; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_fxstale_batch COMMIT: 통화=%ld STALE=%ld 최장경과=%ld일\n", + rec.ccys, rec.stale, rec.worst_age); + + tpclose(); tpterm(); + return rec.stale > 0 ? 2 : 0; +} diff --git a/app/src/cm/batch/cm_healthsnap_batch.pgc b/app/src/cm/batch/cm_healthsnap_batch.pgc new file mode 100644 index 0000000..dd15afc --- /dev/null +++ b/app/src/cm/batch/cm_healthsnap_batch.pgc @@ -0,0 +1,54 @@ +/* + * cm_healthsnap_batch.pgc - cm 시스템 헬스 스냅샷 배치 (XA). + * + * 공통 모듈 4대 지표(기준일 로그량, 채번 카운터 수, 기준일 환율 고시 수, + * 파라미터 수)를 각각 조회해 하나의 스냅샷 로그(svc=HLTHSNAP)로 적재한다. + * 시계열로 쌓이는 스냅샷이 곧 공통 모듈의 헬스 추이 데이터가 된다. + * Usage: cm_healthsnap_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_healthsnap_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + cm_healthsnap_rec_t rec; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_msg[144]; + long h_logs = 0, h_seqs = 0, h_fx = 0, h_params = 0; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*) INTO :h_logs FROM cm_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + EXEC SQL SELECT count(*) INTO :h_seqs FROM cm_seq; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + EXEC SQL SELECT count(*) INTO :h_fx FROM cm_fx_rate WHERE base_date = :h_bizdate; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + EXEC SQL SELECT count(*) INTO :h_params FROM cm_param; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + rec.log_rows = h_logs; rec.seq_rows = h_seqs; + rec.fx_rows = h_fx; rec.param_rows = h_params; + snprintf(h_msg, sizeof(h_msg), "HLTHSNAP logs=%ld seqs=%ld fx=%ld params=%ld", + rec.log_rows, rec.seq_rows, rec.fx_rows, rec.param_rows); + + EXEC SQL INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date) + VALUES (nextval('cm_log_seq'), 'HLTHSNAP', 'I', :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_healthsnap_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_healthsnap_batch COMMIT: logs=%ld seqs=%ld fx=%ld params=%ld\n", + rec.log_rows, rec.seq_rows, rec.fx_rows, rec.param_rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_holchk_batch.pgc b/app/src/cm/batch/cm_holchk_batch.pgc new file mode 100644 index 0000000..014d84b --- /dev/null +++ b/app/src/cm/batch/cm_holchk_batch.pgc @@ -0,0 +1,55 @@ +/* + * cm_holchk_batch.pgc - cm 휴일 캘린더 정합성 점검 배치 (XA). + * + * 기준일이 속한 연도의 휴일을 집계해 주말(토/일)과 겹치는 휴일과 실제로 + * 영업일수를 줄이는 실효 휴일(평일 휴일)을 구분하고, 점검 결과를 cm_log + * (svc=HOLCHK) 에 적재한다. 당해연도 휴일이 하나도 없으면 캘린더 미정비로 + * 보고 W 레벨로 남긴다. Usage: cm_holchk_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_holchk_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + cm_holchk_rec_t rec; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_msg[112], h_lvl[4]; + long h_tot = 0, h_wknd = 0; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN extract(dow FROM holi_date) IN (0, 6) + THEN 1 ELSE 0 END), 0) + INTO :h_tot, :h_wknd + FROM cm_holiday + WHERE date_trunc('year', holi_date) = date_trunc('year', :h_bizdate::date); + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + rec.year_total = h_tot; + rec.on_weekend = h_wknd; + rec.effective = h_tot - h_wknd; + strcpy(h_lvl, h_tot == 0 ? "W" : "I"); + snprintf(h_msg, sizeof(h_msg), "HOLCHK year_total=%ld weekend=%ld effective=%ld", + rec.year_total, rec.on_weekend, rec.effective); + + EXEC SQL INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date) + VALUES (nextval('cm_log_seq'), 'HOLCHK', :h_lvl, :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_holchk_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_holchk_batch COMMIT: 연간휴일=%ld 주말겹침=%ld 실효=%ld\n", + rec.year_total, rec.on_weekend, rec.effective); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_msgsync_batch.pgc b/app/src/cm/batch/cm_msgsync_batch.pgc new file mode 100644 index 0000000..e44952a --- /dev/null +++ b/app/src/cm/batch/cm_msgsync_batch.pgc @@ -0,0 +1,63 @@ +/* + * cm_msgsync_batch.pgc - cm 다국어 메시지 그룹 점검 배치 (커서 집계, XA). + * + * MSG_ 접두 언어 그룹을 커서로 돌며 그룹별 사용중 메시지 수를 세고, 그룹별 + * 현황을 cm_log (svc=MSGSYNC) 에 한 줄씩 적재한다. 기준 언어(MSG_KO) 대비 + * 메시지가 부족한 그룹 수를 표준출력으로 보고한다. + * Usage: cm_msgsync_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_msgsync_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + cm_msgsync_rec_t rec = { 0, 0, 0 }; + long base = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_grp[16], h_msg[96]; + long h_n = 0, h_base = 0; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + /* 기준 언어(KO) 메시지 수 */ + EXEC SQL SELECT count(*) INTO :h_base FROM cm_code + WHERE code_grp = 'MSG_KO' AND use_yn = 'Y'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + base = h_base; + + EXEC SQL DECLARE cur_msgsync CURSOR FOR + SELECT code_grp, count(*) FROM cm_code + WHERE code_grp LIKE 'MSG$_%' ESCAPE '$' AND use_yn = 'Y' + GROUP BY code_grp ORDER BY code_grp; + EXEC SQL OPEN cur_msgsync; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH cur_msgsync INTO :h_grp, :h_n; + if (sqlca.sqlcode != 0) break; + rec.groups++; + rec.messages += h_n; + if (h_n < base) rec.thin++; + snprintf(h_msg, sizeof(h_msg), "MSGSYNC %s cnt=%ld base=%ld", h_grp, h_n, base); + EXEC SQL INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date) + VALUES (nextval('cm_log_seq'), 'MSGSYNC', 'I', :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_msgsync; tpabort(0); return 1; } + } + EXEC SQL CLOSE cur_msgsync; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_msgsync_batch COMMIT: 그룹=%ld 메시지=%ld 부족그룹=%ld (기준 KO=%ld)\n", + rec.groups, rec.messages, rec.thin, base); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_notidigest_batch.pgc b/app/src/cm/batch/cm_notidigest_batch.pgc new file mode 100644 index 0000000..fbbaa9b --- /dev/null +++ b/app/src/cm/batch/cm_notidigest_batch.pgc @@ -0,0 +1,52 @@ +/* + * cm_notidigest_batch.pgc - cm 알림 발송 일별 다이제스트 배치 (XA). + * + * 기준일 NOTI 발송 로그를 채널 접두([SMS>/[EMAIL>/[PUSH>)별로 나눠 집계하고, + * 하루 한 건의 다이제스트를 cm_log (svc=NOTIDIG) 로 적재한다. 발송이 전혀 + * 없던 날도 zero 다이제스트를 남겨 결번을 알 수 있게 한다. + * Usage: cm_notidigest_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_notidigest_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + cm_notidigest_rec_t rec; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_msg[128]; + long h_sms = 0, h_mail = 0, h_push = 0, h_all = 0; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN msg LIKE '[SMS>%' THEN 1 ELSE 0 END), 0), + coalesce(sum(CASE WHEN msg LIKE '[EMAIL>%' THEN 1 ELSE 0 END), 0), + coalesce(sum(CASE WHEN msg LIKE '[PUSH>%' THEN 1 ELSE 0 END), 0) + INTO :h_all, :h_sms, :h_mail, :h_push + FROM cm_log WHERE svc_name = 'NOTI' AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + rec.total = h_all; rec.sms = h_sms; rec.email = h_mail; rec.push = h_push; + snprintf(h_msg, sizeof(h_msg), "NOTIDIG total=%ld sms=%ld email=%ld push=%ld", + rec.total, rec.sms, rec.email, rec.push); + + EXEC SQL INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date) + VALUES (nextval('cm_log_seq'), 'NOTIDIG', 'I', :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_notidigest_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_notidigest_batch COMMIT: bizdate=%s 총=%ld (SMS=%ld EMAIL=%ld PUSH=%ld)\n", + bizdate, rec.total, rec.sms, rec.email, rec.push); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_reqage_batch.pgc b/app/src/cm/batch/cm_reqage_batch.pgc new file mode 100644 index 0000000..e686cbd --- /dev/null +++ b/app/src/cm/batch/cm_reqage_batch.pgc @@ -0,0 +1,56 @@ +/* + * cm_reqage_batch.pgc - cm 파라미터 승인 대기 정체 감시 배치 (XA). + * + * 승인 워크플로 대기 키(REQ:%)의 대기 일수를 집계해 기준(3일)을 넘긴 정체 + * 요청 수와 최장 대기 일수를 구하고, 감시 결과(svc=REQAGE)를 적재한다. + * 정체가 있으면 W 레벨로 남겨 승인 담당자 알림 대상으로 만든다. + * Usage: cm_reqage_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_reqage_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + cm_reqage_rec_t rec; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_msg[112], h_lvl[4]; + long h_pend = 0, h_stale = 0, h_oldest = 0, h_days; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + h_days = CM_REQAGE_STALE_DAYS; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN (:h_bizdate::date - updated_at::date) > :h_days + THEN 1 ELSE 0 END), 0), + coalesce(max(:h_bizdate::date - updated_at::date)::int, 0) + INTO :h_pend, :h_stale, :h_oldest + FROM cm_param WHERE param_key LIKE 'REQ:%'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + rec.pending = h_pend; + rec.stale = h_stale; + rec.oldest_age = h_oldest; + strcpy(h_lvl, rec.stale > 0 ? "W" : "I"); + snprintf(h_msg, sizeof(h_msg), "REQAGE pending=%ld stale=%ld oldest=%ldd", + rec.pending, rec.stale, rec.oldest_age); + + EXEC SQL INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date) + VALUES (nextval('cm_log_seq'), 'REQAGE', :h_lvl, :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_reqage_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_reqage_batch COMMIT: 대기=%ld 정체=%ld 최장=%ld일\n", + rec.pending, rec.stale, rec.oldest_age); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_rerunpick_batch.pgc b/app/src/cm/batch/cm_rerunpick_batch.pgc new file mode 100644 index 0000000..29a8fd0 --- /dev/null +++ b/app/src/cm/batch/cm_rerunpick_batch.pgc @@ -0,0 +1,56 @@ +/* + * cm_rerunpick_batch.pgc - cm 배치 재실행 요청 처리 배치 (XA). + * + * CM_BATRERUN_REQ 가 남긴 기준일 RERUN 요청(W 레벨)을 세고, 요청 전체를 + * 접수 완료(I 레벨) 상태로 UPDATE 전이한 뒤 처리 요약 로그(svc=RERUNPK)를 + * 적재한다. W->I 전이 건수가 곧 이번 사이클에 집어간 요청 수다. + * Usage: cm_rerunpick_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_rerunpick_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + cm_rerunpick_rec_t rec = { 0, 0, 0 }; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_msg[112]; + long h_pend = 0, h_last = 0; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*), coalesce(max(log_id), 0) INTO :h_pend, :h_last + FROM cm_log + WHERE svc_name = 'RERUN' AND log_level = 'W' AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + rec.pending = h_pend; + rec.last_id = h_last; + + if (rec.pending > 0) { + EXEC SQL UPDATE cm_log SET log_level = 'I' + WHERE svc_name = 'RERUN' AND log_level = 'W' AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_rerunpick_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.picked = (long)sqlca.sqlerrd[2]; + } + + snprintf(h_msg, sizeof(h_msg), "RERUNPK pending=%ld picked=%ld last=%ld", + rec.pending, rec.picked, rec.last_id); + EXEC SQL INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date) + VALUES (nextval('cm_log_seq'), 'RERUNPK', 'I', :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_rerunpick_batch COMMIT: 요청=%ld 접수=%ld 마지막ID=%ld\n", + rec.pending, rec.picked, rec.last_id); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_schddue_batch.pgc b/app/src/cm/batch/cm_schddue_batch.pgc new file mode 100644 index 0000000..9d4d9a3 --- /dev/null +++ b/app/src/cm/batch/cm_schddue_batch.pgc @@ -0,0 +1,76 @@ +/* + * cm_schddue_batch.pgc - cm 배치 스케줄 기동 대상 판정 배치 (커서+파싱, XA). + * + * SCHD: 파라미터를 커서로 훑어 "주기 HHMM" 값을 파싱하고, 주기 규칙 + * (DAILY=매일, WEEKLY=월요일, MONTHLY=1일)으로 기준일에 기동해야 할 잡을 + * 판정해 잡별 DUE 로그(svc=SCHDDUE)를 적재한다. 파싱 불가 스케줄은 W 레벨로 + * 남긴다. Usage: cm_schddue_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_schddue_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + cm_schddue_rec_t rec = { 0, 0, 0 }; + char cycle[16]; + long hhmm; + int is_due; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_key[80], h_val[128], h_msg[160], h_lvl[4]; + long h_dow = 0, h_dom = 0; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + /* 기준일의 요일(1=월..7=일)과 일(day of month) */ + EXEC SQL SELECT extract(isodow FROM :h_bizdate::date)::int, + extract(day FROM :h_bizdate::date)::int + INTO :h_dow, :h_dom; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + EXEC SQL DECLARE cur_schd CURSOR FOR + SELECT param_key, param_val FROM cm_param + WHERE param_key LIKE 'SCHD:%' ORDER BY param_key; + EXEC SQL OPEN cur_schd; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH cur_schd INTO :h_key, :h_val; + if (sqlca.sqlcode != 0) break; + rec.jobs++; + hhmm = -1; + if (sscanf(h_val, "%15s %ld", cycle, &hhmm) != 2 || hhmm < 0 || hhmm > 2359) { + rec.broken++; + strcpy(h_lvl, "W"); + snprintf(h_msg, sizeof(h_msg), "SCHDDUE BROKEN %s [%s]", h_key + 5, h_val); + } else { + is_due = 0; + if (strcmp(cycle, "DAILY") == 0) is_due = 1; + else if (strcmp(cycle, "WEEKLY") == 0 && h_dow == 1) is_due = 1; + else if (strcmp(cycle, "MONTHLY") == 0 && h_dom == 1) is_due = 1; + if (!is_due) continue; /* 오늘 대상 아님 */ + rec.due++; + strcpy(h_lvl, "I"); + snprintf(h_msg, sizeof(h_msg), "SCHDDUE %s %s %04ld", h_key + 5, cycle, hhmm); + } + EXEC SQL INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date) + VALUES (nextval('cm_log_seq'), 'SCHDDUE', :h_lvl, :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_schd; tpabort(0); return 1; } + } + EXEC SQL CLOSE cur_schd; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_schddue_batch COMMIT: 스케줄=%ld 기동대상=%ld 형식오류=%ld\n", + rec.jobs, rec.due, rec.broken); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_seqgc_batch.pgc b/app/src/cm/batch/cm_seqgc_batch.pgc new file mode 100644 index 0000000..10bdbe8 --- /dev/null +++ b/app/src/cm/batch/cm_seqgc_batch.pgc @@ -0,0 +1,56 @@ +/* + * cm_seqgc_batch.pgc - cm 일자별 채번 카운터 정리(GC) 배치 (XA). + * + * CM_SEQ_DAILY 가 만드는 일자별 카운터(seq_name = "<이름>:") + * 중 보존일수(30일)가 지난 것을 골라 삭제한다. 키의 날짜부는 + * split_part 로 떼어 컷오프와 문자열 비교한다. 삭제 결과는 cm_log + * (svc=SEQGC) 로 남긴다. Usage: cm_seqgc_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_seqgc_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + cm_seqgc_rec_t rec = { 0, 0 }; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_cutoff[16], h_msg[96]; + long h_scan = 0, h_keep; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + h_keep = CM_SEQGC_KEEP_DAYS; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT (:h_bizdate::date - :h_keep::int)::text INTO :h_cutoff; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + EXEC SQL SELECT count(*) INTO :h_scan FROM cm_seq WHERE seq_name LIKE '%:%'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + rec.scanned = h_scan; + + EXEC SQL DELETE FROM cm_seq + WHERE seq_name LIKE '%:%' + AND split_part(seq_name, ':', 2) < :h_cutoff; + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_seqgc_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.deleted = (long)sqlca.sqlerrd[2]; + + snprintf(h_msg, sizeof(h_msg), "SEQGC cutoff=%s scanned=%ld deleted=%ld", + h_cutoff, rec.scanned, rec.deleted); + EXEC SQL INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date) + VALUES (nextval('cm_log_seq'), 'SEQGC', 'I', :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_seqgc_batch COMMIT: 컷오프=%s 대상=%ld 삭제=%ld\n", + h_cutoff, rec.scanned, rec.deleted); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_seqreport_batch.pgc b/app/src/cm/batch/cm_seqreport_batch.pgc new file mode 100644 index 0000000..0fc93e7 --- /dev/null +++ b/app/src/cm/batch/cm_seqreport_batch.pgc @@ -0,0 +1,52 @@ +/* + * cm_seqreport_batch.pgc - cm 채번 카운터 스냅샷 배치 (커서, XA). + * + * cm_seq 의 모든 카운터를 값 내림차순 커서로 돌며 카운터별 현재값 스냅샷을 + * cm_log (svc=SEQSNAP) 에 한 줄씩 적재한다. 마감 시점 채번 상태 감사와 + * 일자별 채번 사용량 추적에 쓴다. Usage: cm_seqreport_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_seqreport_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + cm_seqreport_rec_t rec = { 0, 0 }; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_name[48], h_msg[96]; + long h_val = 0; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE cur_seqrep CURSOR FOR + SELECT seq_name, seq_val FROM cm_seq ORDER BY seq_val DESC, seq_name; + EXEC SQL OPEN cur_seqrep; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + do { + EXEC SQL FETCH cur_seqrep INTO :h_name, :h_val; + if (sqlca.sqlcode != 0) break; + rec.counters++; + rec.grand += h_val; + snprintf(h_msg, sizeof(h_msg), "SEQSNAP %s=%ld", h_name, h_val); + EXEC SQL INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date) + VALUES (nextval('cm_log_seq'), 'SEQSNAP', 'I', :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_seqrep; tpabort(0); return 1; } + } while (1); + EXEC SQL CLOSE cur_seqrep; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_seqreport_batch COMMIT: bizdate=%s 카운터=%ld 총합=%ld\n", + bizdate, rec.counters, rec.grand); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_warnratio_batch.pgc b/app/src/cm/batch/cm_warnratio_batch.pgc new file mode 100644 index 0000000..8b36cb2 --- /dev/null +++ b/app/src/cm/batch/cm_warnratio_batch.pgc @@ -0,0 +1,54 @@ +/* + * cm_warnratio_batch.pgc - cm 경고 로그 비율 감시 배치 (XA). + * + * 기준일 로그의 W 레벨 비율(bps)을 계산해 감시 결과(svc=WARNRT)를 적재한다. + * 비율이 임계(1500bps)를 넘으면 E 레벨 경보 로그로 남기고 종료코드 3 을 + * 돌려줘 후속 알림 배치가 집어가게 한다. + * Usage: cm_warnratio_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_warnratio_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + cm_warnratio_rec_t rec = { 0, 0, 0, 0 }; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_msg[112], h_lvl[4]; + long h_total = 0, h_warn = 0; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN log_level = 'W' THEN 1 ELSE 0 END), 0) + INTO :h_total, :h_warn + FROM cm_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + rec.total = h_total; + rec.warns = h_warn; + if (rec.total > 0) rec.ratio_bps = rec.warns * 10000L / rec.total; + rec.alarmed = rec.ratio_bps > CM_WARNRATIO_ALERT_BPS; + strcpy(h_lvl, rec.alarmed ? "E" : "I"); + snprintf(h_msg, sizeof(h_msg), "WARNRT total=%ld warns=%ld ratio=%ldbps%s", + rec.total, rec.warns, rec.ratio_bps, rec.alarmed ? " ALERT" : ""); + + EXEC SQL INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date) + VALUES (nextval('cm_log_seq'), 'WARNRT', :h_lvl, :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_warnratio_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_warnratio_batch COMMIT: 총=%ld 경고=%ld 비율=%ldbps%s\n", + rec.total, rec.warns, rec.ratio_bps, rec.alarmed ? " [경보]" : ""); + + tpclose(); tpterm(); + return rec.alarmed ? 3 : 0; +} diff --git a/app/src/cm/cm_svr.pgc b/app/src/cm/cm_svr.pgc index 2b69f51..f430980 100644 --- a/app/src/cm/cm_svr.pgc +++ b/app/src/cm/cm_svr.pgc @@ -2,13 +2,13 @@ * cm_svr.pgc - cm (공통 / common) MODULE SERVER (thin dispatcher). * * The legacy "one module server, many services" pattern: this binary owns NO - * business logic. Each of cm's 30 online services now lives in its own file + * business logic. Each of cm's 75 online services lives in its own file * (app/src/cm/svc/.pgc) with its own copybook header * (app/src/cm/svc/.h); those objects are archived into libcmsvc.a * and linked into this server. Here we only: * - include every service copybook (for the extern prototypes), * - tpopen() the ECPG XA RM, - * - tpadvertise() all 30 services from a {name, fn} table, + * - tpadvertise() all 75 services from a {name, fn} table, * - tpclose() at shutdown. * * XA: the connection is opened once by tpopen() (ECPG XA switch libndrxxaecpg.so); @@ -49,6 +49,53 @@ #include "CM_NETTING_CALC.h" #include "CM_STATUS.h" #include "CM_PING.h" +/* --- 확장 서비스 (코드그룹/파라미터 승인/다국어/환율/캘린더/채번/알림/ + * 배치관리/헬스/감사/마스킹·암호화/체크섬/기간/한글금액) --- */ +#include "CM_CODEGRP_REG.h" +#include "CM_CODEGRP_UPD.h" +#include "CM_CODEGRP_CLOSE.h" +#include "CM_CODEGRP_HIST.h" +#include "CM_CODEGRP_CNT.h" +#include "CM_PARAM_REQ.h" +#include "CM_PARAM_APPROVE.h" +#include "CM_PARAM_REJECT.h" +#include "CM_PARAM_PENDING.h" +#include "CM_MSG_GET.h" +#include "CM_MSG_SET.h" +#include "CM_MSG_FALLBACK.h" +#include "CM_FX_PUBLISH.h" +#include "CM_FX_HIST.h" +#include "CM_FX_VALID.h" +#include "CM_FX_CROSS.h" +#include "CM_FX_AGE.h" +#include "CM_HOLIDAY_REG.h" +#include "CM_HOLIDAY_DEL.h" +#include "CM_HOLIDAY_CNT.h" +#include "CM_BIZDAY_CNT.h" +#include "CM_PREVBIZ.h" +#include "CM_SEQ_DAILY.h" +#include "CM_SEQ_TYPED.h" +#include "CM_SEQ_PEEK.h" +#include "CM_SEQ_RESET.h" +#include "CM_NOTI_SEND.h" +#include "CM_NOTI_HIST.h" +#include "CM_BATSCHD_REG.h" +#include "CM_BATSCHD_GET.h" +#include "CM_BATHIST_CNT.h" +#include "CM_BATRERUN_REQ.h" +#include "CM_HEALTH_DB.h" +#include "CM_HEALTH_SEQ.h" +#include "CM_HEALTH_LOG.h" +#include "CM_AUDIT_BYSVC.h" +#include "CM_AUDIT_BYLVL.h" +#include "CM_AUDIT_RANGE.h" +#include "CM_MASK_CARD.h" +#include "CM_MASK_NAME.h" +#include "CM_MASK_PHONE.h" +#include "CM_ENC_HEX.h" +#include "CM_FILE_CKSUM.h" +#include "CM_DATEDIFF.h" +#include "CM_AMT_HANGUL.h" /* ----- advertise table: {service name, function} (fn is extern from its file) */ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { @@ -67,6 +114,30 @@ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { {"CM_CHECKDIGIT", CM_CHECKDIGIT}, {"CM_INTEREST", CM_INTEREST}, {"CM_PRORATE", CM_PRORATE}, {"CM_NETTING_CALC", CM_NETTING_CALC}, {"CM_STATUS", CM_STATUS}, {"CM_PING", CM_PING}, + /* --- 확장 서비스 --- */ + {"CM_CODEGRP_REG", CM_CODEGRP_REG}, {"CM_CODEGRP_UPD", CM_CODEGRP_UPD}, + {"CM_CODEGRP_CLOSE", CM_CODEGRP_CLOSE}, {"CM_CODEGRP_HIST", CM_CODEGRP_HIST}, + {"CM_CODEGRP_CNT", CM_CODEGRP_CNT}, {"CM_PARAM_REQ", CM_PARAM_REQ}, + {"CM_PARAM_APPROVE", CM_PARAM_APPROVE}, {"CM_PARAM_REJECT", CM_PARAM_REJECT}, + {"CM_PARAM_PENDING", CM_PARAM_PENDING}, {"CM_MSG_GET", CM_MSG_GET}, + {"CM_MSG_SET", CM_MSG_SET}, {"CM_MSG_FALLBACK", CM_MSG_FALLBACK}, + {"CM_FX_PUBLISH", CM_FX_PUBLISH}, {"CM_FX_HIST", CM_FX_HIST}, + {"CM_FX_VALID", CM_FX_VALID}, {"CM_FX_CROSS", CM_FX_CROSS}, + {"CM_FX_AGE", CM_FX_AGE}, {"CM_HOLIDAY_REG", CM_HOLIDAY_REG}, + {"CM_HOLIDAY_DEL", CM_HOLIDAY_DEL}, {"CM_HOLIDAY_CNT", CM_HOLIDAY_CNT}, + {"CM_BIZDAY_CNT", CM_BIZDAY_CNT}, {"CM_PREVBIZ", CM_PREVBIZ}, + {"CM_SEQ_DAILY", CM_SEQ_DAILY}, {"CM_SEQ_TYPED", CM_SEQ_TYPED}, + {"CM_SEQ_PEEK", CM_SEQ_PEEK}, {"CM_SEQ_RESET", CM_SEQ_RESET}, + {"CM_NOTI_SEND", CM_NOTI_SEND}, {"CM_NOTI_HIST", CM_NOTI_HIST}, + {"CM_BATSCHD_REG", CM_BATSCHD_REG}, {"CM_BATSCHD_GET", CM_BATSCHD_GET}, + {"CM_BATHIST_CNT", CM_BATHIST_CNT}, {"CM_BATRERUN_REQ", CM_BATRERUN_REQ}, + {"CM_HEALTH_DB", CM_HEALTH_DB}, {"CM_HEALTH_SEQ", CM_HEALTH_SEQ}, + {"CM_HEALTH_LOG", CM_HEALTH_LOG}, {"CM_AUDIT_BYSVC", CM_AUDIT_BYSVC}, + {"CM_AUDIT_BYLVL", CM_AUDIT_BYLVL}, {"CM_AUDIT_RANGE", CM_AUDIT_RANGE}, + {"CM_MASK_CARD", CM_MASK_CARD}, {"CM_MASK_NAME", CM_MASK_NAME}, + {"CM_MASK_PHONE", CM_MASK_PHONE}, {"CM_ENC_HEX", CM_ENC_HEX}, + {"CM_FILE_CKSUM", CM_FILE_CKSUM}, {"CM_DATEDIFF", CM_DATEDIFF}, + {"CM_AMT_HANGUL", CM_AMT_HANGUL}, {NULL, NULL} }; diff --git a/app/src/cm/dbio/cm_codeaudit_batch.h b/app/src/cm/dbio/cm_codeaudit_batch.h new file mode 100644 index 0000000..62078f8 --- /dev/null +++ b/app/src/cm/dbio/cm_codeaudit_batch.h @@ -0,0 +1,14 @@ +/* + * cm_codeaudit_batch.h - cm cm_codeaudit_batch 배치 전용 카피북 (copybook). + * 폐기 코드 감사 배치의 집계 결과 레코드. Included by cm_codeaudit_batch.pgc. + */ +#ifndef CM_CODEAUDIT_BATCH_H +#define CM_CODEAUDIT_BATCH_H + +typedef struct { + long total; /* 전체 코드 수 */ + long disabled; /* 폐기(use_yn='N') 코드 수 */ + long ratio_bps; /* 폐기 비율 (bps) */ +} cm_codeaudit_rec_t; + +#endif /* CM_CODEAUDIT_BATCH_H */ diff --git a/app/src/cm/dbio/cm_fxfill_batch.h b/app/src/cm/dbio/cm_fxfill_batch.h new file mode 100644 index 0000000..5e9821b --- /dev/null +++ b/app/src/cm/dbio/cm_fxfill_batch.h @@ -0,0 +1,13 @@ +/* + * cm_fxfill_batch.h - cm cm_fxfill_batch 배치 전용 카피북 (copybook). + * 환율 결측일 백필 배치의 결과 레코드. Included by cm_fxfill_batch.pgc. + */ +#ifndef CM_FXFILL_BATCH_H +#define CM_FXFILL_BATCH_H + +typedef struct { + long before; /* 백필 전 기준일 고시 수 */ + long filled; /* 직전 고시가로 백필한 통화 수 */ +} cm_fxfill_rec_t; + +#endif /* CM_FXFILL_BATCH_H */ diff --git a/app/src/cm/dbio/cm_fxstale_batch.h b/app/src/cm/dbio/cm_fxstale_batch.h new file mode 100644 index 0000000..d786b97 --- /dev/null +++ b/app/src/cm/dbio/cm_fxstale_batch.h @@ -0,0 +1,17 @@ +/* + * cm_fxstale_batch.h - cm cm_fxstale_batch 배치 전용 카피북 (copybook). + * 환율 노후 점검 배치의 결과 레코드 + 기본 허용 경과일. Included by + * cm_fxstale_batch.pgc. + */ +#ifndef CM_FXSTALE_BATCH_H +#define CM_FXSTALE_BATCH_H + +#define CM_FXSTALE_LIMIT_DAYS 7 /* 이 일수보다 오래되면 STALE */ + +typedef struct { + long ccys; /* 점검한 통화 수 */ + long stale; /* 노후 통화 수 */ + long worst_age; /* 최장 경과일 */ +} cm_fxstale_rec_t; + +#endif /* CM_FXSTALE_BATCH_H */ diff --git a/app/src/cm/dbio/cm_healthsnap_batch.h b/app/src/cm/dbio/cm_healthsnap_batch.h new file mode 100644 index 0000000..e7dcb05 --- /dev/null +++ b/app/src/cm/dbio/cm_healthsnap_batch.h @@ -0,0 +1,15 @@ +/* + * cm_healthsnap_batch.h - cm cm_healthsnap_batch 배치 전용 카피북 (copybook). + * 시스템 헬스 스냅샷 배치의 지표 레코드. Included by cm_healthsnap_batch.pgc. + */ +#ifndef CM_HEALTHSNAP_BATCH_H +#define CM_HEALTHSNAP_BATCH_H + +typedef struct { + long log_rows; /* 기준일 로그 건수 */ + long seq_rows; /* 채번 카운터 수 */ + long fx_rows; /* 기준일 환율 고시 수 */ + long param_rows; /* 파라미터 수 */ +} cm_healthsnap_rec_t; + +#endif /* CM_HEALTHSNAP_BATCH_H */ diff --git a/app/src/cm/dbio/cm_holchk_batch.h b/app/src/cm/dbio/cm_holchk_batch.h new file mode 100644 index 0000000..7a7028d --- /dev/null +++ b/app/src/cm/dbio/cm_holchk_batch.h @@ -0,0 +1,14 @@ +/* + * cm_holchk_batch.h - cm cm_holchk_batch 배치 전용 카피북 (copybook). + * 휴일 캘린더 정합성 점검 배치의 결과 레코드. Included by cm_holchk_batch.pgc. + */ +#ifndef CM_HOLCHK_BATCH_H +#define CM_HOLCHK_BATCH_H + +typedef struct { + long year_total; /* 당해연도 휴일 수 */ + long on_weekend; /* 주말과 겹치는 휴일 수 */ + long effective; /* 실효 휴일 수 (평일 휴일) */ +} cm_holchk_rec_t; + +#endif /* CM_HOLCHK_BATCH_H */ diff --git a/app/src/cm/dbio/cm_msgsync_batch.h b/app/src/cm/dbio/cm_msgsync_batch.h new file mode 100644 index 0000000..b14de1c --- /dev/null +++ b/app/src/cm/dbio/cm_msgsync_batch.h @@ -0,0 +1,15 @@ +/* + * cm_msgsync_batch.h - cm cm_msgsync_batch 배치 전용 카피북 (copybook). + * 다국어 메시지 그룹 점검 배치의 커서 행 + 누적 레코드. Included by + * cm_msgsync_batch.pgc. + */ +#ifndef CM_MSGSYNC_BATCH_H +#define CM_MSGSYNC_BATCH_H + +typedef struct { + long groups; /* 언어 그룹 수 */ + long messages; /* 메시지 총량 */ + long thin; /* 메시지가 기준 미달인 그룹 수 */ +} cm_msgsync_rec_t; + +#endif /* CM_MSGSYNC_BATCH_H */ diff --git a/app/src/cm/dbio/cm_notidigest_batch.h b/app/src/cm/dbio/cm_notidigest_batch.h new file mode 100644 index 0000000..657fb1e --- /dev/null +++ b/app/src/cm/dbio/cm_notidigest_batch.h @@ -0,0 +1,16 @@ +/* + * cm_notidigest_batch.h - cm cm_notidigest_batch 배치 전용 카피북 (copybook). + * 알림 발송 일별 다이제스트 배치의 채널별 집계 레코드. Included by + * cm_notidigest_batch.pgc. + */ +#ifndef CM_NOTIDIGEST_BATCH_H +#define CM_NOTIDIGEST_BATCH_H + +typedef struct { + long sms; /* SMS 발송 수 */ + long email; /* EMAIL 발송 수 */ + long push; /* PUSH 발송 수 */ + long total; /* 전체 발송 수 */ +} cm_notidigest_rec_t; + +#endif /* CM_NOTIDIGEST_BATCH_H */ diff --git a/app/src/cm/dbio/cm_reqage_batch.h b/app/src/cm/dbio/cm_reqage_batch.h new file mode 100644 index 0000000..7a0b5ba --- /dev/null +++ b/app/src/cm/dbio/cm_reqage_batch.h @@ -0,0 +1,17 @@ +/* + * cm_reqage_batch.h - cm cm_reqage_batch 배치 전용 카피북 (copybook). + * 파라미터 승인 대기 정체 감시 배치의 결과 레코드 + 정체 기준일수. + * Included by cm_reqage_batch.pgc. + */ +#ifndef CM_REQAGE_BATCH_H +#define CM_REQAGE_BATCH_H + +#define CM_REQAGE_STALE_DAYS 3 /* 이 일수 넘게 대기하면 정체 */ + +typedef struct { + long pending; /* 전체 대기 요청 수 */ + long stale; /* 정체 요청 수 */ + long oldest_age; /* 최장 대기 일수 */ +} cm_reqage_rec_t; + +#endif /* CM_REQAGE_BATCH_H */ diff --git a/app/src/cm/dbio/cm_rerunpick_batch.h b/app/src/cm/dbio/cm_rerunpick_batch.h new file mode 100644 index 0000000..7fab01e --- /dev/null +++ b/app/src/cm/dbio/cm_rerunpick_batch.h @@ -0,0 +1,14 @@ +/* + * cm_rerunpick_batch.h - cm cm_rerunpick_batch 배치 전용 카피북 (copybook). + * 배치 재실행 요청 처리 배치의 결과 레코드. Included by cm_rerunpick_batch.pgc. + */ +#ifndef CM_RERUNPICK_BATCH_H +#define CM_RERUNPICK_BATCH_H + +typedef struct { + long pending; /* 미처리 재실행 요청 수 */ + long picked; /* 이번에 접수 처리한 요청 수 */ + long last_id; /* 마지막 처리 요청 로그 ID */ +} cm_rerunpick_rec_t; + +#endif /* CM_RERUNPICK_BATCH_H */ diff --git a/app/src/cm/dbio/cm_schddue_batch.h b/app/src/cm/dbio/cm_schddue_batch.h new file mode 100644 index 0000000..dc076a3 --- /dev/null +++ b/app/src/cm/dbio/cm_schddue_batch.h @@ -0,0 +1,15 @@ +/* + * cm_schddue_batch.h - cm cm_schddue_batch 배치 전용 카피북 (copybook). + * 배치 스케줄 기동 대상 판정 배치의 커서 행 파싱 레코드. Included by + * cm_schddue_batch.pgc. + */ +#ifndef CM_SCHDDUE_BATCH_H +#define CM_SCHDDUE_BATCH_H + +typedef struct { + long jobs; /* 등록 스케줄 수 */ + long due; /* 오늘 기동 대상 수 */ + long broken; /* 형식이 깨진 스케줄 수 */ +} cm_schddue_rec_t; + +#endif /* CM_SCHDDUE_BATCH_H */ diff --git a/app/src/cm/dbio/cm_seqgc_batch.h b/app/src/cm/dbio/cm_seqgc_batch.h new file mode 100644 index 0000000..b039f1b --- /dev/null +++ b/app/src/cm/dbio/cm_seqgc_batch.h @@ -0,0 +1,16 @@ +/* + * cm_seqgc_batch.h - cm cm_seqgc_batch 배치 전용 카피북 (copybook). + * 일자별 채번 카운터 정리(GC) 배치의 결과 레코드 + 보존일수 기본값. + * Included by cm_seqgc_batch.pgc. + */ +#ifndef CM_SEQGC_BATCH_H +#define CM_SEQGC_BATCH_H + +#define CM_SEQGC_KEEP_DAYS 30 /* 일자별 카운터 보존일수 */ + +typedef struct { + long scanned; /* 일자별(:포함) 카운터 수 */ + long deleted; /* 삭제한 카운터 수 */ +} cm_seqgc_rec_t; + +#endif /* CM_SEQGC_BATCH_H */ diff --git a/app/src/cm/dbio/cm_seqreport_batch.h b/app/src/cm/dbio/cm_seqreport_batch.h new file mode 100644 index 0000000..cea0eaa --- /dev/null +++ b/app/src/cm/dbio/cm_seqreport_batch.h @@ -0,0 +1,13 @@ +/* + * cm_seqreport_batch.h - cm cm_seqreport_batch 배치 전용 카피북 (copybook). + * 채번 카운터 스냅샷 배치의 누적 레코드. Included by cm_seqreport_batch.pgc. + */ +#ifndef CM_SEQREPORT_BATCH_H +#define CM_SEQREPORT_BATCH_H + +typedef struct { + long counters; /* 스냅샷 남긴 카운터 수 */ + long grand; /* 카운터 값 총합 */ +} cm_seqreport_rec_t; + +#endif /* CM_SEQREPORT_BATCH_H */ diff --git a/app/src/cm/dbio/cm_warnratio_batch.h b/app/src/cm/dbio/cm_warnratio_batch.h new file mode 100644 index 0000000..7032d56 --- /dev/null +++ b/app/src/cm/dbio/cm_warnratio_batch.h @@ -0,0 +1,18 @@ +/* + * cm_warnratio_batch.h - cm cm_warnratio_batch 배치 전용 카피북 (copybook). + * 경고 로그 비율 감시 배치의 판정 레코드 + 임계값. Included by + * cm_warnratio_batch.pgc. + */ +#ifndef CM_WARNRATIO_BATCH_H +#define CM_WARNRATIO_BATCH_H + +#define CM_WARNRATIO_ALERT_BPS 1500 /* 경고 비율 임계 (15%) */ + +typedef struct { + long total; /* 기준일 로그 총량 */ + long warns; /* W 레벨 건수 */ + long ratio_bps; /* 경고 비율 (bps) */ + int alarmed; /* 1=임계 초과 */ +} cm_warnratio_rec_t; + +#endif /* CM_WARNRATIO_BATCH_H */ diff --git a/app/src/cm/svc/CM_AMT_HANGUL.h b/app/src/cm/svc/CM_AMT_HANGUL.h new file mode 100644 index 0000000..b34a650 --- /dev/null +++ b/app/src/cm/svc/CM_AMT_HANGUL.h @@ -0,0 +1,13 @@ +/* + * CM_AMT_HANGUL.h - cm (공통) 서비스 CM_AMT_HANGUL 카피북 (copybook / record header). + * 금액 한글변환: 정수 금액을 한글 금액 표기(만/억/조)로 변환한다. + * Included by CM_AMT_HANGUL.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_AMT_HANGUL_H +#define CM_AMT_HANGUL_H + +#include "cm_svc.h" + +void CM_AMT_HANGUL(TPSVCINFO *p); + +#endif /* CM_AMT_HANGUL_H */ diff --git a/app/src/cm/svc/CM_AMT_HANGUL.pgc b/app/src/cm/svc/CM_AMT_HANGUL.pgc new file mode 100644 index 0000000..808c25a --- /dev/null +++ b/app/src/cm/svc/CM_AMT_HANGUL.pgc @@ -0,0 +1,59 @@ +/* + * CM_AMT_HANGUL.pgc - cm 모듈 서비스 CM_AMT_HANGUL (one service = one file). + * 금액 한글변환(순수 계산): 정수 금액을 4자리(만 단위) 그룹으로 쪼개 + * 일~구천 + 만/억/조 단위를 붙인 한글 금액 표기로 변환한다(영수증/전표용). + * 0 은 "영원", 음수는 "마이너스" 접두. no EXEC SQL. + */ +#include "CM_AMT_HANGUL.h" + +/* ----- service-local pure tables ----- */ +static const char *CM_HAN_DIGIT[] = + { "", "일", "이", "삼", "사", "오", "육", "칠", "팔", "구" }; +static const char *CM_HAN_POS[] = { "", "십", "백", "천" }; +static const char *CM_HAN_UNIT[] = { "", "만", "억", "조" }; + +/* 0~9999 한 그룹을 한글로. 반환 = 이어붙인 길이 */ +static int cm_han_grp(long v, char *dst) +{ + int pos, dig, n = 0; + for (pos = 3; pos >= 0; pos--) { + long div = 1; int k; + for (k = 0; k < pos; k++) div *= 10; + dig = (int)(v / div % 10); + if (dig == 0) continue; + if (!(dig == 1 && pos > 0)) /* 일십/일백은 십/백으로 */ + n += sprintf(dst + n, "%s", CM_HAN_DIGIT[dig]); + n += sprintf(dst + n, "%s", CM_HAN_POS[pos]); + } + return n; +} + +void CM_AMT_HANGUL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char out[256], grp[64]; + long amt, part[4]; + int gi, top = -1, n = 0; + + amt = getl(b, T_AMOUNT); + if (amt < 0) { n += sprintf(out, "마이너스"); amt = -amt; } + if (amt >= 10000000000000000L) { setl(b, T_RC, 1); FAIL(b); } /* 조 단위 초과 */ + + for (gi = 0; gi < 4; gi++) { /* 만 단위 그룹 분해 */ + part[gi] = amt % 10000; + amt /= 10000; + if (part[gi] != 0) top = gi; + } + if (top < 0) { + strcpy(out, "영원"); + } else { + for (gi = top; gi >= 0; gi--) { + if (part[gi] == 0) continue; + cm_han_grp(part[gi], grp); + n += sprintf(out + n, "%s%s", grp, CM_HAN_UNIT[gi]); + } + sprintf(out + n, "원"); + } + sets_(b, T_STR4, out); + OK(b); +} diff --git a/app/src/cm/svc/CM_AUDIT_BYLVL.h b/app/src/cm/svc/CM_AUDIT_BYLVL.h new file mode 100644 index 0000000..da6fb18 --- /dev/null +++ b/app/src/cm/svc/CM_AUDIT_BYLVL.h @@ -0,0 +1,13 @@ +/* + * CM_AUDIT_BYLVL.h - cm (공통) 서비스 CM_AUDIT_BYLVL 카피북 (copybook / record header). + * 감사로그 레벨별 조회: 레벨(I/W/E) 기준 건수와 최근 메시지를 조회한다. + * Included by CM_AUDIT_BYLVL.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_AUDIT_BYLVL_H +#define CM_AUDIT_BYLVL_H + +#include "cm_svc.h" + +void CM_AUDIT_BYLVL(TPSVCINFO *p); + +#endif /* CM_AUDIT_BYLVL_H */ diff --git a/app/src/cm/svc/CM_AUDIT_BYLVL.pgc b/app/src/cm/svc/CM_AUDIT_BYLVL.pgc new file mode 100644 index 0000000..2b29c81 --- /dev/null +++ b/app/src/cm/svc/CM_AUDIT_BYLVL.pgc @@ -0,0 +1,38 @@ +/* + * CM_AUDIT_BYLVL.pgc - cm 모듈 서비스 CM_AUDIT_BYLVL (one service = one file). + * 감사로그 레벨별 조회(조회 세분): 레벨(I/W/E)을 검증한 뒤 기준일 해당 레벨 + * 건수와 가장 최근 메시지 한 건을 조회한다. 잘못된 레벨은 거절(RC=1). + * runs on the caller's XA branch; advertised by cm_svr at tpsvrinit. + */ +#include "CM_AUDIT_BYLVL.h" + +void CM_AUDIT_BYLVL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_lvl[4], h_date[16], h_lastmsg[256]; + long h_cnt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_STR1, h_lvl, sizeof(h_lvl)); + gets_(b, T_BIZDATE, h_date, sizeof(h_date)); + if (h_lvl[0] != 'I' && h_lvl[0] != 'W' && h_lvl[0] != 'E') + { setl(b, T_RC, 1); FAIL(b); } /* 레벨 오류 */ + h_lvl[1] = 0; + if (h_date[0] == 0) { if (cmdb_get_param("BIZ_DATE", h_date) < 0) strcpy(h_date, "2026-07-19"); } + + EXEC SQL SELECT count(*) INTO :h_cnt + FROM cm_log WHERE log_level = :h_lvl AND biz_date = :h_date; + if (sqlca.sqlcode < 0) FAIL(b); + + h_lastmsg[0] = 0; + if (h_cnt > 0) { + EXEC SQL SELECT msg INTO :h_lastmsg + FROM cm_log WHERE log_level = :h_lvl AND biz_date = :h_date + ORDER BY log_id DESC LIMIT 1; + if (sqlca.sqlcode < 0) FAIL(b); + } + setl(b, T_COUNT, h_cnt); + sets_(b, T_MSG, h_lastmsg); + OK(b); +} diff --git a/app/src/cm/svc/CM_AUDIT_BYSVC.h b/app/src/cm/svc/CM_AUDIT_BYSVC.h new file mode 100644 index 0000000..82d8f7e --- /dev/null +++ b/app/src/cm/svc/CM_AUDIT_BYSVC.h @@ -0,0 +1,13 @@ +/* + * CM_AUDIT_BYSVC.h - cm (공통) 서비스 CM_AUDIT_BYSVC 카피북 (copybook / record header). + * 감사로그 서비스별 조회: 서비스명 기준 건수와 ID 범위를 조회한다. + * Included by CM_AUDIT_BYSVC.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_AUDIT_BYSVC_H +#define CM_AUDIT_BYSVC_H + +#include "cm_svc.h" + +void CM_AUDIT_BYSVC(TPSVCINFO *p); + +#endif /* CM_AUDIT_BYSVC_H */ diff --git a/app/src/cm/svc/CM_AUDIT_BYSVC.pgc b/app/src/cm/svc/CM_AUDIT_BYSVC.pgc new file mode 100644 index 0000000..cd37334 --- /dev/null +++ b/app/src/cm/svc/CM_AUDIT_BYSVC.pgc @@ -0,0 +1,36 @@ +/* + * CM_AUDIT_BYSVC.pgc - cm 모듈 서비스 CM_AUDIT_BYSVC (one service = one file). + * 감사로그 서비스별 조회(조회 세분): 서비스명 기준으로 기준일 로그 건수와 + * log_id 최소/최대 범위를 집계한다. 접미 와일드카드(prefix%) 검색을 지원. + * runs on the caller's XA branch; advertised by cm_svr at tpsvrinit. + */ +#include "CM_AUDIT_BYSVC.h" + +void CM_AUDIT_BYSVC(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_pat[72], h_date[16]; + long h_cnt, h_lo, h_hi; + EXEC SQL END DECLARE SECTION; + char svc[64]; + size_t sl; + + gets_(b, T_SVCNAME, svc, sizeof(svc)); + gets_(b, T_BIZDATE, h_date, sizeof(h_date)); + if (svc[0] == 0) FAIL(b); + if (h_date[0] == 0) { if (cmdb_get_param("BIZ_DATE", h_date) < 0) strcpy(h_date, "2026-07-19"); } + sl = strlen(svc); + if (svc[sl - 1] == '*') snprintf(h_pat, sizeof(h_pat), "%.*s%%", (int)(sl - 1), svc); + else snprintf(h_pat, sizeof(h_pat), "%s", svc); + + EXEC SQL SELECT count(*), coalesce(min(log_id), 0), coalesce(max(log_id), 0) + INTO :h_cnt, :h_lo, :h_hi + FROM cm_log WHERE svc_name LIKE :h_pat AND biz_date = :h_date; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + setl(b, T_ID1, h_lo); + setl(b, T_ID2, h_hi); + OK(b); +} diff --git a/app/src/cm/svc/CM_AUDIT_RANGE.h b/app/src/cm/svc/CM_AUDIT_RANGE.h new file mode 100644 index 0000000..fda0897 --- /dev/null +++ b/app/src/cm/svc/CM_AUDIT_RANGE.h @@ -0,0 +1,13 @@ +/* + * CM_AUDIT_RANGE.h - cm (공통) 서비스 CM_AUDIT_RANGE 카피북 (copybook / record header). + * 감사로그 ID 구간 조회: log_id 구간의 건수와 오류 건수를 조회한다. + * Included by CM_AUDIT_RANGE.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_AUDIT_RANGE_H +#define CM_AUDIT_RANGE_H + +#include "cm_svc.h" + +void CM_AUDIT_RANGE(TPSVCINFO *p); + +#endif /* CM_AUDIT_RANGE_H */ diff --git a/app/src/cm/svc/CM_AUDIT_RANGE.pgc b/app/src/cm/svc/CM_AUDIT_RANGE.pgc new file mode 100644 index 0000000..ed6f98e --- /dev/null +++ b/app/src/cm/svc/CM_AUDIT_RANGE.pgc @@ -0,0 +1,32 @@ +/* + * CM_AUDIT_RANGE.pgc - cm 모듈 서비스 CM_AUDIT_RANGE (one service = one file). + * 감사로그 ID 구간 조회(조회 세분): log_id 구간(id1~id2, 최대 폭 10000)의 + * 총 건수와 오류 건수, 구간 내 고유 서비스 수를 집계한다. 구간 역전/과대는 + * 거절한다. runs on the caller's XA branch; advertised by cm_svr. + */ +#include "CM_AUDIT_RANGE.h" + +void CM_AUDIT_RANGE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_lo, h_hi, h_cnt, h_err, h_svcs; + EXEC SQL END DECLARE SECTION; + + h_lo = getl(b, T_ID1); + h_hi = getl(b, T_ID2); + if (h_lo <= 0 || h_hi < h_lo) { setl(b, T_RC, 1); FAIL(b); } /* 구간 역전 */ + if (h_hi - h_lo > 10000) { setl(b, T_RC, 2); FAIL(b); } /* 구간 과대 */ + + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN log_level = 'E' THEN 1 ELSE 0 END), 0), + count(DISTINCT svc_name) + INTO :h_cnt, :h_err, :h_svcs + FROM cm_log WHERE log_id BETWEEN :h_lo AND :h_hi; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + setl(b, T_ARG1, h_err); + setl(b, T_ARG2, h_svcs); + OK(b); +} diff --git a/app/src/cm/svc/CM_BATHIST_CNT.h b/app/src/cm/svc/CM_BATHIST_CNT.h new file mode 100644 index 0000000..cd19beb --- /dev/null +++ b/app/src/cm/svc/CM_BATHIST_CNT.h @@ -0,0 +1,13 @@ +/* + * CM_BATHIST_CNT.h - cm (공통) 서비스 CM_BATHIST_CNT 카피북 (copybook / record header). + * 배치 실행 이력 집계: 기준일 잡의 실행/오류/정상 건수를 집계한다. + * Included by CM_BATHIST_CNT.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_BATHIST_CNT_H +#define CM_BATHIST_CNT_H + +#include "cm_svc.h" + +void CM_BATHIST_CNT(TPSVCINFO *p); + +#endif /* CM_BATHIST_CNT_H */ diff --git a/app/src/cm/svc/CM_BATHIST_CNT.pgc b/app/src/cm/svc/CM_BATHIST_CNT.pgc new file mode 100644 index 0000000..044646c --- /dev/null +++ b/app/src/cm/svc/CM_BATHIST_CNT.pgc @@ -0,0 +1,36 @@ +/* + * CM_BATHIST_CNT.pgc - cm 모듈 서비스 CM_BATHIST_CNT (one service = one file). + * 배치 실행 이력 집계: 기준일 해당 잡(svc_name)의 로그를 레벨별로 집계해 + * 총 실행/오류/경고 건수를 구하고 정상 건수는 C 에서 산출한다. 마지막 실행 + * ID 도 함께 반환한다. runs on the caller's XA branch. + */ +#include "CM_BATHIST_CNT.h" + +void CM_BATHIST_CNT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_job[48], h_date[16]; + long h_total, h_err, h_warn, h_lastid; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, h_job, sizeof(h_job)); + gets_(b, T_BIZDATE, h_date, sizeof(h_date)); + if (h_job[0] == 0) FAIL(b); + if (h_date[0] == 0) { if (cmdb_get_param("BIZ_DATE", h_date) < 0) strcpy(h_date, "2026-07-19"); } + + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN log_level = 'E' THEN 1 ELSE 0 END), 0), + coalesce(sum(CASE WHEN log_level = 'W' THEN 1 ELSE 0 END), 0), + coalesce(max(log_id), 0) + INTO :h_total, :h_err, :h_warn, :h_lastid + FROM cm_log WHERE svc_name = :h_job AND biz_date = :h_date; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_total); + setl(b, T_ARG1, h_err); + setl(b, T_ARG2, h_warn); + setl(b, T_ARG3, h_total - h_err - h_warn); /* 정상 건수 */ + setl(b, T_ID1, h_lastid); + OK(b); +} diff --git a/app/src/cm/svc/CM_BATRERUN_REQ.h b/app/src/cm/svc/CM_BATRERUN_REQ.h new file mode 100644 index 0000000..49cac51 --- /dev/null +++ b/app/src/cm/svc/CM_BATRERUN_REQ.h @@ -0,0 +1,13 @@ +/* + * CM_BATRERUN_REQ.h - cm (공통) 서비스 CM_BATRERUN_REQ 카피북 (copybook / record header). + * 배치 재실행 요청: 실행 이력이 있는 잡에 대해 재실행 요청을 채번/기록한다. + * Included by CM_BATRERUN_REQ.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_BATRERUN_REQ_H +#define CM_BATRERUN_REQ_H + +#include "cm_svc.h" + +void CM_BATRERUN_REQ(TPSVCINFO *p); + +#endif /* CM_BATRERUN_REQ_H */ diff --git a/app/src/cm/svc/CM_BATRERUN_REQ.pgc b/app/src/cm/svc/CM_BATRERUN_REQ.pgc new file mode 100644 index 0000000..019789a --- /dev/null +++ b/app/src/cm/svc/CM_BATRERUN_REQ.pgc @@ -0,0 +1,39 @@ +/* + * CM_BATRERUN_REQ.pgc - cm 모듈 서비스 CM_BATRERUN_REQ (one service = one file). + * 배치 재실행 요청: 기준일에 실행 이력이 실제로 있는 잡만 재실행 요청을 받아 + * RERUN 카운터로 요청번호를 채번하고 RERUN 로그에 적재한다. 이력이 없는 잡은 + * 거절(RC=1). runs on the caller's XA branch (single XA tx). + */ +#include "CM_BATRERUN_REQ.h" + +void CM_BATRERUN_REQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char reason[96], ticket[224]; + long reqno; + EXEC SQL BEGIN DECLARE SECTION; + char h_job[48], h_date[16]; + long h_ran; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, h_job, sizeof(h_job)); + gets_(b, T_BIZDATE, h_date, sizeof(h_date)); + gets_(b, T_REASON, reason, sizeof(reason)); + if (h_job[0] == 0 || h_date[0] == 0) FAIL(b); + if (reason[0] == 0) strcpy(reason, "MANUAL"); + + EXEC SQL SELECT count(*) INTO :h_ran + FROM cm_log WHERE svc_name = :h_job AND biz_date = :h_date; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_ran == 0) { setl(b, T_RC, 1); FAIL(b); } /* 실행 이력 없음 */ + + reqno = cmdb_next_seq("RERUN"); + if (reqno < 0) FAIL(b); + snprintf(ticket, sizeof(ticket), "#%ld %s %s (%s)", reqno, h_job, h_date, reason); + if (cmdb_log("RERUN", "W", ticket, h_date) < 0) FAIL(b); + + setl(b, T_ID1, reqno); /* 재실행 요청번호 */ + setl(b, T_COUNT, h_ran); /* 당일 기존 실행 수 */ + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/cm/svc/CM_BATSCHD_GET.h b/app/src/cm/svc/CM_BATSCHD_GET.h new file mode 100644 index 0000000..42bb9a0 --- /dev/null +++ b/app/src/cm/svc/CM_BATSCHD_GET.h @@ -0,0 +1,13 @@ +/* + * CM_BATSCHD_GET.h - cm (공통) 서비스 CM_BATSCHD_GET 카피북 (copybook / record header). + * 배치 스케줄 조회: 잡의 등록된 주기/시각을 조회한다. + * Included by CM_BATSCHD_GET.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_BATSCHD_GET_H +#define CM_BATSCHD_GET_H + +#include "cm_svc.h" + +void CM_BATSCHD_GET(TPSVCINFO *p); + +#endif /* CM_BATSCHD_GET_H */ diff --git a/app/src/cm/svc/CM_BATSCHD_GET.pgc b/app/src/cm/svc/CM_BATSCHD_GET.pgc new file mode 100644 index 0000000..82080ec --- /dev/null +++ b/app/src/cm/svc/CM_BATSCHD_GET.pgc @@ -0,0 +1,27 @@ +/* + * CM_BATSCHD_GET.pgc - cm 모듈 서비스 CM_BATSCHD_GET (one service = one file). + * 배치 스케줄 조회: SCHD: 파라미터를 읽어 주기와 실행시각을 분리해 + * 돌려준다. 저장 형식이 깨져 있으면 형식 오류(RC=2)로 구분해 실패한다. + * runs on the caller's XA branch; advertised by cm_svr at tpsvrinit. + */ +#include "CM_BATSCHD_GET.h" + +void CM_BATSCHD_GET(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char job[32], key[48], stored[128], cycle[16]; + long hhmm = -1; + + gets_(b, T_KEY1, job, sizeof(job)); + if (job[0] == 0) FAIL(b); + snprintf(key, sizeof(key), "SCHD:%s", job); + if (cmdb_get_param(key, stored) < 0) { setl(b, T_RC, 1); FAIL(b); } /* 미등록 */ + + if (sscanf(stored, "%15s %ld", cycle, &hhmm) != 2 || hhmm < 0 || hhmm > 2359) + { setl(b, T_RC, 2); FAIL(b); } /* 형식 오류 */ + + sets_(b, T_STR4, cycle); + setl(b, T_ARG1, hhmm); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/cm/svc/CM_BATSCHD_REG.h b/app/src/cm/svc/CM_BATSCHD_REG.h new file mode 100644 index 0000000..a6bab60 --- /dev/null +++ b/app/src/cm/svc/CM_BATSCHD_REG.h @@ -0,0 +1,13 @@ +/* + * CM_BATSCHD_REG.h - cm (공통) 서비스 CM_BATSCHD_REG 카피북 (copybook / record header). + * 배치 스케줄 등록: 잡의 실행 주기와 시각을 검증 후 등록한다. + * Included by CM_BATSCHD_REG.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_BATSCHD_REG_H +#define CM_BATSCHD_REG_H + +#include "cm_svc.h" + +void CM_BATSCHD_REG(TPSVCINFO *p); + +#endif /* CM_BATSCHD_REG_H */ diff --git a/app/src/cm/svc/CM_BATSCHD_REG.pgc b/app/src/cm/svc/CM_BATSCHD_REG.pgc new file mode 100644 index 0000000..c187ba5 --- /dev/null +++ b/app/src/cm/svc/CM_BATSCHD_REG.pgc @@ -0,0 +1,36 @@ +/* + * CM_BATSCHD_REG.pgc - cm 모듈 서비스 CM_BATSCHD_REG (one service = one file). + * 배치 스케줄 등록: 잡 이름과 주기(DAILY/WEEKLY/MONTHLY) + 실행시각(HHMM)을 + * 검증한 뒤 SCHD: 파라미터로 저장한다. 시각은 0000~2359 범위 검사. + * runs on the caller's XA branch; advertised by cm_svr at tpsvrinit. + */ +#include "CM_BATSCHD_REG.h" + +void CM_BATSCHD_REG(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char job[32], cycle[12], key[48], val[32], bizdate[16], hist[128]; + long hhmm; + int cyc_ok = 0; + + gets_(b, T_KEY1, job, sizeof(job)); + gets_(b, T_STR1, cycle, sizeof(cycle)); + hhmm = getl(b, T_ARG1); + if (job[0] == 0) FAIL(b); + if (strcmp(cycle, "DAILY") == 0) cyc_ok = 1; + else if (strcmp(cycle, "WEEKLY") == 0) cyc_ok = 1; + else if (strcmp(cycle, "MONTHLY") == 0) cyc_ok = 1; + if (!cyc_ok) { setl(b, T_RC, 1); FAIL(b); } /* 주기 오류 */ + if (hhmm < 0 || hhmm > 2359 || (hhmm % 100) > 59) { setl(b, T_RC, 2); FAIL(b); } + + snprintf(key, sizeof(key), "SCHD:%s", job); + snprintf(val, sizeof(val), "%s %04ld", cycle, hhmm); + if (cmdb_set_param(key, val) < 0) FAIL(b); + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) { if (cmdb_get_param("BIZ_DATE", bizdate) < 0) strcpy(bizdate, "2026-07-19"); } + snprintf(hist, sizeof(hist), "SCHD %s = %s", job, val); + cmdb_log("BATSCHD", "I", hist, bizdate); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/cm/svc/CM_BIZDAY_CNT.h b/app/src/cm/svc/CM_BIZDAY_CNT.h new file mode 100644 index 0000000..8efe8d0 --- /dev/null +++ b/app/src/cm/svc/CM_BIZDAY_CNT.h @@ -0,0 +1,13 @@ +/* + * CM_BIZDAY_CNT.h - cm (공통) 서비스 CM_BIZDAY_CNT 카피북 (copybook / record header). + * 영업일수 계산: 두 날짜 사이의 영업일 수(주말+휴일 제외)를 계산한다. + * Included by CM_BIZDAY_CNT.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_BIZDAY_CNT_H +#define CM_BIZDAY_CNT_H + +#include "cm_svc.h" + +void CM_BIZDAY_CNT(TPSVCINFO *p); + +#endif /* CM_BIZDAY_CNT_H */ diff --git a/app/src/cm/svc/CM_BIZDAY_CNT.pgc b/app/src/cm/svc/CM_BIZDAY_CNT.pgc new file mode 100644 index 0000000..27b4982 --- /dev/null +++ b/app/src/cm/svc/CM_BIZDAY_CNT.pgc @@ -0,0 +1,56 @@ +/* + * CM_BIZDAY_CNT.pgc - cm 모듈 서비스 CM_BIZDAY_CNT (one service = one file). + * 영업일수 계산: from~to 사이를 하루씩 진행하며 주말은 순수 요일 공식 + * (Sakamoto)으로, 휴일은 cm_holiday 조회로 제외하고 영업일 수를 센다. + * 순수 계산 + EXEC SQL(dbio) 혼합. 최대 400일 구간까지만 허용한다. + */ +#include "CM_BIZDAY_CNT.h" + +/* ----- service-local pure helper(s) ----- */ +/* Sakamoto 요일 공식: 0=일 ... 6=토 */ +static int cm_dow_of(int y, int m, int d) +{ + static const int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4}; + if (m < 3) y -= 1; + return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7; +} + +static void cm_step_day(int *y, int *m, int *d) +{ + static const int md[] = {31,28,31,30,31,30,31,31,30,31,30,31}; + int lim = md[*m - 1]; + if (*m == 2 && ((*y % 4 == 0 && *y % 100 != 0) || *y % 400 == 0)) lim = 29; + if (++*d > lim) { *d = 1; if (++*m > 12) { *m = 1; ++*y; } } +} + +void CM_BIZDAY_CNT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char from[16], to[16], cur[16]; + int y, m, d, dow, hol, guard = 0; + long biz = 0, weekend = 0, holidays = 0; + + gets_(b, T_STR1, from, sizeof(from)); + gets_(b, T_STR2, to, sizeof(to)); + if (sscanf(from, "%d-%d-%d", &y, &m, &d) != 3) FAIL(b); + if (to[0] == 0 || strcmp(from, to) > 0) FAIL(b); + + sprintf(cur, "%04d-%02d-%02d", y, m, d); + while (strcmp(cur, to) <= 0) { + if (++guard > 400) { setl(b, T_RC, 2); FAIL(b); } /* 구간 과대 */ + dow = cm_dow_of(y, m, d); + if (dow == 0 || dow == 6) { + weekend++; + } else { + hol = cmdb_is_holiday(cur); + if (hol < 0) FAIL(b); + if (hol == 1) holidays++; else biz++; + } + cm_step_day(&y, &m, &d); + sprintf(cur, "%04d-%02d-%02d", y, m, d); + } + setl(b, T_COUNT, biz); + setl(b, T_ARG1, weekend); + setl(b, T_ARG2, holidays); + OK(b); +} diff --git a/app/src/cm/svc/CM_CODEGRP_CLOSE.h b/app/src/cm/svc/CM_CODEGRP_CLOSE.h new file mode 100644 index 0000000..b9383e0 --- /dev/null +++ b/app/src/cm/svc/CM_CODEGRP_CLOSE.h @@ -0,0 +1,13 @@ +/* + * CM_CODEGRP_CLOSE.h - cm (공통) 서비스 CM_CODEGRP_CLOSE 카피북 (copybook / record header). + * 코드그룹 코드 폐기: use_yn='N' 처리(논리 삭제)하고 폐기 이력을 남긴다. + * Included by CM_CODEGRP_CLOSE.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_CODEGRP_CLOSE_H +#define CM_CODEGRP_CLOSE_H + +#include "cm_svc.h" + +void CM_CODEGRP_CLOSE(TPSVCINFO *p); + +#endif /* CM_CODEGRP_CLOSE_H */ diff --git a/app/src/cm/svc/CM_CODEGRP_CLOSE.pgc b/app/src/cm/svc/CM_CODEGRP_CLOSE.pgc new file mode 100644 index 0000000..710658a --- /dev/null +++ b/app/src/cm/svc/CM_CODEGRP_CLOSE.pgc @@ -0,0 +1,34 @@ +/* + * CM_CODEGRP_CLOSE.pgc - cm 모듈 서비스 CM_CODEGRP_CLOSE (one service = one file). + * 코드그룹 코드 폐기: 사용중(use_yn='Y')인 코드만 use_yn='N' 으로 논리 삭제하고, + * 이미 폐기됐거나 없는 코드는 결과코드로 구분해 실패 처리한다. runs on the + * caller's XA branch (no EXEC SQL CONNECT); advertised by cm_svr at tpsvrinit. + */ +#include "CM_CODEGRP_CLOSE.h" + +void CM_CODEGRP_CLOSE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char hist[160], reason[80], bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + char h_grp[32], h_code[32]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, h_grp, sizeof(h_grp)); + gets_(b, T_KEY2, h_code, sizeof(h_code)); + gets_(b, T_REASON, reason, sizeof(reason)); + if (h_grp[0] == 0 || h_code[0] == 0) FAIL(b); + if (reason[0] == 0) strcpy(reason, "ADMIN"); + + EXEC SQL UPDATE cm_code SET use_yn = 'N', updated_at = now() + WHERE code_grp = :h_grp AND code = :h_code AND use_yn = 'Y'; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlerrd[2] == 0) { setl(b, T_RC, 1); FAIL(b); } /* 미존재/기폐기 */ + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) { if (cmdb_get_param("BIZ_DATE", bizdate) < 0) strcpy(bizdate, "2026-07-19"); } + snprintf(hist, sizeof(hist), "%s/%s CLOSE (%s)", h_grp, h_code, reason); + cmdb_log("CODEGRP", "W", hist, bizdate); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/cm/svc/CM_CODEGRP_CNT.h b/app/src/cm/svc/CM_CODEGRP_CNT.h new file mode 100644 index 0000000..45d5bf7 --- /dev/null +++ b/app/src/cm/svc/CM_CODEGRP_CNT.h @@ -0,0 +1,13 @@ +/* + * CM_CODEGRP_CNT.h - cm (공통) 서비스 CM_CODEGRP_CNT 카피북 (copybook / record header). + * 코드그룹 현황: 그룹의 전체/사용중 코드 수와 사용률(bps)을 조회한다. + * Included by CM_CODEGRP_CNT.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_CODEGRP_CNT_H +#define CM_CODEGRP_CNT_H + +#include "cm_svc.h" + +void CM_CODEGRP_CNT(TPSVCINFO *p); + +#endif /* CM_CODEGRP_CNT_H */ diff --git a/app/src/cm/svc/CM_CODEGRP_CNT.pgc b/app/src/cm/svc/CM_CODEGRP_CNT.pgc new file mode 100644 index 0000000..67dd573 --- /dev/null +++ b/app/src/cm/svc/CM_CODEGRP_CNT.pgc @@ -0,0 +1,33 @@ +/* + * CM_CODEGRP_CNT.pgc - cm 모듈 서비스 CM_CODEGRP_CNT (one service = one file). + * 코드그룹 현황: 그룹의 전체 코드 수와 사용중(use_yn='Y') 코드 수를 한 번의 + * 조건부 집계로 읽고, 사용률(bps)을 C 에서 계산해 반환한다. runs on the + * caller's XA branch (no EXEC SQL CONNECT); advertised by cm_svr. + */ +#include "CM_CODEGRP_CNT.h" + +void CM_CODEGRP_CNT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long ratio_bps = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_grp[32]; + long h_total, h_active; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, h_grp, sizeof(h_grp)); + if (h_grp[0] == 0) FAIL(b); + + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN use_yn = 'Y' THEN 1 ELSE 0 END), 0) + INTO :h_total, :h_active + FROM cm_code WHERE code_grp = :h_grp; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_total == 0) { setl(b, T_RC, 1); FAIL(b); } /* 그룹 없음 */ + + ratio_bps = h_active * 10000L / h_total; + setl(b, T_COUNT, h_total); + setl(b, T_ARG1, h_active); + setl(b, T_ARG2, ratio_bps); + OK(b); +} diff --git a/app/src/cm/svc/CM_CODEGRP_HIST.h b/app/src/cm/svc/CM_CODEGRP_HIST.h new file mode 100644 index 0000000..c7900ae --- /dev/null +++ b/app/src/cm/svc/CM_CODEGRP_HIST.h @@ -0,0 +1,13 @@ +/* + * CM_CODEGRP_HIST.h - cm (공통) 서비스 CM_CODEGRP_HIST 카피북 (copybook / record header). + * 코드그룹 변경 이력 조회: 그룹별 등록/변경/폐기 이력 건수와 최근 이력을 조회한다. + * Included by CM_CODEGRP_HIST.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_CODEGRP_HIST_H +#define CM_CODEGRP_HIST_H + +#include "cm_svc.h" + +void CM_CODEGRP_HIST(TPSVCINFO *p); + +#endif /* CM_CODEGRP_HIST_H */ diff --git a/app/src/cm/svc/CM_CODEGRP_HIST.pgc b/app/src/cm/svc/CM_CODEGRP_HIST.pgc new file mode 100644 index 0000000..4358c65 --- /dev/null +++ b/app/src/cm/svc/CM_CODEGRP_HIST.pgc @@ -0,0 +1,33 @@ +/* + * CM_CODEGRP_HIST.pgc - cm 모듈 서비스 CM_CODEGRP_HIST (one service = one file). + * 코드그룹 변경 이력 조회: CODEGRP 이력 로그에서 그룹 접두 패턴으로 건수와 + * 마지막 이력 ID 를 집계하고, 마지막 이력 메시지 원문을 함께 반환한다. + * runs on the caller's XA branch; advertised by cm_svr at tpsvrinit. + */ +#include "CM_CODEGRP_HIST.h" + +void CM_CODEGRP_HIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char last[256]; + EXEC SQL BEGIN DECLARE SECTION; + char h_pat[40]; + long h_cnt, h_last; + EXEC SQL END DECLARE SECTION; + char grp[32]; + + gets_(b, T_KEY1, grp, sizeof(grp)); + if (grp[0] == 0) FAIL(b); + snprintf(h_pat, sizeof(h_pat), "%s/%%", grp); + + EXEC SQL SELECT count(*), coalesce(max(log_id), 0) INTO :h_cnt, :h_last + FROM cm_log WHERE svc_name = 'CODEGRP' AND msg LIKE :h_pat; + if (sqlca.sqlcode < 0) FAIL(b); + + last[0] = 0; + if (h_last > 0 && cmdb_log_message(h_last, last) < 0) last[0] = 0; + setl(b, T_COUNT, h_cnt); + setl(b, T_ID1, h_last); + sets_(b, T_MSG, last); + OK(b); +} diff --git a/app/src/cm/svc/CM_CODEGRP_REG.h b/app/src/cm/svc/CM_CODEGRP_REG.h new file mode 100644 index 0000000..c2dc57e --- /dev/null +++ b/app/src/cm/svc/CM_CODEGRP_REG.h @@ -0,0 +1,13 @@ +/* + * CM_CODEGRP_REG.h - cm (공통) 서비스 CM_CODEGRP_REG 카피북 (copybook / record header). + * 코드그룹 코드 등록: 중복 검사 후 cm_code 신규 코드를 등록하고 등록 이력을 남긴다. + * Included by CM_CODEGRP_REG.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_CODEGRP_REG_H +#define CM_CODEGRP_REG_H + +#include "cm_svc.h" + +void CM_CODEGRP_REG(TPSVCINFO *p); + +#endif /* CM_CODEGRP_REG_H */ diff --git a/app/src/cm/svc/CM_CODEGRP_REG.pgc b/app/src/cm/svc/CM_CODEGRP_REG.pgc new file mode 100644 index 0000000..f38dbb3 --- /dev/null +++ b/app/src/cm/svc/CM_CODEGRP_REG.pgc @@ -0,0 +1,39 @@ +/* + * CM_CODEGRP_REG.pgc - cm 모듈 서비스 CM_CODEGRP_REG (one service = one file). + * 코드그룹 코드 등록: 코드 중복을 검사한 뒤 cm_code 에 신규 코드를 등록하고, + * 등록 이력을 공통로그(cm_log, svc=CODEGRP)에 남긴다. runs on the caller's + * XA branch (no EXEC SQL CONNECT); advertised by cm_svr at tpsvrinit. + */ +#include "CM_CODEGRP_REG.h" + +void CM_CODEGRP_REG(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char hist[192], bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + char h_grp[32], h_code[32], h_name[128]; + long h_sort, h_dup; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, h_grp, sizeof(h_grp)); + gets_(b, T_KEY2, h_code, sizeof(h_code)); + gets_(b, T_STR1, h_name, sizeof(h_name)); + h_sort = getl(b, T_ARG1); + if (h_grp[0] == 0 || h_code[0] == 0 || h_name[0] == 0) FAIL(b); + + EXEC SQL SELECT count(*) INTO :h_dup FROM cm_code + WHERE code_grp = :h_grp AND code = :h_code; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_dup > 0) { setl(b, T_RC, 1); FAIL(b); } /* 이미 존재 */ + + EXEC SQL INSERT INTO cm_code (code_grp, code, code_name, sort_ord, use_yn) + VALUES (:h_grp, :h_code, :h_name, :h_sort, 'Y'); + if (sqlca.sqlcode < 0) FAIL(b); + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) { if (cmdb_get_param("BIZ_DATE", bizdate) < 0) strcpy(bizdate, "2026-07-19"); } + snprintf(hist, sizeof(hist), "%s/%s REG %s", h_grp, h_code, h_name); + if (cmdb_log("CODEGRP", "I", hist, bizdate) < 0) FAIL(b); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/cm/svc/CM_CODEGRP_UPD.h b/app/src/cm/svc/CM_CODEGRP_UPD.h new file mode 100644 index 0000000..99b5e87 --- /dev/null +++ b/app/src/cm/svc/CM_CODEGRP_UPD.h @@ -0,0 +1,13 @@ +/* + * CM_CODEGRP_UPD.h - cm (공통) 서비스 CM_CODEGRP_UPD 카피북 (copybook / record header). + * 코드그룹 코드 변경: 코드명/정렬순서를 변경하고 변경 전 값을 이력으로 남긴다. + * Included by CM_CODEGRP_UPD.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_CODEGRP_UPD_H +#define CM_CODEGRP_UPD_H + +#include "cm_svc.h" + +void CM_CODEGRP_UPD(TPSVCINFO *p); + +#endif /* CM_CODEGRP_UPD_H */ diff --git a/app/src/cm/svc/CM_CODEGRP_UPD.pgc b/app/src/cm/svc/CM_CODEGRP_UPD.pgc new file mode 100644 index 0000000..f74c8cc --- /dev/null +++ b/app/src/cm/svc/CM_CODEGRP_UPD.pgc @@ -0,0 +1,44 @@ +/* + * CM_CODEGRP_UPD.pgc - cm 모듈 서비스 CM_CODEGRP_UPD (one service = one file). + * 코드그룹 코드 변경: 변경 전 코드명을 먼저 조회해 이력 메시지에 담고, + * 코드명/정렬순서를 갱신한다(대상 없으면 실패). runs on the caller's + * XA branch (no EXEC SQL CONNECT); advertised by cm_svr at tpsvrinit. + */ +#include "CM_CODEGRP_UPD.h" + +void CM_CODEGRP_UPD(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char hist[256], bizdate[16]; + long rows; + EXEC SQL BEGIN DECLARE SECTION; + char h_grp[32], h_code[32], h_new[128], h_old[128]; + long h_sort; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, h_grp, sizeof(h_grp)); + gets_(b, T_KEY2, h_code, sizeof(h_code)); + gets_(b, T_STR1, h_new, sizeof(h_new)); + h_sort = getl(b, T_ARG1); + if (h_grp[0] == 0 || h_code[0] == 0 || h_new[0] == 0) FAIL(b); + + /* 변경 전 값 확보 (이력용) */ + EXEC SQL SELECT code_name INTO :h_old FROM cm_code + WHERE code_grp = :h_grp AND code = :h_code; + if (sqlca.sqlcode != 0) { setl(b, T_RC, 1); FAIL(b); } /* 미존재 포함 */ + + EXEC SQL UPDATE cm_code + SET code_name = :h_new, sort_ord = :h_sort, updated_at = now() + WHERE code_grp = :h_grp AND code = :h_code; + if (sqlca.sqlcode < 0) FAIL(b); + rows = (long)sqlca.sqlerrd[2]; + if (rows != 1) { setl(b, T_RC, 2); FAIL(b); } + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) { if (cmdb_get_param("BIZ_DATE", bizdate) < 0) strcpy(bizdate, "2026-07-19"); } + snprintf(hist, sizeof(hist), "%s/%s UPD %s -> %s", h_grp, h_code, h_old, h_new); + cmdb_log("CODEGRP", "I", hist, bizdate); + sets_(b, T_STR4, h_old); /* 변경 전 코드명 반환 */ + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/cm/svc/CM_DATEDIFF.h b/app/src/cm/svc/CM_DATEDIFF.h new file mode 100644 index 0000000..a062ef2 --- /dev/null +++ b/app/src/cm/svc/CM_DATEDIFF.h @@ -0,0 +1,13 @@ +/* + * CM_DATEDIFF.h - cm (공통) 서비스 CM_DATEDIFF 카피북 (copybook / record header). + * 기간 계산: 두 날짜의 일수 차이를 역법 공식으로 계산한다. + * Included by CM_DATEDIFF.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_DATEDIFF_H +#define CM_DATEDIFF_H + +#include "cm_svc.h" + +void CM_DATEDIFF(TPSVCINFO *p); + +#endif /* CM_DATEDIFF_H */ diff --git a/app/src/cm/svc/CM_DATEDIFF.pgc b/app/src/cm/svc/CM_DATEDIFF.pgc new file mode 100644 index 0000000..14d398e --- /dev/null +++ b/app/src/cm/svc/CM_DATEDIFF.pgc @@ -0,0 +1,40 @@ +/* + * CM_DATEDIFF.pgc - cm 모듈 서비스 CM_DATEDIFF (one service = one file). + * 기간 계산 유틸(순수 계산): 두 날짜를 역법 공식으로 절대 일련일수로 바꿔 + * 하루씩 세지 않고 차이를 구한다. 부호 있는 차이와 절대값을 모두 반환한다. + * no EXEC SQL; advertised by cm_svr at tpsvrinit. + */ +#include "CM_DATEDIFF.h" + +/* ----- service-local pure helper(s) ----- */ +/* 그레고리력 -> 일련일수 (civil days-from-epoch 공식, 루프 없음) */ +static long cm_serial(int y, int m, int d) +{ + long era, yoe, doy, doe; + y -= m <= 2; + era = (y >= 0 ? y : y - 399) / 400; + yoe = y - era * 400; + doy = (153L * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; + doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return era * 146097L + doe - 719468L; +} + +void CM_DATEDIFF(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char d1[16], d2[16]; + int y1, m1, dd1, y2, m2, dd2; + long diff; + + gets_(b, T_STR1, d1, sizeof(d1)); + gets_(b, T_STR2, d2, sizeof(d2)); + if (sscanf(d1, "%d-%d-%d", &y1, &m1, &dd1) != 3) FAIL(b); + if (sscanf(d2, "%d-%d-%d", &y2, &m2, &dd2) != 3) FAIL(b); + if (m1 < 1 || m1 > 12 || dd1 < 1 || dd1 > 31) { setl(b, T_RC, 1); FAIL(b); } + if (m2 < 1 || m2 > 12 || dd2 < 1 || dd2 > 31) { setl(b, T_RC, 1); FAIL(b); } + + diff = cm_serial(y2, m2, dd2) - cm_serial(y1, m1, dd1); + setl(b, T_ARG1, diff); /* 부호 있는 일수 차 */ + setl(b, T_ARG2, diff < 0 ? -diff : diff); /* 절대 일수 */ + OK(b); +} diff --git a/app/src/cm/svc/CM_ENC_HEX.h b/app/src/cm/svc/CM_ENC_HEX.h new file mode 100644 index 0000000..63fc7dc --- /dev/null +++ b/app/src/cm/svc/CM_ENC_HEX.h @@ -0,0 +1,13 @@ +/* + * CM_ENC_HEX.h - cm (공통) 서비스 CM_ENC_HEX 카피북 (copybook / record header). + * 간이 암복호화: 키 순환 XOR + HEX 인코딩/디코딩을 수행한다. + * Included by CM_ENC_HEX.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_ENC_HEX_H +#define CM_ENC_HEX_H + +#include "cm_svc.h" + +void CM_ENC_HEX(TPSVCINFO *p); + +#endif /* CM_ENC_HEX_H */ diff --git a/app/src/cm/svc/CM_ENC_HEX.pgc b/app/src/cm/svc/CM_ENC_HEX.pgc new file mode 100644 index 0000000..e4323a6 --- /dev/null +++ b/app/src/cm/svc/CM_ENC_HEX.pgc @@ -0,0 +1,50 @@ +/* + * CM_ENC_HEX.pgc - cm 모듈 서비스 CM_ENC_HEX (one service = one file). + * 간이 암복호화(암호화 유틸 세분, 순수 계산): 키 문자열을 순환시키며 XOR 한 + * 결과를 HEX 로 인코딩(T_ARG1=0)하거나, HEX 를 디코딩 후 XOR 로 복원 + * (T_ARG1=1)한다. 저장용 난독화 수준의 가역 변환이다. no EXEC SQL. + */ +#include "CM_ENC_HEX.h" + +/* ----- service-local pure helper(s) ----- */ +static int cm_hex_val(char c) +{ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + return -1; +} + +void CM_ENC_HEX(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char in[128], key[32], out[260]; + long mode; + int i, klen, hi, lo; + + gets_(b, T_STR1, in, sizeof(in)); + gets_(b, T_KEY1, key, sizeof(key)); + mode = getl(b, T_ARG1); + if (in[0] == 0) FAIL(b); + if (key[0] == 0) strcpy(key, "CMKEY"); + klen = (int)strlen(key); + + if (mode == 0) { /* 암호화: XOR -> HEX */ + for (i = 0; in[i]; i++) + sprintf(out + i * 2, "%02X", (unsigned char)(in[i] ^ key[i % klen])); + out[i * 2] = 0; + } else { /* 복호화: HEX -> XOR */ + int len = (int)strlen(in); + if (len % 2 != 0) { setl(b, T_RC, 2); FAIL(b); } + for (i = 0; i < len / 2; i++) { + hi = cm_hex_val(in[i * 2]); + lo = cm_hex_val(in[i * 2 + 1]); + if (hi < 0 || lo < 0) { setl(b, T_RC, 3); FAIL(b); } /* HEX 오류 */ + out[i] = (char)(((hi << 4) | lo) ^ key[i % klen]); + } + out[i] = 0; + } + sets_(b, T_STR4, out); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/cm/svc/CM_FILE_CKSUM.h b/app/src/cm/svc/CM_FILE_CKSUM.h new file mode 100644 index 0000000..d14eedf --- /dev/null +++ b/app/src/cm/svc/CM_FILE_CKSUM.h @@ -0,0 +1,13 @@ +/* + * CM_FILE_CKSUM.h - cm (공통) 서비스 CM_FILE_CKSUM 카피북 (copybook / record header). + * 파일 체크섬: Adler-32 체크섬과 바이트 합계를 계산한다. + * Included by CM_FILE_CKSUM.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_FILE_CKSUM_H +#define CM_FILE_CKSUM_H + +#include "cm_svc.h" + +void CM_FILE_CKSUM(TPSVCINFO *p); + +#endif /* CM_FILE_CKSUM_H */ diff --git a/app/src/cm/svc/CM_FILE_CKSUM.pgc b/app/src/cm/svc/CM_FILE_CKSUM.pgc new file mode 100644 index 0000000..f2db447 --- /dev/null +++ b/app/src/cm/svc/CM_FILE_CKSUM.pgc @@ -0,0 +1,30 @@ +/* + * CM_FILE_CKSUM.pgc - cm 모듈 서비스 CM_FILE_CKSUM (one service = one file). + * 파일 체크섬(순수 계산): 파일 내용 문자열에 대해 Adler-32 체크섬(mod 65521 + * 이중 누적)과 단순 바이트 합계를 계산한다. 인터페이스 파일 무결성 대사용. + * no EXEC SQL; advertised by cm_svr at tpsvrinit. + */ +#include "CM_FILE_CKSUM.h" + +void CM_FILE_CKSUM(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char data[512], hex[16]; + unsigned long a = 1, s2 = 0, bytesum = 0; + int i; + + gets_(b, T_STR1, data, sizeof(data)); + if (data[0] == 0) FAIL(b); + + for (i = 0; data[i]; i++) { + a = (a + (unsigned char)data[i]) % 65521UL; /* Adler-32 하위 누적 */ + s2 = (s2 + a) % 65521UL; /* Adler-32 상위 누적 */ + bytesum += (unsigned char)data[i]; + } + sprintf(hex, "%08lX", (s2 << 16) | a); + sets_(b, T_STR4, hex); /* Adler-32 (HEX) */ + setl(b, T_ARG1, (long)((s2 << 16) | a)); + setl(b, T_ARG2, (long)bytesum); /* 단순 바이트 합 */ + setl(b, T_COUNT, i); /* 길이 */ + OK(b); +} diff --git a/app/src/cm/svc/CM_FX_AGE.h b/app/src/cm/svc/CM_FX_AGE.h new file mode 100644 index 0000000..eff648e --- /dev/null +++ b/app/src/cm/svc/CM_FX_AGE.h @@ -0,0 +1,13 @@ +/* + * CM_FX_AGE.h - cm (공통) 서비스 CM_FX_AGE 카피북 (copybook / record header). + * 환율 최신성 조회: 통화의 마지막 고시일 경과일수를 조회한다. + * Included by CM_FX_AGE.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_FX_AGE_H +#define CM_FX_AGE_H + +#include "cm_svc.h" + +void CM_FX_AGE(TPSVCINFO *p); + +#endif /* CM_FX_AGE_H */ diff --git a/app/src/cm/svc/CM_FX_AGE.pgc b/app/src/cm/svc/CM_FX_AGE.pgc new file mode 100644 index 0000000..d0c5134 --- /dev/null +++ b/app/src/cm/svc/CM_FX_AGE.pgc @@ -0,0 +1,35 @@ +/* + * CM_FX_AGE.pgc - cm 모듈 서비스 CM_FX_AGE (one service = one file). + * 환율 최신성 조회: 통화의 마지막 고시일과 오늘 사이 경과일수를 DB 날짜 + * 연산으로 구한다. 고시 이력이 아예 없으면 실패, 경과일수가 한도(T_ARG2, + * 기본 7일)를 넘으면 STALE 판정(RC=1). runs on the caller's XA branch. + */ +#include "CM_FX_AGE.h" + +void CM_FX_AGE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long limit; + EXEC SQL BEGIN DECLARE SECTION; + char h_ccy[8], h_last[16]; + long h_age, h_n; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CCY, h_ccy, sizeof(h_ccy)); + if (h_ccy[0] == 0) FAIL(b); + limit = getl(b, T_ARG2); + if (limit <= 0) limit = 7; + + EXEC SQL SELECT count(*), + coalesce((current_date - max(base_date))::int, -1), + coalesce(max(base_date)::text, '') + INTO :h_n, :h_age, :h_last + FROM cm_fx_rate WHERE ccy = :h_ccy; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_n == 0) { setl(b, T_RC, 2); FAIL(b); } /* 고시 이력 없음 */ + + setl(b, T_ARG1, h_age); + sets_(b, T_STR4, h_last); + setl(b, T_RC, h_age > limit ? 1 : 0); /* 1=STALE */ + OK(b); +} diff --git a/app/src/cm/svc/CM_FX_CROSS.h b/app/src/cm/svc/CM_FX_CROSS.h new file mode 100644 index 0000000..6ada6eb --- /dev/null +++ b/app/src/cm/svc/CM_FX_CROSS.h @@ -0,0 +1,13 @@ +/* + * CM_FX_CROSS.h - cm (공통) 서비스 CM_FX_CROSS 카피북 (copybook / record header). + * 크로스 환율 계산: 두 통화의 원화 환율로 크로스 레이트를 계산한다. + * Included by CM_FX_CROSS.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_FX_CROSS_H +#define CM_FX_CROSS_H + +#include "cm_svc.h" + +void CM_FX_CROSS(TPSVCINFO *p); + +#endif /* CM_FX_CROSS_H */ diff --git a/app/src/cm/svc/CM_FX_CROSS.pgc b/app/src/cm/svc/CM_FX_CROSS.pgc new file mode 100644 index 0000000..685eb9b --- /dev/null +++ b/app/src/cm/svc/CM_FX_CROSS.pgc @@ -0,0 +1,31 @@ +/* + * CM_FX_CROSS.pgc - cm 모듈 서비스 CM_FX_CROSS (one service = one file). + * 크로스 환율 계산: 두 통화의 같은 기준일 원화 환율을 조회해 ccy1 1단위당 + * ccy2 수량을 bps(x10000) 정밀도로 계산한다(예: USD/JPY). 분모 통화가 0 + * 이면 실패. EXEC SQL 조회 + 순수 계산 혼합. runs on the caller's XA branch. + */ +#include "CM_FX_CROSS.h" + +void CM_FX_CROSS(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char c1[8], c2[8], bizdate[16]; + long r1, r2, cross; + gets_(b, T_CCY, c1, sizeof(c1)); + gets_(b, T_STR1, c2, sizeof(c2)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (c1[0] == 0 || c2[0] == 0) FAIL(b); + if (bizdate[0] == 0) { if (cmdb_get_param("BIZ_DATE", bizdate) < 0) strcpy(bizdate, "2026-07-19"); } + if (strcmp(c1, c2) == 0) { /* 동일 통화 = 1.0000 */ + setl(b, T_ARG1, 10000L); + OK(b); + } + if (cmdb_get_fx_rate(c1, bizdate, &r1) < 0) { setl(b, T_RC, 1); FAIL(b); } + if (cmdb_get_fx_rate(c2, bizdate, &r2) < 0) { setl(b, T_RC, 2); FAIL(b); } + if (r2 == 0) FAIL(b); + cross = r1 * 10000L / r2; /* ccy1/ccy2 x10000 */ + setl(b, T_ARG1, cross); + setl(b, T_AMT1, r1); + setl(b, T_AMT2, r2); + OK(b); +} diff --git a/app/src/cm/svc/CM_FX_HIST.h b/app/src/cm/svc/CM_FX_HIST.h new file mode 100644 index 0000000..c836e84 --- /dev/null +++ b/app/src/cm/svc/CM_FX_HIST.h @@ -0,0 +1,13 @@ +/* + * CM_FX_HIST.h - cm (공통) 서비스 CM_FX_HIST 카피북 (copybook / record header). + * 환율 이력 조회: 기간 내 고시 건수와 최소/최대 환율을 조회한다. + * Included by CM_FX_HIST.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_FX_HIST_H +#define CM_FX_HIST_H + +#include "cm_svc.h" + +void CM_FX_HIST(TPSVCINFO *p); + +#endif /* CM_FX_HIST_H */ diff --git a/app/src/cm/svc/CM_FX_HIST.pgc b/app/src/cm/svc/CM_FX_HIST.pgc new file mode 100644 index 0000000..984536c --- /dev/null +++ b/app/src/cm/svc/CM_FX_HIST.pgc @@ -0,0 +1,36 @@ +/* + * CM_FX_HIST.pgc - cm 모듈 서비스 CM_FX_HIST (one service = one file). + * 환율 이력 조회: 통화의 기간(from~to) 내 고시 건수, 최소/최대 환율, 마지막 + * 고시일을 한 번에 집계한다. 기간에 고시가 전혀 없으면 not-found 실패. + * runs on the caller's XA branch; advertised by cm_svr at tpsvrinit. + */ +#include "CM_FX_HIST.h" + +void CM_FX_HIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_ccy[8], h_from[16], h_to[16], h_lastday[16]; + long h_cnt, h_lo, h_hi; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CCY, h_ccy, sizeof(h_ccy)); + gets_(b, T_STR1, h_from, sizeof(h_from)); + gets_(b, T_STR2, h_to, sizeof(h_to)); + if (h_ccy[0] == 0 || h_from[0] == 0 || h_to[0] == 0) FAIL(b); + if (strcmp(h_from, h_to) > 0) { setl(b, T_RC, 2); FAIL(b); } /* 기간 역전 */ + + EXEC SQL SELECT count(*), coalesce(min(rate_bps), 0), coalesce(max(rate_bps), 0), + coalesce(max(base_date)::text, '') + INTO :h_cnt, :h_lo, :h_hi, :h_lastday + FROM cm_fx_rate + WHERE ccy = :h_ccy AND base_date BETWEEN :h_from AND :h_to; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt == 0) { setl(b, T_RC, 1); FAIL(b); } + + setl(b, T_COUNT, h_cnt); + setl(b, T_AMT1, h_lo); + setl(b, T_AMT2, h_hi); + sets_(b, T_STR4, h_lastday); + OK(b); +} diff --git a/app/src/cm/svc/CM_FX_PUBLISH.h b/app/src/cm/svc/CM_FX_PUBLISH.h new file mode 100644 index 0000000..6d7c68f --- /dev/null +++ b/app/src/cm/svc/CM_FX_PUBLISH.h @@ -0,0 +1,13 @@ +/* + * CM_FX_PUBLISH.h - cm (공통) 서비스 CM_FX_PUBLISH 카피북 (copybook / record header). + * 환율 고시: 기준일 통화 환율을 고시(upsert)하고 고시 이력을 남긴다. + * Included by CM_FX_PUBLISH.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_FX_PUBLISH_H +#define CM_FX_PUBLISH_H + +#include "cm_svc.h" + +void CM_FX_PUBLISH(TPSVCINFO *p); + +#endif /* CM_FX_PUBLISH_H */ diff --git a/app/src/cm/svc/CM_FX_PUBLISH.pgc b/app/src/cm/svc/CM_FX_PUBLISH.pgc new file mode 100644 index 0000000..d3101bc --- /dev/null +++ b/app/src/cm/svc/CM_FX_PUBLISH.pgc @@ -0,0 +1,40 @@ +/* + * CM_FX_PUBLISH.pgc - cm 모듈 서비스 CM_FX_PUBLISH (one service = one file). + * 환율 고시: 기준일/통화의 환율(rate_bps>0)을 고시한다. 이미 고시된 날이면 + * 재고시(UPDATE), 아니면 신규 INSERT. 재고시 여부와 고시 이력을 남긴다. + * runs on the caller's XA branch; advertised by cm_svr at tpsvrinit. + */ +#include "CM_FX_PUBLISH.h" + +void CM_FX_PUBLISH(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char hist[128]; + long repub = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_ccy[8], h_date[16]; + long h_rate; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CCY, h_ccy, sizeof(h_ccy)); + gets_(b, T_BIZDATE, h_date, sizeof(h_date)); + h_rate = getl(b, T_ARG1); + if (h_ccy[0] == 0 || h_date[0] == 0) FAIL(b); + if (h_rate <= 0) { setl(b, T_RC, 2); FAIL(b); } /* 0 이하 환율 고시 불가 */ + + EXEC SQL UPDATE cm_fx_rate SET rate_bps = :h_rate, updated_at = now() + WHERE ccy = :h_ccy AND base_date = :h_date; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlerrd[2] > 0) { + repub = 1; + } else { + EXEC SQL INSERT INTO cm_fx_rate (ccy, base_date, rate_bps) + VALUES (:h_ccy, :h_date, :h_rate); + if (sqlca.sqlcode < 0) FAIL(b); + } + snprintf(hist, sizeof(hist), "FXPUB %s %s %ld%s", h_ccy, h_date, h_rate, + repub ? " (REPUB)" : ""); + cmdb_log("FXPUB", repub ? "W" : "I", hist, h_date); + setl(b, T_RC, repub); + OK(b); +} diff --git a/app/src/cm/svc/CM_FX_VALID.h b/app/src/cm/svc/CM_FX_VALID.h new file mode 100644 index 0000000..0ab1553 --- /dev/null +++ b/app/src/cm/svc/CM_FX_VALID.h @@ -0,0 +1,13 @@ +/* + * CM_FX_VALID.h - cm (공통) 서비스 CM_FX_VALID 카피북 (copybook / record header). + * 환율 유효성 검증: 직전 고시 대비 변동폭(bps)이 허용 한도 이내인지 검증한다. + * Included by CM_FX_VALID.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_FX_VALID_H +#define CM_FX_VALID_H + +#include "cm_svc.h" + +void CM_FX_VALID(TPSVCINFO *p); + +#endif /* CM_FX_VALID_H */ diff --git a/app/src/cm/svc/CM_FX_VALID.pgc b/app/src/cm/svc/CM_FX_VALID.pgc new file mode 100644 index 0000000..6e76209 --- /dev/null +++ b/app/src/cm/svc/CM_FX_VALID.pgc @@ -0,0 +1,39 @@ +/* + * CM_FX_VALID.pgc - cm 모듈 서비스 CM_FX_VALID (one service = one file). + * 환율 유효성 검증: 입력 환율을 해당 통화의 직전 고시가와 비교해 변동폭(bps) + * 을 계산하고, 허용 한도(파라미터 FX_TOL_BPS, 기본 1000bps=10%)를 넘으면 + * 이상 고시로 판정한다(RC=1). 직전 고시가 없으면 최초 고시로 통과(RC=0). + */ +#include "CM_FX_VALID.h" + +void CM_FX_VALID(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char tol_s[128]; + long tol = 1000, move_bps = 0, verdict = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_ccy[8], h_date[16]; + long h_prev, h_new; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CCY, h_ccy, sizeof(h_ccy)); + gets_(b, T_BIZDATE, h_date, sizeof(h_date)); + h_new = getl(b, T_ARG1); + if (h_ccy[0] == 0 || h_date[0] == 0 || h_new <= 0) FAIL(b); + if (cmdb_get_param("FX_TOL_BPS", tol_s) == 0) tol = atol(tol_s); + if (tol <= 0) tol = 1000; + + EXEC SQL SELECT rate_bps INTO :h_prev FROM cm_fx_rate + WHERE ccy = :h_ccy AND base_date < :h_date + ORDER BY base_date DESC LIMIT 1; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 0 && h_prev > 0) { + move_bps = (h_new - h_prev) * 10000L / h_prev; + if (move_bps < 0) move_bps = -move_bps; + if (move_bps > tol) verdict = 1; /* 허용 한도 초과 */ + } + setl(b, T_ARG2, move_bps); + setl(b, T_ARG3, tol); + setl(b, T_RC, verdict); + OK(b); +} diff --git a/app/src/cm/svc/CM_HEALTH_DB.h b/app/src/cm/svc/CM_HEALTH_DB.h new file mode 100644 index 0000000..8503539 --- /dev/null +++ b/app/src/cm/svc/CM_HEALTH_DB.h @@ -0,0 +1,13 @@ +/* + * CM_HEALTH_DB.h - cm (공통) 서비스 CM_HEALTH_DB 카피북 (copybook / record header). + * 시스템 헬스(DB): DB 왕복과 서버일자-영업일 일치 여부를 점검한다. + * Included by CM_HEALTH_DB.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_HEALTH_DB_H +#define CM_HEALTH_DB_H + +#include "cm_svc.h" + +void CM_HEALTH_DB(TPSVCINFO *p); + +#endif /* CM_HEALTH_DB_H */ diff --git a/app/src/cm/svc/CM_HEALTH_DB.pgc b/app/src/cm/svc/CM_HEALTH_DB.pgc new file mode 100644 index 0000000..4c6f3ba --- /dev/null +++ b/app/src/cm/svc/CM_HEALTH_DB.pgc @@ -0,0 +1,31 @@ +/* + * CM_HEALTH_DB.pgc - cm 모듈 서비스 CM_HEALTH_DB (one service = one file). + * 시스템 헬스(DB 세분): DB 왕복(SELECT)으로 연결을 확인하고 DB 서버 일자와 + * 운영 영업일(BIZ_DATE)의 일치 여부를 점검한다. 불일치는 성공으로 두되 + * 드리프트 플래그(T_ARG2=1)로 알린다. runs on the caller's XA branch. + */ +#include "CM_HEALTH_DB.h" + +void CM_HEALTH_DB(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[128]; + long drift = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_today[16], h_now[32]; + EXEC SQL END DECLARE SECTION; + + EXEC SQL SELECT current_date::text, to_char(now(), 'HH24:MI:SS') + INTO :h_today, :h_now; + if (sqlca.sqlcode < 0) { sets_(b, T_STATUS, "DOWN"); FAIL(b); } + + if (cmdb_get_param("BIZ_DATE", bizdate) == 0 && strcmp(bizdate, h_today) != 0) + drift = 1; /* 영업일-서버일 불일치 */ + + sets_(b, T_STATUS, "UP"); + sets_(b, T_STR4, h_today); + sets_(b, T_STR5, h_now); + setl(b, T_ARG2, drift); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/cm/svc/CM_HEALTH_LOG.h b/app/src/cm/svc/CM_HEALTH_LOG.h new file mode 100644 index 0000000..675822d --- /dev/null +++ b/app/src/cm/svc/CM_HEALTH_LOG.h @@ -0,0 +1,13 @@ +/* + * CM_HEALTH_LOG.h - cm (공통) 서비스 CM_HEALTH_LOG 카피북 (copybook / record header). + * 시스템 헬스(로그): 금일 로그량과 오류비율로 등급(G/W/C)을 판정한다. + * Included by CM_HEALTH_LOG.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_HEALTH_LOG_H +#define CM_HEALTH_LOG_H + +#include "cm_svc.h" + +void CM_HEALTH_LOG(TPSVCINFO *p); + +#endif /* CM_HEALTH_LOG_H */ diff --git a/app/src/cm/svc/CM_HEALTH_LOG.pgc b/app/src/cm/svc/CM_HEALTH_LOG.pgc new file mode 100644 index 0000000..fd042a4 --- /dev/null +++ b/app/src/cm/svc/CM_HEALTH_LOG.pgc @@ -0,0 +1,36 @@ +/* + * CM_HEALTH_LOG.pgc - cm 모듈 서비스 CM_HEALTH_LOG (one service = one file). + * 시스템 헬스(로그 세분): 금일 로그 총량과 오류(E) 비율(bps)을 계산해 등급을 + * 판정한다. 오류율 2000bps 이상 C(위험), 500bps 이상 W(주의), 그 외 G(정상). + * runs on the caller's XA branch; advertised by cm_svr at tpsvrinit. + */ +#include "CM_HEALTH_LOG.h" + +void CM_HEALTH_LOG(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char grade[2] = "G"; + long ratio = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_date[16]; + long h_total, h_err; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_date, sizeof(h_date)); + if (h_date[0] == 0) { if (cmdb_get_param("BIZ_DATE", h_date) < 0) strcpy(h_date, "2026-07-19"); } + + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN log_level = 'E' THEN 1 ELSE 0 END), 0) + INTO :h_total, :h_err + FROM cm_log WHERE biz_date = :h_date; + if (sqlca.sqlcode < 0) FAIL(b); + + if (h_total > 0) ratio = h_err * 10000L / h_total; + if (ratio >= 2000) grade[0] = 'C'; + else if (ratio >= 500) grade[0] = 'W'; + sets_(b, T_STATUS, grade); + setl(b, T_COUNT, h_total); + setl(b, T_ARG1, h_err); + setl(b, T_ARG2, ratio); + OK(b); +} diff --git a/app/src/cm/svc/CM_HEALTH_SEQ.h b/app/src/cm/svc/CM_HEALTH_SEQ.h new file mode 100644 index 0000000..2275aaf --- /dev/null +++ b/app/src/cm/svc/CM_HEALTH_SEQ.h @@ -0,0 +1,13 @@ +/* + * CM_HEALTH_SEQ.h - cm (공통) 서비스 CM_HEALTH_SEQ 카피북 (copybook / record header). + * 시스템 헬스(채번): 카운터 수/최대값/음수 카운터 여부를 점검한다. + * Included by CM_HEALTH_SEQ.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_HEALTH_SEQ_H +#define CM_HEALTH_SEQ_H + +#include "cm_svc.h" + +void CM_HEALTH_SEQ(TPSVCINFO *p); + +#endif /* CM_HEALTH_SEQ_H */ diff --git a/app/src/cm/svc/CM_HEALTH_SEQ.pgc b/app/src/cm/svc/CM_HEALTH_SEQ.pgc new file mode 100644 index 0000000..99ec611 --- /dev/null +++ b/app/src/cm/svc/CM_HEALTH_SEQ.pgc @@ -0,0 +1,32 @@ +/* + * CM_HEALTH_SEQ.pgc - cm 모듈 서비스 CM_HEALTH_SEQ (one service = one file). + * 시스템 헬스(채번 세분): 카운터 개수/최대값과 음수로 굴러간 비정상 카운터 + * 수를 점검한다. 음수 카운터가 있으면 상태 WARN, 카운터가 하나도 없으면 + * EMPTY 로 보고한다. runs on the caller's XA branch. + */ +#include "CM_HEALTH_SEQ.h" + +void CM_HEALTH_SEQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + const char *state; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_max, h_neg; + EXEC SQL END DECLARE SECTION; + + EXEC SQL SELECT count(*), coalesce(max(seq_val), 0), + coalesce(sum(CASE WHEN seq_val < 0 THEN 1 ELSE 0 END), 0) + INTO :h_cnt, :h_max, :h_neg + FROM cm_seq; + if (sqlca.sqlcode < 0) FAIL(b); + + if (h_cnt == 0) state = "EMPTY"; + else if (h_neg > 0) state = "WARN"; + else state = "OK"; + sets_(b, T_STATUS, state); + setl(b, T_COUNT, h_cnt); + setl(b, T_ARG1, h_max); + setl(b, T_ARG2, h_neg); + setl(b, T_RC, h_neg > 0 ? 1 : 0); + OK(b); +} diff --git a/app/src/cm/svc/CM_HOLIDAY_CNT.h b/app/src/cm/svc/CM_HOLIDAY_CNT.h new file mode 100644 index 0000000..c8c7346 --- /dev/null +++ b/app/src/cm/svc/CM_HOLIDAY_CNT.h @@ -0,0 +1,13 @@ +/* + * CM_HOLIDAY_CNT.h - cm (공통) 서비스 CM_HOLIDAY_CNT 카피북 (copybook / record header). + * 기간 휴일 조회: 기간 내 휴일 수와 첫 휴일을 조회한다. + * Included by CM_HOLIDAY_CNT.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_HOLIDAY_CNT_H +#define CM_HOLIDAY_CNT_H + +#include "cm_svc.h" + +void CM_HOLIDAY_CNT(TPSVCINFO *p); + +#endif /* CM_HOLIDAY_CNT_H */ diff --git a/app/src/cm/svc/CM_HOLIDAY_CNT.pgc b/app/src/cm/svc/CM_HOLIDAY_CNT.pgc new file mode 100644 index 0000000..05a897d --- /dev/null +++ b/app/src/cm/svc/CM_HOLIDAY_CNT.pgc @@ -0,0 +1,35 @@ +/* + * CM_HOLIDAY_CNT.pgc - cm 모듈 서비스 CM_HOLIDAY_CNT (one service = one file). + * 기간 휴일 조회(캘린더 조회 세분): 국가별로 기간 내 휴일 수와 첫/마지막 + * 휴일 날짜를 집계한다. 조회 전용이라 이력을 남기지 않는다. runs on the + * caller's XA branch (no EXEC SQL CONNECT); advertised by cm_svr. + */ +#include "CM_HOLIDAY_CNT.h" + +void CM_HOLIDAY_CNT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_from[16], h_to[16], h_nat[8], h_first[16], h_last[16]; + long h_cnt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_STR1, h_from, sizeof(h_from)); + gets_(b, T_STR2, h_to, sizeof(h_to)); + gets_(b, T_STR3, h_nat, sizeof(h_nat)); + if (h_from[0] == 0 || h_to[0] == 0) FAIL(b); + if (h_nat[0] == 0) strcpy(h_nat, "KR"); + + EXEC SQL SELECT count(*), + coalesce(min(holi_date)::text, ''), + coalesce(max(holi_date)::text, '') + INTO :h_cnt, :h_first, :h_last + FROM cm_holiday + WHERE nation = :h_nat AND holi_date >= :h_from AND holi_date <= :h_to; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + sets_(b, T_STR4, h_first); + sets_(b, T_STR5, h_last); + OK(b); +} diff --git a/app/src/cm/svc/CM_HOLIDAY_DEL.h b/app/src/cm/svc/CM_HOLIDAY_DEL.h new file mode 100644 index 0000000..3cfb446 --- /dev/null +++ b/app/src/cm/svc/CM_HOLIDAY_DEL.h @@ -0,0 +1,13 @@ +/* + * CM_HOLIDAY_DEL.h - cm (공통) 서비스 CM_HOLIDAY_DEL 카피북 (copybook / record header). + * 휴일 삭제: 캘린더에서 휴일을 삭제하고 삭제 이력을 남긴다. + * Included by CM_HOLIDAY_DEL.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_HOLIDAY_DEL_H +#define CM_HOLIDAY_DEL_H + +#include "cm_svc.h" + +void CM_HOLIDAY_DEL(TPSVCINFO *p); + +#endif /* CM_HOLIDAY_DEL_H */ diff --git a/app/src/cm/svc/CM_HOLIDAY_DEL.pgc b/app/src/cm/svc/CM_HOLIDAY_DEL.pgc new file mode 100644 index 0000000..15b74ae --- /dev/null +++ b/app/src/cm/svc/CM_HOLIDAY_DEL.pgc @@ -0,0 +1,33 @@ +/* + * CM_HOLIDAY_DEL.pgc - cm 모듈 서비스 CM_HOLIDAY_DEL (one service = one file). + * 휴일 삭제(캘린더 관리): 삭제 전에 휴일명을 읽어 이력에 남기고 cm_holiday + * 에서 제거한다. 과거일 삭제는 캘린더 정합성을 위해 금지한다(RC=3). + * runs on the caller's XA branch; advertised by cm_svr at tpsvrinit. + */ +#include "CM_HOLIDAY_DEL.h" + +void CM_HOLIDAY_DEL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char today[128], hist[160]; + EXEC SQL BEGIN DECLARE SECTION; + char h_date[16], h_name[80]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_STR1, h_date, sizeof(h_date)); + if (h_date[0] == 0) FAIL(b); + if (cmdb_get_param("BIZ_DATE", today) == 0 && strcmp(h_date, today) < 0) + { setl(b, T_RC, 3); FAIL(b); } /* 과거 휴일 삭제 금지 */ + + EXEC SQL SELECT holi_name INTO :h_name FROM cm_holiday WHERE holi_date = :h_date; + if (sqlca.sqlcode != 0) { setl(b, T_RC, 1); FAIL(b); } /* 미등록 */ + + EXEC SQL DELETE FROM cm_holiday WHERE holi_date = :h_date; + if (sqlca.sqlcode < 0 || sqlca.sqlerrd[2] == 0) FAIL(b); + + snprintf(hist, sizeof(hist), "CAL DEL %s %s", h_date, h_name); + cmdb_log("CAL", "W", hist, h_date); + sets_(b, T_STR4, h_name); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/cm/svc/CM_HOLIDAY_REG.h b/app/src/cm/svc/CM_HOLIDAY_REG.h new file mode 100644 index 0000000..bb33135 --- /dev/null +++ b/app/src/cm/svc/CM_HOLIDAY_REG.h @@ -0,0 +1,13 @@ +/* + * CM_HOLIDAY_REG.h - cm (공통) 서비스 CM_HOLIDAY_REG 카피북 (copybook / record header). + * 휴일 등록: 캘린더에 휴일을 등록하고 등록 이력을 남긴다. + * Included by CM_HOLIDAY_REG.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_HOLIDAY_REG_H +#define CM_HOLIDAY_REG_H + +#include "cm_svc.h" + +void CM_HOLIDAY_REG(TPSVCINFO *p); + +#endif /* CM_HOLIDAY_REG_H */ diff --git a/app/src/cm/svc/CM_HOLIDAY_REG.pgc b/app/src/cm/svc/CM_HOLIDAY_REG.pgc new file mode 100644 index 0000000..77c6ab3 --- /dev/null +++ b/app/src/cm/svc/CM_HOLIDAY_REG.pgc @@ -0,0 +1,39 @@ +/* + * CM_HOLIDAY_REG.pgc - cm 모듈 서비스 CM_HOLIDAY_REG (one service = one file). + * 휴일 등록(캘린더 관리): 날짜 형식을 검증하고 중복 등록을 거른 뒤 cm_holiday + * 에 휴일을 넣는다. 국가코드는 기본 KR. 등록 이력을 CAL 로그로 남긴다. + * runs on the caller's XA branch; advertised by cm_svr at tpsvrinit. + */ +#include "CM_HOLIDAY_REG.h" + +void CM_HOLIDAY_REG(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char hist[160]; + int y, m, d; + EXEC SQL BEGIN DECLARE SECTION; + char h_date[16], h_name[80], h_nat[8]; + long h_dup; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_STR1, h_date, sizeof(h_date)); + gets_(b, T_STR2, h_name, sizeof(h_name)); + gets_(b, T_STR3, h_nat, sizeof(h_nat)); + if (h_name[0] == 0) FAIL(b); + if (sscanf(h_date, "%d-%d-%d", &y, &m, &d) != 3 || m < 1 || m > 12 || d < 1 || d > 31) + { setl(b, T_RC, 2); FAIL(b); } /* 날짜 형식 오류 */ + if (h_nat[0] == 0) strcpy(h_nat, "KR"); + + EXEC SQL SELECT count(*) INTO :h_dup FROM cm_holiday WHERE holi_date = :h_date; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_dup != 0) { setl(b, T_RC, 1); FAIL(b); } /* 이미 등록됨 */ + + EXEC SQL INSERT INTO cm_holiday (holi_date, holi_name, nation) + VALUES (:h_date, :h_name, :h_nat); + if (sqlca.sqlcode < 0) FAIL(b); + + snprintf(hist, sizeof(hist), "CAL REG %s %s (%s)", h_date, h_name, h_nat); + cmdb_log("CAL", "I", hist, h_date); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/cm/svc/CM_MASK_CARD.h b/app/src/cm/svc/CM_MASK_CARD.h new file mode 100644 index 0000000..9db7524 --- /dev/null +++ b/app/src/cm/svc/CM_MASK_CARD.h @@ -0,0 +1,13 @@ +/* + * CM_MASK_CARD.h - cm (공통) 서비스 CM_MASK_CARD 카피북 (copybook / record header). + * 카드번호 마스킹: 숫자 검증 후 앞6/뒤4만 노출하고 자릿수를 반환한다. + * Included by CM_MASK_CARD.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_MASK_CARD_H +#define CM_MASK_CARD_H + +#include "cm_svc.h" + +void CM_MASK_CARD(TPSVCINFO *p); + +#endif /* CM_MASK_CARD_H */ diff --git a/app/src/cm/svc/CM_MASK_CARD.pgc b/app/src/cm/svc/CM_MASK_CARD.pgc new file mode 100644 index 0000000..a4f8da4 --- /dev/null +++ b/app/src/cm/svc/CM_MASK_CARD.pgc @@ -0,0 +1,31 @@ +/* + * CM_MASK_CARD.pgc - cm 모듈 서비스 CM_MASK_CARD (one service = one file). + * 카드번호 마스킹(마스킹 세분, 순수 계산): 전체가 숫자(13~19자리)인지 먼저 + * 검증하고, 앞 6자리(BIN)와 뒤 4자리만 남긴 채 가운데를 '*' 처리한다. + * 마스킹된 자릿수를 함께 반환한다. no EXEC SQL; advertised by cm_svr. + */ +#include "CM_MASK_CARD.h" + +void CM_MASK_CARD(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char pan[32], out[32]; + int len = 0, i, hidden = 0; + + gets_(b, T_STR1, pan, sizeof(pan)); + while (pan[len]) { + if (pan[len] < '0' || pan[len] > '9') { setl(b, T_RC, 1); FAIL(b); } /* 숫자 아님 */ + len++; + } + if (len < 13 || len > 19) { setl(b, T_RC, 2); FAIL(b); } /* 길이 오류 */ + + for (i = 0; i < len; i++) { + if (i >= 6 && i < len - 4) { out[i] = '*'; hidden++; } + else out[i] = pan[i]; + } + out[len] = 0; + sets_(b, T_STR4, out); + setl(b, T_ARG1, hidden); /* 마스킹된 자릿수 */ + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/cm/svc/CM_MASK_NAME.h b/app/src/cm/svc/CM_MASK_NAME.h new file mode 100644 index 0000000..bf4d2f3 --- /dev/null +++ b/app/src/cm/svc/CM_MASK_NAME.h @@ -0,0 +1,13 @@ +/* + * CM_MASK_NAME.h - cm (공통) 서비스 CM_MASK_NAME 카피북 (copybook / record header). + * 성명 마스킹: UTF-8 문자 단위로 첫 글자만 남기고 마스킹한다. + * Included by CM_MASK_NAME.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_MASK_NAME_H +#define CM_MASK_NAME_H + +#include "cm_svc.h" + +void CM_MASK_NAME(TPSVCINFO *p); + +#endif /* CM_MASK_NAME_H */ diff --git a/app/src/cm/svc/CM_MASK_NAME.pgc b/app/src/cm/svc/CM_MASK_NAME.pgc new file mode 100644 index 0000000..3e75a07 --- /dev/null +++ b/app/src/cm/svc/CM_MASK_NAME.pgc @@ -0,0 +1,45 @@ +/* + * CM_MASK_NAME.pgc - cm 모듈 서비스 CM_MASK_NAME (one service = one file). + * 성명 마스킹(마스킹 세분, 순수 계산): UTF-8 문자 단위로 첫 글자만 남기고 + * 나머지 글자를 글자당 '*' 하나로 치환한다(한글 3바이트 안전 처리). 글자 + * 수를 함께 반환한다. no EXEC SQL; advertised by cm_svr. + */ +#include "CM_MASK_NAME.h" + +/* ----- service-local pure helper(s) ----- */ +/* UTF-8 선두 바이트로 문자 바이트 길이 판정 */ +static int cm_u8_len(unsigned char c) +{ + if (c < 0x80) return 1; + if ((c & 0xE0) == 0xC0) return 2; + if ((c & 0xF0) == 0xE0) return 3; + if ((c & 0xF8) == 0xF0) return 4; + return 1; /* 깨진 바이트 = 1 */ +} + +void CM_MASK_NAME(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char name[64], out[64]; + int i = 0, o = 0, chars = 0, step; + + gets_(b, T_STR1, name, sizeof(name)); + if (name[0] == 0) FAIL(b); + + while (name[i] && o < (int)sizeof(out) - 4) { + step = cm_u8_len((unsigned char)name[i]); + if (chars == 0) { /* 첫 글자는 원문 유지 */ + memcpy(out + o, name + i, step); + o += step; + } else { + out[o++] = '*'; /* 이후 글자당 '*' 하나 */ + } + i += step; + chars++; + } + out[o] = 0; + if (chars < 2) { setl(b, T_RC, 1); FAIL(b); } /* 한 글자 성명은 마스킹 무의미 */ + sets_(b, T_STR4, out); + setl(b, T_ARG1, chars); /* 전체 글자 수 */ + OK(b); +} diff --git a/app/src/cm/svc/CM_MASK_PHONE.h b/app/src/cm/svc/CM_MASK_PHONE.h new file mode 100644 index 0000000..dec37ea --- /dev/null +++ b/app/src/cm/svc/CM_MASK_PHONE.h @@ -0,0 +1,13 @@ +/* + * CM_MASK_PHONE.h - cm (공통) 서비스 CM_MASK_PHONE 카피북 (copybook / record header). + * 전화번호 마스킹: 구분자를 보존하며 국번 이후 가운데 자리를 마스킹한다. + * Included by CM_MASK_PHONE.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_MASK_PHONE_H +#define CM_MASK_PHONE_H + +#include "cm_svc.h" + +void CM_MASK_PHONE(TPSVCINFO *p); + +#endif /* CM_MASK_PHONE_H */ diff --git a/app/src/cm/svc/CM_MASK_PHONE.pgc b/app/src/cm/svc/CM_MASK_PHONE.pgc new file mode 100644 index 0000000..d847d15 --- /dev/null +++ b/app/src/cm/svc/CM_MASK_PHONE.pgc @@ -0,0 +1,35 @@ +/* + * CM_MASK_PHONE.pgc - cm 모듈 서비스 CM_MASK_PHONE (one service = one file). + * 전화번호 마스킹(마스킹 세분, 순수 계산): 하이픈 등 구분자는 위치를 그대로 + * 보존하면서, 숫자 기준 앞 3자리(국번)와 뒤 4자리를 제외한 가운데 숫자만 + * '*' 처리한다(010-1234-5678 -> 010-****-5678). no EXEC SQL. + */ +#include "CM_MASK_PHONE.h" + +void CM_MASK_PHONE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char in[32], out[32]; + int i, digits = 0, seen = 0, n = 0; + + gets_(b, T_STR1, in, sizeof(in)); + for (i = 0; in[i]; i++) { /* 1차: 숫자 개수 세기 */ + if (in[i] >= '0' && in[i] <= '9') digits++; + else if (in[i] != '-' && in[i] != ' ') { setl(b, T_RC, 1); FAIL(b); } + n++; + } + if (digits < 9 || digits > 11) { setl(b, T_RC, 2); FAIL(b); } /* 자릿수 오류 */ + + for (i = 0; i < n; i++) { /* 2차: 가운데 숫자만 마스킹 */ + if (in[i] >= '0' && in[i] <= '9') { + seen++; + out[i] = (seen > 3 && seen <= digits - 4) ? '*' : in[i]; + } else { + out[i] = in[i]; /* 구분자 보존 */ + } + } + out[n] = 0; + sets_(b, T_STR4, out); + setl(b, T_ARG1, digits); + OK(b); +} diff --git a/app/src/cm/svc/CM_MSG_FALLBACK.h b/app/src/cm/svc/CM_MSG_FALLBACK.h new file mode 100644 index 0000000..1213e2c --- /dev/null +++ b/app/src/cm/svc/CM_MSG_FALLBACK.h @@ -0,0 +1,13 @@ +/* + * CM_MSG_FALLBACK.h - cm (공통) 서비스 CM_MSG_FALLBACK 카피북 (copybook / record header). + * 다국어 메시지 폴백 조회: 요청 언어에 없으면 한국어(KO)로 폴백한다. + * Included by CM_MSG_FALLBACK.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_MSG_FALLBACK_H +#define CM_MSG_FALLBACK_H + +#include "cm_svc.h" + +void CM_MSG_FALLBACK(TPSVCINFO *p); + +#endif /* CM_MSG_FALLBACK_H */ diff --git a/app/src/cm/svc/CM_MSG_FALLBACK.pgc b/app/src/cm/svc/CM_MSG_FALLBACK.pgc new file mode 100644 index 0000000..cc1f0b5 --- /dev/null +++ b/app/src/cm/svc/CM_MSG_FALLBACK.pgc @@ -0,0 +1,42 @@ +/* + * CM_MSG_FALLBACK.pgc - cm 모듈 서비스 CM_MSG_FALLBACK (one service = one file). + * 다국어 메시지 폴백 조회: 요청 언어 그룹에서 먼저 찾고, 없으면 기본 언어 + * (MSG_KO)로 폴백, 그마저 없으면 메시지 ID 자체를 돌려준다. 어떤 단계에서 + * 찾았는지 결과코드(0/1/2)로 알려준다. runs on the caller's XA branch. + */ +#include "CM_MSG_FALLBACK.h" + +void CM_MSG_FALLBACK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long stage = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_grp[16], h_id[32], h_text[128]; + EXEC SQL END DECLARE SECTION; + char lang[8]; + + gets_(b, T_STR1, lang, sizeof(lang)); + gets_(b, T_KEY1, h_id, sizeof(h_id)); + if (h_id[0] == 0) FAIL(b); + if (lang[0] == 0 || (lang[0] | 32) == 'k') strcpy(lang, "KO"); + snprintf(h_grp, sizeof(h_grp), "MSG_%c%c", + lang[0] & ~32, lang[1] ? (lang[1] & ~32) : 'O'); + + EXEC SQL SELECT code_name INTO :h_text FROM cm_code + WHERE code_grp = :h_grp AND code = :h_id AND use_yn = 'Y'; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode != 0) { + stage = 1; /* 기본 언어 폴백 */ + EXEC SQL SELECT code_name INTO :h_text FROM cm_code + WHERE code_grp = 'MSG_KO' AND code = :h_id AND use_yn = 'Y'; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode != 0) { + stage = 2; /* 최종 폴백: ID 그대로 */ + strncpy(h_text, h_id, sizeof(h_text) - 1); + h_text[sizeof(h_text) - 1] = 0; + } + } + sets_(b, T_MSG, h_text); + setl(b, T_RC, stage); + OK(b); +} diff --git a/app/src/cm/svc/CM_MSG_GET.h b/app/src/cm/svc/CM_MSG_GET.h new file mode 100644 index 0000000..c659e05 --- /dev/null +++ b/app/src/cm/svc/CM_MSG_GET.h @@ -0,0 +1,13 @@ +/* + * CM_MSG_GET.h - cm (공통) 서비스 CM_MSG_GET 카피북 (copybook / record header). + * 다국어 메시지 조회: 언어별 메시지 그룹(MSG_xx)에서 메시지를 조회한다. + * Included by CM_MSG_GET.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_MSG_GET_H +#define CM_MSG_GET_H + +#include "cm_svc.h" + +void CM_MSG_GET(TPSVCINFO *p); + +#endif /* CM_MSG_GET_H */ diff --git a/app/src/cm/svc/CM_MSG_GET.pgc b/app/src/cm/svc/CM_MSG_GET.pgc new file mode 100644 index 0000000..51812af --- /dev/null +++ b/app/src/cm/svc/CM_MSG_GET.pgc @@ -0,0 +1,33 @@ +/* + * CM_MSG_GET.pgc - cm 모듈 서비스 CM_MSG_GET (one service = one file). + * 다국어 메시지 조회: 언어코드(KO/EN/JA...)로 메시지 그룹 MSG_ 을 정해 + * cm_code 에서 사용중 메시지만 조회한다. 없으면 not-found 실패. runs on the + * caller's XA branch (no EXEC SQL CONNECT); advertised by cm_svr. + */ +#include "CM_MSG_GET.h" + +void CM_MSG_GET(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + int i; + EXEC SQL BEGIN DECLARE SECTION; + char h_grp[16], h_id[32], h_text[128]; + EXEC SQL END DECLARE SECTION; + char lang[8]; + + gets_(b, T_STR1, lang, sizeof(lang)); + gets_(b, T_KEY1, h_id, sizeof(h_id)); + if (h_id[0] == 0) FAIL(b); + if (lang[0] == 0) strcpy(lang, "KO"); + for (i = 0; lang[i]; i++) /* 언어코드 대문자 정규화 */ + if (lang[i] >= 'a' && lang[i] <= 'z') lang[i] -= 32; + snprintf(h_grp, sizeof(h_grp), "MSG_%.2s", lang); + + EXEC SQL SELECT code_name INTO :h_text FROM cm_code + WHERE code_grp = :h_grp AND code = :h_id AND use_yn = 'Y'; + if (sqlca.sqlcode != 0) { setl(b, T_RC, 1); FAIL(b); } + + sets_(b, T_MSG, h_text); + sets_(b, T_STR4, h_grp); + OK(b); +} diff --git a/app/src/cm/svc/CM_MSG_SET.h b/app/src/cm/svc/CM_MSG_SET.h new file mode 100644 index 0000000..4930061 --- /dev/null +++ b/app/src/cm/svc/CM_MSG_SET.h @@ -0,0 +1,13 @@ +/* + * CM_MSG_SET.h - cm (공통) 서비스 CM_MSG_SET 카피북 (copybook / record header). + * 다국어 메시지 등록/수정: 언어별 메시지를 upsert 한다. + * Included by CM_MSG_SET.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_MSG_SET_H +#define CM_MSG_SET_H + +#include "cm_svc.h" + +void CM_MSG_SET(TPSVCINFO *p); + +#endif /* CM_MSG_SET_H */ diff --git a/app/src/cm/svc/CM_MSG_SET.pgc b/app/src/cm/svc/CM_MSG_SET.pgc new file mode 100644 index 0000000..05aeb6f --- /dev/null +++ b/app/src/cm/svc/CM_MSG_SET.pgc @@ -0,0 +1,38 @@ +/* + * CM_MSG_SET.pgc - cm 모듈 서비스 CM_MSG_SET (one service = one file). + * 다국어 메시지 등록/수정(upsert): MSG_ 그룹에 UPDATE 를 먼저 시도하고 + * 대상이 없으면 INSERT 한다. 신규 여부를 결과코드로 알려준다. runs on the + * caller's XA branch (no EXEC SQL CONNECT); advertised by cm_svr. + */ +#include "CM_MSG_SET.h" + +void CM_MSG_SET(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long created = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_grp[16], h_id[32], h_text[128]; + EXEC SQL END DECLARE SECTION; + char lang[8]; + + gets_(b, T_STR1, lang, sizeof(lang)); + gets_(b, T_KEY1, h_id, sizeof(h_id)); + gets_(b, T_MSG, h_text, sizeof(h_text)); + if (h_id[0] == 0 || h_text[0] == 0) FAIL(b); + if (lang[0] == 0) strcpy(lang, "KO"); + if (lang[0] >= 'a') lang[0] -= 32; + if (lang[1] >= 'a') lang[1] -= 32; + snprintf(h_grp, sizeof(h_grp), "MSG_%.2s", lang); + + EXEC SQL UPDATE cm_code SET code_name = :h_text, use_yn = 'Y', updated_at = now() + WHERE code_grp = :h_grp AND code = :h_id; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlerrd[2] == 0) { + EXEC SQL INSERT INTO cm_code (code_grp, code, code_name, sort_ord, use_yn) + VALUES (:h_grp, :h_id, :h_text, 0, 'Y'); + if (sqlca.sqlcode < 0) FAIL(b); + created = 1; + } + setl(b, T_RC, created); /* 1=신규, 0=수정 */ + OK(b); +} diff --git a/app/src/cm/svc/CM_NOTI_HIST.h b/app/src/cm/svc/CM_NOTI_HIST.h new file mode 100644 index 0000000..264f75a --- /dev/null +++ b/app/src/cm/svc/CM_NOTI_HIST.h @@ -0,0 +1,13 @@ +/* + * CM_NOTI_HIST.h - cm (공통) 서비스 CM_NOTI_HIST 카피북 (copybook / record header). + * 알림 발송 이력: 기준일 채널별 발송 건수와 최근 발송 ID를 조회한다. + * Included by CM_NOTI_HIST.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_NOTI_HIST_H +#define CM_NOTI_HIST_H + +#include "cm_svc.h" + +void CM_NOTI_HIST(TPSVCINFO *p); + +#endif /* CM_NOTI_HIST_H */ diff --git a/app/src/cm/svc/CM_NOTI_HIST.pgc b/app/src/cm/svc/CM_NOTI_HIST.pgc new file mode 100644 index 0000000..5545819 --- /dev/null +++ b/app/src/cm/svc/CM_NOTI_HIST.pgc @@ -0,0 +1,35 @@ +/* + * CM_NOTI_HIST.pgc - cm 모듈 서비스 CM_NOTI_HIST (one service = one file). + * 알림 발송 이력: 기준일 NOTI 로그에서 채널 접두 패턴([SMS>...] 등)으로 발송 + * 건수와 마지막 발송 ID/내용을 조회한다. 채널 미지정 시 전체 채널을 집계. + * runs on the caller's XA branch; advertised by cm_svr at tpsvrinit. + */ +#include "CM_NOTI_HIST.h" + +void CM_NOTI_HIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char ch[12], lastmsg[256]; + EXEC SQL BEGIN DECLARE SECTION; + char h_date[16], h_pat[24]; + long h_cnt, h_lastid; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CHANNEL, ch, sizeof(ch)); + gets_(b, T_BIZDATE, h_date, sizeof(h_date)); + if (h_date[0] == 0) { if (cmdb_get_param("BIZ_DATE", h_date) < 0) strcpy(h_date, "2026-07-19"); } + if (ch[0] == 0) snprintf(h_pat, sizeof(h_pat), "[%%"); + else snprintf(h_pat, sizeof(h_pat), "[%s>%%", ch); + + EXEC SQL SELECT count(*), coalesce(max(log_id), 0) INTO :h_cnt, :h_lastid + FROM cm_log + WHERE svc_name = 'NOTI' AND biz_date = :h_date AND msg LIKE :h_pat; + if (sqlca.sqlcode < 0) FAIL(b); + + lastmsg[0] = 0; + if (h_lastid > 0) cmdb_log_message(h_lastid, lastmsg); + setl(b, T_COUNT, h_cnt); + setl(b, T_ID1, h_lastid); + sets_(b, T_MSG, lastmsg); + OK(b); +} diff --git a/app/src/cm/svc/CM_NOTI_SEND.h b/app/src/cm/svc/CM_NOTI_SEND.h new file mode 100644 index 0000000..2b53bb3 --- /dev/null +++ b/app/src/cm/svc/CM_NOTI_SEND.h @@ -0,0 +1,13 @@ +/* + * CM_NOTI_SEND.h - cm (공통) 서비스 CM_NOTI_SEND 카피북 (copybook / record header). + * 알림 발송: 채널/수신자별 알림을 발송(기록)하고 발송 ID를 반환한다. + * Included by CM_NOTI_SEND.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_NOTI_SEND_H +#define CM_NOTI_SEND_H + +#include "cm_svc.h" + +void CM_NOTI_SEND(TPSVCINFO *p); + +#endif /* CM_NOTI_SEND_H */ diff --git a/app/src/cm/svc/CM_NOTI_SEND.pgc b/app/src/cm/svc/CM_NOTI_SEND.pgc new file mode 100644 index 0000000..31a44fd --- /dev/null +++ b/app/src/cm/svc/CM_NOTI_SEND.pgc @@ -0,0 +1,33 @@ +/* + * CM_NOTI_SEND.pgc - cm 모듈 서비스 CM_NOTI_SEND (one service = one file). + * 알림 발송: 채널(SMS/EMAIL/PUSH 검증) + 수신자 + 본문을 발송 큐 성격의 + * NOTI 로그로 적재하고 발송 ID 를 돌려준다. 본문은 200자에서 절단하며, + * 채널이 유효하지 않으면 거절한다. runs on the caller's XA branch. + */ +#include "CM_NOTI_SEND.h" + +void CM_NOTI_SEND(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char ch[12], target[64], body[256], packed[320], bizdate[16]; + long id; + int ok = 0; + + gets_(b, T_CHANNEL, ch, sizeof(ch)); + gets_(b, T_STR1, target, sizeof(target)); + gets_(b, T_MSG, body, sizeof(body)); + if (ch[0] == 0) strcpy(ch, "SMS"); + if (target[0] == 0 || body[0] == 0) FAIL(b); + if (strcmp(ch, "SMS") == 0 || strcmp(ch, "EMAIL") == 0 || strcmp(ch, "PUSH") == 0) ok = 1; + if (!ok) { setl(b, T_RC, 1); FAIL(b); } /* 미지원 채널 */ + if (strlen(body) > 200) body[200] = 0; /* 본문 절단 */ + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) { if (cmdb_get_param("BIZ_DATE", bizdate) < 0) strcpy(bizdate, "2026-07-19"); } + snprintf(packed, sizeof(packed), "[%s>%s] %s", ch, target, body); + id = cmdb_log("NOTI", "I", packed, bizdate); + if (id < 0) FAIL(b); + setl(b, T_ID1, id); /* 발송 ID */ + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/cm/svc/CM_PARAM_APPROVE.h b/app/src/cm/svc/CM_PARAM_APPROVE.h new file mode 100644 index 0000000..66d499c --- /dev/null +++ b/app/src/cm/svc/CM_PARAM_APPROVE.h @@ -0,0 +1,13 @@ +/* + * CM_PARAM_APPROVE.h - cm (공통) 서비스 CM_PARAM_APPROVE 카피북 (copybook / record header). + * 파라미터 변경 승인: 대기 중 요청 값을 본 키에 반영하고 요청을 종결한다. + * Included by CM_PARAM_APPROVE.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_PARAM_APPROVE_H +#define CM_PARAM_APPROVE_H + +#include "cm_svc.h" + +void CM_PARAM_APPROVE(TPSVCINFO *p); + +#endif /* CM_PARAM_APPROVE_H */ diff --git a/app/src/cm/svc/CM_PARAM_APPROVE.pgc b/app/src/cm/svc/CM_PARAM_APPROVE.pgc new file mode 100644 index 0000000..e6f2df3 --- /dev/null +++ b/app/src/cm/svc/CM_PARAM_APPROVE.pgc @@ -0,0 +1,36 @@ +/* + * CM_PARAM_APPROVE.pgc - cm 모듈 서비스 CM_PARAM_APPROVE (one service = one file). + * 파라미터 변경 승인(승인 워크플로 2단계): 대기 키(REQ:)의 요청 값을 읽어 + * 본 키에 반영한 뒤 대기 행을 삭제한다. 대기 요청이 없으면 실패. 반영 값과 + * 승인자를 이력에 남긴다. runs on the caller's XA branch (single XA tx). + */ +#include "CM_PARAM_APPROVE.h" + +void CM_PARAM_APPROVE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char key[64], who[32], pend[128], hist[256], bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + char h_reqkey[72]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_STR1, key, sizeof(key)); + gets_(b, T_STR3, who, sizeof(who)); + if (key[0] == 0) FAIL(b); + if (who[0] == 0) strcpy(who, "ADMIN"); + snprintf(h_reqkey, sizeof(h_reqkey), "REQ:%s", key); + + if (cmdb_get_param(h_reqkey, pend) < 0) { setl(b, T_RC, 1); FAIL(b); } /* 대기 없음 */ + if (cmdb_set_param(key, pend) < 0) FAIL(b); + + EXEC SQL DELETE FROM cm_param WHERE param_key = :h_reqkey; + if (sqlca.sqlcode < 0) FAIL(b); + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) { if (cmdb_get_param("BIZ_DATE", bizdate) < 0) strcpy(bizdate, "2026-07-19"); } + snprintf(hist, sizeof(hist), "APPROVE %s=%s by %s", key, pend, who); + cmdb_log("PARAMWF", "I", hist, bizdate); + sets_(b, T_STR4, pend); /* 반영된 값 */ + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/cm/svc/CM_PARAM_PENDING.h b/app/src/cm/svc/CM_PARAM_PENDING.h new file mode 100644 index 0000000..bfa76ee --- /dev/null +++ b/app/src/cm/svc/CM_PARAM_PENDING.h @@ -0,0 +1,13 @@ +/* + * CM_PARAM_PENDING.h - cm (공통) 서비스 CM_PARAM_PENDING 카피북 (copybook / record header). + * 파라미터 승인 대기 현황: 대기 건수와 가장 오래된 요청 키를 조회한다. + * Included by CM_PARAM_PENDING.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_PARAM_PENDING_H +#define CM_PARAM_PENDING_H + +#include "cm_svc.h" + +void CM_PARAM_PENDING(TPSVCINFO *p); + +#endif /* CM_PARAM_PENDING_H */ diff --git a/app/src/cm/svc/CM_PARAM_PENDING.pgc b/app/src/cm/svc/CM_PARAM_PENDING.pgc new file mode 100644 index 0000000..50afb98 --- /dev/null +++ b/app/src/cm/svc/CM_PARAM_PENDING.pgc @@ -0,0 +1,29 @@ +/* + * CM_PARAM_PENDING.pgc - cm 모듈 서비스 CM_PARAM_PENDING (one service = one file). + * 파라미터 승인 대기 현황: 대기 키(REQ:%) 건수, 가장 오래된 요청 키, 최장 + * 대기 일수를 한 번에 집계해 돌려준다(승인 담당자 대시보드용). runs on the + * caller's XA branch (no EXEC SQL CONNECT); advertised by cm_svr. + */ +#include "CM_PARAM_PENDING.h" + +void CM_PARAM_PENDING(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_maxage; + char h_oldest[80]; + EXEC SQL END DECLARE SECTION; + + EXEC SQL SELECT count(*), + coalesce(min(param_key), ''), + coalesce(max((now()::date - updated_at::date))::int, 0) + INTO :h_cnt, :h_oldest, :h_maxage + FROM cm_param WHERE param_key LIKE 'REQ:%'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + sets_(b, T_STR4, h_oldest); + setl(b, T_ARG1, h_maxage); + setl(b, T_RC, h_cnt > 0 ? 1 : 0); /* 1=처리할 대기 있음 */ + OK(b); +} diff --git a/app/src/cm/svc/CM_PARAM_REJECT.h b/app/src/cm/svc/CM_PARAM_REJECT.h new file mode 100644 index 0000000..a0bcdc7 --- /dev/null +++ b/app/src/cm/svc/CM_PARAM_REJECT.h @@ -0,0 +1,13 @@ +/* + * CM_PARAM_REJECT.h - cm (공통) 서비스 CM_PARAM_REJECT 카피북 (copybook / record header). + * 파라미터 변경 반려: 대기 중 요청을 사유와 함께 반려(삭제)한다. + * Included by CM_PARAM_REJECT.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_PARAM_REJECT_H +#define CM_PARAM_REJECT_H + +#include "cm_svc.h" + +void CM_PARAM_REJECT(TPSVCINFO *p); + +#endif /* CM_PARAM_REJECT_H */ diff --git a/app/src/cm/svc/CM_PARAM_REJECT.pgc b/app/src/cm/svc/CM_PARAM_REJECT.pgc new file mode 100644 index 0000000..cc1eda6 --- /dev/null +++ b/app/src/cm/svc/CM_PARAM_REJECT.pgc @@ -0,0 +1,33 @@ +/* + * CM_PARAM_REJECT.pgc - cm 모듈 서비스 CM_PARAM_REJECT (one service = one file). + * 파라미터 변경 반려(승인 워크플로 종결): 대기 키(REQ:)를 삭제만 하고 본 + * 키는 유지한다. 삭제 건수가 0 이면 대기 요청이 없는 것이므로 실패. 반려 + * 사유는 W 레벨 이력으로 남긴다. runs on the caller's XA branch. + */ +#include "CM_PARAM_REJECT.h" + +void CM_PARAM_REJECT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char key[64], reason[96], hist[224], bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + char h_reqkey[72]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_STR1, key, sizeof(key)); + gets_(b, T_REASON, reason, sizeof(reason)); + if (key[0] == 0) FAIL(b); + if (reason[0] == 0) strcpy(reason, "NO REASON"); + snprintf(h_reqkey, sizeof(h_reqkey), "REQ:%s", key); + + EXEC SQL DELETE FROM cm_param WHERE param_key = :h_reqkey; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlerrd[2] != 1) { setl(b, T_RC, 1); FAIL(b); } /* 대기 없음 */ + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) { if (cmdb_get_param("BIZ_DATE", bizdate) < 0) strcpy(bizdate, "2026-07-19"); } + snprintf(hist, sizeof(hist), "REJECT %s (%s)", key, reason); + cmdb_log("PARAMWF", "W", hist, bizdate); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/cm/svc/CM_PARAM_REQ.h b/app/src/cm/svc/CM_PARAM_REQ.h new file mode 100644 index 0000000..5f9a0f6 --- /dev/null +++ b/app/src/cm/svc/CM_PARAM_REQ.h @@ -0,0 +1,13 @@ +/* + * CM_PARAM_REQ.h - cm (공통) 서비스 CM_PARAM_REQ 카피북 (copybook / record header). + * 파라미터 변경 요청: 승인 전 대기 키(REQ:)로 변경 요청을 접수한다. + * Included by CM_PARAM_REQ.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_PARAM_REQ_H +#define CM_PARAM_REQ_H + +#include "cm_svc.h" + +void CM_PARAM_REQ(TPSVCINFO *p); + +#endif /* CM_PARAM_REQ_H */ diff --git a/app/src/cm/svc/CM_PARAM_REQ.pgc b/app/src/cm/svc/CM_PARAM_REQ.pgc new file mode 100644 index 0000000..9897c82 --- /dev/null +++ b/app/src/cm/svc/CM_PARAM_REQ.pgc @@ -0,0 +1,31 @@ +/* + * CM_PARAM_REQ.pgc - cm 모듈 서비스 CM_PARAM_REQ (one service = one file). + * 파라미터 변경 요청(승인 워크플로 1단계): 본 키는 건드리지 않고 대기 키 + * (REQ:)에 요청 값을 적재한다. 동일 키의 미결 요청이 있으면 거절하고, + * 접수 사실을 요청자와 함께 이력에 남긴다. runs on the caller's XA branch. + */ +#include "CM_PARAM_REQ.h" + +void CM_PARAM_REQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char key[64], val[128], who[32], reqkey[72], hist[256], bizdate[16], cur[128]; + gets_(b, T_STR1, key, sizeof(key)); + gets_(b, T_STR2, val, sizeof(val)); + gets_(b, T_STR3, who, sizeof(who)); + if (key[0] == 0 || val[0] == 0) FAIL(b); + if (who[0] == 0) strcpy(who, "SYSTEM"); + if (strncmp(key, "REQ:", 4) == 0) { setl(b, T_RC, 3); FAIL(b); } /* 중첩 요청 금지 */ + + snprintf(reqkey, sizeof(reqkey), "REQ:%s", key); + if (cmdb_get_param(reqkey, cur) == 0) { setl(b, T_RC, 1); FAIL(b); } /* 미결 요청 존재 */ + if (cmdb_set_param(reqkey, val) < 0) FAIL(b); + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) { if (cmdb_get_param("BIZ_DATE", bizdate) < 0) strcpy(bizdate, "2026-07-19"); } + snprintf(hist, sizeof(hist), "REQ %s=%s by %s", key, val, who); + cmdb_log("PARAMWF", "I", hist, bizdate); + sets_(b, T_STR4, reqkey); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/cm/svc/CM_PREVBIZ.h b/app/src/cm/svc/CM_PREVBIZ.h new file mode 100644 index 0000000..33661a6 --- /dev/null +++ b/app/src/cm/svc/CM_PREVBIZ.h @@ -0,0 +1,13 @@ +/* + * CM_PREVBIZ.h - cm (공통) 서비스 CM_PREVBIZ 카피북 (copybook / record header). + * 직전 영업일 조회: 주말/휴일을 거슬러 직전 영업일을 구한다. + * Included by CM_PREVBIZ.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_PREVBIZ_H +#define CM_PREVBIZ_H + +#include "cm_svc.h" + +void CM_PREVBIZ(TPSVCINFO *p); + +#endif /* CM_PREVBIZ_H */ diff --git a/app/src/cm/svc/CM_PREVBIZ.pgc b/app/src/cm/svc/CM_PREVBIZ.pgc new file mode 100644 index 0000000..0b8c986 --- /dev/null +++ b/app/src/cm/svc/CM_PREVBIZ.pgc @@ -0,0 +1,49 @@ +/* + * CM_PREVBIZ.pgc - cm 모듈 서비스 CM_PREVBIZ (one service = one file). + * 직전 영업일 조회: 기준일에서 하루씩 거슬러 내려가며 토/일(순수 요일 공식)과 + * 휴일(cm_holiday)을 건너뛴 첫 영업일을 구한다. 건너뛴 일수도 함께 반환. + * 순수 계산 + EXEC SQL(dbio) 혼합; runs on the caller's XA branch. + */ +#include "CM_PREVBIZ.h" + +/* ----- service-local pure helper(s) ----- */ +static int cm_back_dow(int y, int m, int d) /* 0=일..6=토 (Zeller 변형) */ +{ + int a = (14 - m) / 12, yy = y - a, mm = m + 12 * a - 2; + return (d + yy + yy/4 - yy/100 + yy/400 + (31 * mm) / 12) % 7; +} + +static void cm_back_day(int *y, int *m, int *d) +{ + static const int md[] = {31,28,31,30,31,30,31,31,30,31,30,31}; + if (--*d >= 1) return; + if (--*m < 1) { *m = 12; --*y; } + *d = md[*m - 1]; + if (*m == 2 && ((*y % 4 == 0 && *y % 100 != 0) || *y % 400 == 0)) *d = 29; +} + +void CM_PREVBIZ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char in[16], cur[16]; + int y, m, d, w, hol; + long skipped = 0; + + gets_(b, T_STR1, in, sizeof(in)); + if (in[0] == 0) { if (cmdb_get_param("BIZ_DATE", in) < 0) strcpy(in, "2026-07-19"); } + if (sscanf(in, "%d-%d-%d", &y, &m, &d) != 3) FAIL(b); + + for (;;) { + cm_back_day(&y, &m, &d); + if (++skipped > 30) FAIL(b); /* 30일 내 영업일 없음 = 이상 */ + sprintf(cur, "%04d-%02d-%02d", y, m, d); + w = cm_back_dow(y, m, d); + if (w == 0 || w == 6) continue; /* 주말 */ + hol = cmdb_is_holiday(cur); + if (hol < 0) FAIL(b); + if (hol == 0) break; /* 영업일 확정 */ + } + sets_(b, T_STR3, cur); + setl(b, T_ARG1, skipped); /* 거슬러간 일수 */ + OK(b); +} diff --git a/app/src/cm/svc/CM_SEQ_DAILY.h b/app/src/cm/svc/CM_SEQ_DAILY.h new file mode 100644 index 0000000..5a627ed --- /dev/null +++ b/app/src/cm/svc/CM_SEQ_DAILY.h @@ -0,0 +1,13 @@ +/* + * CM_SEQ_DAILY.h - cm (공통) 서비스 CM_SEQ_DAILY 카피북 (copybook / record header). + * 일자별 채번: 이름+일자 단위로 매일 1부터 시작하는 일련번호를 채번한다. + * Included by CM_SEQ_DAILY.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_SEQ_DAILY_H +#define CM_SEQ_DAILY_H + +#include "cm_svc.h" + +void CM_SEQ_DAILY(TPSVCINFO *p); + +#endif /* CM_SEQ_DAILY_H */ diff --git a/app/src/cm/svc/CM_SEQ_DAILY.pgc b/app/src/cm/svc/CM_SEQ_DAILY.pgc new file mode 100644 index 0000000..1084926 --- /dev/null +++ b/app/src/cm/svc/CM_SEQ_DAILY.pgc @@ -0,0 +1,33 @@ +/* + * CM_SEQ_DAILY.pgc - cm 모듈 서비스 CM_SEQ_DAILY (one service = one file). + * 일자별 채번(채번 세분): 이름+영업일을 합친 키(:)로 매일 1부터 + * 시작하는 일련번호를 채번하고, YYYYMMDD-NNNNNN 형태의 전표번호 문자열도 + * 만들어 준다. EXEC SQL(dbio 채번) + 순수 포맷 혼합. + */ +#include "CM_SEQ_DAILY.h" + +void CM_SEQ_DAILY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char name[24], bizdate[16], key[48], compact[12], formatted[32]; + long v; + int i, j; + + gets_(b, T_KEY1, name, sizeof(name)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (name[0] == 0) strcpy(name, "DAILY"); + if (bizdate[0] == 0) { if (cmdb_get_param("BIZ_DATE", bizdate) < 0) strcpy(bizdate, "2026-07-19"); } + + snprintf(key, sizeof(key), "%s:%s", name, bizdate); + v = cmdb_next_seq(key); + if (v < 0) FAIL(b); + + for (i = 0, j = 0; bizdate[i] && j < 8; i++) /* '-' 제거한 YYYYMMDD */ + if (bizdate[i] != '-') compact[j++] = bizdate[i]; + compact[j] = 0; + snprintf(formatted, sizeof(formatted), "%s-%06ld", compact, v); + + setl(b, T_ARG1, v); + sets_(b, T_STR4, formatted); + OK(b); +} diff --git a/app/src/cm/svc/CM_SEQ_PEEK.h b/app/src/cm/svc/CM_SEQ_PEEK.h new file mode 100644 index 0000000..f05e6d9 --- /dev/null +++ b/app/src/cm/svc/CM_SEQ_PEEK.h @@ -0,0 +1,13 @@ +/* + * CM_SEQ_PEEK.h - cm (공통) 서비스 CM_SEQ_PEEK 카피북 (copybook / record header). + * 채번 현재값 조회: 증가 없이 카운터의 현재값만 조회한다. + * Included by CM_SEQ_PEEK.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_SEQ_PEEK_H +#define CM_SEQ_PEEK_H + +#include "cm_svc.h" + +void CM_SEQ_PEEK(TPSVCINFO *p); + +#endif /* CM_SEQ_PEEK_H */ diff --git a/app/src/cm/svc/CM_SEQ_PEEK.pgc b/app/src/cm/svc/CM_SEQ_PEEK.pgc new file mode 100644 index 0000000..3d7bb8a --- /dev/null +++ b/app/src/cm/svc/CM_SEQ_PEEK.pgc @@ -0,0 +1,29 @@ +/* + * CM_SEQ_PEEK.pgc - cm 모듈 서비스 CM_SEQ_PEEK (one service = one file). + * 채번 현재값 조회(채번 세분): 카운터를 증가시키지 않고 cm_seq 의 현재값과 + * 마지막 채번 시각을 그대로 읽는다. 카운터가 없으면 not-found 실패. + * runs on the caller's XA branch; advertised by cm_svr at tpsvrinit. + */ +#include "CM_SEQ_PEEK.h" + +void CM_SEQ_PEEK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_name[48], h_when[32]; + long h_val; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, h_name, sizeof(h_name)); + if (h_name[0] == 0) FAIL(b); + + EXEC SQL SELECT seq_val, to_char(updated_at, 'YYYY-MM-DD HH24:MI:SS') + INTO :h_val, :h_when + FROM cm_seq WHERE seq_name = :h_name; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode != 0) { setl(b, T_RC, 1); FAIL(b); } /* 카운터 없음 */ + + setl(b, T_ARG1, h_val); + sets_(b, T_STR4, h_when); + OK(b); +} diff --git a/app/src/cm/svc/CM_SEQ_RESET.h b/app/src/cm/svc/CM_SEQ_RESET.h new file mode 100644 index 0000000..f6691af --- /dev/null +++ b/app/src/cm/svc/CM_SEQ_RESET.h @@ -0,0 +1,13 @@ +/* + * CM_SEQ_RESET.h - cm (공통) 서비스 CM_SEQ_RESET 카피북 (copybook / record header). + * 채번 초기화: 운영 파라미터 허용 시 카운터를 지정값으로 재설정한다. + * Included by CM_SEQ_RESET.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_SEQ_RESET_H +#define CM_SEQ_RESET_H + +#include "cm_svc.h" + +void CM_SEQ_RESET(TPSVCINFO *p); + +#endif /* CM_SEQ_RESET_H */ diff --git a/app/src/cm/svc/CM_SEQ_RESET.pgc b/app/src/cm/svc/CM_SEQ_RESET.pgc new file mode 100644 index 0000000..46a26b7 --- /dev/null +++ b/app/src/cm/svc/CM_SEQ_RESET.pgc @@ -0,0 +1,42 @@ +/* + * CM_SEQ_RESET.pgc - cm 모듈 서비스 CM_SEQ_RESET (one service = one file). + * 채번 초기화(채번 세분): 운영 파라미터 SEQ_RESET_YN 이 'Y' 일 때만 카운터를 + * 지정값(기본 0)으로 재설정한다. 없던 카운터는 새로 만들고, 이전 값을 이력에 + * 남긴다. runs on the caller's XA branch (single XA tx). + */ +#include "CM_SEQ_RESET.h" + +void CM_SEQ_RESET(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char hist[128], bizdate[16]; + long prev = -1; + EXEC SQL BEGIN DECLARE SECTION; + char h_name[48]; + long h_new, h_prev; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, h_name, sizeof(h_name)); + h_new = getl(b, T_ARG1); + if (h_name[0] == 0 || h_new < 0) FAIL(b); + if (cmdb_param_is_yes("SEQ_RESET_YN") != 1) { setl(b, T_RC, 9); FAIL(b); } /* 운영 잠금 */ + + EXEC SQL SELECT seq_val INTO :h_prev FROM cm_seq WHERE seq_name = :h_name; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 0) { + prev = h_prev; + EXEC SQL UPDATE cm_seq SET seq_val = :h_new, updated_at = now() + WHERE seq_name = :h_name; + } else { + EXEC SQL INSERT INTO cm_seq (seq_name, seq_val) VALUES (:h_name, :h_new); + } + if (sqlca.sqlcode < 0) FAIL(b); + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) { if (cmdb_get_param("BIZ_DATE", bizdate) < 0) strcpy(bizdate, "2026-07-19"); } + snprintf(hist, sizeof(hist), "SEQ RESET %s %ld -> %ld", h_name, prev, h_new); + cmdb_log("SEQADM", "W", hist, bizdate); + setl(b, T_ARG2, prev); /* 이전 값 (-1=신규) */ + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/cm/svc/CM_SEQ_TYPED.h b/app/src/cm/svc/CM_SEQ_TYPED.h new file mode 100644 index 0000000..232fe05 --- /dev/null +++ b/app/src/cm/svc/CM_SEQ_TYPED.h @@ -0,0 +1,13 @@ +/* + * CM_SEQ_TYPED.h - cm (공통) 서비스 CM_SEQ_TYPED 카피북 (copybook / record header). + * 유형별 채번: 허용 유형별 접두어가 붙은 채번 문자열을 생성한다. + * Included by CM_SEQ_TYPED.pgc and by cm_svr.pgc (prototype for tpadvertise). + */ +#ifndef CM_SEQ_TYPED_H +#define CM_SEQ_TYPED_H + +#include "cm_svc.h" + +void CM_SEQ_TYPED(TPSVCINFO *p); + +#endif /* CM_SEQ_TYPED_H */ diff --git a/app/src/cm/svc/CM_SEQ_TYPED.pgc b/app/src/cm/svc/CM_SEQ_TYPED.pgc new file mode 100644 index 0000000..a5b153b --- /dev/null +++ b/app/src/cm/svc/CM_SEQ_TYPED.pgc @@ -0,0 +1,36 @@ +/* + * CM_SEQ_TYPED.pgc - cm 모듈 서비스 CM_SEQ_TYPED (one service = one file). + * 유형별 채번(채번 세분): 허용된 채번 유형(SLIP/TICKET/BATCH/NOTI)만 유형별 + * 접두어를 붙여 채번한다. 미허용 유형은 거절(RC=1). 접두어 매핑 테이블은 + * 서비스 내 정적 테이블로 관리한다. EXEC SQL(dbio) + 순수 매핑 혼합. + */ +#include "CM_SEQ_TYPED.h" + +/* ----- service-local static mapping ----- */ +static const struct { const char *type; const char *prefix; } CM_SEQ_MAP[] = { + { "SLIP", "SLP" }, + { "TICKET", "TKT" }, + { "BATCH", "BAT" }, + { "NOTI", "NTF" }, +}; + +void CM_SEQ_TYPED(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char type[16], out[32]; + const char *prefix = NULL; + long v; + int i, n = (int)(sizeof(CM_SEQ_MAP) / sizeof(CM_SEQ_MAP[0])); + + gets_(b, T_KEY1, type, sizeof(type)); + for (i = 0; i < n; i++) + if (strcmp(type, CM_SEQ_MAP[i].type) == 0) { prefix = CM_SEQ_MAP[i].prefix; break; } + if (prefix == NULL) { setl(b, T_RC, 1); FAIL(b); } /* 미허용 유형 */ + + v = cmdb_next_seq(type); + if (v < 0) FAIL(b); + snprintf(out, sizeof(out), "%s-%08ld", prefix, v); + setl(b, T_ARG1, v); + sets_(b, T_STR4, out); + OK(b); +} diff --git a/app/src/lg/batch/lg_agedpend_batch.pgc b/app/src/lg/batch/lg_agedpend_batch.pgc new file mode 100644 index 0000000..e54eb0b --- /dev/null +++ b/app/src/lg/batch/lg_agedpend_batch.pgc @@ -0,0 +1,60 @@ +/* + * lg_agedpend_batch.pgc - lg 미결전표 에이징 배치 (XA). + * + * 기준일 대비 미결(D) 전표의 경과일을 4버킷(<=1 / <=7 / <=30 / 30+)으로 + * 나눠 히스토그램을 만들고, 미결 전표에 걸린 차변 금액 합계도 산출한다. + * 단일 CASE 집계 SQL 1방 + 금액 조인 1방. ONE global XA transaction. + * Usage: lg_agedpend_batch [AS-OF-DATE] + */ +#include +#include +#include +#include +#include "lg_dbio.h" +#include "lg_agedpend_batch.h" + +int main(int argc, char **argv) +{ + lg_agedpend_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_asof[16]; + long h_b1, h_b7, h_b30, h_bold, h_amt; + EXEC SQL END DECLARE SECTION; + + memset(&rec, 0, sizeof(rec)); + strncpy(rec.as_of, (argc > 1) ? argv[1] : "2026-07-19", sizeof(rec.as_of)-1); + strncpy(h_asof, rec.as_of, sizeof(h_asof)-1); h_asof[sizeof(h_asof)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT + coalesce(sum(CASE WHEN CAST(:h_asof AS date) - biz_date <= 1 THEN 1 ELSE 0 END), 0), + coalesce(sum(CASE WHEN CAST(:h_asof AS date) - biz_date BETWEEN 2 AND 7 THEN 1 ELSE 0 END), 0), + coalesce(sum(CASE WHEN CAST(:h_asof AS date) - biz_date BETWEEN 8 AND 30 THEN 1 ELSE 0 END), 0), + coalesce(sum(CASE WHEN CAST(:h_asof AS date) - biz_date > 30 THEN 1 ELSE 0 END), 0) + INTO :h_b1, :h_b7, :h_b30, :h_bold + FROM lg_voucher WHERE status = 'D'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "AGE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + EXEC SQL SELECT coalesce(sum(l.amount), 0) INTO :h_amt + FROM lg_ledger_line l JOIN lg_voucher v ON v.voucher_id = l.voucher_id + WHERE v.status = 'D' AND l.dc = 'D'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "AMT FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + + rec.bucket_1d = h_b1; rec.bucket_7d = h_b7; + rec.bucket_30d = h_b30; rec.bucket_old = h_bold; + rec.amount_total = h_amt; + printf("--- 미결 에이징 (기준 %s) ---\n", rec.as_of); + printf(" ~1일 %ld | ~7일 %ld | ~30일 %ld | 30일+ %ld\n", + rec.bucket_1d, rec.bucket_7d, rec.bucket_30d, rec.bucket_old); + printf(">>> lg_agedpend_batch COMMIT: 미결 %ld건 차변누계 %ld\n", + rec.bucket_1d + rec.bucket_7d + rec.bucket_30d + rec.bucket_old, rec.amount_total); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_baddebt_batch.pgc b/app/src/lg/batch/lg_baddebt_batch.pgc new file mode 100644 index 0000000..c1b42cf --- /dev/null +++ b/app/src/lg/batch/lg_baddebt_batch.pgc @@ -0,0 +1,70 @@ +/* + * lg_baddebt_batch.pgc - lg 대손 일괄 처리 배치 (커서 + XA). + * + * 기준 경과일(threshold, 기본 90일)을 넘긴 미대사(A) 매입 채권을 가맹점별로 + * 합산해 D:BADDEBT_EXP / C:RECV 균형 전표로 일괄 상각하고 가맹점 잔액을 + * 재산출한다. 모두 ONE global XA transaction 에서 수행. + * Usage: lg_baddebt_batch [YYYY-MM-DD] [THRESHOLD_DAYS] + */ +#include +#include +#include +#include +#include "lg_dbio.h" +#include "lg_baddebt_batch.h" + +int main(int argc, char **argv) +{ + lg_baddebt_rec_t rec; + long vid; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_days, h_amt; + EXEC SQL END DECLARE SECTION; + + memset(&rec, 0, sizeof(rec)); + strncpy(rec.biz_date, (argc > 1) ? argv[1] : "2026-07-19", sizeof(rec.biz_date)-1); + rec.threshold_days = (argc > 2) ? atol(argv[2]) : 90; + if (rec.threshold_days <= 0) rec.threshold_days = 90; + strncpy(h_bizdate, rec.biz_date, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + h_days = rec.threshold_days; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE bd_cur CURSOR FOR + SELECT merchant_id, coalesce(sum(amount), 0) + FROM purchase + WHERE status = 'A' AND CAST(:h_bizdate AS date) - biz_date > :h_days + GROUP BY merchant_id HAVING sum(amount) > 0 + ORDER BY merchant_id; + EXEC SQL OPEN bd_cur; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH bd_cur INTO :h_merch, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d]\n", sqlca.sqlcode); EXEC SQL CLOSE bd_cur; tpabort(0); return 1; } + + vid = lgdb_next_voucher_id(); + if (vid < 0) { EXEC SQL CLOSE bd_cur; tpabort(0); return 1; } + if (lgdb_insert_voucher(vid, "ADJ", 0, "P", rec.biz_date, "대손 일괄상각") < 0 || + lgdb_insert_line(vid, "BADDEBT_EXP", "D", h_amt, h_merch, rec.biz_date, "posted") < 0 || + lgdb_insert_line(vid, "RECV", "C", h_amt, h_merch, rec.biz_date, "posted") < 0 || + lgdb_upsert_merch_bal(h_merch, rec.biz_date) < 0) + { EXEC SQL CLOSE bd_cur; tpabort(0); return 1; } + printf(" 대손: %s %ld (vid=%ld)\n", h_merch, h_amt, vid); + rec.written_off += h_amt; + rec.merchants++; + } + EXEC SQL CLOSE bd_cur; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_baddebt_batch COMMIT: %ld일 초과 %ld가맹점 대손 %ld 상각 (차=대 균형)\n", + rec.threshold_days, rec.merchants, rec.written_off); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_balproof_batch.pgc b/app/src/lg/batch/lg_balproof_batch.pgc new file mode 100644 index 0000000..e63c3f5 --- /dev/null +++ b/app/src/lg/batch/lg_balproof_batch.pgc @@ -0,0 +1,63 @@ +/* + * lg_balproof_batch.pgc - lg 잔액 증명 배치 (커서 + XA). + * + * 당일 계정잔액(lg_account_bal) 각 행을 원장 라인 재집계와 대조하고, + * 불일치 계정은 dbio 재계산(lgdb_recalc_acct_bal)으로 즉시 보정한다. + * 검증/불일치/보정 건수를 보고. 모두 ONE global XA transaction 에서 수행. + * Usage: lg_balproof_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" +#include "lg_balproof_batch.h" + +int main(int argc, char **argv) +{ + lg_balproof_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_acct[32]; + long h_bal, h_real; + EXEC SQL END DECLARE SECTION; + + memset(&rec, 0, sizeof(rec)); + strncpy(rec.biz_date, (argc > 1) ? argv[1] : "2026-07-19", sizeof(rec.biz_date)-1); + strncpy(h_bizdate, rec.biz_date, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE bp_cur CURSOR FOR + SELECT a.acct, a.balance, + coalesce((SELECT sum(CASE WHEN l.dc = 'D' THEN l.amount ELSE -l.amount END) + FROM lg_ledger_line l + WHERE l.acct = a.acct AND l.biz_date = a.biz_date), 0) + FROM lg_account_bal a WHERE a.biz_date = :h_bizdate + ORDER BY a.acct; + EXEC SQL OPEN bp_cur; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH bp_cur INTO :h_acct, :h_bal, :h_real; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d]\n", sqlca.sqlcode); EXEC SQL CLOSE bp_cur; tpabort(0); return 1; } + rec.verified++; + if (h_bal != h_real) { + printf(" 불일치: %s 기록 %ld vs 재계산 %ld -> 보정\n", h_acct, h_bal, h_real); + rec.mismatched++; + if (lgdb_recalc_acct_bal(h_acct, rec.biz_date) < 0) { EXEC SQL CLOSE bp_cur; tpabort(0); return 1; } + rec.repaired++; + } + } + EXEC SQL CLOSE bp_cur; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_balproof_batch COMMIT: %s 검증 %ld 불일치 %ld 보정 %ld\n", + rec.biz_date, rec.verified, rec.mismatched, rec.repaired); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_closechk_batch.pgc b/app/src/lg/batch/lg_closechk_batch.pgc new file mode 100644 index 0000000..a29e64c --- /dev/null +++ b/app/src/lg/batch/lg_closechk_batch.pgc @@ -0,0 +1,70 @@ +/* + * lg_closechk_batch.pgc - lg 결산 체크리스트 배치 (XA). + * + * 결산 직전 점검 4항목을 순서대로 수행해 체크리스트를 출력한다: + * [1] 미결(D) 전표 [2] 차변합<>대변합 불균형 전표 [3] 0원 라인 + * [4] 헤더 없는 고아 라인. 실패 항목 수를 exit code 로 반영하지 않고 + * 리포트만 남긴다(운영 판단용). ONE global XA transaction 에서 수행. + * Usage: lg_closechk_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" +#include "lg_closechk_batch.h" + +int main(int argc, char **argv) +{ + lg_closechk_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_n1, h_n2, h_n3, h_n4; + EXEC SQL END DECLARE SECTION; + + memset(&rec, 0, sizeof(rec)); + strncpy(rec.biz_date, (argc > 1) ? argv[1] : "2026-07-19", sizeof(rec.biz_date)-1); + strncpy(h_bizdate, rec.biz_date, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*) INTO :h_n1 + FROM lg_voucher WHERE biz_date = :h_bizdate AND status = 'D'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + EXEC SQL SELECT count(*) INTO :h_n2 FROM ( + SELECT voucher_id FROM lg_ledger_line WHERE biz_date = :h_bizdate + GROUP BY voucher_id + HAVING sum(CASE WHEN dc = 'D' THEN amount ELSE -amount END) <> 0) q; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + EXEC SQL SELECT count(*) INTO :h_n3 + FROM lg_ledger_line WHERE biz_date = :h_bizdate AND amount = 0; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + EXEC SQL SELECT count(*) INTO :h_n4 + FROM lg_ledger_line l + WHERE l.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM lg_voucher v WHERE v.voucher_id = l.voucher_id); + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + + rec.draft_vouchers = h_n1; rec.imbalanced = h_n2; + rec.zero_lines = h_n3; rec.orphan_lines = h_n4; + rec.failed_items = (h_n1 > 0) + (h_n2 > 0) + (h_n3 > 0) + (h_n4 > 0); + + printf("--- 결산 체크리스트 (%s) ---\n", rec.biz_date); + printf(" [1] 미결 전표 : %6ld %s\n", rec.draft_vouchers, h_n1 ? "NG" : "OK"); + printf(" [2] 불균형 전표 : %6ld %s\n", rec.imbalanced, h_n2 ? "NG" : "OK"); + printf(" [3] 0원 라인 : %6ld %s\n", rec.zero_lines, h_n3 ? "NG" : "OK"); + printf(" [4] 고아 라인 : %6ld %s\n", rec.orphan_lines, h_n4 ? "NG" : "OK"); + printf(">>> lg_closechk_batch COMMIT: 실패 %ld/4 항목 -> %s\n", + rec.failed_items, (rec.failed_items == 0) ? "결산 가능" : "결산 보류"); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_deptpl_batch.pgc b/app/src/lg/batch/lg_deptpl_batch.pgc new file mode 100644 index 0000000..520243b --- /dev/null +++ b/app/src/lg/batch/lg_deptpl_batch.pgc @@ -0,0 +1,65 @@ +/* + * lg_deptpl_batch.pgc - lg 부문별 손익 집계 배치 (커서 + XA). + * + * 당일 매입을 업종/부문(category)별로 GROUP BY 하여 건수/매출/수수료 손익을 + * 산출하고 부문별 수수료 점유율(bps)을 계산해 리포트로 출력한다. 취소(C) + * 매입은 제외. 조회 전용이지만 ONE XA transaction 규율은 동일하게 지킨다. + * Usage: lg_deptpl_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" +#include "lg_deptpl_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + lg_deptpl_rec_t rec; + long depts = 0, totfee = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_cat[32]; + long h_cnt, h_gross, h_fee, h_totfee; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT coalesce(sum(fee), 0) INTO :h_totfee + FROM purchase WHERE biz_date = :h_bizdate AND status <> 'C'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "SUM FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + + EXEC SQL DECLARE dpb_cur CURSOR FOR + SELECT category, count(*), coalesce(sum(amount), 0), coalesce(sum(fee), 0) + FROM purchase WHERE biz_date = :h_bizdate AND status <> 'C' + GROUP BY category ORDER BY 4 DESC; + EXEC SQL OPEN dpb_cur; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + printf("--- 부문별 손익 (%s) ---\n", bizdate); + for (;;) { + EXEC SQL FETCH dpb_cur INTO :h_cat, :h_cnt, :h_gross, :h_fee; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d]\n", sqlca.sqlcode); EXEC SQL CLOSE dpb_cur; tpabort(0); return 1; } + memset(&rec, 0, sizeof(rec)); + strncpy(rec.category, h_cat, sizeof(rec.category)-1); + rec.txns = h_cnt; rec.gross = h_gross; rec.fee = h_fee; + rec.share_bps = (h_totfee > 0) ? h_fee * 10000 / h_totfee : 0; + printf(" %-12s %6ld건 매출 %12ld 수수료 %10ld (%ldbps)\n", + rec.category, rec.txns, rec.gross, rec.fee, rec.share_bps); + totfee += rec.fee; + depts++; + } + EXEC SQL CLOSE dpb_cur; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_deptpl_batch COMMIT: 부문 %ld개 총수수료손익 %ld\n", depts, totfee); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_dupscan_batch.pgc b/app/src/lg/batch/lg_dupscan_batch.pgc new file mode 100644 index 0000000..db2cc81 --- /dev/null +++ b/app/src/lg/batch/lg_dupscan_batch.pgc @@ -0,0 +1,60 @@ +/* + * lg_dupscan_batch.pgc - lg 중복 분개 스캔 배치 (커서 + XA). + * + * 당일 동일 (전표, 계정, 차대, 금액) 조합이 2건 이상인 이중기표 후보 그룹을 + * 커서로 출력하고, 초과 라인 수와 초과 금액(그룹 금액 x (건수-1))을 누적해 + * 잠재 과대계상 규모를 보고한다. 검사 전용. ONE global XA transaction. + * Usage: lg_dupscan_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" +#include "lg_dupscan_batch.h" + +int main(int argc, char **argv) +{ + lg_dupscan_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_acct[32], h_dc[4]; + long h_vid, h_amt, h_n; + EXEC SQL END DECLARE SECTION; + + memset(&rec, 0, sizeof(rec)); + strncpy(rec.biz_date, (argc > 1) ? argv[1] : "2026-07-19", sizeof(rec.biz_date)-1); + strncpy(h_bizdate, rec.biz_date, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE ds_cur CURSOR FOR + SELECT voucher_id, acct, dc, amount, count(*) + FROM lg_ledger_line + WHERE biz_date = :h_bizdate + GROUP BY voucher_id, acct, dc, amount + HAVING count(*) > 1 + ORDER BY count(*) DESC, voucher_id; + EXEC SQL OPEN ds_cur; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH ds_cur INTO :h_vid, :h_acct, :h_dc, :h_amt, :h_n; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d]\n", sqlca.sqlcode); EXEC SQL CLOSE ds_cur; tpabort(0); return 1; } + printf(" 중복: vid=%ld %s %s %ld x%ld건\n", h_vid, h_acct, h_dc, h_amt, h_n); + rec.extra_lines += h_n - 1; + rec.extra_amount += h_amt * (h_n - 1); + rec.groups++; + } + EXEC SQL CLOSE ds_cur; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_dupscan_batch COMMIT: %s 중복그룹 %ld 초과라인 %ld 잠재과대계상 %ld\n", + rec.biz_date, rec.groups, rec.extra_lines, rec.extra_amount); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_feerecog_batch.pgc b/app/src/lg/batch/lg_feerecog_batch.pgc new file mode 100644 index 0000000..983ba60 --- /dev/null +++ b/app/src/lg/batch/lg_feerecog_batch.pgc @@ -0,0 +1,74 @@ +/* + * lg_feerecog_batch.pgc - lg 수수료 수익 인식 배치 (커서 + XA). + * + * 당일 정산완료(S) 매입 수수료를 가맹점별로 묶어 가맹점당 한 장의 + * D:RECV / C:FEE_INC 균형 전표(FEE)로 전기한다. 가맹점 단위 전기이므로 + * 원장 라인에 merchant_id 가 남아 가맹점 잔액 대사에 쓰인다. + * 모두 ONE global XA transaction 에서 수행. + * Usage: lg_feerecog_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" +#include "lg_feerecog_batch.h" + +int main(int argc, char **argv) +{ + lg_feerecog_rec_t rec; + long vid; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_fee; + EXEC SQL END DECLARE SECTION; + + memset(&rec, 0, sizeof(rec)); + strncpy(rec.biz_date, (argc > 1) ? argv[1] : "2026-07-19", sizeof(rec.biz_date)-1); + strncpy(h_bizdate, rec.biz_date, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE fr_cur CURSOR FOR + SELECT merchant_id, coalesce(sum(fee), 0) + FROM purchase WHERE biz_date = :h_bizdate AND status = 'S' + GROUP BY merchant_id HAVING sum(fee) > 0 + ORDER BY merchant_id; + EXEC SQL OPEN fr_cur; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH fr_cur INTO :h_merch, :h_fee; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d]\n", sqlca.sqlcode); EXEC SQL CLOSE fr_cur; tpabort(0); return 1; } + + vid = lgdb_next_voucher_id(); + if (vid < 0) { EXEC SQL CLOSE fr_cur; tpabort(0); return 1; } + if (lgdb_insert_voucher(vid, "FEE", 0, "P", rec.biz_date, "수수료 수익 인식 배치") < 0 || + lgdb_insert_line(vid, "RECV", "D", h_fee, h_merch, rec.biz_date, "posted") < 0 || + lgdb_insert_line(vid, "FEE_INC", "C", h_fee, h_merch, rec.biz_date, "posted") < 0 || + lgdb_upsert_merch_bal(h_merch, rec.biz_date) < 0) + { EXEC SQL CLOSE fr_cur; tpabort(0); return 1; } + rec.fee_total += h_fee; + rec.merchants++; + rec.vouchers++; + } + EXEC SQL CLOSE fr_cur; + + /* 인식 총액을 계정잔액에도 반영 (차=대 동액) */ + if (rec.fee_total > 0) { + if (lgdb_upsert_acct_bal("RECV", rec.biz_date, rec.fee_total, 0) < 0 || + lgdb_upsert_acct_bal("FEE_INC", rec.biz_date, 0, rec.fee_total) < 0) + { tpabort(0); return 1; } + } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_feerecog_batch COMMIT: %s 가맹점 %ld곳 %ld전표 수수료 %ld 인식\n", + rec.biz_date, rec.merchants, rec.vouchers, rec.fee_total); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_monthclose_batch.pgc b/app/src/lg/batch/lg_monthclose_batch.pgc new file mode 100644 index 0000000..071c145 --- /dev/null +++ b/app/src/lg/batch/lg_monthclose_batch.pgc @@ -0,0 +1,80 @@ +/* + * lg_monthclose_batch.pgc - lg 월차 결산 분개 배치 (커서 + XA). + * + * 월 구간 [FROM ~ TO] 의 손익계정(FEE_INC/BADDEBT_EXP/FEE_DEPT_%)을 커서로 + * 돌며 각 순액을 RETAINED 로 대체하는 결산 전표 라인을 만든다. 전 계정을 + * 하나의 결산 전표에 담되 상대 라인은 RETAINED 한 줄로 몰아 차변합=대변합을 + * 보장한다 (복식부기 균형). 모두 ONE global XA transaction 에서 수행. + * Usage: lg_monthclose_batch [FROM] [TO] + */ +#include +#include +#include +#include +#include "lg_dbio.h" +#include "lg_monthclose_batch.h" + +int main(int argc, char **argv) +{ + lg_monthclose_rec_t rec; + long vid, retained = 0, mag; + + EXEC SQL BEGIN DECLARE SECTION; + char h_from[16], h_to[16], h_acct[32]; + long h_net; + EXEC SQL END DECLARE SECTION; + + memset(&rec, 0, sizeof(rec)); + strncpy(rec.month_from, (argc > 1) ? argv[1] : "2026-07-01", sizeof(rec.month_from)-1); + strncpy(rec.month_to, (argc > 2) ? argv[2] : "2026-07-31", sizeof(rec.month_to)-1); + strncpy(h_from, rec.month_from, sizeof(h_from)-1); h_from[sizeof(h_from)-1] = 0; + strncpy(h_to, rec.month_to, sizeof(h_to)-1); h_to[sizeof(h_to)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + vid = lgdb_next_voucher_id(); + if (vid < 0) { tpabort(0); return 1; } + if (lgdb_insert_voucher(vid, "ADJ", 0, "P", rec.month_to, "월차결산 배치") < 0) { tpabort(0); return 1; } + rec.closing_vid = vid; + + EXEC SQL DECLARE mc_cur CURSOR FOR + SELECT acct, coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE -amount END), 0) + FROM lg_ledger_line + WHERE biz_date >= :h_from AND biz_date <= :h_to + AND (acct = 'FEE_INC' OR acct = 'BADDEBT_EXP' OR acct LIKE 'FEE_DEPT%') + GROUP BY acct ORDER BY acct; + EXEC SQL OPEN mc_cur; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH mc_cur INTO :h_acct, :h_net; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d]\n", sqlca.sqlcode); EXEC SQL CLOSE mc_cur; tpabort(0); return 1; } + if (h_net == 0) continue; + mag = (h_net > 0) ? h_net : -h_net; + /* 대변 잔액(수익)은 D 로, 차변 잔액(비용)은 C 로 소거 */ + if (lgdb_insert_line(vid, h_acct, (h_net > 0) ? "D" : "C", mag, + "", rec.month_to, "posted") < 0) + { EXEC SQL CLOSE mc_cur; tpabort(0); return 1; } + retained += h_net; + rec.accts++; + } + EXEC SQL CLOSE mc_cur; + + /* 상대 라인: 순이익이면 C:RETAINED, 순손실이면 D:RETAINED -> 전표 균형 완성 */ + if (retained != 0) { + mag = (retained > 0) ? retained : -retained; + if (lgdb_insert_line(vid, "RETAINED", (retained > 0) ? "C" : "D", mag, + "", rec.month_to, "posted") < 0) { tpabort(0); return 1; } + } + rec.net_income = retained; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_monthclose_batch COMMIT: [%s~%s] vid=%ld 손익계정 %ld개 순이익 %ld\n", + rec.month_from, rec.month_to, rec.closing_vid, rec.accts, rec.net_income); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_payrecon_batch.pgc b/app/src/lg/batch/lg_payrecon_batch.pgc new file mode 100644 index 0000000..d07ef5e --- /dev/null +++ b/app/src/lg/batch/lg_payrecon_batch.pgc @@ -0,0 +1,69 @@ +/* + * lg_payrecon_batch.pgc - lg 원장-지급 대사 배치 (커서 + XA). + * + * 당일 가맹점 잔액 테이블(lg_merch_bal)의 순액(대변-차변 기준으로 재구성한 + * credit_total - debit_total)과 정산 지급액을 가맹점별로 비교, 최악 갭 + * 가맹점을 추적해 리포트한다. 모두 ONE global XA transaction 에서 수행. + * Usage: lg_payrecon_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" +#include "lg_payrecon_batch.h" + +int main(int argc, char **argv) +{ + lg_payrecon_rec_t rec; + long gap, absgap, worstabs = -1; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_cr, h_dr, h_paid; + EXEC SQL END DECLARE SECTION; + + memset(&rec, 0, sizeof(rec)); + strncpy(rec.biz_date, (argc > 1) ? argv[1] : "2026-07-19", sizeof(rec.biz_date)-1); + strncpy(h_bizdate, rec.biz_date, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE pr_cur CURSOR FOR + SELECT b.merchant_id, b.credit_total, b.debit_total, + coalesce((SELECT sum(s.net) FROM settlement s + WHERE s.merchant_id = b.merchant_id + AND s.biz_date = b.biz_date), 0) + FROM lg_merch_bal b WHERE b.biz_date = :h_bizdate + ORDER BY b.merchant_id; + EXEC SQL OPEN pr_cur; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH pr_cur INTO :h_merch, :h_cr, :h_dr, :h_paid; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d]\n", sqlca.sqlcode); EXEC SQL CLOSE pr_cur; tpabort(0); return 1; } + gap = (h_cr - h_dr) - h_paid; + rec.checked++; + if (gap != 0) { + rec.bad++; + absgap = (gap > 0) ? gap : -gap; + if (absgap > worstabs) { + worstabs = absgap; + rec.worst_gap = gap; + strncpy(rec.worst_merch, h_merch, sizeof(rec.worst_merch)-1); + rec.worst_merch[sizeof(rec.worst_merch)-1] = 0; + } + } + } + EXEC SQL CLOSE pr_cur; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_payrecon_batch COMMIT: %s 점검 %ld곳 불일치 %ld곳 최악 %s(%ld)\n", + rec.biz_date, rec.checked, rec.bad, rec.bad ? rec.worst_merch : "-", rec.worst_gap); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_pendsweep_batch.pgc b/app/src/lg/batch/lg_pendsweep_batch.pgc new file mode 100644 index 0000000..a8c8580 --- /dev/null +++ b/app/src/lg/batch/lg_pendsweep_batch.pgc @@ -0,0 +1,52 @@ +/* + * lg_pendsweep_batch.pgc - lg 미결전표 일괄 정리 배치 (XA). + * + * 기준일 이전에 작성(D)된 채 방치된 미결전표를 일괄 X(취소) 처리하고 소속 + * 라인을 closed 로 전환한다. 두 UPDATE 의 처리 건수를 대조해 보고한다. + * 모두 ONE global XA transaction 에서 수행. + * Usage: lg_pendsweep_batch [CUTOFF-DATE] + */ +#include +#include +#include +#include +#include "lg_dbio.h" +#include "lg_pendsweep_batch.h" + +int main(int argc, char **argv) +{ + lg_pendsweep_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_cutoff[16]; + EXEC SQL END DECLARE SECTION; + + memset(&rec, 0, sizeof(rec)); + strncpy(rec.cutoff, (argc > 1) ? argv[1] : "2026-07-01", sizeof(rec.cutoff)-1); + strncpy(h_cutoff, rec.cutoff, sizeof(h_cutoff)-1); h_cutoff[sizeof(h_cutoff)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + /* 라인을 먼저 closed 처리 (전표 상태 갱신 전에 대상 식별) */ + EXEC SQL UPDATE lg_ledger_line SET status = 'closed' + WHERE status = 'posted' + AND voucher_id IN (SELECT voucher_id FROM lg_voucher + WHERE status = 'D' AND biz_date < :h_cutoff); + if (sqlca.sqlcode < 0) { fprintf(stderr, "LINE UPD FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.closed_lines = sqlca.sqlerrd[2]; + + EXEC SQL UPDATE lg_voucher + SET status = 'X', memo = memo || ' [미결 일괄정리]', updated_at = now() + WHERE status = 'D' AND biz_date < :h_cutoff; + if (sqlca.sqlcode < 0) { fprintf(stderr, "VCH UPD FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.swept_vouchers = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_pendsweep_batch COMMIT: %s 이전 미결 %ld전표 / %ld라인 정리\n", + rec.cutoff, rec.swept_vouchers, rec.closed_lines); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_periodtb_batch.pgc b/app/src/lg/batch/lg_periodtb_batch.pgc new file mode 100644 index 0000000..9234733 --- /dev/null +++ b/app/src/lg/batch/lg_periodtb_batch.pgc @@ -0,0 +1,61 @@ +/* + * lg_periodtb_batch.pgc - lg 기간별 시산표 배치 (INSERT..SELECT + XA). + * + * [FROM ~ TO] 구간 원장 라인을 계정별로 집계해 기말일(TO) 키로 lg_trial_bal + * 에 세트 지향 한 방(INSERT..SELECT..ON CONFLICT)으로 적재한 뒤, 적재 결과를 + * 재조회해 총차변=총대변 균형을 검증한다. ONE global XA transaction. + * Usage: lg_periodtb_batch [FROM] [TO] + */ +#include +#include +#include +#include +#include "lg_dbio.h" +#include "lg_periodtb_batch.h" + +int main(int argc, char **argv) +{ + lg_periodtb_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_from[16], h_to[16]; + long h_rows, h_dr, h_cr; + EXEC SQL END DECLARE SECTION; + + memset(&rec, 0, sizeof(rec)); + strncpy(rec.date_from, (argc > 1) ? argv[1] : "2026-07-01", sizeof(rec.date_from)-1); + strncpy(rec.date_to, (argc > 2) ? argv[2] : "2026-07-19", sizeof(rec.date_to)-1); + strncpy(h_from, rec.date_from, sizeof(h_from)-1); h_from[sizeof(h_from)-1] = 0; + strncpy(h_to, rec.date_to, sizeof(h_to)-1); h_to[sizeof(h_to)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL INSERT INTO lg_trial_bal (acct, biz_date, debit_total, credit_total, diff) + SELECT acct, CAST(:h_to AS date), + coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0), + coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0), + coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE -amount END), 0) + FROM lg_ledger_line + WHERE biz_date >= :h_from AND biz_date <= :h_to + GROUP BY acct + ON CONFLICT (acct, biz_date) DO UPDATE + SET debit_total = EXCLUDED.debit_total, credit_total = EXCLUDED.credit_total, + diff = EXCLUDED.diff, updated_at = now(); + if (sqlca.sqlcode < 0) { fprintf(stderr, "LOAD FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_rows = sqlca.sqlerrd[2]; + + EXEC SQL SELECT coalesce(sum(debit_total), 0), coalesce(sum(credit_total), 0) + INTO :h_dr, :h_cr FROM lg_trial_bal WHERE biz_date = :h_to; + if (sqlca.sqlcode < 0) { fprintf(stderr, "VERIFY FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + rec.accts = h_rows; rec.dr_total = h_dr; rec.cr_total = h_cr; + printf(">>> lg_periodtb_batch COMMIT: [%s~%s] %ld계정 차변 %ld 대변 %ld %s\n", + rec.date_from, rec.date_to, rec.accts, rec.dr_total, rec.cr_total, + (rec.dr_total == rec.cr_total) ? "(균형)" : "(불균형!)"); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_settlerecon_batch.pgc b/app/src/lg/batch/lg_settlerecon_batch.pgc new file mode 100644 index 0000000..93e72a8 --- /dev/null +++ b/app/src/lg/batch/lg_settlerecon_batch.pgc @@ -0,0 +1,76 @@ +/* + * lg_settlerecon_batch.pgc - lg 원장-정산 대사 배치 (커서 + XA). + * + * 당일 정산(settlement) 순액과 원장 MERCH_PAY 대변 합계를 가맹점별로 FULL + * JOIN 비교해 일치/불일치를 판정한다. 전 가맹점 일치 시 당일 SETTLE 전표를 + * M(대사)으로 일괄 승격. 모두 ONE global XA transaction 에서 수행. + * Usage: lg_settlerecon_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" +#include "lg_settlerecon_batch.h" + +int main(int argc, char **argv) +{ + lg_settlerecon_rec_t rec; + long gap; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_setnet, h_ledcr, h_marked; + EXEC SQL END DECLARE SECTION; + + memset(&rec, 0, sizeof(rec)); + strncpy(rec.biz_date, (argc > 1) ? argv[1] : "2026-07-19", sizeof(rec.biz_date)-1); + strncpy(h_bizdate, rec.biz_date, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE sr_cur CURSOR FOR + SELECT coalesce(s.mid, l.mid), + coalesce(s.net, 0), coalesce(l.cr, 0) + FROM (SELECT merchant_id AS mid, sum(net) AS net + FROM settlement WHERE biz_date = :h_bizdate GROUP BY merchant_id) s + FULL JOIN (SELECT merchant_id AS mid, sum(amount) AS cr + FROM lg_ledger_line + WHERE biz_date = :h_bizdate AND acct = 'MERCH_PAY' AND dc = 'C' + AND merchant_id <> '' + GROUP BY merchant_id) l ON l.mid = s.mid + ORDER BY 1; + EXEC SQL OPEN sr_cur; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH sr_cur INTO :h_merch, :h_setnet, :h_ledcr; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d]\n", sqlca.sqlcode); EXEC SQL CLOSE sr_cur; tpabort(0); return 1; } + gap = h_setnet - h_ledcr; + if (gap == 0) rec.matched++; + else { + printf(" 불일치: %s 정산 %ld vs 원장 %ld (갭 %ld)\n", h_merch, h_setnet, h_ledcr, gap); + rec.gap_total += (gap > 0) ? gap : -gap; + rec.mismatched++; + } + } + EXEC SQL CLOSE sr_cur; + + h_marked = 0; + if (rec.mismatched == 0 && rec.matched > 0) { + EXEC SQL UPDATE lg_voucher SET status = 'M', updated_at = now() + WHERE voucher_type = 'SETTLE' AND biz_date = :h_bizdate AND status = 'P'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "MARK FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + h_marked = sqlca.sqlerrd[2]; + } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_settlerecon_batch COMMIT: %s 일치 %ld / 불일치 %ld (절대갭 %ld) %ld전표 대사\n", + rec.biz_date, rec.matched, rec.mismatched, rec.gap_total, h_marked); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_statacct_batch.pgc b/app/src/lg/batch/lg_statacct_batch.pgc new file mode 100644 index 0000000..ae1aa32 --- /dev/null +++ b/app/src/lg/batch/lg_statacct_batch.pgc @@ -0,0 +1,65 @@ +/* + * lg_statacct_batch.pgc - lg 계정별 원장 통계 배치 (커서 + XA). + * + * 당일 원장을 계정과목별로 훑어 라인 수/차변/대변/잔액/평균 분개금액을 + * 리포트하고, 마지막에 전 계정 합계로 총차변=총대변(복식부기 균형)을 + * 검산한다. 조회 전용 통계. ONE global XA transaction 에서 수행. + * Usage: lg_statacct_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dbio.h" +#include "lg_statacct_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + lg_statacct_line_t rec; + long accts = 0, grand_dr = 0, grand_cr = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_acct[32]; + long h_lines, h_dr, h_cr; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE sab_cur CURSOR FOR + SELECT acct, count(*), + coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0), + coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0) + FROM lg_ledger_line WHERE biz_date = :h_bizdate + GROUP BY acct ORDER BY acct; + EXEC SQL OPEN sab_cur; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + printf("--- 계정별 원장 통계 (%s) ---\n", bizdate); + for (;;) { + EXEC SQL FETCH sab_cur INTO :h_acct, :h_lines, :h_dr, :h_cr; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d]\n", sqlca.sqlcode); EXEC SQL CLOSE sab_cur; tpabort(0); return 1; } + memset(&rec, 0, sizeof(rec)); + strncpy(rec.acct, h_acct, sizeof(rec.acct)-1); + rec.lines = h_lines; rec.debit = h_dr; rec.credit = h_cr; + rec.balance = h_dr - h_cr; + rec.avg_line = (h_lines > 0) ? (h_dr + h_cr) / h_lines : 0; + printf(" %-14s %5ld라인 차 %12ld 대 %12ld 잔액 %12ld 평균 %10ld\n", + rec.acct, rec.lines, rec.debit, rec.credit, rec.balance, rec.avg_line); + grand_dr += h_dr; grand_cr += h_cr; + accts++; + } + EXEC SQL CLOSE sab_cur; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> lg_statacct_batch COMMIT: %ld계정 총차변 %ld 총대변 %ld %s\n", + accts, grand_dr, grand_cr, (grand_dr == grand_cr) ? "(균형)" : "(불균형!)"); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/dbio/lg_agedpend_batch.h b/app/src/lg/dbio/lg_agedpend_batch.h new file mode 100644 index 0000000..de332ac --- /dev/null +++ b/app/src/lg/dbio/lg_agedpend_batch.h @@ -0,0 +1,17 @@ +/* + * lg_agedpend_batch.h - lg 미결전표 에이징 배치 전용 copybook. + * 경과일 버킷별 미결 히스토그램 레이아웃. + */ +#ifndef LG_AGEDPEND_BATCH_H +#define LG_AGEDPEND_BATCH_H + +typedef struct { + char as_of[16]; + long bucket_1d; + long bucket_7d; + long bucket_30d; + long bucket_old; + long amount_total; +} lg_agedpend_rec_t; + +#endif /* LG_AGEDPEND_BATCH_H */ diff --git a/app/src/lg/dbio/lg_baddebt_batch.h b/app/src/lg/dbio/lg_baddebt_batch.h new file mode 100644 index 0000000..384a8c5 --- /dev/null +++ b/app/src/lg/dbio/lg_baddebt_batch.h @@ -0,0 +1,15 @@ +/* + * lg_baddebt_batch.h - lg 대손 일괄 처리 배치 전용 copybook. + * 가맹점별 대손 상각 결과 레이아웃. + */ +#ifndef LG_BADDEBT_BATCH_H +#define LG_BADDEBT_BATCH_H + +typedef struct { + char biz_date[16]; + long threshold_days; + long merchants; + long written_off; +} lg_baddebt_rec_t; + +#endif /* LG_BADDEBT_BATCH_H */ diff --git a/app/src/lg/dbio/lg_balproof_batch.h b/app/src/lg/dbio/lg_balproof_batch.h new file mode 100644 index 0000000..24383c7 --- /dev/null +++ b/app/src/lg/dbio/lg_balproof_batch.h @@ -0,0 +1,15 @@ +/* + * lg_balproof_batch.h - lg 잔액 증명 배치 전용 copybook. + * 계정잔액 검증/보정 결과 레이아웃. + */ +#ifndef LG_BALPROOF_BATCH_H +#define LG_BALPROOF_BATCH_H + +typedef struct { + char biz_date[16]; + long verified; + long mismatched; + long repaired; +} lg_balproof_rec_t; + +#endif /* LG_BALPROOF_BATCH_H */ diff --git a/app/src/lg/dbio/lg_closechk_batch.h b/app/src/lg/dbio/lg_closechk_batch.h new file mode 100644 index 0000000..13d7a86 --- /dev/null +++ b/app/src/lg/dbio/lg_closechk_batch.h @@ -0,0 +1,17 @@ +/* + * lg_closechk_batch.h - lg 결산 체크리스트 배치 전용 copybook. + * 결산 전 점검 항목별 결과 레이아웃. + */ +#ifndef LG_CLOSECHK_BATCH_H +#define LG_CLOSECHK_BATCH_H + +typedef struct { + char biz_date[16]; + long draft_vouchers; + long imbalanced; + long zero_lines; + long orphan_lines; + long failed_items; +} lg_closechk_rec_t; + +#endif /* LG_CLOSECHK_BATCH_H */ diff --git a/app/src/lg/dbio/lg_deptpl_batch.h b/app/src/lg/dbio/lg_deptpl_batch.h new file mode 100644 index 0000000..9012ac5 --- /dev/null +++ b/app/src/lg/dbio/lg_deptpl_batch.h @@ -0,0 +1,16 @@ +/* + * lg_deptpl_batch.h - lg 부문별 손익 집계 배치 전용 copybook. + * 업종/부문별 수수료 손익 리포트 라인 레이아웃. + */ +#ifndef LG_DEPTPL_BATCH_H +#define LG_DEPTPL_BATCH_H + +typedef struct { + char category[32]; + long txns; + long gross; + long fee; + long share_bps; +} lg_deptpl_rec_t; + +#endif /* LG_DEPTPL_BATCH_H */ diff --git a/app/src/lg/dbio/lg_dupscan_batch.h b/app/src/lg/dbio/lg_dupscan_batch.h new file mode 100644 index 0000000..90b7953 --- /dev/null +++ b/app/src/lg/dbio/lg_dupscan_batch.h @@ -0,0 +1,15 @@ +/* + * lg_dupscan_batch.h - lg 중복 분개 스캔 배치 전용 copybook. + * 이중기표 후보 그룹 레이아웃. + */ +#ifndef LG_DUPSCAN_BATCH_H +#define LG_DUPSCAN_BATCH_H + +typedef struct { + char biz_date[16]; + long groups; + long extra_lines; + long extra_amount; +} lg_dupscan_rec_t; + +#endif /* LG_DUPSCAN_BATCH_H */ diff --git a/app/src/lg/dbio/lg_feerecog_batch.h b/app/src/lg/dbio/lg_feerecog_batch.h new file mode 100644 index 0000000..37a53aa --- /dev/null +++ b/app/src/lg/dbio/lg_feerecog_batch.h @@ -0,0 +1,15 @@ +/* + * lg_feerecog_batch.h - lg 수수료 수익 인식 배치 전용 copybook. + * 가맹점별 수수료 인식 전기 결과 레이아웃. + */ +#ifndef LG_FEERECOG_BATCH_H +#define LG_FEERECOG_BATCH_H + +typedef struct { + char biz_date[16]; + long merchants; + long vouchers; + long fee_total; +} lg_feerecog_rec_t; + +#endif /* LG_FEERECOG_BATCH_H */ diff --git a/app/src/lg/dbio/lg_monthclose_batch.h b/app/src/lg/dbio/lg_monthclose_batch.h new file mode 100644 index 0000000..2dd2b81 --- /dev/null +++ b/app/src/lg/dbio/lg_monthclose_batch.h @@ -0,0 +1,16 @@ +/* + * lg_monthclose_batch.h - lg 월차 결산 배치 전용 copybook. + * 월 구간 계정별 결산 집계 레이아웃. + */ +#ifndef LG_MONTHCLOSE_BATCH_H +#define LG_MONTHCLOSE_BATCH_H + +typedef struct { + char month_from[16]; + char month_to[16]; + long accts; + long closing_vid; + long net_income; +} lg_monthclose_rec_t; + +#endif /* LG_MONTHCLOSE_BATCH_H */ diff --git a/app/src/lg/dbio/lg_payrecon_batch.h b/app/src/lg/dbio/lg_payrecon_batch.h new file mode 100644 index 0000000..9500e69 --- /dev/null +++ b/app/src/lg/dbio/lg_payrecon_batch.h @@ -0,0 +1,16 @@ +/* + * lg_payrecon_batch.h - lg 원장-지급 대사 배치 전용 copybook. + * 가맹점 잔액 대 지급액 대사 결과 레이아웃. + */ +#ifndef LG_PAYRECON_BATCH_H +#define LG_PAYRECON_BATCH_H + +typedef struct { + char biz_date[16]; + long checked; + long bad; + long worst_gap; + char worst_merch[64]; +} lg_payrecon_rec_t; + +#endif /* LG_PAYRECON_BATCH_H */ diff --git a/app/src/lg/dbio/lg_pendsweep_batch.h b/app/src/lg/dbio/lg_pendsweep_batch.h new file mode 100644 index 0000000..e8abfbb --- /dev/null +++ b/app/src/lg/dbio/lg_pendsweep_batch.h @@ -0,0 +1,14 @@ +/* + * lg_pendsweep_batch.h - lg 미결전표 일괄 정리 배치 전용 copybook. + * 기준일 이전 미결전표 정리 결과 레이아웃. + */ +#ifndef LG_PENDSWEEP_BATCH_H +#define LG_PENDSWEEP_BATCH_H + +typedef struct { + char cutoff[16]; + long swept_vouchers; + long closed_lines; +} lg_pendsweep_rec_t; + +#endif /* LG_PENDSWEEP_BATCH_H */ diff --git a/app/src/lg/dbio/lg_periodtb_batch.h b/app/src/lg/dbio/lg_periodtb_batch.h new file mode 100644 index 0000000..1d9a001 --- /dev/null +++ b/app/src/lg/dbio/lg_periodtb_batch.h @@ -0,0 +1,16 @@ +/* + * lg_periodtb_batch.h - lg 기간별 시산표 배치 전용 copybook. + * 기간 시산표 적재 결과 레이아웃. + */ +#ifndef LG_PERIODTB_BATCH_H +#define LG_PERIODTB_BATCH_H + +typedef struct { + char date_from[16]; + char date_to[16]; + long accts; + long dr_total; + long cr_total; +} lg_periodtb_rec_t; + +#endif /* LG_PERIODTB_BATCH_H */ diff --git a/app/src/lg/dbio/lg_settlerecon_batch.h b/app/src/lg/dbio/lg_settlerecon_batch.h new file mode 100644 index 0000000..24b43cb --- /dev/null +++ b/app/src/lg/dbio/lg_settlerecon_batch.h @@ -0,0 +1,15 @@ +/* + * lg_settlerecon_batch.h - lg 원장-정산 대사 배치 전용 copybook. + * 가맹점별 정산 대 원장 대사 결과 레이아웃. + */ +#ifndef LG_SETTLERECON_BATCH_H +#define LG_SETTLERECON_BATCH_H + +typedef struct { + char biz_date[16]; + long matched; + long mismatched; + long gap_total; +} lg_settlerecon_rec_t; + +#endif /* LG_SETTLERECON_BATCH_H */ diff --git a/app/src/lg/dbio/lg_statacct_batch.h b/app/src/lg/dbio/lg_statacct_batch.h new file mode 100644 index 0000000..665b607 --- /dev/null +++ b/app/src/lg/dbio/lg_statacct_batch.h @@ -0,0 +1,17 @@ +/* + * lg_statacct_batch.h - lg 계정별 원장 통계 배치 전용 copybook. + * 계정 통계 리포트 라인 레이아웃. + */ +#ifndef LG_STATACCT_BATCH_H +#define LG_STATACCT_BATCH_H + +typedef struct { + char acct[32]; + long lines; + long debit; + long credit; + long balance; + long avg_line; +} lg_statacct_line_t; + +#endif /* LG_STATACCT_BATCH_H */ diff --git a/app/src/lg/lg_svr.pgc b/app/src/lg/lg_svr.pgc index 03dae28..e374538 100644 --- a/app/src/lg/lg_svr.pgc +++ b/app/src/lg/lg_svr.pgc @@ -2,7 +2,7 @@ * lg_svr.pgc - lg (원장/ledger) MODULE SERVER (thin dispatcher). * * The legacy "one module server, many services" pattern: this binary owns NO - * business logic. Each of lg's 30 online services now lives in its own file + * business logic. Each of lg's 75 online services now lives in its own file * (app/src/lg/svc/.pgc) with its own copybook header * (app/src/lg/svc/.h); those objects are archived into liblgsvc.a and * linked into this server. Here we only: @@ -52,6 +52,51 @@ #include "LG_FEEPOST.h" #include "LG_STATUS.h" #include "LG_CONFIRM.h" +#include "LG_ACCTLEDGER.h" +#include "LG_ACCTRANGE.h" +#include "LG_MONTHCLOSE.h" +#include "LG_QTRCLOSE.h" +#include "LG_REDENTRY.h" +#include "LG_BLACKENTRY.h" +#include "LG_PENDLIST.h" +#include "LG_PENDAGE.h" +#include "LG_APPROVE.h" +#include "LG_REJECT.h" +#include "LG_APPRQUEUE.h" +#include "LG_DEPTPL.h" +#include "LG_FEERECOG.h" +#include "LG_FEEACCRUE.h" +#include "LG_RECVACCRUE.h" +#include "LG_PAYACCRUE.h" +#include "LG_BADDEBT.h" +#include "LG_BADDEBTRCV.h" +#include "LG_SETTLERECON.h" +#include "LG_PAYRECON.h" +#include "LG_BALPROOF.h" +#include "LG_MERCHPROOF.h" +#include "LG_PERIODTB.h" +#include "LG_MASKINQ.h" +#include "LG_CLOSECHK.h" +#include "LG_STATDC.h" +#include "LG_STATACCT.h" +#include "LG_STATTYPE.h" +#include "LG_STATMERCH.h" +#include "LG_VOIDVOUCHER.h" +#include "LG_REOPEN.h" +#include "LG_ACCTHIST.h" +#include "LG_DAYCLOSE.h" +#include "LG_NETPOS.h" +#include "LG_LINECOUNT.h" +#include "LG_BIGLINE.h" +#include "LG_SUSPENSE.h" +#include "LG_ACCRUEREV.h" +#include "LG_FEESPLIT.h" +#include "LG_PLCLOSE.h" +#include "LG_TBDIFF.h" +#include "LG_VOUCHTRACE.h" +#include "LG_MEMOAUDIT.h" +#include "LG_ZEROLINE.h" +#include "LG_DUPSCAN.h" /* ----- advertise table: {service name, function} (fn is extern from its file) */ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { @@ -70,6 +115,29 @@ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { {"LG_TRANSFER", LG_TRANSFER}, {"LG_SETTLEPOST", LG_SETTLEPOST}, {"LG_ACQPOST", LG_ACQPOST}, {"LG_FEEPOST", LG_FEEPOST}, {"LG_STATUS", LG_STATUS}, {"LG_CONFIRM", LG_CONFIRM}, + {"LG_ACCTLEDGER", LG_ACCTLEDGER},{"LG_ACCTRANGE", LG_ACCTRANGE}, + {"LG_MONTHCLOSE", LG_MONTHCLOSE},{"LG_QTRCLOSE", LG_QTRCLOSE}, + {"LG_REDENTRY", LG_REDENTRY}, {"LG_BLACKENTRY", LG_BLACKENTRY}, + {"LG_PENDLIST", LG_PENDLIST}, {"LG_PENDAGE", LG_PENDAGE}, + {"LG_APPROVE", LG_APPROVE}, {"LG_REJECT", LG_REJECT}, + {"LG_APPRQUEUE", LG_APPRQUEUE}, {"LG_DEPTPL", LG_DEPTPL}, + {"LG_FEERECOG", LG_FEERECOG}, {"LG_FEEACCRUE", LG_FEEACCRUE}, + {"LG_RECVACCRUE", LG_RECVACCRUE},{"LG_PAYACCRUE", LG_PAYACCRUE}, + {"LG_BADDEBT", LG_BADDEBT}, {"LG_BADDEBTRCV", LG_BADDEBTRCV}, + {"LG_SETTLERECON", LG_SETTLERECON},{"LG_PAYRECON", LG_PAYRECON}, + {"LG_BALPROOF", LG_BALPROOF}, {"LG_MERCHPROOF", LG_MERCHPROOF}, + {"LG_PERIODTB", LG_PERIODTB}, {"LG_MASKINQ", LG_MASKINQ}, + {"LG_CLOSECHK", LG_CLOSECHK}, {"LG_STATDC", LG_STATDC}, + {"LG_STATACCT", LG_STATACCT}, {"LG_STATTYPE", LG_STATTYPE}, + {"LG_STATMERCH", LG_STATMERCH}, {"LG_VOIDVOUCHER", LG_VOIDVOUCHER}, + {"LG_REOPEN", LG_REOPEN}, {"LG_ACCTHIST", LG_ACCTHIST}, + {"LG_DAYCLOSE", LG_DAYCLOSE}, {"LG_NETPOS", LG_NETPOS}, + {"LG_LINECOUNT", LG_LINECOUNT}, {"LG_BIGLINE", LG_BIGLINE}, + {"LG_SUSPENSE", LG_SUSPENSE}, {"LG_ACCRUEREV", LG_ACCRUEREV}, + {"LG_FEESPLIT", LG_FEESPLIT}, {"LG_PLCLOSE", LG_PLCLOSE}, + {"LG_TBDIFF", LG_TBDIFF}, {"LG_VOUCHTRACE", LG_VOUCHTRACE}, + {"LG_MEMOAUDIT", LG_MEMOAUDIT}, {"LG_ZEROLINE", LG_ZEROLINE}, + {"LG_DUPSCAN", LG_DUPSCAN}, {NULL, NULL} }; diff --git a/app/src/lg/svc/LG_ACCRUEREV.h b/app/src/lg/svc/LG_ACCRUEREV.h new file mode 100644 index 0000000..97d1e57 --- /dev/null +++ b/app/src/lg/svc/LG_ACCRUEREV.h @@ -0,0 +1,21 @@ +/* + * LG_ACCRUEREV.h - lg 서비스 LG_ACCRUEREV 카피북 (copybook / record header). + * 계상 환입 분개: 기한 경과 계상 전표를 적색 환입 후 X 처리. + */ +#ifndef LG_ACCRUEREV_H +#define LG_ACCRUEREV_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_accruerev_rec_t; + +void LG_ACCRUEREV(TPSVCINFO *p); + +#endif /* LG_ACCRUEREV_H */ diff --git a/app/src/lg/svc/LG_ACCRUEREV.pgc b/app/src/lg/svc/LG_ACCRUEREV.pgc new file mode 100644 index 0000000..16322eb --- /dev/null +++ b/app/src/lg/svc/LG_ACCRUEREV.pgc @@ -0,0 +1,56 @@ +/* + * LG_ACCRUEREV.pgc - lg 모듈 서비스 LG_ACCRUEREV (one service = one file). + * 계상 환입 분개: 기준일(T_BIZDATE) 이전의 미결 '계상' 전표를 커서로 찾아 + * 각각 차/대변 반전 환입 전표를 만들고 원전표를 X 처리한다 (자동 환입). + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_ACCRUEREV.h" + +void LG_ACCRUEREV(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long reversed = 0, lastnew = 0, nvid; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_vid, h_nvid; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL DECLARE ar_cur CURSOR FOR + SELECT voucher_id FROM lg_voucher + WHERE status = 'D' AND memo LIKE '%계상%' AND biz_date < :h_bizdate + ORDER BY voucher_id; + EXEC SQL OPEN ar_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH ar_cur INTO :h_vid; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ar_cur; FAIL(b); } + + nvid = lgdb_next_voucher_id(); + if (nvid < 0) { EXEC SQL CLOSE ar_cur; FAIL(b); } + if (lgdb_insert_voucher(nvid, "REVERSE", h_vid, "P", bizdate, "계상 자동환입") < 0) + { EXEC SQL CLOSE ar_cur; FAIL(b); } + h_nvid = nvid; + EXEC SQL INSERT INTO lg_ledger_line + (line_id, voucher_id, acct, dc, amount, merchant_id, biz_date, status) + SELECT nextval('lg_line_seq'), :h_nvid, acct, + CASE WHEN dc = 'C' THEN 'D' ELSE 'C' END, + amount, merchant_id, :h_bizdate, 'posted' + FROM lg_ledger_line WHERE voucher_id = :h_vid; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ar_cur; FAIL(b); } + if (lgdb_update_voucher_status(h_vid, "X") < 0) { EXEC SQL CLOSE ar_cur; FAIL(b); } + lastnew = nvid; + reversed++; + } + EXEC SQL CLOSE ar_cur; + + setl(b, T_COUNT, reversed); setl(b, T_ID2, lastnew); + userlog("LG_ACCRUEREV 기준 %s 이전 계상 %ld건 환입", bizdate, reversed); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_ACCTHIST.h b/app/src/lg/svc/LG_ACCTHIST.h new file mode 100644 index 0000000..d54a530 --- /dev/null +++ b/app/src/lg/svc/LG_ACCTHIST.h @@ -0,0 +1,21 @@ +/* + * LG_ACCTHIST.h - lg 서비스 LG_ACCTHIST 카피북 (copybook / record header). + * 계정 잔액 이력 조회: 기간 계정잔액 행수·최종/최소/최대 잔액. + */ +#ifndef LG_ACCTHIST_H +#define LG_ACCTHIST_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_accthist_rec_t; + +void LG_ACCTHIST(TPSVCINFO *p); + +#endif /* LG_ACCTHIST_H */ diff --git a/app/src/lg/svc/LG_ACCTHIST.pgc b/app/src/lg/svc/LG_ACCTHIST.pgc new file mode 100644 index 0000000..e82aed2 --- /dev/null +++ b/app/src/lg/svc/LG_ACCTHIST.pgc @@ -0,0 +1,50 @@ +/* + * LG_ACCTHIST.pgc - lg 모듈 서비스 LG_ACCTHIST (one service = one file). + * 계정 잔액 이력 조회: [T_BIZDATE ~ T_STR1] 구간의 계정잔액(lg_account_bal) + * 행을 커서로 훑어 행수·최종 잔액·최소/최대 잔액을 반환한다 (추이 분석용). + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_ACCTHIST.h" + +void LG_ACCTHIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char acct[32], dfrom[16], dto[16]; + long rows = 0, last = 0, lo = 0, hi = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_acct[32], h_from[16], h_to[16]; + long h_bal; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, acct, sizeof(acct)); if (acct[0] == 0) strcpy(acct, "RECV"); + gets_(b, T_BIZDATE, dfrom, sizeof(dfrom)); + gets_(b, T_STR1, dto, sizeof(dto)); if (dto[0] == 0) strcpy(dto, dfrom); + strncpy(h_acct, acct, sizeof(h_acct)-1); h_acct[sizeof(h_acct)-1] = 0; + strncpy(h_from, dfrom, sizeof(h_from)-1); h_from[sizeof(h_from)-1] = 0; + strncpy(h_to, dto, sizeof(h_to)-1); h_to[sizeof(h_to)-1] = 0; + + EXEC SQL DECLARE ah_cur CURSOR FOR + SELECT balance FROM lg_account_bal + WHERE acct = :h_acct AND biz_date >= :h_from AND biz_date <= :h_to + ORDER BY biz_date; + EXEC SQL OPEN ah_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH ah_cur INTO :h_bal; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ah_cur; FAIL(b); } + if (rows == 0) { lo = h_bal; hi = h_bal; } + if (h_bal < lo) lo = h_bal; + if (h_bal > hi) hi = h_bal; + last = h_bal; + rows++; + } + EXEC SQL CLOSE ah_cur; + + setl(b, T_COUNT, rows); setl(b, T_AMT2, last); + setl(b, T_AMT3, lo); setl(b, T_AMT4, hi); + userlog("LG_ACCTHIST %s [%s~%s] %ld일 최종 %ld (min %ld / max %ld)", acct, dfrom, dto, rows, last, lo, hi); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_ACCTLEDGER.h b/app/src/lg/svc/LG_ACCTLEDGER.h new file mode 100644 index 0000000..1bb7797 --- /dev/null +++ b/app/src/lg/svc/LG_ACCTLEDGER.h @@ -0,0 +1,21 @@ +/* + * LG_ACCTLEDGER.h - lg 서비스 LG_ACCTLEDGER 카피북 (copybook / record header). + * 계정과목별 원장 라인 조회: 차변/대변 건수·합계와 최종 라인 ID. + */ +#ifndef LG_ACCTLEDGER_H +#define LG_ACCTLEDGER_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_acctledger_rec_t; + +void LG_ACCTLEDGER(TPSVCINFO *p); + +#endif /* LG_ACCTLEDGER_H */ diff --git a/app/src/lg/svc/LG_ACCTLEDGER.pgc b/app/src/lg/svc/LG_ACCTLEDGER.pgc new file mode 100644 index 0000000..7646c42 --- /dev/null +++ b/app/src/lg/svc/LG_ACCTLEDGER.pgc @@ -0,0 +1,47 @@ +/* + * LG_ACCTLEDGER.pgc - lg 모듈 서비스 LG_ACCTLEDGER (one service = one file). + * 계정과목별 원장조회: 지정 계정/영업일의 라인을 커서로 훑어 차변/대변 + * 건수·합계와 최종 라인 ID 를 돌려준다 (복식부기: 차변-대변 = 잔액). + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_ACCTLEDGER.h" + +void LG_ACCTLEDGER(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char acct[32], bizdate[16]; + long dcnt = 0, ccnt = 0, dsum = 0, csum = 0, lastid = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_acct[32], h_bizdate[16], h_dc[4]; + long h_line, h_amt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, acct, sizeof(acct)); if (acct[0] == 0) strcpy(acct, "RECV"); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_acct, acct, sizeof(h_acct)-1); h_acct[sizeof(h_acct)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL DECLARE al_cur CURSOR FOR + SELECT line_id, dc, amount FROM lg_ledger_line + WHERE acct = :h_acct AND biz_date = :h_bizdate + ORDER BY line_id; + EXEC SQL OPEN al_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH al_cur INTO :h_line, :h_dc, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE al_cur; FAIL(b); } + if (h_dc[0] == 'D') { dcnt++; dsum += h_amt; } + else { ccnt++; csum += h_amt; } + lastid = h_line; + } + EXEC SQL CLOSE al_cur; + + setl(b, T_ARG1, dcnt); setl(b, T_ARG2, ccnt); + setl(b, T_AMT2, dsum); setl(b, T_AMT3, csum); + setl(b, T_DELTA, dsum - csum); setl(b, T_ID1, lastid); + userlog("LG_ACCTLEDGER %s %s 차변 %ld건/%ld 대변 %ld건/%ld", acct, bizdate, dcnt, dsum, ccnt, csum); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_ACCTRANGE.h b/app/src/lg/svc/LG_ACCTRANGE.h new file mode 100644 index 0000000..65463de --- /dev/null +++ b/app/src/lg/svc/LG_ACCTRANGE.h @@ -0,0 +1,21 @@ +/* + * LG_ACCTRANGE.h - lg 서비스 LG_ACCTRANGE 카피북 (copybook / record header). + * 계정과목 기간별 원장 집계: 기간 차변/대변 합계와 잔액. + */ +#ifndef LG_ACCTRANGE_H +#define LG_ACCTRANGE_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_acctrange_rec_t; + +void LG_ACCTRANGE(TPSVCINFO *p); + +#endif /* LG_ACCTRANGE_H */ diff --git a/app/src/lg/svc/LG_ACCTRANGE.pgc b/app/src/lg/svc/LG_ACCTRANGE.pgc new file mode 100644 index 0000000..ac9d3e1 --- /dev/null +++ b/app/src/lg/svc/LG_ACCTRANGE.pgc @@ -0,0 +1,38 @@ +/* + * LG_ACCTRANGE.pgc - lg 모듈 서비스 LG_ACCTRANGE (one service = one file). + * 계정과목 기간별 원장집계: [T_BIZDATE ~ T_STR1] 구간의 차변/대변 합계와 + * 라인 건수를 단문 집계로 산출하고 잔액(차변-대변)을 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_ACCTRANGE.h" + +void LG_ACCTRANGE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char acct[32], dfrom[16], dto[16]; + EXEC SQL BEGIN DECLARE SECTION; + char h_acct[32], h_from[16], h_to[16]; + long h_dr, h_cr, h_cnt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, acct, sizeof(acct)); if (acct[0] == 0) strcpy(acct, "RECV"); + gets_(b, T_BIZDATE, dfrom, sizeof(dfrom)); + gets_(b, T_STR1, dto, sizeof(dto)); if (dto[0] == 0) strcpy(dto, dfrom); + strncpy(h_acct, acct, sizeof(h_acct)-1); h_acct[sizeof(h_acct)-1] = 0; + strncpy(h_from, dfrom, sizeof(h_from)-1); h_from[sizeof(h_from)-1] = 0; + strncpy(h_to, dto, sizeof(h_to)-1); h_to[sizeof(h_to)-1] = 0; + + EXEC SQL SELECT coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0), + coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0), + count(*) + INTO :h_dr, :h_cr, :h_cnt FROM lg_ledger_line + WHERE acct = :h_acct AND biz_date >= :h_from AND biz_date <= :h_to; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_AMT2, h_dr); setl(b, T_AMT3, h_cr); + setl(b, T_COUNT, h_cnt); setl(b, T_DELTA, h_dr - h_cr); + userlog("LG_ACCTRANGE %s [%s~%s] 잔액=%ld", acct, dfrom, dto, h_dr - h_cr); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_APPROVE.h b/app/src/lg/svc/LG_APPROVE.h new file mode 100644 index 0000000..18cd23c --- /dev/null +++ b/app/src/lg/svc/LG_APPROVE.h @@ -0,0 +1,21 @@ +/* + * LG_APPROVE.h - lg 서비스 LG_APPROVE 카피북 (copybook / record header). + * 전표 승인: 차변합=대변합 균형 검증 후 D→P 승격. + */ +#ifndef LG_APPROVE_H +#define LG_APPROVE_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_approve_rec_t; + +void LG_APPROVE(TPSVCINFO *p); + +#endif /* LG_APPROVE_H */ diff --git a/app/src/lg/svc/LG_APPROVE.pgc b/app/src/lg/svc/LG_APPROVE.pgc new file mode 100644 index 0000000..c47eb75 --- /dev/null +++ b/app/src/lg/svc/LG_APPROVE.pgc @@ -0,0 +1,39 @@ +/* + * LG_APPROVE.pgc - lg 모듈 서비스 LG_APPROVE (one service = one file). + * 전표 승인 워크플로: 작성(D) 전표만 대상으로 차변합=대변합 균형을 검증한 뒤 + * P(전기)로 승격한다. 불균형 전표는 승인 거부 (복식부기 원칙 강제). + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_APPROVE.h" + +void LG_APPROVE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char st[4]; + long vid = getl(b, T_ID1); + EXEC SQL BEGIN DECLARE SECTION; + long h_vid, h_dr, h_cr; + EXEC SQL END DECLARE SECTION; + + if (lgdb_get_voucher_status(vid, st) < 0) { Bchg(b, T_MSG, 0, "전표 없음", 0L); FAIL(b); } + if (st[0] != 'D') { Bchg(b, T_MSG, 0, "작성 상태 전표만 승인 가능", 0L); FAIL(b); } + + h_vid = vid; + EXEC SQL SELECT coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0), + coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0) + INTO :h_dr, :h_cr FROM lg_ledger_line WHERE voucher_id = :h_vid; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_dr != h_cr || h_dr == 0) { + setl(b, T_DELTA, h_dr - h_cr); + Bchg(b, T_MSG, 0, "차변/대변 불균형 - 승인 거부", 0L); + FAIL(b); + } + if (lgdb_update_voucher_status(vid, "P") < 0) FAIL(b); + + setl(b, T_AMT2, h_dr); + Bchg(b, T_STATUS, 0, "P", 0L); + userlog("LG_APPROVE vid=%ld 승인 (차=대=%ld 균형 확인)", vid, h_dr); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_APPRQUEUE.h b/app/src/lg/svc/LG_APPRQUEUE.h new file mode 100644 index 0000000..8e3e0c4 --- /dev/null +++ b/app/src/lg/svc/LG_APPRQUEUE.h @@ -0,0 +1,21 @@ +/* + * LG_APPRQUEUE.h - lg 서비스 LG_APPRQUEUE 카피북 (copybook / record header). + * 승인 대기 큐 조회: 미결(D) 전표 건수와 대기 범위(최소/최대/최고령). + */ +#ifndef LG_APPRQUEUE_H +#define LG_APPRQUEUE_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_apprqueue_rec_t; + +void LG_APPRQUEUE(TPSVCINFO *p); + +#endif /* LG_APPRQUEUE_H */ diff --git a/app/src/lg/svc/LG_APPRQUEUE.pgc b/app/src/lg/svc/LG_APPRQUEUE.pgc new file mode 100644 index 0000000..4ae9347 --- /dev/null +++ b/app/src/lg/svc/LG_APPRQUEUE.pgc @@ -0,0 +1,36 @@ +/* + * LG_APPRQUEUE.pgc - lg 모듈 서비스 LG_APPRQUEUE (one service = one file). + * 전표 승인 대기 큐 조회: 미결(D) 전표의 대기 건수와 전표 ID 범위(최소/최대), + * 균형/불균형 대기 건수를 나누어 반환한다 (승인 워크플로 대시보드용). + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_APPRQUEUE.h" + +void LG_APPRQUEUE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_total, h_minv, h_maxv, h_balanced; + EXEC SQL END DECLARE SECTION; + + EXEC SQL SELECT count(*), coalesce(min(voucher_id), 0), coalesce(max(voucher_id), 0) + INTO :h_total, :h_minv, :h_maxv FROM lg_voucher WHERE status = 'D'; + if (sqlca.sqlcode < 0) FAIL(b); + + /* 대기 전표 중 차변합=대변합 균형인 건수 (즉시 승인 가능분) */ + EXEC SQL SELECT count(*) INTO :h_balanced FROM ( + SELECT l.voucher_id + FROM lg_ledger_line l JOIN lg_voucher v ON v.voucher_id = l.voucher_id + WHERE v.status = 'D' + GROUP BY l.voucher_id + HAVING sum(CASE WHEN l.dc = 'D' THEN l.amount ELSE -l.amount END) = 0) q; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_total); + setl(b, T_ID1, h_minv); setl(b, T_ID2, h_maxv); + setl(b, T_ARG1, h_balanced); setl(b, T_ARG2, h_total - h_balanced); + userlog("LG_APPRQUEUE 대기 %ld건 (균형 %ld / 불균형 %ld)", h_total, h_balanced, h_total - h_balanced); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_BADDEBT.h b/app/src/lg/svc/LG_BADDEBT.h new file mode 100644 index 0000000..008f684 --- /dev/null +++ b/app/src/lg/svc/LG_BADDEBT.h @@ -0,0 +1,21 @@ +/* + * LG_BADDEBT.h - lg 서비스 LG_BADDEBT 카피북 (copybook / record header). + * 대손 처리 분개: 가맹점 채권을 대손비용으로 대체(차/대변 균형). + */ +#ifndef LG_BADDEBT_H +#define LG_BADDEBT_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_baddebt_rec_t; + +void LG_BADDEBT(TPSVCINFO *p); + +#endif /* LG_BADDEBT_H */ diff --git a/app/src/lg/svc/LG_BADDEBT.pgc b/app/src/lg/svc/LG_BADDEBT.pgc new file mode 100644 index 0000000..07729c5 --- /dev/null +++ b/app/src/lg/svc/LG_BADDEBT.pgc @@ -0,0 +1,36 @@ +/* + * LG_BADDEBT.pgc - lg 모듈 서비스 LG_BADDEBT (one service = one file). + * 대손 처리 분개: 가맹점(T_MERCHANT) 채권 T_AMOUNT 를 상각 — + * D:BADDEBT_EXP / C:RECV 균형 전표를 전기하고 가맹점 잔액을 재산출한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_BADDEBT.h" + +void LG_BADDEBT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16], reason[64], memo[96]; + long vid, amount = getl(b, T_AMOUNT); + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + gets_(b, T_REASON, reason, sizeof(reason)); + if (merch[0] == 0 || amount <= 0) { Bchg(b, T_MSG, 0, "가맹점/금액 필요", 0L); FAIL(b); } + snprintf(memo, sizeof(memo), "대손상각:%s", reason[0] ? reason : "회수불능"); + + vid = lgdb_next_voucher_id(); + if (vid < 0) FAIL(b); + if (lgdb_insert_voucher(vid, "ADJ", 0, "P", bizdate, memo) < 0) FAIL(b); + if (lgdb_insert_line(vid, "BADDEBT_EXP", "D", amount, merch, bizdate, "posted") < 0) FAIL(b); + if (lgdb_insert_line(vid, "RECV", "C", amount, merch, bizdate, "posted") < 0) FAIL(b); + if (lgdb_upsert_acct_bal("BADDEBT_EXP", bizdate, amount, 0) < 0) FAIL(b); + if (lgdb_upsert_acct_bal("RECV", bizdate, 0, amount) < 0) FAIL(b); + if (lgdb_upsert_merch_bal(merch, bizdate) < 0) FAIL(b); + + setl(b, T_ID1, vid); + Bchg(b, T_STATUS, 0, "P", 0L); + userlog("LG_BADDEBT vid=%ld %s 대손 %ld 상각 (차=대 균형)", vid, merch, amount); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_BADDEBTRCV.h b/app/src/lg/svc/LG_BADDEBTRCV.h new file mode 100644 index 0000000..07a3276 --- /dev/null +++ b/app/src/lg/svc/LG_BADDEBTRCV.h @@ -0,0 +1,21 @@ +/* + * LG_BADDEBTRCV.h - lg 서비스 LG_BADDEBTRCV 카피북 (copybook / record header). + * 대손 회수 분개: 상각 채권 회수액을 현금/대손환입으로 전기. + */ +#ifndef LG_BADDEBTRCV_H +#define LG_BADDEBTRCV_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_baddebtrcv_rec_t; + +void LG_BADDEBTRCV(TPSVCINFO *p); + +#endif /* LG_BADDEBTRCV_H */ diff --git a/app/src/lg/svc/LG_BADDEBTRCV.pgc b/app/src/lg/svc/LG_BADDEBTRCV.pgc new file mode 100644 index 0000000..86fec69 --- /dev/null +++ b/app/src/lg/svc/LG_BADDEBTRCV.pgc @@ -0,0 +1,46 @@ +/* + * LG_BADDEBTRCV.pgc - lg 모듈 서비스 LG_BADDEBTRCV (one service = one file). + * 대손 회수(환입) 분개: 상각했던 채권이 회수되면 D:CASH / C:BADDEBT_RCV 로 + * 환입 전기한다. 회수액이 기존 상각 누계(D BADDEBT_EXP 합)를 넘으면 거부. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_BADDEBTRCV.h" + +void LG_BADDEBTRCV(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + long vid, amount = getl(b, T_AMOUNT); + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64]; + long h_written; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (merch[0] == 0 || amount <= 0) { Bchg(b, T_MSG, 0, "가맹점/금액 필요", 0L); FAIL(b); } + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + /* 기존 대손 상각 누계 확인 - 초과 환입 방지 */ + EXEC SQL SELECT coalesce(sum(amount), 0) INTO :h_written + FROM lg_ledger_line + WHERE acct = 'BADDEBT_EXP' AND dc = 'D' AND merchant_id = :h_merch; + if (sqlca.sqlcode < 0) FAIL(b); + if (amount > h_written) { + setl(b, T_AMT2, h_written); + Bchg(b, T_MSG, 0, "상각 누계 초과 환입 불가", 0L); + FAIL(b); + } + + vid = lgdb_next_voucher_id(); + if (vid < 0) FAIL(b); + if (lgdb_insert_voucher(vid, "ADJ", 0, "P", bizdate, "대손 회수 환입") < 0) FAIL(b); + if (lgdb_insert_line(vid, "CASH", "D", amount, merch, bizdate, "posted") < 0) FAIL(b); + if (lgdb_insert_line(vid, "BADDEBT_RCV", "C", amount, merch, bizdate, "posted") < 0) FAIL(b); + + setl(b, T_ID1, vid); setl(b, T_AMT2, h_written - amount); + userlog("LG_BADDEBTRCV vid=%ld %s 회수 %ld (잔여 상각 %ld)", vid, merch, amount, h_written - amount); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_BALPROOF.h b/app/src/lg/svc/LG_BALPROOF.h new file mode 100644 index 0000000..371cc22 --- /dev/null +++ b/app/src/lg/svc/LG_BALPROOF.h @@ -0,0 +1,21 @@ +/* + * LG_BALPROOF.h - lg 서비스 LG_BALPROOF 카피북 (copybook / record header). + * 잔액 증명: 계정잔액 테이블과 원장 라인 재계산치 대조. + */ +#ifndef LG_BALPROOF_H +#define LG_BALPROOF_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_balproof_rec_t; + +void LG_BALPROOF(TPSVCINFO *p); + +#endif /* LG_BALPROOF_H */ diff --git a/app/src/lg/svc/LG_BALPROOF.pgc b/app/src/lg/svc/LG_BALPROOF.pgc new file mode 100644 index 0000000..63132e3 --- /dev/null +++ b/app/src/lg/svc/LG_BALPROOF.pgc @@ -0,0 +1,44 @@ +/* + * LG_BALPROOF.pgc - lg 모듈 서비스 LG_BALPROOF (one service = one file). + * 잔액 증명: 계정잔액 테이블(lg_account_bal)의 기록 잔액과 원장 라인에서 + * 재계산한 잔액(차변-대변)을 대조해 증명서 판정(OK/NG)과 오차를 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_BALPROOF.h" + +void LG_BALPROOF(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char acct[32], bizdate[16]; + long recorded = 0, recomputed; + EXEC SQL BEGIN DECLARE SECTION; + char h_acct[32], h_bizdate[16]; + long h_bal, h_dr, h_cr; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, acct, sizeof(acct)); if (acct[0] == 0) strcpy(acct, "RECV"); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_acct, acct, sizeof(h_acct)-1); h_acct[sizeof(h_acct)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + h_bal = 0; + EXEC SQL SELECT balance INTO :h_bal FROM lg_account_bal + WHERE acct = :h_acct AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode != 100) recorded = h_bal; + + EXEC SQL SELECT coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0), + coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0) + INTO :h_dr, :h_cr FROM lg_ledger_line + WHERE acct = :h_acct AND biz_date = :h_bizdate AND status = 'posted'; + if (sqlca.sqlcode < 0) FAIL(b); + recomputed = h_dr - h_cr; + + setl(b, T_AMT2, recorded); setl(b, T_AMT3, recomputed); + setl(b, T_DELTA, recorded - recomputed); + Bchg(b, T_STATUS, 0, (recorded == recomputed) ? "OK" : "NG", 0L); + userlog("LG_BALPROOF %s %s 기록 %ld vs 재계산 %ld", acct, bizdate, recorded, recomputed); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_BIGLINE.h b/app/src/lg/svc/LG_BIGLINE.h new file mode 100644 index 0000000..6728f28 --- /dev/null +++ b/app/src/lg/svc/LG_BIGLINE.h @@ -0,0 +1,21 @@ +/* + * LG_BIGLINE.h - lg 서비스 LG_BIGLINE 카피북 (copybook / record header). + * 고액 분개 조회: 임계금액 이상 라인 건수/최대금액/해당 전표. + */ +#ifndef LG_BIGLINE_H +#define LG_BIGLINE_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_bigline_rec_t; + +void LG_BIGLINE(TPSVCINFO *p); + +#endif /* LG_BIGLINE_H */ diff --git a/app/src/lg/svc/LG_BIGLINE.pgc b/app/src/lg/svc/LG_BIGLINE.pgc new file mode 100644 index 0000000..fceab47 --- /dev/null +++ b/app/src/lg/svc/LG_BIGLINE.pgc @@ -0,0 +1,45 @@ +/* + * LG_BIGLINE.pgc - lg 모듈 서비스 LG_BIGLINE (one service = one file). + * 고액 분개 조회: 임계금액(T_AMOUNT, 기본 1000만) 이상 라인의 건수·합계와 + * 최고액 라인의 ID/전표/계정을 반환한다 (고액 이상거래 모니터링). + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_BIGLINE.h" + +void LG_BIGLINE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long thresh = getl(b, T_AMOUNT); + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_acct[32]; + long h_thresh, h_cnt, h_sum, h_lid, h_vid, h_top; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (thresh <= 0) thresh = 10000000; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + h_thresh = thresh; + + EXEC SQL SELECT count(*), coalesce(sum(amount), 0) + INTO :h_cnt, :h_sum FROM lg_ledger_line + WHERE biz_date = :h_bizdate AND amount >= :h_thresh; + if (sqlca.sqlcode < 0) FAIL(b); + + h_lid = 0; h_vid = 0; h_top = 0; h_acct[0] = 0; + if (h_cnt > 0) { + EXEC SQL SELECT line_id, voucher_id, acct, amount + INTO :h_lid, :h_vid, :h_acct, :h_top FROM lg_ledger_line + WHERE biz_date = :h_bizdate AND amount >= :h_thresh + ORDER BY amount DESC, line_id LIMIT 1; + if (sqlca.sqlcode < 0) FAIL(b); + } + + setl(b, T_COUNT, h_cnt); setl(b, T_GROSS, h_sum); + setl(b, T_ID1, h_lid); setl(b, T_ID2, h_vid); setl(b, T_AMT2, h_top); + Bchg(b, T_KEY1, 0, h_acct, 0L); + userlog("LG_BIGLINE %s 임계 %ld 이상 %ld건 최고 %ld(line %ld)", bizdate, thresh, h_cnt, h_top, h_lid); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_BLACKENTRY.h b/app/src/lg/svc/LG_BLACKENTRY.h new file mode 100644 index 0000000..1f92bea --- /dev/null +++ b/app/src/lg/svc/LG_BLACKENTRY.h @@ -0,0 +1,21 @@ +/* + * LG_BLACKENTRY.h - lg 서비스 LG_BLACKENTRY 카피북 (copybook / record header). + * 흑색 정정 분개: 적색 취소 후 정정 금액으로 재분개. + */ +#ifndef LG_BLACKENTRY_H +#define LG_BLACKENTRY_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_blackentry_rec_t; + +void LG_BLACKENTRY(TPSVCINFO *p); + +#endif /* LG_BLACKENTRY_H */ diff --git a/app/src/lg/svc/LG_BLACKENTRY.pgc b/app/src/lg/svc/LG_BLACKENTRY.pgc new file mode 100644 index 0000000..dbf940a --- /dev/null +++ b/app/src/lg/svc/LG_BLACKENTRY.pgc @@ -0,0 +1,58 @@ +/* + * LG_BLACKENTRY.pgc - lg 모듈 서비스 LG_BLACKENTRY (one service = one file). + * 흑색 정정 분개: 원전표(T_ID1)를 반전 상쇄한 뒤 정정 금액(T_AMOUNT)으로 + * 원전표의 대표 차변/대변 계정을 재분개한다 (두 전표 모두 차변합=대변합). + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_BLACKENTRY.h" + +void LG_BLACKENTRY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], merch[64]; + long orig = getl(b, T_ID1), newamt = getl(b, T_AMOUNT); + long rvid, nvid; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_dacct[32], h_cacct[32]; + long h_orig, h_rvid; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + gets_(b, T_MERCHANT, merch, sizeof(merch)); + if (newamt <= 0) { Bchg(b, T_MSG, 0, "정정 금액 오류", 0L); FAIL(b); } + h_orig = orig; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + /* 원전표의 대표 차변/대변 계정과목 파악 */ + EXEC SQL SELECT max(CASE WHEN dc = 'D' THEN acct ELSE '' END), + max(CASE WHEN dc = 'C' THEN acct ELSE '' END) + INTO :h_dacct, :h_cacct FROM lg_ledger_line WHERE voucher_id = :h_orig; + if (sqlca.sqlcode < 0 || h_dacct[0] == 0 || h_cacct[0] == 0) FAIL(b); + + /* 1) 적색(반전) 전표 */ + rvid = lgdb_next_voucher_id(); + if (rvid < 0) FAIL(b); + if (lgdb_insert_voucher(rvid, "REVERSE", orig, "P", bizdate, "흑색정정-취소분") < 0) FAIL(b); + h_rvid = rvid; + EXEC SQL INSERT INTO lg_ledger_line + (line_id, voucher_id, acct, dc, amount, merchant_id, biz_date, status) + SELECT nextval('lg_line_seq'), :h_rvid, acct, + CASE dc WHEN 'D' THEN 'C' ELSE 'D' END, + amount, merchant_id, :h_bizdate, 'posted' + FROM lg_ledger_line WHERE voucher_id = :h_orig; + if (sqlca.sqlcode < 0 || sqlca.sqlerrd[2] == 0) FAIL(b); + + /* 2) 흑색(재분개) 전표 - 정정 금액으로 균형 재전기 */ + nvid = lgdb_next_voucher_id(); + if (nvid < 0) FAIL(b); + if (lgdb_insert_voucher(nvid, "ADJ", orig, "P", bizdate, "흑색정정-재분개") < 0) FAIL(b); + if (lgdb_insert_line(nvid, h_dacct, "D", newamt, merch, bizdate, "posted") < 0) FAIL(b); + if (lgdb_insert_line(nvid, h_cacct, "C", newamt, merch, bizdate, "posted") < 0) FAIL(b); + if (lgdb_update_voucher_status(orig, "X") < 0) FAIL(b); + + setl(b, T_ID2, nvid); setl(b, T_ARG1, rvid); + userlog("LG_BLACKENTRY %ld 취소(%ld) 후 재분개(%ld) %ld", orig, rvid, nvid, newamt); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_CLOSECHK.h b/app/src/lg/svc/LG_CLOSECHK.h new file mode 100644 index 0000000..492d353 --- /dev/null +++ b/app/src/lg/svc/LG_CLOSECHK.h @@ -0,0 +1,21 @@ +/* + * LG_CLOSECHK.h - lg 서비스 LG_CLOSECHK 카피북 (copybook / record header). + * 결산 체크리스트: 미결/불균형/0원라인/미대사 점검 후 OK-NG 판정. + */ +#ifndef LG_CLOSECHK_H +#define LG_CLOSECHK_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_closechk_rec_t; + +void LG_CLOSECHK(TPSVCINFO *p); + +#endif /* LG_CLOSECHK_H */ diff --git a/app/src/lg/svc/LG_CLOSECHK.pgc b/app/src/lg/svc/LG_CLOSECHK.pgc new file mode 100644 index 0000000..0b44b16 --- /dev/null +++ b/app/src/lg/svc/LG_CLOSECHK.pgc @@ -0,0 +1,53 @@ +/* + * LG_CLOSECHK.pgc - lg 모듈 서비스 LG_CLOSECHK (one service = one file). + * 결산 체크리스트: 당일 (1)미결 전표 (2)불균형 전표 (3)0원 라인 + * (4)시산표 미적재 계정을 각각 점검해 4항목 카운트와 종합 판정을 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_CLOSECHK.h" + +void LG_CLOSECHK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long fails; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_draft, h_imbal, h_zero, h_notb; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + /* (1) 미결 전표 */ + EXEC SQL SELECT count(*) INTO :h_draft + FROM lg_voucher WHERE biz_date = :h_bizdate AND status = 'D'; + if (sqlca.sqlcode < 0) FAIL(b); + /* (2) 차변합 != 대변합 전표 */ + EXEC SQL SELECT count(*) INTO :h_imbal FROM ( + SELECT voucher_id FROM lg_ledger_line WHERE biz_date = :h_bizdate + GROUP BY voucher_id + HAVING sum(CASE WHEN dc = 'D' THEN amount ELSE -amount END) <> 0) q; + if (sqlca.sqlcode < 0) FAIL(b); + /* (3) 0원 라인 */ + EXEC SQL SELECT count(*) INTO :h_zero + FROM lg_ledger_line WHERE biz_date = :h_bizdate AND amount = 0; + if (sqlca.sqlcode < 0) FAIL(b); + /* (4) 원장에 있으나 시산표에 미적재된 계정 */ + EXEC SQL SELECT count(*) INTO :h_notb FROM ( + SELECT DISTINCT l.acct FROM lg_ledger_line l + WHERE l.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM lg_trial_bal t + WHERE t.acct = l.acct AND t.biz_date = :h_bizdate)) q; + if (sqlca.sqlcode < 0) FAIL(b); + + fails = (h_draft > 0) + (h_imbal > 0) + (h_zero > 0) + (h_notb > 0); + setl(b, T_ARG1, h_draft); setl(b, T_ARG2, h_imbal); + setl(b, T_ARG3, h_zero); setl(b, T_ARG4, h_notb); + setl(b, T_RC, fails); + Bchg(b, T_STATUS, 0, (fails == 0) ? "OK" : "NG", 0L); + userlog("LG_CLOSECHK %s 미결=%ld 불균형=%ld 0원=%ld 시산누락=%ld", bizdate, h_draft, h_imbal, h_zero, h_notb); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_DAYCLOSE.h b/app/src/lg/svc/LG_DAYCLOSE.h new file mode 100644 index 0000000..660525d --- /dev/null +++ b/app/src/lg/svc/LG_DAYCLOSE.h @@ -0,0 +1,21 @@ +/* + * LG_DAYCLOSE.h - lg 서비스 LG_DAYCLOSE 카피북 (copybook / record header). + * 일마감: 당일 미결 전표 일괄 승격 후 관련 계정 잔액 재계산. + */ +#ifndef LG_DAYCLOSE_H +#define LG_DAYCLOSE_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_dayclose_rec_t; + +void LG_DAYCLOSE(TPSVCINFO *p); + +#endif /* LG_DAYCLOSE_H */ diff --git a/app/src/lg/svc/LG_DAYCLOSE.pgc b/app/src/lg/svc/LG_DAYCLOSE.pgc new file mode 100644 index 0000000..14e770a --- /dev/null +++ b/app/src/lg/svc/LG_DAYCLOSE.pgc @@ -0,0 +1,48 @@ +/* + * LG_DAYCLOSE.pgc - lg 모듈 서비스 LG_DAYCLOSE (one service = one file). + * 일마감: 당일 균형(차변합=대변합) 미결 전표를 일괄 P 승격한 뒤, 당일 사용된 + * 계정과목을 커서로 돌며 계정잔액을 원장 라인 기준으로 재계산(dbio)한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_DAYCLOSE.h" + +void LG_DAYCLOSE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long promoted, naccts = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_acct[32]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + /* 균형 전표만 승격 - 불균형 미결은 남겨서 LG_CLOSECHK 에 걸리게 한다 */ + EXEC SQL UPDATE lg_voucher v SET status = 'P', updated_at = now() + WHERE v.biz_date = :h_bizdate AND v.status = 'D' + AND 0 = (SELECT coalesce(sum(CASE WHEN l.dc = 'D' THEN l.amount ELSE -l.amount END), 1) + FROM lg_ledger_line l WHERE l.voucher_id = v.voucher_id); + if (sqlca.sqlcode < 0) FAIL(b); + promoted = sqlca.sqlerrd[2]; + + EXEC SQL DECLARE dcl_cur CURSOR FOR + SELECT DISTINCT acct FROM lg_ledger_line WHERE biz_date = :h_bizdate; + EXEC SQL OPEN dcl_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH dcl_cur INTO :h_acct; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE dcl_cur; FAIL(b); } + if (lgdb_recalc_acct_bal(h_acct, bizdate) < 0) { EXEC SQL CLOSE dcl_cur; FAIL(b); } + naccts++; + } + EXEC SQL CLOSE dcl_cur; + + setl(b, T_ARG1, promoted); setl(b, T_COUNT, naccts); + Bchg(b, T_STATUS, 0, "P", 0L); + userlog("LG_DAYCLOSE %s 전표 %ld건 승격, 계정 %ld개 잔액 재계산", bizdate, promoted, naccts); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_DEPTPL.h b/app/src/lg/svc/LG_DEPTPL.h new file mode 100644 index 0000000..4e4c5a1 --- /dev/null +++ b/app/src/lg/svc/LG_DEPTPL.h @@ -0,0 +1,21 @@ +/* + * LG_DEPTPL.h - lg 서비스 LG_DEPTPL 카피북 (copybook / record header). + * 부문별 손익 조회: 매입 업종(category)별 수수료 손익 상위 부문. + */ +#ifndef LG_DEPTPL_H +#define LG_DEPTPL_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_deptpl_rec_t; + +void LG_DEPTPL(TPSVCINFO *p); + +#endif /* LG_DEPTPL_H */ diff --git a/app/src/lg/svc/LG_DEPTPL.pgc b/app/src/lg/svc/LG_DEPTPL.pgc new file mode 100644 index 0000000..f3b8ebd --- /dev/null +++ b/app/src/lg/svc/LG_DEPTPL.pgc @@ -0,0 +1,47 @@ +/* + * LG_DEPTPL.pgc - lg 모듈 서비스 LG_DEPTPL (one service = one file). + * 부문별 손익 조회: 당일 매입을 업종/부문(category)별로 묶어 수수료 손익을 + * 커서로 집계 — 부문 수, 총수수료, 최대 손익 부문명/금액을 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_DEPTPL.h" + +void LG_DEPTPL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], best[32]; + long depts = 0, totfee = 0, bestfee = -1; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_cat[32]; + long h_fee, h_cnt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + best[0] = 0; + + EXEC SQL DECLARE dp_cur CURSOR FOR + SELECT category, coalesce(sum(fee), 0), count(*) + FROM purchase + WHERE biz_date = :h_bizdate AND status <> 'C' + GROUP BY category ORDER BY 2 DESC; + EXEC SQL OPEN dp_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH dp_cur INTO :h_cat, :h_fee, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE dp_cur; FAIL(b); } + if (h_fee > bestfee) { bestfee = h_fee; strncpy(best, h_cat, sizeof(best)-1); best[sizeof(best)-1] = 0; } + totfee += h_fee; + depts++; + } + EXEC SQL CLOSE dp_cur; + + setl(b, T_COUNT, depts); setl(b, T_GROSS, totfee); + setl(b, T_AMT2, bestfee < 0 ? 0 : bestfee); + Bchg(b, T_CATEGORY, 0, best, 0L); + userlog("LG_DEPTPL %s 부문 %ld개 총수수료 %ld 최대부문 %s", bizdate, depts, totfee, best); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_DUPSCAN.h b/app/src/lg/svc/LG_DUPSCAN.h new file mode 100644 index 0000000..356b33b --- /dev/null +++ b/app/src/lg/svc/LG_DUPSCAN.h @@ -0,0 +1,21 @@ +/* + * LG_DUPSCAN.h - lg 서비스 LG_DUPSCAN 카피북 (copybook / record header). + * 중복 분개 스캔: 동일 전표·계정·차대·금액 중복 그룹 검출. + */ +#ifndef LG_DUPSCAN_H +#define LG_DUPSCAN_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_dupscan_rec_t; + +void LG_DUPSCAN(TPSVCINFO *p); + +#endif /* LG_DUPSCAN_H */ diff --git a/app/src/lg/svc/LG_DUPSCAN.pgc b/app/src/lg/svc/LG_DUPSCAN.pgc new file mode 100644 index 0000000..3826a76 --- /dev/null +++ b/app/src/lg/svc/LG_DUPSCAN.pgc @@ -0,0 +1,48 @@ +/* + * LG_DUPSCAN.pgc - lg 모듈 서비스 LG_DUPSCAN (one service = one file). + * 중복 분개 스캔: 당일 동일 (전표, 계정, 차대, 금액) 조합이 2건 이상인 중복 + * 그룹을 GROUP BY/HAVING 으로 찾아 그룹 수·중복 초과 라인 수·최대 중복 + * 전표를 반환한다 (이중기표 탐지). + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_DUPSCAN.h" + +void LG_DUPSCAN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long groups = 0, extra = 0, worstvid = 0, worstn = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_vid, h_n; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL DECLARE dup_cur CURSOR FOR + SELECT voucher_id, count(*) FROM lg_ledger_line + WHERE biz_date = :h_bizdate + GROUP BY voucher_id, acct, dc, amount + HAVING count(*) > 1 + ORDER BY count(*) DESC; + EXEC SQL OPEN dup_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH dup_cur INTO :h_vid, :h_n; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE dup_cur; FAIL(b); } + if (groups == 0) { worstvid = h_vid; worstn = h_n; } + extra += h_n - 1; + groups++; + } + EXEC SQL CLOSE dup_cur; + + setl(b, T_COUNT, groups); setl(b, T_ARG1, extra); + setl(b, T_ID1, worstvid); setl(b, T_ARG2, worstn); + Bchg(b, T_STATUS, 0, (groups == 0) ? "OK" : "NG", 0L); + userlog("LG_DUPSCAN %s 중복그룹 %ld 초과라인 %ld 최다 vid=%ld(%ld건)", bizdate, groups, extra, worstvid, worstn); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_FEEACCRUE.h b/app/src/lg/svc/LG_FEEACCRUE.h new file mode 100644 index 0000000..6021219 --- /dev/null +++ b/app/src/lg/svc/LG_FEEACCRUE.h @@ -0,0 +1,21 @@ +/* + * LG_FEEACCRUE.h - lg 서비스 LG_FEEACCRUE 카피북 (copybook / record header). + * 수수료 미수 계상: 대사완료 미정산 수수료를 미결 전표로 계상. + */ +#ifndef LG_FEEACCRUE_H +#define LG_FEEACCRUE_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_feeaccrue_rec_t; + +void LG_FEEACCRUE(TPSVCINFO *p); + +#endif /* LG_FEEACCRUE_H */ diff --git a/app/src/lg/svc/LG_FEEACCRUE.pgc b/app/src/lg/svc/LG_FEEACCRUE.pgc new file mode 100644 index 0000000..70e96b0 --- /dev/null +++ b/app/src/lg/svc/LG_FEEACCRUE.pgc @@ -0,0 +1,41 @@ +/* + * LG_FEEACCRUE.pgc - lg 모듈 서비스 LG_FEEACCRUE (one service = one file). + * 수수료 미수 계상: 대사완료(M)했지만 아직 미정산인 매입의 수수료를 + * D:FEE_RECV / C:FEE_INC 미결(D) 전표로 발생주의 계상한다 (승인 후 전기). + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_FEEACCRUE.h" + +void LG_FEEACCRUE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long vid, accrue; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_fee; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT coalesce(sum(fee), 0) INTO :h_fee + FROM purchase WHERE biz_date = :h_bizdate AND status = 'M'; + if (sqlca.sqlcode < 0) FAIL(b); + accrue = h_fee; + if (accrue <= 0) { Bchg(b, T_MSG, 0, "계상 대상 수수료 없음", 0L); setl(b, T_FEE, 0); OK(b); } + + vid = lgdb_next_voucher_id(); + if (vid < 0) FAIL(b); + /* 미결(D) 상태 계상 전표 - LG_APPROVE 승인 후 전기 대상 */ + if (lgdb_insert_voucher(vid, "FEE", 0, "D", bizdate, "수수료 미수 계상") < 0) FAIL(b); + if (lgdb_insert_line(vid, "FEE_RECV", "D", accrue, "", bizdate, "posted") < 0) FAIL(b); + if (lgdb_insert_line(vid, "FEE_INC", "C", accrue, "", bizdate, "posted") < 0) FAIL(b); + + setl(b, T_ID1, vid); setl(b, T_FEE, accrue); + Bchg(b, T_STATUS, 0, "D", 0L); + userlog("LG_FEEACCRUE vid=%ld 미수수수료 %ld 계상(미결)", vid, accrue); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_FEERECOG.h b/app/src/lg/svc/LG_FEERECOG.h new file mode 100644 index 0000000..16498aa --- /dev/null +++ b/app/src/lg/svc/LG_FEERECOG.h @@ -0,0 +1,21 @@ +/* + * LG_FEERECOG.h - lg 서비스 LG_FEERECOG 카피북 (copybook / record header). + * 수수료 수익 인식: 정산완료 매입 수수료를 RECV/FEE_INC 로 전기. + */ +#ifndef LG_FEERECOG_H +#define LG_FEERECOG_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_feerecog_rec_t; + +void LG_FEERECOG(TPSVCINFO *p); + +#endif /* LG_FEERECOG_H */ diff --git a/app/src/lg/svc/LG_FEERECOG.pgc b/app/src/lg/svc/LG_FEERECOG.pgc new file mode 100644 index 0000000..0f7ef9c --- /dev/null +++ b/app/src/lg/svc/LG_FEERECOG.pgc @@ -0,0 +1,43 @@ +/* + * LG_FEERECOG.pgc - lg 모듈 서비스 LG_FEERECOG (one service = one file). + * 수수료 수익 인식: 당일 정산완료(S) 매입 수수료 합계를 + * D:RECV / C:FEE_INC 균형 전표(FEE)로 전기하고 양 계정잔액에 반영한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_FEERECOG.h" + +void LG_FEERECOG(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long vid, fee; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_fee, h_n; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT coalesce(sum(fee), 0), count(*) + INTO :h_fee, :h_n FROM purchase + WHERE biz_date = :h_bizdate AND status = 'S'; + if (sqlca.sqlcode < 0) FAIL(b); + fee = h_fee; + if (fee <= 0) { setl(b, T_FEE, 0); Bchg(b, T_STATUS, 0, "N", 0L); OK(b); } + + vid = lgdb_next_voucher_id(); + if (vid < 0) FAIL(b); + if (lgdb_insert_voucher(vid, "FEE", h_n, "P", bizdate, "수수료 수익 인식") < 0) FAIL(b); + if (lgdb_insert_line(vid, "RECV", "D", fee, "", bizdate, "posted") < 0) FAIL(b); + if (lgdb_insert_line(vid, "FEE_INC", "C", fee, "", bizdate, "posted") < 0) FAIL(b); + if (lgdb_upsert_acct_bal("RECV", bizdate, fee, 0) < 0) FAIL(b); + if (lgdb_upsert_acct_bal("FEE_INC", bizdate, 0, fee) < 0) FAIL(b); + + setl(b, T_ID1, vid); setl(b, T_FEE, fee); setl(b, T_COUNT, h_n); + Bchg(b, T_STATUS, 0, "P", 0L); + userlog("LG_FEERECOG vid=%ld %s 수수료수익 %ld (%ld건, 차=대 균형)", vid, bizdate, fee, h_n); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_FEESPLIT.h b/app/src/lg/svc/LG_FEESPLIT.h new file mode 100644 index 0000000..256180f --- /dev/null +++ b/app/src/lg/svc/LG_FEESPLIT.h @@ -0,0 +1,21 @@ +/* + * LG_FEESPLIT.h - lg 서비스 LG_FEESPLIT 카피북 (copybook / record header). + * 수수료 부문 배부: 수수료수익을 비율(bps)로 2개 부문에 균형 배부. + */ +#ifndef LG_FEESPLIT_H +#define LG_FEESPLIT_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_feesplit_rec_t; + +void LG_FEESPLIT(TPSVCINFO *p); + +#endif /* LG_FEESPLIT_H */ diff --git a/app/src/lg/svc/LG_FEESPLIT.pgc b/app/src/lg/svc/LG_FEESPLIT.pgc new file mode 100644 index 0000000..1724282 --- /dev/null +++ b/app/src/lg/svc/LG_FEESPLIT.pgc @@ -0,0 +1,48 @@ +/* + * LG_FEESPLIT.pgc - lg 모듈 서비스 LG_FEESPLIT (one service = one file). + * 수수료 부문 배부: 당일 FEE_INC 대변 합계를 배부율(T_ARG1 bps)로 부문 A/B 에 + * 나눠 D:FEE_INC / C:FEE_DEPT_A + C:FEE_DEPT_B 3라인 균형 전표로 배부한다. + * 나머지(단수차)는 B 부문에 귀속시켜 차변합=대변합을 보장한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_FEESPLIT.h" + +void LG_FEESPLIT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long bps = getl(b, T_ARG1); + long vid, total, parta, partb; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_total; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bps <= 0 || bps >= 10000) bps = 5000; /* 기본 50:50 */ + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT coalesce(sum(amount), 0) INTO :h_total + FROM lg_ledger_line + WHERE acct = 'FEE_INC' AND dc = 'C' AND biz_date = :h_bizdate AND status = 'posted'; + if (sqlca.sqlcode < 0) FAIL(b); + total = h_total; + if (total <= 0) { setl(b, T_GROSS, 0); Bchg(b, T_STATUS, 0, "N", 0L); OK(b); } + + parta = total * bps / 10000; + partb = total - parta; /* 단수차 포함 - 배부 후에도 차변합 == 대변합 */ + + vid = lgdb_next_voucher_id(); + if (vid < 0) FAIL(b); + if (lgdb_insert_voucher(vid, "ADJ", bps, "P", bizdate, "수수료 부문배부") < 0) FAIL(b); + if (lgdb_insert_line(vid, "FEE_INC", "D", total, "", bizdate, "posted") < 0) FAIL(b); + if (lgdb_insert_line(vid, "FEE_DEPT_A", "C", parta, "", bizdate, "posted") < 0) FAIL(b); + if (lgdb_insert_line(vid, "FEE_DEPT_B", "C", partb, "", bizdate, "posted") < 0) FAIL(b); + + setl(b, T_ID1, vid); setl(b, T_GROSS, total); + setl(b, T_AMT2, parta); setl(b, T_AMT3, partb); + userlog("LG_FEESPLIT vid=%ld 총 %ld -> A %ld / B %ld (bps=%ld, 균형)", vid, total, parta, partb, bps); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_LINECOUNT.h b/app/src/lg/svc/LG_LINECOUNT.h new file mode 100644 index 0000000..ba983dc --- /dev/null +++ b/app/src/lg/svc/LG_LINECOUNT.h @@ -0,0 +1,21 @@ +/* + * LG_LINECOUNT.h - lg 서비스 LG_LINECOUNT 카피북 (copybook / record header). + * 원장 라인 집계: posted/closed 건수와 전표 수. + */ +#ifndef LG_LINECOUNT_H +#define LG_LINECOUNT_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_linecount_rec_t; + +void LG_LINECOUNT(TPSVCINFO *p); + +#endif /* LG_LINECOUNT_H */ diff --git a/app/src/lg/svc/LG_LINECOUNT.pgc b/app/src/lg/svc/LG_LINECOUNT.pgc new file mode 100644 index 0000000..b7c9e71 --- /dev/null +++ b/app/src/lg/svc/LG_LINECOUNT.pgc @@ -0,0 +1,37 @@ +/* + * LG_LINECOUNT.pgc - lg 모듈 서비스 LG_LINECOUNT (one service = one file). + * 원장 라인 집계: 당일 라인의 posted/closed 상태별 건수, 관련 전표 수, + * 전표당 평균 라인 수(x100 고정소수)를 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_LINECOUNT.h" + +void LG_LINECOUNT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long per100; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_posted, h_closed, h_vouchers; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT + coalesce(sum(CASE WHEN status = 'posted' THEN 1 ELSE 0 END), 0), + coalesce(sum(CASE WHEN status = 'closed' THEN 1 ELSE 0 END), 0), + count(DISTINCT voucher_id) + INTO :h_posted, :h_closed, :h_vouchers + FROM lg_ledger_line WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + per100 = (h_vouchers > 0) ? (h_posted + h_closed) * 100 / h_vouchers : 0; + setl(b, T_ARG1, h_posted); setl(b, T_ARG2, h_closed); + setl(b, T_COUNT, h_vouchers); setl(b, T_ARG3, per100); + userlog("LG_LINECOUNT %s posted=%ld closed=%ld 전표=%ld (평균 x100=%ld)", bizdate, h_posted, h_closed, h_vouchers, per100); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_MASKINQ.h b/app/src/lg/svc/LG_MASKINQ.h new file mode 100644 index 0000000..be5ae17 --- /dev/null +++ b/app/src/lg/svc/LG_MASKINQ.h @@ -0,0 +1,21 @@ +/* + * LG_MASKINQ.h - lg 서비스 LG_MASKINQ 카피북 (copybook / record header). + * 원장 마스킹 조회: 가맹점 ID 를 마스킹한 원장 라인 조회. + */ +#ifndef LG_MASKINQ_H +#define LG_MASKINQ_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_maskinq_rec_t; + +void LG_MASKINQ(TPSVCINFO *p); + +#endif /* LG_MASKINQ_H */ diff --git a/app/src/lg/svc/LG_MASKINQ.pgc b/app/src/lg/svc/LG_MASKINQ.pgc new file mode 100644 index 0000000..4af1e89 --- /dev/null +++ b/app/src/lg/svc/LG_MASKINQ.pgc @@ -0,0 +1,37 @@ +/* + * LG_MASKINQ.pgc - lg 모듈 서비스 LG_MASKINQ (one service = one file). + * 원장 마스킹 조회: 라인 ID(T_ID1)의 원장 라인을 가맹점 ID 마스킹(앞 2자리 + * 노출 + '****') 상태로 조회 — 개인정보 최소노출 조회 채널용. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_MASKINQ.h" + +void LG_MASKINQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long lid = getl(b, T_ID1); + EXEC SQL BEGIN DECLARE SECTION; + long h_lid, h_amt, h_vid; + char h_acct[32], h_dc[4], h_masked[72], h_date[16]; + EXEC SQL END DECLARE SECTION; + + h_lid = lid; + EXEC SQL SELECT voucher_id, acct, dc, amount, + CASE WHEN merchant_id = '' THEN '' + ELSE left(merchant_id, 2) || '****' END, + to_char(biz_date, 'YYYY-MM-DD') + INTO :h_vid, :h_acct, :h_dc, :h_amt, :h_masked, :h_date + FROM lg_ledger_line WHERE line_id = :h_lid; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { Bchg(b, T_MSG, 0, "라인 없음", 0L); FAIL(b); } + + setl(b, T_ID2, h_vid); setl(b, T_AMOUNT, h_amt); + Bchg(b, T_KEY1, 0, h_acct, 0L); + Bchg(b, T_STR1, 0, h_dc, 0L); + Bchg(b, T_MERCHANT, 0, h_masked, 0L); + Bchg(b, T_BIZDATE, 0, h_date, 0L); + userlog("LG_MASKINQ line=%ld 마스킹 조회 (%s)", lid, h_masked); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_MEMOAUDIT.h b/app/src/lg/svc/LG_MEMOAUDIT.h new file mode 100644 index 0000000..e83ee3b --- /dev/null +++ b/app/src/lg/svc/LG_MEMOAUDIT.h @@ -0,0 +1,21 @@ +/* + * LG_MEMOAUDIT.h - lg 서비스 LG_MEMOAUDIT 카피북 (copybook / record header). + * 전표 메모 감사: 메모 없는 전기(P) 전표 적발. + */ +#ifndef LG_MEMOAUDIT_H +#define LG_MEMOAUDIT_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_memoaudit_rec_t; + +void LG_MEMOAUDIT(TPSVCINFO *p); + +#endif /* LG_MEMOAUDIT_H */ diff --git a/app/src/lg/svc/LG_MEMOAUDIT.pgc b/app/src/lg/svc/LG_MEMOAUDIT.pgc new file mode 100644 index 0000000..7b5ed6b --- /dev/null +++ b/app/src/lg/svc/LG_MEMOAUDIT.pgc @@ -0,0 +1,37 @@ +/* + * LG_MEMOAUDIT.pgc - lg 모듈 서비스 LG_MEMOAUDIT (one service = one file). + * 전표 메모 감사: 당일 전기(P) 전표 중 메모가 비어 있는 건수와 표본 전표 ID, + * 전체 대비 비율(bps)을 반환한다 (기표 규정 준수 감사). + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_MEMOAUDIT.h" + +void LG_MEMOAUDIT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long bps; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_empty, h_all, h_sample; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN memo = '' THEN 1 ELSE 0 END), 0), + coalesce(min(CASE WHEN memo = '' THEN voucher_id END), 0) + INTO :h_all, :h_empty, :h_sample + FROM lg_voucher WHERE biz_date = :h_bizdate AND status = 'P'; + if (sqlca.sqlcode < 0) FAIL(b); + + bps = (h_all > 0) ? h_empty * 10000 / h_all : 0; + setl(b, T_COUNT, h_all); setl(b, T_ARG1, h_empty); + setl(b, T_ID1, h_sample); setl(b, T_ARG2, bps); + Bchg(b, T_STATUS, 0, (h_empty == 0) ? "OK" : "NG", 0L); + userlog("LG_MEMOAUDIT %s 전기 %ld건 중 메모누락 %ld건 (%ldbps)", bizdate, h_all, h_empty, bps); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_MERCHPROOF.h b/app/src/lg/svc/LG_MERCHPROOF.h new file mode 100644 index 0000000..f15c88b --- /dev/null +++ b/app/src/lg/svc/LG_MERCHPROOF.h @@ -0,0 +1,21 @@ +/* + * LG_MERCHPROOF.h - lg 서비스 LG_MERCHPROOF 카피북 (copybook / record header). + * 가맹점 잔액 증명: 가맹점 잔액 테이블과 라인 재계산치 대조. + */ +#ifndef LG_MERCHPROOF_H +#define LG_MERCHPROOF_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_merchproof_rec_t; + +void LG_MERCHPROOF(TPSVCINFO *p); + +#endif /* LG_MERCHPROOF_H */ diff --git a/app/src/lg/svc/LG_MERCHPROOF.pgc b/app/src/lg/svc/LG_MERCHPROOF.pgc new file mode 100644 index 0000000..28a6bb1 --- /dev/null +++ b/app/src/lg/svc/LG_MERCHPROOF.pgc @@ -0,0 +1,48 @@ +/* + * LG_MERCHPROOF.pgc - lg 모듈 서비스 LG_MERCHPROOF (one service = one file). + * 가맹점 잔액 증명: 가맹점 잔액 테이블(lg_merch_bal)의 차변/대변 누계와 + * 원장 라인 재집계를 항목별(차변·대변·잔액)로 대조해 3중 검증 결과를 낸다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_MERCHPROOF.h" + +void LG_MERCHPROOF(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + int mismatch = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16]; + long h_tdr, h_tcr, h_tbal, h_ldr, h_lcr; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (merch[0] == 0) { Bchg(b, T_MSG, 0, "가맹점 필요", 0L); FAIL(b); } + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + h_tdr = h_tcr = h_tbal = 0; + EXEC SQL SELECT debit_total, credit_total, balance + INTO :h_tdr, :h_tcr, :h_tbal FROM lg_merch_bal + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0), + coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0) + INTO :h_ldr, :h_lcr FROM lg_ledger_line + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + if (h_tdr != h_ldr) mismatch++; + if (h_tcr != h_lcr) mismatch++; + if (h_tbal != h_ldr - h_lcr) mismatch++; + + setl(b, T_AMT2, h_ldr); setl(b, T_AMT3, h_lcr); + setl(b, T_AMT4, h_tbal); setl(b, T_ARG1, mismatch); + Bchg(b, T_STATUS, 0, (mismatch == 0) ? "OK" : "NG", 0L); + userlog("LG_MERCHPROOF %s %s 불일치 항목 %d개", merch, bizdate, mismatch); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_MONTHCLOSE.h b/app/src/lg/svc/LG_MONTHCLOSE.h new file mode 100644 index 0000000..5e1bd8c --- /dev/null +++ b/app/src/lg/svc/LG_MONTHCLOSE.h @@ -0,0 +1,21 @@ +/* + * LG_MONTHCLOSE.h - lg 서비스 LG_MONTHCLOSE 카피북 (copybook / record header). + * 월차 결산 분개: 당월 수수료수익을 이익잉여금으로 대체(차변/대변 균형). + */ +#ifndef LG_MONTHCLOSE_H +#define LG_MONTHCLOSE_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_monthclose_rec_t; + +void LG_MONTHCLOSE(TPSVCINFO *p); + +#endif /* LG_MONTHCLOSE_H */ diff --git a/app/src/lg/svc/LG_MONTHCLOSE.pgc b/app/src/lg/svc/LG_MONTHCLOSE.pgc new file mode 100644 index 0000000..ab87b19 --- /dev/null +++ b/app/src/lg/svc/LG_MONTHCLOSE.pgc @@ -0,0 +1,47 @@ +/* + * LG_MONTHCLOSE.pgc - lg 모듈 서비스 LG_MONTHCLOSE (one service = one file). + * 월차 결산 분개: [T_BIZDATE=월초 ~ T_STR1=월말] 구간 FEE_INC 순액(대변-차변)을 + * 이익잉여금(RETAINED)으로 대체하는 균형 전표(D:FEE_INC / C:RETAINED)를 전기. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_MONTHCLOSE.h" + +void LG_MONTHCLOSE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char dfrom[16], dto[16]; + long vid, net; + EXEC SQL BEGIN DECLARE SECTION; + char h_from[16], h_to[16]; + long h_net; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, dfrom, sizeof(dfrom)); + gets_(b, T_STR1, dto, sizeof(dto)); + if (dfrom[0] == 0 || dto[0] == 0) { Bchg(b, T_MSG, 0, "월 구간 필요", 0L); FAIL(b); } + strncpy(h_from, dfrom, sizeof(h_from)-1); h_from[sizeof(h_from)-1] = 0; + strncpy(h_to, dto, sizeof(h_to)-1); h_to[sizeof(h_to)-1] = 0; + + EXEC SQL SELECT coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE -amount END), 0) + INTO :h_net FROM lg_ledger_line + WHERE acct = 'FEE_INC' AND biz_date >= :h_from AND biz_date <= :h_to + AND status = 'posted'; + if (sqlca.sqlcode < 0) FAIL(b); + net = h_net; + if (net <= 0) { setl(b, T_AMT2, net); Bchg(b, T_STATUS, 0, "N", 0L); OK(b); } + + vid = lgdb_next_voucher_id(); + if (vid < 0) FAIL(b); + if (lgdb_insert_voucher(vid, "ADJ", 0, "P", dto, "월차결산 손익대체") < 0) FAIL(b); + if (lgdb_insert_line(vid, "FEE_INC", "D", net, "", dto, "posted") < 0) FAIL(b); + if (lgdb_insert_line(vid, "RETAINED", "C", net, "", dto, "posted") < 0) FAIL(b); + if (lgdb_upsert_acct_bal("FEE_INC", dto, net, 0) < 0) FAIL(b); + if (lgdb_upsert_acct_bal("RETAINED", dto, 0, net) < 0) FAIL(b); + + setl(b, T_ID1, vid); setl(b, T_AMT2, net); + Bchg(b, T_STATUS, 0, "P", 0L); + userlog("LG_MONTHCLOSE vid=%ld [%s~%s] 손익대체 %ld (차=대 균형)", vid, dfrom, dto, net); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_NETPOS.h b/app/src/lg/svc/LG_NETPOS.h new file mode 100644 index 0000000..305b3c3 --- /dev/null +++ b/app/src/lg/svc/LG_NETPOS.h @@ -0,0 +1,21 @@ +/* + * LG_NETPOS.h - lg 서비스 LG_NETPOS 카피북 (copybook / record header). + * 순포지션 조회: RECV 잔액 - MERCH_PAY 잔액 순액 산출. + */ +#ifndef LG_NETPOS_H +#define LG_NETPOS_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_netpos_rec_t; + +void LG_NETPOS(TPSVCINFO *p); + +#endif /* LG_NETPOS_H */ diff --git a/app/src/lg/svc/LG_NETPOS.pgc b/app/src/lg/svc/LG_NETPOS.pgc new file mode 100644 index 0000000..e1fc618 --- /dev/null +++ b/app/src/lg/svc/LG_NETPOS.pgc @@ -0,0 +1,26 @@ +/* + * LG_NETPOS.pgc - lg 모듈 서비스 LG_NETPOS (one service = one file). + * 순포지션 조회: 당일 채권(RECV) 잔액과 가맹점미지급(MERCH_PAY) 잔액을 + * 계정잔액 테이블에서 dbio 로 읽어 순포지션(RECV - MERCH_PAY)을 산출한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_NETPOS.h" + +void LG_NETPOS(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long recv = 0, pay = 0, net; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (lgdb_get_acct_bal("RECV", bizdate, &recv) < 0) FAIL(b); + if (lgdb_get_acct_bal("MERCH_PAY", bizdate, &pay) < 0) FAIL(b); + + net = recv - pay; + setl(b, T_AMT2, recv); setl(b, T_AMT3, pay); setl(b, T_NET, net); + Bchg(b, T_STATUS, 0, (net >= 0) ? "L" : "S", 0L); /* L=롱, S=숏 포지션 */ + userlog("LG_NETPOS %s RECV %ld - MERCH_PAY %ld = %ld", bizdate, recv, pay, net); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_PAYACCRUE.h b/app/src/lg/svc/LG_PAYACCRUE.h new file mode 100644 index 0000000..ce6c5c4 --- /dev/null +++ b/app/src/lg/svc/LG_PAYACCRUE.h @@ -0,0 +1,21 @@ +/* + * LG_PAYACCRUE.h - lg 서비스 LG_PAYACCRUE 카피북 (copybook / record header). + * 미지급 계상 분개: 정산 순액과 원장 대변의 갭을 미지급 계상. + */ +#ifndef LG_PAYACCRUE_H +#define LG_PAYACCRUE_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_payaccrue_rec_t; + +void LG_PAYACCRUE(TPSVCINFO *p); + +#endif /* LG_PAYACCRUE_H */ diff --git a/app/src/lg/svc/LG_PAYACCRUE.pgc b/app/src/lg/svc/LG_PAYACCRUE.pgc new file mode 100644 index 0000000..36042f8 --- /dev/null +++ b/app/src/lg/svc/LG_PAYACCRUE.pgc @@ -0,0 +1,47 @@ +/* + * LG_PAYACCRUE.pgc - lg 모듈 서비스 LG_PAYACCRUE (one service = one file). + * 미지급 계상 분개: 당일 정산 순액 합계와 원장 MERCH_PAY 대변 합계의 갭을 + * 계산해, 갭이 양수이면 D:SUSP / C:MERCH_PAY 균형 전표로 미지급을 계상한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_PAYACCRUE.h" + +void LG_PAYACCRUE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long vid, gap; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_settle, h_posted; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT coalesce(sum(net), 0) INTO :h_settle + FROM settlement WHERE biz_date = :h_bizdate AND status = 'SETTLED'; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(amount), 0) INTO :h_posted + FROM lg_ledger_line + WHERE acct = 'MERCH_PAY' AND dc = 'C' AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + gap = h_settle - h_posted; + setl(b, T_NET, h_settle); setl(b, T_AMT3, h_posted); setl(b, T_DELTA, gap); + if (gap <= 0) { Bchg(b, T_STATUS, 0, "N", 0L); OK(b); } + + vid = lgdb_next_voucher_id(); + if (vid < 0) FAIL(b); + if (lgdb_insert_voucher(vid, "SETTLE", 0, "P", bizdate, "미지급 계상") < 0) FAIL(b); + if (lgdb_insert_line(vid, "SUSP", "D", gap, "", bizdate, "posted") < 0) FAIL(b); + if (lgdb_insert_line(vid, "MERCH_PAY", "C", gap, "", bizdate, "posted") < 0) FAIL(b); + if (lgdb_upsert_acct_bal("MERCH_PAY", bizdate, 0, gap) < 0) FAIL(b); + + setl(b, T_ID1, vid); + Bchg(b, T_STATUS, 0, "P", 0L); + userlog("LG_PAYACCRUE vid=%ld 미지급 갭 %ld 계상 (정산 %ld vs 원장 %ld)", vid, gap, h_settle, h_posted); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_PAYRECON.h b/app/src/lg/svc/LG_PAYRECON.h new file mode 100644 index 0000000..21d0ca4 --- /dev/null +++ b/app/src/lg/svc/LG_PAYRECON.h @@ -0,0 +1,21 @@ +/* + * LG_PAYRECON.h - lg 서비스 LG_PAYRECON 카피북 (copybook / record header). + * 원장-지급 대사: 가맹점 원장 잔액과 정산 지급액 비교. + */ +#ifndef LG_PAYRECON_H +#define LG_PAYRECON_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_payrecon_rec_t; + +void LG_PAYRECON(TPSVCINFO *p); + +#endif /* LG_PAYRECON_H */ diff --git a/app/src/lg/svc/LG_PAYRECON.pgc b/app/src/lg/svc/LG_PAYRECON.pgc new file mode 100644 index 0000000..876b875 --- /dev/null +++ b/app/src/lg/svc/LG_PAYRECON.pgc @@ -0,0 +1,42 @@ +/* + * LG_PAYRECON.pgc - lg 모듈 서비스 LG_PAYRECON (one service = one file). + * 원장-지급 대사: 가맹점(T_MERCHANT)의 원장 라인 순액(대변-차변)과 정산 + * 지급 순액을 비교해 갭을 반환한다 — 지급 대사 담당자 화면용. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_PAYRECON.h" + +void LG_PAYRECON(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + long gap; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16]; + long h_ledger, h_paid, h_paycnt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (merch[0] == 0) { Bchg(b, T_MSG, 0, "가맹점 필요", 0L); FAIL(b); } + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE -amount END), 0) + INTO :h_ledger FROM lg_ledger_line + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(net), 0), count(*) + INTO :h_paid, :h_paycnt FROM settlement + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + gap = h_ledger - h_paid; + setl(b, T_AMT2, h_ledger); setl(b, T_NET, h_paid); + setl(b, T_DELTA, gap); setl(b, T_COUNT, h_paycnt); + Bchg(b, T_STATUS, 0, (gap == 0) ? "M" : "U", 0L); + userlog("LG_PAYRECON %s %s 원장 %ld vs 지급 %ld 갭 %ld", merch, bizdate, h_ledger, h_paid, gap); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_PENDAGE.h b/app/src/lg/svc/LG_PENDAGE.h new file mode 100644 index 0000000..7d982de --- /dev/null +++ b/app/src/lg/svc/LG_PENDAGE.h @@ -0,0 +1,21 @@ +/* + * LG_PENDAGE.h - lg 서비스 LG_PENDAGE 카피북 (copybook / record header). + * 미결전표 경과일 집계: 경과일 구간별(0-1/2-7/8+) 미결 건수. + */ +#ifndef LG_PENDAGE_H +#define LG_PENDAGE_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_pendage_rec_t; + +void LG_PENDAGE(TPSVCINFO *p); + +#endif /* LG_PENDAGE_H */ diff --git a/app/src/lg/svc/LG_PENDAGE.pgc b/app/src/lg/svc/LG_PENDAGE.pgc new file mode 100644 index 0000000..03604ee --- /dev/null +++ b/app/src/lg/svc/LG_PENDAGE.pgc @@ -0,0 +1,32 @@ +/* + * LG_PENDAGE.pgc - lg 모듈 서비스 LG_PENDAGE (one service = one file). + * 미결전표 경과일 집계: 작성(D) 전표를 경과일 구간(0-1일 / 2-7일 / 8일+)별로 + * 건수 집계하고 최장 경과일을 반환한다 (에이징 리포트). + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_PENDAGE.h" + +void LG_PENDAGE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_b0, h_b1, h_b2, h_maxage; + EXEC SQL END DECLARE SECTION; + + EXEC SQL SELECT + coalesce(sum(CASE WHEN CURRENT_DATE - biz_date <= 1 THEN 1 ELSE 0 END), 0), + coalesce(sum(CASE WHEN CURRENT_DATE - biz_date BETWEEN 2 AND 7 THEN 1 ELSE 0 END), 0), + coalesce(sum(CASE WHEN CURRENT_DATE - biz_date >= 8 THEN 1 ELSE 0 END), 0), + coalesce(max(CURRENT_DATE - biz_date), 0) + INTO :h_b0, :h_b1, :h_b2, :h_maxage + FROM lg_voucher WHERE status = 'D'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_ARG1, h_b0); setl(b, T_ARG2, h_b1); setl(b, T_ARG3, h_b2); + setl(b, T_ARG4, h_maxage); + setl(b, T_COUNT, h_b0 + h_b1 + h_b2); + userlog("LG_PENDAGE 에이징 0-1일=%ld 2-7일=%ld 8일+=%ld 최장=%ld일", h_b0, h_b1, h_b2, h_maxage); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_PENDLIST.h b/app/src/lg/svc/LG_PENDLIST.h new file mode 100644 index 0000000..1aedf01 --- /dev/null +++ b/app/src/lg/svc/LG_PENDLIST.h @@ -0,0 +1,21 @@ +/* + * LG_PENDLIST.h - lg 서비스 LG_PENDLIST 카피북 (copybook / record header). + * 미결전표 목록 집계: 작성(D) 상태 전표 건수/금액/최고령 전표. + */ +#ifndef LG_PENDLIST_H +#define LG_PENDLIST_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_pendlist_rec_t; + +void LG_PENDLIST(TPSVCINFO *p); + +#endif /* LG_PENDLIST_H */ diff --git a/app/src/lg/svc/LG_PENDLIST.pgc b/app/src/lg/svc/LG_PENDLIST.pgc new file mode 100644 index 0000000..95a5a5e --- /dev/null +++ b/app/src/lg/svc/LG_PENDLIST.pgc @@ -0,0 +1,40 @@ +/* + * LG_PENDLIST.pgc - lg 모듈 서비스 LG_PENDLIST (one service = one file). + * 미결전표 관리(목록 집계): 작성(D) 상태 전표의 건수, 연계 라인 총차변액, + * 최고령 전표 ID/영업일을 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_PENDLIST.h" + +void LG_PENDLIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_oldest, h_amt; + char h_odate[16]; + EXEC SQL END DECLARE SECTION; + + EXEC SQL SELECT count(*), coalesce(min(voucher_id), 0) + INTO :h_cnt, :h_oldest FROM lg_voucher WHERE status = 'D'; + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT coalesce(sum(l.amount), 0) + INTO :h_amt + FROM lg_ledger_line l JOIN lg_voucher v ON v.voucher_id = l.voucher_id + WHERE v.status = 'D' AND l.dc = 'D'; + if (sqlca.sqlcode < 0) FAIL(b); + + h_odate[0] = 0; + if (h_oldest > 0) { + EXEC SQL SELECT to_char(biz_date, 'YYYY-MM-DD') INTO :h_odate + FROM lg_voucher WHERE voucher_id = :h_oldest; + if (sqlca.sqlcode < 0) FAIL(b); + } + + setl(b, T_COUNT, h_cnt); setl(b, T_ID1, h_oldest); setl(b, T_AMT2, h_amt); + Bchg(b, T_STR2, 0, h_odate, 0L); + userlog("LG_PENDLIST 미결 %ld건 차변누계 %ld 최고령 %ld", h_cnt, h_amt, h_oldest); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_PERIODTB.h b/app/src/lg/svc/LG_PERIODTB.h new file mode 100644 index 0000000..c3808a0 --- /dev/null +++ b/app/src/lg/svc/LG_PERIODTB.h @@ -0,0 +1,21 @@ +/* + * LG_PERIODTB.h - lg 서비스 LG_PERIODTB 카피북 (copybook / record header). + * 기간별 시산표: 기간 계정별 차/대변 집계를 기말일 시산표로 적재. + */ +#ifndef LG_PERIODTB_H +#define LG_PERIODTB_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_periodtb_rec_t; + +void LG_PERIODTB(TPSVCINFO *p); + +#endif /* LG_PERIODTB_H */ diff --git a/app/src/lg/svc/LG_PERIODTB.pgc b/app/src/lg/svc/LG_PERIODTB.pgc new file mode 100644 index 0000000..bfcf3c8 --- /dev/null +++ b/app/src/lg/svc/LG_PERIODTB.pgc @@ -0,0 +1,55 @@ +/* + * LG_PERIODTB.pgc - lg 모듈 서비스 LG_PERIODTB (one service = one file). + * 기간별 시산표: [T_BIZDATE ~ T_STR1] 구간을 계정별 GROUP BY 커서로 집계해 + * 기말일 키로 lg_trial_bal 에 적재 — 계정 수와 총대차차액을 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_PERIODTB.h" + +void LG_PERIODTB(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char dfrom[16], dto[16]; + long naccts = 0, netdiff = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_from[16], h_to[16], h_acct[32]; + long h_dr, h_cr; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, dfrom, sizeof(dfrom)); + gets_(b, T_STR1, dto, sizeof(dto)); + if (dfrom[0] == 0 || dto[0] == 0) { Bchg(b, T_MSG, 0, "기간 필요", 0L); FAIL(b); } + strncpy(h_from, dfrom, sizeof(h_from)-1); h_from[sizeof(h_from)-1] = 0; + strncpy(h_to, dto, sizeof(h_to)-1); h_to[sizeof(h_to)-1] = 0; + + EXEC SQL DECLARE ptb_cur CURSOR FOR + SELECT acct, + coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0), + coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0) + FROM lg_ledger_line + WHERE biz_date >= :h_from AND biz_date <= :h_to + GROUP BY acct ORDER BY acct; + EXEC SQL OPEN ptb_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH ptb_cur INTO :h_acct, :h_dr, :h_cr; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ptb_cur; FAIL(b); } + EXEC SQL INSERT INTO lg_trial_bal (acct, biz_date, debit_total, credit_total, diff) + VALUES (:h_acct, :h_to, :h_dr, :h_cr, :h_dr - :h_cr) + ON CONFLICT (acct, biz_date) DO UPDATE + SET debit_total = EXCLUDED.debit_total, credit_total = EXCLUDED.credit_total, + diff = EXCLUDED.diff, updated_at = now(); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ptb_cur; FAIL(b); } + netdiff += h_dr - h_cr; + naccts++; + } + EXEC SQL CLOSE ptb_cur; + + setl(b, T_COUNT, naccts); setl(b, T_DELTA, netdiff); + Bchg(b, T_STATUS, 0, (netdiff == 0) ? "OK" : "NG", 0L); + userlog("LG_PERIODTB [%s~%s] 계정 %ld개 총대차차액 %ld", dfrom, dto, naccts, netdiff); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_PLCLOSE.h b/app/src/lg/svc/LG_PLCLOSE.h new file mode 100644 index 0000000..b06bc93 --- /dev/null +++ b/app/src/lg/svc/LG_PLCLOSE.h @@ -0,0 +1,21 @@ +/* + * LG_PLCLOSE.h - lg 서비스 LG_PLCLOSE 카피북 (copybook / record header). + * 손익 대체 분개: 당일 수수료수익 순액을 이익잉여금으로 대체. + */ +#ifndef LG_PLCLOSE_H +#define LG_PLCLOSE_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_plclose_rec_t; + +void LG_PLCLOSE(TPSVCINFO *p); + +#endif /* LG_PLCLOSE_H */ diff --git a/app/src/lg/svc/LG_PLCLOSE.pgc b/app/src/lg/svc/LG_PLCLOSE.pgc new file mode 100644 index 0000000..d099215 --- /dev/null +++ b/app/src/lg/svc/LG_PLCLOSE.pgc @@ -0,0 +1,63 @@ +/* + * LG_PLCLOSE.pgc - lg 모듈 서비스 LG_PLCLOSE (one service = one file). + * 손익 대체 분개(일 단위): 당일 손익계정(FEE_INC, BADDEBT_EXP)의 순액을 각각 + * 커서로 산출해 이익잉여금(RETAINED)으로 대체 — 수익은 D/C, 비용은 C/D 로 + * 방향을 바꿔 모두 균형 전기한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_PLCLOSE.h" + +void LG_PLCLOSE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long vid, moved = 0, closed = 0, net, mag; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_acct[32]; + long h_net; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL DECLARE pl_cur CURSOR FOR + SELECT acct, coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE -amount END), 0) + FROM lg_ledger_line + WHERE biz_date = :h_bizdate AND acct IN ('FEE_INC', 'BADDEBT_EXP') + GROUP BY acct; + EXEC SQL OPEN pl_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH pl_cur INTO :h_acct, :h_net; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pl_cur; FAIL(b); } + net = h_net; + if (net == 0) continue; + mag = (net > 0) ? net : -net; + + vid = lgdb_next_voucher_id(); + if (vid < 0) { EXEC SQL CLOSE pl_cur; FAIL(b); } + if (lgdb_insert_voucher(vid, "ADJ", 0, "P", bizdate, "손익 대체") < 0) + { EXEC SQL CLOSE pl_cur; FAIL(b); } + if (net > 0) { + /* 수익 잔액(대변) -> D:손익계정 / C:RETAINED */ + if (lgdb_insert_line(vid, h_acct, "D", mag, "", bizdate, "posted") < 0 || + lgdb_insert_line(vid, "RETAINED", "C", mag, "", bizdate, "posted") < 0) + { EXEC SQL CLOSE pl_cur; FAIL(b); } + } else { + /* 비용 잔액(차변) -> D:RETAINED / C:손익계정 */ + if (lgdb_insert_line(vid, "RETAINED", "D", mag, "", bizdate, "posted") < 0 || + lgdb_insert_line(vid, h_acct, "C", mag, "", bizdate, "posted") < 0) + { EXEC SQL CLOSE pl_cur; FAIL(b); } + } + moved += net; + closed++; + } + EXEC SQL CLOSE pl_cur; + + setl(b, T_COUNT, closed); setl(b, T_NET, moved); + userlog("LG_PLCLOSE %s 손익계정 %ld개 대체 순이익 %ld", bizdate, closed, moved); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_QTRCLOSE.h b/app/src/lg/svc/LG_QTRCLOSE.h new file mode 100644 index 0000000..9828ce2 --- /dev/null +++ b/app/src/lg/svc/LG_QTRCLOSE.h @@ -0,0 +1,21 @@ +/* + * LG_QTRCLOSE.h - lg 서비스 LG_QTRCLOSE 카피북 (copybook / record header). + * 분기 결산 분개: 분기 수수료수익 대체 전기(차변/대변 균형). + */ +#ifndef LG_QTRCLOSE_H +#define LG_QTRCLOSE_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_qtrclose_rec_t; + +void LG_QTRCLOSE(TPSVCINFO *p); + +#endif /* LG_QTRCLOSE_H */ diff --git a/app/src/lg/svc/LG_QTRCLOSE.pgc b/app/src/lg/svc/LG_QTRCLOSE.pgc new file mode 100644 index 0000000..28f4bf4 --- /dev/null +++ b/app/src/lg/svc/LG_QTRCLOSE.pgc @@ -0,0 +1,47 @@ +/* + * LG_QTRCLOSE.pgc - lg 모듈 서비스 LG_QTRCLOSE (one service = one file). + * 분기 결산 분개: 연도(T_ARG1)/분기(T_ARG2)로 분기 구간을 산출해 그 구간의 + * FEE_INC 순액을 RETAINED 로 대체 전기한다 (차변/대변 균형 분개). + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_QTRCLOSE.h" + +void LG_QTRCLOSE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long year = getl(b, T_ARG1), qtr = getl(b, T_ARG2); + long vid, net; + static const char *QEND[4] = { "03-31", "06-30", "09-30", "12-31" }; + EXEC SQL BEGIN DECLARE SECTION; + char h_from[16], h_to[16]; + long h_net; + EXEC SQL END DECLARE SECTION; + + if (year < 2000 || qtr < 1 || qtr > 4) { Bchg(b, T_MSG, 0, "연도/분기 오류", 0L); FAIL(b); } + snprintf(h_from, sizeof(h_from), "%04ld-%02ld-01", year, (qtr - 1) * 3 + 1); + snprintf(h_to, sizeof(h_to), "%04ld-%s", year, QEND[qtr - 1]); + + EXEC SQL SELECT coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE -amount END), 0) + INTO :h_net FROM lg_ledger_line + WHERE acct = 'FEE_INC' AND biz_date >= :h_from AND biz_date <= :h_to; + if (sqlca.sqlcode < 0) FAIL(b); + net = h_net; + + if (net > 0) { + vid = lgdb_next_voucher_id(); + if (vid < 0) FAIL(b); + if (lgdb_insert_voucher(vid, "ADJ", qtr, "P", h_to, "분기결산 손익대체") < 0) FAIL(b); + if (lgdb_insert_line(vid, "FEE_INC", "D", net, "", h_to, "posted") < 0) FAIL(b); + if (lgdb_insert_line(vid, "RETAINED", "C", net, "", h_to, "posted") < 0) FAIL(b); + setl(b, T_ID1, vid); + Bchg(b, T_STATUS, 0, "P", 0L); + } else { + Bchg(b, T_STATUS, 0, "N", 0L); + } + setl(b, T_AMT2, net); + Bchg(b, T_STR2, 0, h_to, 0L); + userlog("LG_QTRCLOSE %ldQ%ld 대체 %ld", year, qtr, net); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_RECVACCRUE.h b/app/src/lg/svc/LG_RECVACCRUE.h new file mode 100644 index 0000000..b2e2c11 --- /dev/null +++ b/app/src/lg/svc/LG_RECVACCRUE.h @@ -0,0 +1,21 @@ +/* + * LG_RECVACCRUE.h - lg 서비스 LG_RECVACCRUE 카피북 (copybook / record header). + * 미수 계상 분개: 접수 상태 매입액을 미수/가수 계정에 미결 계상. + */ +#ifndef LG_RECVACCRUE_H +#define LG_RECVACCRUE_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_recvaccrue_rec_t; + +void LG_RECVACCRUE(TPSVCINFO *p); + +#endif /* LG_RECVACCRUE_H */ diff --git a/app/src/lg/svc/LG_RECVACCRUE.pgc b/app/src/lg/svc/LG_RECVACCRUE.pgc new file mode 100644 index 0000000..1d0f51b --- /dev/null +++ b/app/src/lg/svc/LG_RECVACCRUE.pgc @@ -0,0 +1,39 @@ +/* + * LG_RECVACCRUE.pgc - lg 모듈 서비스 LG_RECVACCRUE (one service = one file). + * 미수 계상 분개: 접수(A) 상태 매입 총액을 D:RECV_ACCR / C:SUSP 로 미결 계상 + * — 매입 확정 전 미수채권의 발생주의 인식. 건수 0 이면 계상 생략. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_RECVACCRUE.h" + +void LG_RECVACCRUE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long vid; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_amt, h_cnt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT coalesce(sum(amount), 0), count(*) + INTO :h_amt, :h_cnt FROM purchase + WHERE biz_date = :h_bizdate AND status = 'A'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt == 0) { setl(b, T_COUNT, 0); Bchg(b, T_STATUS, 0, "N", 0L); OK(b); } + + vid = lgdb_next_voucher_id(); + if (vid < 0) FAIL(b); + if (lgdb_insert_voucher(vid, "ADJ", h_cnt, "D", bizdate, "매입 미수 계상") < 0) FAIL(b); + if (lgdb_insert_line(vid, "RECV_ACCR", "D", h_amt, "", bizdate, "posted") < 0) FAIL(b); + if (lgdb_insert_line(vid, "SUSP", "C", h_amt, "", bizdate, "posted") < 0) FAIL(b); + + setl(b, T_ID1, vid); setl(b, T_AMT2, h_amt); setl(b, T_COUNT, h_cnt); + userlog("LG_RECVACCRUE vid=%ld 미수 %ld (%ld건) 계상", vid, h_amt, h_cnt); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_REDENTRY.h b/app/src/lg/svc/LG_REDENTRY.h new file mode 100644 index 0000000..8da8486 --- /dev/null +++ b/app/src/lg/svc/LG_REDENTRY.h @@ -0,0 +1,21 @@ +/* + * LG_REDENTRY.h - lg 서비스 LG_REDENTRY 카피북 (copybook / record header). + * 적색 정정 분개: 원전표 라인의 차/대변을 뒤집어 반대 분개 생성. + */ +#ifndef LG_REDENTRY_H +#define LG_REDENTRY_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_redentry_rec_t; + +void LG_REDENTRY(TPSVCINFO *p); + +#endif /* LG_REDENTRY_H */ diff --git a/app/src/lg/svc/LG_REDENTRY.pgc b/app/src/lg/svc/LG_REDENTRY.pgc new file mode 100644 index 0000000..2d40c33 --- /dev/null +++ b/app/src/lg/svc/LG_REDENTRY.pgc @@ -0,0 +1,45 @@ +/* + * LG_REDENTRY.pgc - lg 모듈 서비스 LG_REDENTRY (one service = one file). + * 적색 정정 분개: 원전표(T_ID1)의 모든 라인을 차/대변 반전(INSERT..SELECT CASE) + * 하여 새 REVERSE 전표로 복제 — 원분개와 합치면 차변합=대변합 그대로 0 상쇄. + * 원전표는 X(취소) 처리한다. 호출자의 XA 브랜치에서 수행; lg_svr 가 광고한다. + */ +#include "LG_REDENTRY.h" + +void LG_REDENTRY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], st[4]; + long orig = getl(b, T_ID1), nvid, copied; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_orig, h_nvid; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (lgdb_get_voucher_status(orig, st) < 0) FAIL(b); + if (st[0] == 'X') { Bchg(b, T_MSG, 0, "이미 취소된 전표", 0L); FAIL(b); } + + nvid = lgdb_next_voucher_id(); + if (nvid < 0) FAIL(b); + if (lgdb_insert_voucher(nvid, "REVERSE", orig, "P", bizdate, "적색 정정 분개") < 0) FAIL(b); + + h_orig = orig; h_nvid = nvid; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL INSERT INTO lg_ledger_line + (line_id, voucher_id, acct, dc, amount, merchant_id, biz_date, status) + SELECT nextval('lg_line_seq'), :h_nvid, acct, + CASE WHEN dc = 'D' THEN 'C' ELSE 'D' END, + amount, merchant_id, :h_bizdate, 'posted' + FROM lg_ledger_line WHERE voucher_id = :h_orig; + if (sqlca.sqlcode < 0) FAIL(b); + copied = sqlca.sqlerrd[2]; + if (copied == 0) { Bchg(b, T_MSG, 0, "원전표 라인 없음", 0L); FAIL(b); } + if (lgdb_update_voucher_status(orig, "X") < 0) FAIL(b); + + setl(b, T_ID2, nvid); setl(b, T_COUNT, copied); + userlog("LG_REDENTRY 원전표 %ld -> 적색전표 %ld (%ld라인 반전)", orig, nvid, copied); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_REJECT.h b/app/src/lg/svc/LG_REJECT.h new file mode 100644 index 0000000..3419d1b --- /dev/null +++ b/app/src/lg/svc/LG_REJECT.h @@ -0,0 +1,21 @@ +/* + * LG_REJECT.h - lg 서비스 LG_REJECT 카피북 (copybook / record header). + * 전표 반려: D 상태 전표를 X 처리하고 반려 사유를 메모에 축적. + */ +#ifndef LG_REJECT_H +#define LG_REJECT_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_reject_rec_t; + +void LG_REJECT(TPSVCINFO *p); + +#endif /* LG_REJECT_H */ diff --git a/app/src/lg/svc/LG_REJECT.pgc b/app/src/lg/svc/LG_REJECT.pgc new file mode 100644 index 0000000..bce0d59 --- /dev/null +++ b/app/src/lg/svc/LG_REJECT.pgc @@ -0,0 +1,35 @@ +/* + * LG_REJECT.pgc - lg 모듈 서비스 LG_REJECT (one service = one file). + * 전표 반려 워크플로: 작성(D) 전표를 X(취소) 처리하고 반려 사유(T_REASON)를 + * 메모에 이어붙인다. 이미 전기/마감된 전표는 반려 불가. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_REJECT.h" + +void LG_REJECT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char reason[64]; + long vid = getl(b, T_ID1); + EXEC SQL BEGIN DECLARE SECTION; + long h_vid; + char h_reason[80]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_REASON, reason, sizeof(reason)); + if (reason[0] == 0) strcpy(reason, "사유미기재"); + h_vid = vid; + snprintf(h_reason, sizeof(h_reason), " [반려:%s]", reason); + + EXEC SQL UPDATE lg_voucher + SET status = 'X', memo = memo || :h_reason, updated_at = now() + WHERE voucher_id = :h_vid AND status = 'D'; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlerrd[2] == 0) { Bchg(b, T_MSG, 0, "작성 상태 전표만 반려 가능", 0L); FAIL(b); } + + Bchg(b, T_STATUS, 0, "X", 0L); + userlog("LG_REJECT vid=%ld 반려: %s", vid, reason); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_REOPEN.h b/app/src/lg/svc/LG_REOPEN.h new file mode 100644 index 0000000..67e15f5 --- /dev/null +++ b/app/src/lg/svc/LG_REOPEN.h @@ -0,0 +1,21 @@ +/* + * LG_REOPEN.h - lg 서비스 LG_REOPEN 카피북 (copybook / record header). + * 취소 전표 재개: X 전표를 D 로 되돌리고 라인 posted 복원. + */ +#ifndef LG_REOPEN_H +#define LG_REOPEN_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_reopen_rec_t; + +void LG_REOPEN(TPSVCINFO *p); + +#endif /* LG_REOPEN_H */ diff --git a/app/src/lg/svc/LG_REOPEN.pgc b/app/src/lg/svc/LG_REOPEN.pgc new file mode 100644 index 0000000..ba94ce0 --- /dev/null +++ b/app/src/lg/svc/LG_REOPEN.pgc @@ -0,0 +1,34 @@ +/* + * LG_REOPEN.pgc - lg 모듈 서비스 LG_REOPEN (one service = one file). + * 취소 전표 재개: X(취소) 전표를 D(작성)로 되돌리고 closed 라인을 posted 로 + * 복원한다 — 승인 워크플로 재진입용. 취소 상태가 아니면 거부. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_REOPEN.h" + +void LG_REOPEN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long vid = getl(b, T_ID1), reopened; + EXEC SQL BEGIN DECLARE SECTION; + long h_vid; + EXEC SQL END DECLARE SECTION; + + h_vid = vid; + EXEC SQL UPDATE lg_voucher SET status = 'D', updated_at = now() + WHERE voucher_id = :h_vid AND status = 'X'; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlerrd[2] == 0) { Bchg(b, T_MSG, 0, "취소 상태 전표만 재개 가능", 0L); FAIL(b); } + + EXEC SQL UPDATE lg_ledger_line SET status = 'posted' + WHERE voucher_id = :h_vid AND status = 'closed'; + if (sqlca.sqlcode < 0) FAIL(b); + reopened = sqlca.sqlerrd[2]; + + setl(b, T_COUNT, reopened); + Bchg(b, T_STATUS, 0, "D", 0L); + userlog("LG_REOPEN vid=%ld 재개, %ld라인 posted 복원", vid, reopened); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_SETTLERECON.h b/app/src/lg/svc/LG_SETTLERECON.h new file mode 100644 index 0000000..7bc8da9 --- /dev/null +++ b/app/src/lg/svc/LG_SETTLERECON.h @@ -0,0 +1,21 @@ +/* + * LG_SETTLERECON.h - lg 서비스 LG_SETTLERECON 카피북 (copybook / record header). + * 원장-정산 대사: 정산 순액 합계와 원장 MERCH_PAY 대변 합계 비교. + */ +#ifndef LG_SETTLERECON_H +#define LG_SETTLERECON_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_settlerecon_rec_t; + +void LG_SETTLERECON(TPSVCINFO *p); + +#endif /* LG_SETTLERECON_H */ diff --git a/app/src/lg/svc/LG_SETTLERECON.pgc b/app/src/lg/svc/LG_SETTLERECON.pgc new file mode 100644 index 0000000..30c5ad8 --- /dev/null +++ b/app/src/lg/svc/LG_SETTLERECON.pgc @@ -0,0 +1,47 @@ +/* + * LG_SETTLERECON.pgc - lg 모듈 서비스 LG_SETTLERECON (one service = one file). + * 원장-정산 대사: 당일 정산(settlement) 순액 합계와 원장 MERCH_PAY 대변 + * 합계를 비교, 일치하면 당일 SETTLE 전표를 M(대사)으로 승격한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_SETTLERECON.h" + +void LG_SETTLERECON(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_setnet, h_ledcr, h_marked; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT coalesce(sum(net), 0) INTO :h_setnet + FROM settlement WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(amount), 0) INTO :h_ledcr + FROM lg_ledger_line + WHERE acct = 'MERCH_PAY' AND dc = 'C' AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_NET, h_setnet); setl(b, T_AMT3, h_ledcr); + setl(b, T_DELTA, h_setnet - h_ledcr); + + h_marked = 0; + if (h_setnet == h_ledcr && h_setnet > 0) { + EXEC SQL UPDATE lg_voucher SET status = 'M', updated_at = now() + WHERE voucher_type = 'SETTLE' AND biz_date = :h_bizdate AND status = 'P'; + if (sqlca.sqlcode < 0) FAIL(b); + h_marked = sqlca.sqlerrd[2]; + Bchg(b, T_STATUS, 0, "M", 0L); + } else { + Bchg(b, T_STATUS, 0, "U", 0L); + } + setl(b, T_COUNT, h_marked); + userlog("LG_SETTLERECON %s 정산 %ld vs 원장 %ld -> %ld전표 대사", bizdate, h_setnet, h_ledcr, h_marked); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_STATACCT.h b/app/src/lg/svc/LG_STATACCT.h new file mode 100644 index 0000000..133a85b --- /dev/null +++ b/app/src/lg/svc/LG_STATACCT.h @@ -0,0 +1,21 @@ +/* + * LG_STATACCT.h - lg 서비스 LG_STATACCT 카피북 (copybook / record header). + * 계정별 원장 통계: 계정 수와 최대 잔액 계정 산출. + */ +#ifndef LG_STATACCT_H +#define LG_STATACCT_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_statacct_rec_t; + +void LG_STATACCT(TPSVCINFO *p); + +#endif /* LG_STATACCT_H */ diff --git a/app/src/lg/svc/LG_STATACCT.pgc b/app/src/lg/svc/LG_STATACCT.pgc new file mode 100644 index 0000000..de966a2 --- /dev/null +++ b/app/src/lg/svc/LG_STATACCT.pgc @@ -0,0 +1,48 @@ +/* + * LG_STATACCT.pgc - lg 모듈 서비스 LG_STATACCT (one service = one file). + * 원장 통계 세분(계정): 당일 사용된 계정과목 수와 잔액 절대값이 가장 큰 + * 계정(이름/잔액)을 커서 순회로 찾아 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_STATACCT.h" + +void LG_STATACCT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], top[32]; + long n = 0, topbal = 0, topabs = -1, absb; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_acct[32]; + long h_bal; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + top[0] = 0; + + EXEC SQL DECLARE sa_cur CURSOR FOR + SELECT acct, coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE -amount END), 0) + FROM lg_ledger_line WHERE biz_date = :h_bizdate + GROUP BY acct; + EXEC SQL OPEN sa_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH sa_cur INTO :h_acct, :h_bal; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE sa_cur; FAIL(b); } + absb = (h_bal < 0) ? -h_bal : h_bal; + if (absb > topabs) { + topabs = absb; topbal = h_bal; + strncpy(top, h_acct, sizeof(top)-1); top[sizeof(top)-1] = 0; + } + n++; + } + EXEC SQL CLOSE sa_cur; + + setl(b, T_COUNT, n); setl(b, T_AMT2, topbal); + Bchg(b, T_KEY1, 0, top, 0L); + userlog("LG_STATACCT %s 계정 %ld개 최대잔액 %s=%ld", bizdate, n, top, topbal); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_STATDC.h b/app/src/lg/svc/LG_STATDC.h new file mode 100644 index 0000000..721419c --- /dev/null +++ b/app/src/lg/svc/LG_STATDC.h @@ -0,0 +1,21 @@ +/* + * LG_STATDC.h - lg 서비스 LG_STATDC 카피북 (copybook / record header). + * 차대변 통계 세분: 차변/대변 건수·평균금액·최대금액. + */ +#ifndef LG_STATDC_H +#define LG_STATDC_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_statdc_rec_t; + +void LG_STATDC(TPSVCINFO *p); + +#endif /* LG_STATDC_H */ diff --git a/app/src/lg/svc/LG_STATDC.pgc b/app/src/lg/svc/LG_STATDC.pgc new file mode 100644 index 0000000..40a0c7b --- /dev/null +++ b/app/src/lg/svc/LG_STATDC.pgc @@ -0,0 +1,44 @@ +/* + * LG_STATDC.pgc - lg 모듈 서비스 LG_STATDC (one service = one file). + * 원장 통계 세분(차대변): 당일 차변/대변 각각의 건수·합계·최대금액을 구하고 + * 평균 분개 금액을 C 측에서 계산해 반환한다 (0건 나눗셈 보호). + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_STATDC.h" + +void LG_STATDC(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long davg, cavg; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_dcnt, h_dsum, h_dmax, h_ccnt, h_csum, h_cmax; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT + coalesce(sum(CASE WHEN dc = 'D' THEN 1 ELSE 0 END), 0), + coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0), + coalesce(max(CASE WHEN dc = 'D' THEN amount END), 0), + coalesce(sum(CASE WHEN dc = 'C' THEN 1 ELSE 0 END), 0), + coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0), + coalesce(max(CASE WHEN dc = 'C' THEN amount END), 0) + INTO :h_dcnt, :h_dsum, :h_dmax, :h_ccnt, :h_csum, :h_cmax + FROM lg_ledger_line WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + davg = (h_dcnt > 0) ? h_dsum / h_dcnt : 0; + cavg = (h_ccnt > 0) ? h_csum / h_ccnt : 0; + + setl(b, T_ARG1, h_dcnt); setl(b, T_ARG2, h_ccnt); + setl(b, T_AMT2, h_dsum); setl(b, T_AMT3, h_csum); + setl(b, T_ARG3, davg); setl(b, T_ARG4, cavg); + setl(b, T_AMT4, (h_dmax > h_cmax) ? h_dmax : h_cmax); + userlog("LG_STATDC %s 차 %ld건 평균 %ld / 대 %ld건 평균 %ld", bizdate, h_dcnt, davg, h_ccnt, cavg); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_STATMERCH.h b/app/src/lg/svc/LG_STATMERCH.h new file mode 100644 index 0000000..3cc1037 --- /dev/null +++ b/app/src/lg/svc/LG_STATMERCH.h @@ -0,0 +1,21 @@ +/* + * LG_STATMERCH.h - lg 서비스 LG_STATMERCH 카피북 (copybook / record header). + * 가맹점별 원장 통계: 라인 건수·기간·차/대변 합계. + */ +#ifndef LG_STATMERCH_H +#define LG_STATMERCH_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_statmerch_rec_t; + +void LG_STATMERCH(TPSVCINFO *p); + +#endif /* LG_STATMERCH_H */ diff --git a/app/src/lg/svc/LG_STATMERCH.pgc b/app/src/lg/svc/LG_STATMERCH.pgc new file mode 100644 index 0000000..bcc360e --- /dev/null +++ b/app/src/lg/svc/LG_STATMERCH.pgc @@ -0,0 +1,40 @@ +/* + * LG_STATMERCH.pgc - lg 모듈 서비스 LG_STATMERCH (one service = one file). + * 원장 통계 세분(가맹점): 특정 가맹점의 원장 라인 건수, 최초/최종 영업일, + * 차변/대변 합계와 순액을 한 번의 집계로 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_STATMERCH.h" + +void LG_STATMERCH(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64]; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_first[16], h_last[16]; + long h_cnt, h_dr, h_cr; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + if (merch[0] == 0) { Bchg(b, T_MSG, 0, "가맹점 필요", 0L); FAIL(b); } + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + h_first[0] = 0; h_last[0] = 0; + + EXEC SQL SELECT count(*), + coalesce(to_char(min(biz_date), 'YYYY-MM-DD'), ''), + coalesce(to_char(max(biz_date), 'YYYY-MM-DD'), ''), + coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0), + coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0) + INTO :h_cnt, :h_first, :h_last, :h_dr, :h_cr + FROM lg_ledger_line WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + setl(b, T_AMT2, h_dr); setl(b, T_AMT3, h_cr); setl(b, T_DELTA, h_dr - h_cr); + Bchg(b, T_STR1, 0, h_first, 0L); + Bchg(b, T_STR2, 0, h_last, 0L); + userlog("LG_STATMERCH %s %ld건 [%s~%s] 순액 %ld", merch, h_cnt, h_first, h_last, h_dr - h_cr); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_STATTYPE.h b/app/src/lg/svc/LG_STATTYPE.h new file mode 100644 index 0000000..a59df6c --- /dev/null +++ b/app/src/lg/svc/LG_STATTYPE.h @@ -0,0 +1,21 @@ +/* + * LG_STATTYPE.h - lg 서비스 LG_STATTYPE 카피북 (copybook / record header). + * 전표유형별 통계: 전표 유형 수와 최다 유형 산출. + */ +#ifndef LG_STATTYPE_H +#define LG_STATTYPE_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_stattype_rec_t; + +void LG_STATTYPE(TPSVCINFO *p); + +#endif /* LG_STATTYPE_H */ diff --git a/app/src/lg/svc/LG_STATTYPE.pgc b/app/src/lg/svc/LG_STATTYPE.pgc new file mode 100644 index 0000000..74d498a --- /dev/null +++ b/app/src/lg/svc/LG_STATTYPE.pgc @@ -0,0 +1,48 @@ +/* + * LG_STATTYPE.pgc - lg 모듈 서비스 LG_STATTYPE (one service = one file). + * 원장 통계 세분(전표유형): 당일 전표를 유형(voucher_type)별로 집계해 + * 유형 수, 총 전표 수, 최다 유형명/건수를 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_STATTYPE.h" + +void LG_STATTYPE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], toptype[24]; + long types = 0, total = 0, topcnt = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_type[24]; + long h_cnt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + toptype[0] = 0; + + EXEC SQL DECLARE st_cur CURSOR FOR + SELECT voucher_type, count(*) FROM lg_voucher + WHERE biz_date = :h_bizdate + GROUP BY voucher_type ORDER BY count(*) DESC, voucher_type; + EXEC SQL OPEN st_cur; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH st_cur INTO :h_type, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE st_cur; FAIL(b); } + if (types == 0) { + topcnt = h_cnt; + strncpy(toptype, h_type, sizeof(toptype)-1); toptype[sizeof(toptype)-1] = 0; + } + total += h_cnt; + types++; + } + EXEC SQL CLOSE st_cur; + + setl(b, T_ARG1, types); setl(b, T_COUNT, total); setl(b, T_ARG2, topcnt); + Bchg(b, T_STR2, 0, toptype, 0L); + userlog("LG_STATTYPE %s 유형 %ld개 전표 %ld건 최다 %s(%ld)", bizdate, types, total, toptype, topcnt); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_SUSPENSE.h b/app/src/lg/svc/LG_SUSPENSE.h new file mode 100644 index 0000000..26439fa --- /dev/null +++ b/app/src/lg/svc/LG_SUSPENSE.h @@ -0,0 +1,21 @@ +/* + * LG_SUSPENSE.h - lg 서비스 LG_SUSPENSE 카피북 (copybook / record header). + * 가지급 정리 분개: SUSP 계정 순액을 ADJ 로 정리(차/대변 균형). + */ +#ifndef LG_SUSPENSE_H +#define LG_SUSPENSE_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_suspense_rec_t; + +void LG_SUSPENSE(TPSVCINFO *p); + +#endif /* LG_SUSPENSE_H */ diff --git a/app/src/lg/svc/LG_SUSPENSE.pgc b/app/src/lg/svc/LG_SUSPENSE.pgc new file mode 100644 index 0000000..1e4538b --- /dev/null +++ b/app/src/lg/svc/LG_SUSPENSE.pgc @@ -0,0 +1,50 @@ +/* + * LG_SUSPENSE.pgc - lg 모듈 서비스 LG_SUSPENSE (one service = one file). + * 가지급(SUSP) 정리 분개: 당일 SUSP 계정 순액(차-대)을 0 으로 만드는 반대 + * 방향 분개(상대계정 ADJ)를 전기한다 — 순액 부호에 따라 차/대 방향 결정. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_SUSPENSE.h" + +void LG_SUSPENSE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long vid, net, mag; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_net; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE -amount END), 0) + INTO :h_net FROM lg_ledger_line + WHERE acct = 'SUSP' AND biz_date = :h_bizdate AND status = 'posted'; + if (sqlca.sqlcode < 0) FAIL(b); + net = h_net; + setl(b, T_DELTA, net); + if (net == 0) { Bchg(b, T_STATUS, 0, "N", 0L); OK(b); } + mag = (net > 0) ? net : -net; + + vid = lgdb_next_voucher_id(); + if (vid < 0) FAIL(b); + if (lgdb_insert_voucher(vid, "ADJ", 0, "P", bizdate, "가지급 정리") < 0) FAIL(b); + if (net > 0) { + /* SUSP 차변 잔류 -> C:SUSP / D:ADJ 로 소거 */ + if (lgdb_insert_line(vid, "ADJ", "D", mag, "", bizdate, "posted") < 0) FAIL(b); + if (lgdb_insert_line(vid, "SUSP", "C", mag, "", bizdate, "posted") < 0) FAIL(b); + } else { + /* SUSP 대변 잔류 -> D:SUSP / C:ADJ 로 소거 */ + if (lgdb_insert_line(vid, "SUSP", "D", mag, "", bizdate, "posted") < 0) FAIL(b); + if (lgdb_insert_line(vid, "ADJ", "C", mag, "", bizdate, "posted") < 0) FAIL(b); + } + + setl(b, T_ID1, vid); setl(b, T_AMT2, mag); + Bchg(b, T_STATUS, 0, "P", 0L); + userlog("LG_SUSPENSE vid=%ld 가지급 %ld 정리 (차=대 균형 유지)", vid, mag); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_TBDIFF.h b/app/src/lg/svc/LG_TBDIFF.h new file mode 100644 index 0000000..b4b58cc --- /dev/null +++ b/app/src/lg/svc/LG_TBDIFF.h @@ -0,0 +1,21 @@ +/* + * LG_TBDIFF.h - lg 서비스 LG_TBDIFF 카피북 (copybook / record header). + * 시산표 차액 조회: 불균형 계정 수·차액 절대값 합·최악 계정. + */ +#ifndef LG_TBDIFF_H +#define LG_TBDIFF_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_tbdiff_rec_t; + +void LG_TBDIFF(TPSVCINFO *p); + +#endif /* LG_TBDIFF_H */ diff --git a/app/src/lg/svc/LG_TBDIFF.pgc b/app/src/lg/svc/LG_TBDIFF.pgc new file mode 100644 index 0000000..e2c4c0f --- /dev/null +++ b/app/src/lg/svc/LG_TBDIFF.pgc @@ -0,0 +1,43 @@ +/* + * LG_TBDIFF.pgc - lg 모듈 서비스 LG_TBDIFF (one service = one file). + * 시산표 차액 조회: 당일 시산표(lg_trial_bal)에서 불균형(diff<>0) 계정 수, + * 차액 절대값 합계, 차액이 가장 큰 계정명/차액을 반환한다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_TBDIFF.h" + +void LG_TBDIFF(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_worst[32]; + long h_bad, h_abssum, h_worstdiff; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*), coalesce(sum(abs(diff)), 0) + INTO :h_bad, :h_abssum FROM lg_trial_bal + WHERE biz_date = :h_bizdate AND diff <> 0; + if (sqlca.sqlcode < 0) FAIL(b); + + h_worst[0] = 0; h_worstdiff = 0; + if (h_bad > 0) { + EXEC SQL SELECT acct, diff INTO :h_worst, :h_worstdiff + FROM lg_trial_bal + WHERE biz_date = :h_bizdate AND diff <> 0 + ORDER BY abs(diff) DESC, acct LIMIT 1; + if (sqlca.sqlcode < 0) FAIL(b); + } + + setl(b, T_COUNT, h_bad); setl(b, T_GROSS, h_abssum); + setl(b, T_DELTA, h_worstdiff); + Bchg(b, T_KEY1, 0, h_worst, 0L); + Bchg(b, T_STATUS, 0, (h_bad == 0) ? "OK" : "NG", 0L); + userlog("LG_TBDIFF %s 불균형 %ld계정 절대차액 %ld 최악 %s(%ld)", bizdate, h_bad, h_abssum, h_worst, h_worstdiff); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_VOIDVOUCHER.h b/app/src/lg/svc/LG_VOIDVOUCHER.h new file mode 100644 index 0000000..ac3b576 --- /dev/null +++ b/app/src/lg/svc/LG_VOIDVOUCHER.h @@ -0,0 +1,21 @@ +/* + * LG_VOIDVOUCHER.h - lg 서비스 LG_VOIDVOUCHER 카피북 (copybook / record header). + * 전표 취소: 마감연계 외 전표를 X 처리하고 라인 closed 전환. + */ +#ifndef LG_VOIDVOUCHER_H +#define LG_VOIDVOUCHER_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_voidvoucher_rec_t; + +void LG_VOIDVOUCHER(TPSVCINFO *p); + +#endif /* LG_VOIDVOUCHER_H */ diff --git a/app/src/lg/svc/LG_VOIDVOUCHER.pgc b/app/src/lg/svc/LG_VOIDVOUCHER.pgc new file mode 100644 index 0000000..0ae8255 --- /dev/null +++ b/app/src/lg/svc/LG_VOIDVOUCHER.pgc @@ -0,0 +1,33 @@ +/* + * LG_VOIDVOUCHER.pgc - lg 모듈 서비스 LG_VOIDVOUCHER (one service = one file). + * 전표 취소: 마감연계(S) 상태가 아닌 전표를 X 처리하고 소속 라인 전부를 + * closed 로 전환한다 (마감된 전표는 LG_REDENTRY 로 반전할 것). + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_VOIDVOUCHER.h" + +void LG_VOIDVOUCHER(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long vid = getl(b, T_ID1), closed; + EXEC SQL BEGIN DECLARE SECTION; + long h_vid; + EXEC SQL END DECLARE SECTION; + + h_vid = vid; + EXEC SQL UPDATE lg_voucher SET status = 'X', updated_at = now() + WHERE voucher_id = :h_vid AND status <> 'S' AND status <> 'X'; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlerrd[2] == 0) { Bchg(b, T_MSG, 0, "취소 불가 전표(마감/기취소)", 0L); FAIL(b); } + + EXEC SQL UPDATE lg_ledger_line SET status = 'closed' WHERE voucher_id = :h_vid; + if (sqlca.sqlcode < 0) FAIL(b); + closed = sqlca.sqlerrd[2]; + + setl(b, T_COUNT, closed); + Bchg(b, T_STATUS, 0, "X", 0L); + userlog("LG_VOIDVOUCHER vid=%ld 취소, %ld라인 closed", vid, closed); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_VOUCHTRACE.h b/app/src/lg/svc/LG_VOUCHTRACE.h new file mode 100644 index 0000000..cc41ef5 --- /dev/null +++ b/app/src/lg/svc/LG_VOUCHTRACE.h @@ -0,0 +1,21 @@ +/* + * LG_VOUCHTRACE.h - lg 서비스 LG_VOUCHTRACE 카피북 (copybook / record header). + * 전표 추적: 전표 헤더+라인 수+차/대변 합계+균형 여부. + */ +#ifndef LG_VOUCHTRACE_H +#define LG_VOUCHTRACE_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_vouchtrace_rec_t; + +void LG_VOUCHTRACE(TPSVCINFO *p); + +#endif /* LG_VOUCHTRACE_H */ diff --git a/app/src/lg/svc/LG_VOUCHTRACE.pgc b/app/src/lg/svc/LG_VOUCHTRACE.pgc new file mode 100644 index 0000000..71c4c77 --- /dev/null +++ b/app/src/lg/svc/LG_VOUCHTRACE.pgc @@ -0,0 +1,43 @@ +/* + * LG_VOUCHTRACE.pgc - lg 모듈 서비스 LG_VOUCHTRACE (one service = one file). + * 전표 추적: 전표(T_ID1)의 헤더(유형/상태/연계 ID)와 라인 통계(건수, 차변합, + * 대변합)를 함께 조회하고 복식부기 균형 여부를 판정해 돌려준다. + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_VOUCHTRACE.h" + +void LG_VOUCHTRACE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long vid = getl(b, T_ID1); + EXEC SQL BEGIN DECLARE SECTION; + long h_vid, h_ref, h_lines, h_dr, h_cr; + char h_type[24], h_st[4], h_date[16]; + EXEC SQL END DECLARE SECTION; + + h_vid = vid; + EXEC SQL SELECT voucher_type, status, ref_id, to_char(biz_date, 'YYYY-MM-DD') + INTO :h_type, :h_st, :h_ref, :h_date + FROM lg_voucher WHERE voucher_id = :h_vid; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { Bchg(b, T_MSG, 0, "전표 없음", 0L); FAIL(b); } + + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN dc = 'D' THEN amount ELSE 0 END), 0), + coalesce(sum(CASE WHEN dc = 'C' THEN amount ELSE 0 END), 0) + INTO :h_lines, :h_dr, :h_cr + FROM lg_ledger_line WHERE voucher_id = :h_vid; + if (sqlca.sqlcode < 0) FAIL(b); + + Bchg(b, T_STR1, 0, h_type, 0L); + Bchg(b, T_STATUS, 0, h_st, 0L); + Bchg(b, T_BIZDATE, 0, h_date, 0L); + setl(b, T_ID2, h_ref); setl(b, T_COUNT, h_lines); + setl(b, T_AMT2, h_dr); setl(b, T_AMT3, h_cr); + setl(b, T_RC, (h_dr == h_cr) ? 0 : 1); + userlog("LG_VOUCHTRACE vid=%ld %s/%s 라인 %ld 차 %ld 대 %ld %s", vid, h_type, h_st, h_lines, h_dr, h_cr, + (h_dr == h_cr) ? "균형" : "불균형"); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/lg/svc/LG_ZEROLINE.h b/app/src/lg/svc/LG_ZEROLINE.h new file mode 100644 index 0000000..f4b2454 --- /dev/null +++ b/app/src/lg/svc/LG_ZEROLINE.h @@ -0,0 +1,21 @@ +/* + * LG_ZEROLINE.h - lg 서비스 LG_ZEROLINE 카피북 (copybook / record header). + * 0원 분개 검출: 금액 0 라인을 검출하여 closed 처리. + */ +#ifndef LG_ZEROLINE_H +#define LG_ZEROLINE_H + +#include "lg_svc.h" + +typedef struct { + long id1; + long id2; + long amount; + char status[8]; + char key1[32]; + char biz_date[16]; +} lg_lg_zeroline_rec_t; + +void LG_ZEROLINE(TPSVCINFO *p); + +#endif /* LG_ZEROLINE_H */ diff --git a/app/src/lg/svc/LG_ZEROLINE.pgc b/app/src/lg/svc/LG_ZEROLINE.pgc new file mode 100644 index 0000000..b103ae3 --- /dev/null +++ b/app/src/lg/svc/LG_ZEROLINE.pgc @@ -0,0 +1,37 @@ +/* + * LG_ZEROLINE.pgc - lg 모듈 서비스 LG_ZEROLINE (one service = one file). + * 0원 분개 검출/정리: 당일 금액 0 인 posted 라인을 검출해 closed 처리하고 + * 정리 건수와 잔여 posted 라인 수를 반환한다 (원장 위생 관리). + * 호출자의 XA 브랜치에서 수행(EXEC SQL CONNECT 없음); lg_svr 가 광고한다. + */ +#include "LG_ZEROLINE.h" + +void LG_ZEROLINE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long swept; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_left; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL UPDATE lg_ledger_line SET status = 'closed' + WHERE biz_date = :h_bizdate AND amount = 0 AND status = 'posted'; + if (sqlca.sqlcode < 0) FAIL(b); + swept = sqlca.sqlerrd[2]; + + EXEC SQL SELECT count(*) INTO :h_left + FROM lg_ledger_line WHERE biz_date = :h_bizdate AND status = 'posted'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, swept); setl(b, T_ARG1, h_left); + Bchg(b, T_STATUS, 0, (swept > 0) ? "C" : "N", 0L); + userlog("LG_ZEROLINE %s 0원 라인 %ld건 정리, 잔여 posted %ld건", bizdate, swept, h_left); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/mg/batch/mg_chanload_batch.pgc b/app/src/mg/batch/mg_chanload_batch.pgc new file mode 100644 index 0000000..8d76444 --- /dev/null +++ b/app/src/mg/batch/mg_chanload_batch.pgc @@ -0,0 +1,59 @@ +/* + * mg_chanload_batch.pgc - mg 채널 부하 점유율 순위 배치 (커서 + XA). + * + * 채널별 당일 건수x금액 합성 부하지수를 커서로 내림차순 순회하며 + * 순위표와 최대 부하 채널의 점유율(%) 을 산출한다 (채널 부하 감시). + * Usage: mg_chanload_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_chanload_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long rank = 0, share; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_chan[16]; + long h_cnt, h_load; + EXEC SQL END DECLARE SECTION; + mg_chanload_rec_t rec; + memset(&rec, 0, sizeof(rec)); + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + /* 부하지수 = 건수*1000 + 금액/10000 (건수 우선, 금액 보정) */ + EXEC SQL DECLARE clb CURSOR FOR + SELECT channel, count(*), + count(*) * 1000 + coalesce(sum(amount),0) / 10000 + FROM mg_msg_log WHERE biz_date = :h_bizdate + GROUP BY channel + ORDER BY 3 DESC; + EXEC SQL OPEN clb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH clb INTO :h_chan, :h_cnt, :h_load; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE clb; tpabort(0); return 1; } + rank++; + rec.channels++; + rec.grand_total += h_load; + if (rank == 1) { rec.top_load = h_load; strncpy(rec.top_chan, h_chan, sizeof(rec.top_chan)-1); } + printf(" %ld위 [%s] 건수=%ld 부하지수=%ld\n", rank, h_chan, h_cnt, h_load); + } + EXEC SQL CLOSE clb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + share = (rec.grand_total > 0) ? (rec.top_load * 100L) / rec.grand_total : 0; + printf(">>> mg_chanload_batch COMMIT: bizdate=%s 채널=%ld개 1위=%s 점유율=%ld%%\n", + bizdate, rec.channels, rec.top_chan[0] ? rec.top_chan : "-", share); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_dupscan_batch.pgc b/app/src/mg/batch/mg_dupscan_batch.pgc new file mode 100644 index 0000000..83c4da7 --- /dev/null +++ b/app/src/mg/batch/mg_dupscan_batch.pgc @@ -0,0 +1,53 @@ +/* + * mg_dupscan_batch.pgc - mg 중복 STAN 전문 전수 스캔 배치 (커서 + XA). + * + * GROUP BY stan HAVING count(*)>1 커서로 당일 중복 순번 그룹을 전수 조사해 + * 그룹 수/중복 전문 수/최다 중복 STAN 을 리포트한다 (중복전문 검출의 일배치판). + * Usage: mg_dupscan_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_dupscan_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_stan, h_cnt; + EXEC SQL END DECLARE SECTION; + mg_dupscan_rec_t rec; + memset(&rec, 0, sizeof(rec)); + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE dsc CURSOR FOR + SELECT stan, count(*) FROM mg_msg_log + WHERE biz_date = :h_bizdate + GROUP BY stan HAVING count(*) > 1 + ORDER BY count(*) DESC; + EXEC SQL OPEN dsc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH dsc INTO :h_stan, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE dsc; tpabort(0); return 1; } + rec.dup_groups++; + rec.dup_msgs += h_cnt - 1; /* 원본 1건 제외한 중복분 */ + if (h_cnt > rec.worst_cnt) { rec.worst_cnt = h_cnt; rec.worst_stan = h_stan; } + } + EXEC SQL CLOSE dsc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_dupscan_batch COMMIT: bizdate=%s 중복그룹=%ld 중복전문=%ld 최다STAN=%ld(%ld건)\n", + bizdate, rec.dup_groups, rec.dup_msgs, rec.worst_stan, rec.worst_cnt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_gapfill_batch.pgc b/app/src/mg/batch/mg_gapfill_batch.pgc new file mode 100644 index 0000000..b33022f --- /dev/null +++ b/app/src/mg/batch/mg_gapfill_batch.pgc @@ -0,0 +1,55 @@ +/* + * mg_gapfill_batch.pgc - mg STAN 순번 갭 정밀 탐지 배치 (커서 + XA). + * + * 당일 고유 STAN 을 오름차순 커서로 훑으며 이전 순번과의 간격을 재서 + * 갭 개수와 최대 결번 폭을 찾아낸다 (순번 갭 검출의 일배치판). + * Usage: mg_gapfill_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_gapfill_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long prev = -1; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_stan; + EXEC SQL END DECLARE SECTION; + mg_gapfill_rec_t rec; + memset(&rec, 0, sizeof(rec)); + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE gfc CURSOR FOR + SELECT DISTINCT stan FROM mg_msg_log + WHERE biz_date = :h_bizdate ORDER BY stan; + EXEC SQL OPEN gfc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH gfc INTO :h_stan; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE gfc; tpabort(0); return 1; } + rec.scanned++; + if (prev >= 0 && h_stan - prev > 1) { /* 연속이 깨진 지점 = 결번 구간 */ + rec.gaps++; + if (h_stan - prev - 1 > rec.widest) rec.widest = h_stan - prev - 1; + } + prev = h_stan; + } + EXEC SQL CLOSE gfc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_gapfill_batch COMMIT: bizdate=%s 스캔=%ld 갭구간=%ld 최대결번폭=%ld\n", + bizdate, rec.scanned, rec.gaps, rec.widest); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_hourstat_batch.pgc b/app/src/mg/batch/mg_hourstat_batch.pgc new file mode 100644 index 0000000..49a5631 --- /dev/null +++ b/app/src/mg/batch/mg_hourstat_batch.pgc @@ -0,0 +1,54 @@ +/* + * mg_hourstat_batch.pgc - mg 시간대별 전문량 집계 배치 (커서 + XA). + * + * created_at 의 시(hour) 단위로 당일 전문을 묶어 시간대별 건수를 뽑고 + * 피크아워와 그 비중을 리포트한다 (시간대 세분 통계의 일배치판). + * Usage: mg_hourstat_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_hourstat_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_hour, h_cnt; + EXEC SQL END DECLARE SECTION; + mg_hourstat_rec_t rec; + memset(&rec, 0, sizeof(rec)); + rec.peak_hour = -1; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE hsc CURSOR FOR + SELECT extract(hour FROM created_at)::int, count(*) + FROM mg_msg_log WHERE biz_date = :h_bizdate + GROUP BY 1 ORDER BY 1; + EXEC SQL OPEN hsc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH hsc INTO :h_hour, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE hsc; tpabort(0); return 1; } + rec.hours++; + rec.total += h_cnt; + printf(" [%02ld시] %ld건\n", h_hour, h_cnt); + if (h_cnt > rec.peak_cnt) { rec.peak_cnt = h_cnt; rec.peak_hour = h_hour; } + } + EXEC SQL CLOSE hsc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_hourstat_batch COMMIT: bizdate=%s 활동시간대=%ld 총=%ld 피크=%ld시(%ld건)\n", + bizdate, rec.hours, rec.total, rec.peak_hour, rec.peak_cnt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_keyver_batch.pgc b/app/src/mg/batch/mg_keyver_batch.pgc new file mode 100644 index 0000000..bb85a37 --- /dev/null +++ b/app/src/mg/batch/mg_keyver_batch.pgc @@ -0,0 +1,53 @@ +/* + * mg_keyver_batch.pgc - mg 세대키 일괄 승급/세션키 재발급 배치 (2단계 갱신 + XA). + * + * KEYVER 카운터를 한 번 올린 뒤 UP 채널 전부의 세션키를 새 세대 + * "Kvvvv-<채널>" 로 재발급한다 (키 전환의 전 채널 일괄판). + * Usage: mg_keyver_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_keyver_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + long h_kv; + char h_prefix[16]; + EXEC SQL END DECLARE SECTION; + mg_keyver_rec_t rec; + memset(&rec, 0, sizeof(rec)); + + (void)bizdate; /* 키 승급은 영업일 무관 전 채널 대상 */ + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + /* 1단계: 세대키 번호 승급 */ + EXEC SQL UPDATE mg_seq SET seq_val = seq_val + 1, updated_at = now() + WHERE seq_name = 'KEYVER' + RETURNING seq_val INTO :h_kv; + if (sqlca.sqlcode < 0 || sqlca.sqlerrd[2] == 0) { + fprintf(stderr, "KEYVER 승급 FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; + } + rec.new_keyver = h_kv; + + /* 2단계: UP 채널 세션키 재발급 (채널명이 키 꼬리에 박혀 채널별로 달라짐) */ + sprintf(h_prefix, "K%04ld-", h_kv); + EXEC SQL UPDATE mg_channel + SET session_key = :h_prefix || channel, updated_at = now() + WHERE status = 'UP'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "세션키 재발급 FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.rekeyed = (long)sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_keyver_batch COMMIT: 신규 키버전=%ld 재발급 채널=%ld개\n", + rec.new_keyver, rec.rekeyed); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_macaudit_batch.pgc b/app/src/mg/batch/mg_macaudit_batch.pgc new file mode 100644 index 0000000..90c1850 --- /dev/null +++ b/app/src/mg/batch/mg_macaudit_batch.pgc @@ -0,0 +1,61 @@ +/* + * mg_macaudit_batch.pgc - mg MAC 누락 전문 감사/보정 배치 (행단위 커서 갱신 + XA). + * + * mac 이 비어 있는 당일 전문을 커서로 돌며 raw_msg 로 가산 체크섬을 재계산해 + * 행마다 채워 넣는다 (무결성 사후 보정: MG_MAC 계열과 동일 알고리즘). + * Usage: mg_macaudit_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_macaudit_batch.h" + +/* 가산 체크섬 4-hex (온라인 MG_MAC 과 같은 계열, 배치측 독립 구현). */ +static void mac_stamp(const char *s, char *out) +{ + unsigned int a = 0; int i; + for (i = 0; s[i]; i++) a = (a + (unsigned char)s[i] * 31u) & 0xFFFF; + sprintf(out, "%04X", a); +} + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_raw[512], h_mac[8]; + long h_id; + EXEC SQL END DECLARE SECTION; + mg_macaudit_rec_t rec; + memset(&rec, 0, sizeof(rec)); + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE mab CURSOR FOR + SELECT msg_id, raw_msg FROM mg_msg_log + WHERE biz_date = :h_bizdate AND mac = '' AND raw_msg <> ''; + EXEC SQL OPEN mab; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH mab INTO :h_id, :h_raw; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE mab; tpabort(0); return 1; } + rec.audited++; + mac_stamp(h_raw, h_mac); + EXEC SQL UPDATE mg_msg_log SET mac = :h_mac WHERE msg_id = :h_id; + if (sqlca.sqlcode < 0) { fprintf(stderr, "보정 FAIL msg_id=%ld [%d]\n", h_id, sqlca.sqlcode); EXEC SQL CLOSE mab; tpabort(0); return 1; } + rec.stamped++; + } + EXEC SQL CLOSE mab; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_macaudit_batch COMMIT: bizdate=%s 감사=%ld건 보정=%ld건\n", + bizdate, rec.audited, rec.stamped); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_mtimix_batch.pgc b/app/src/mg/batch/mg_mtimix_batch.pgc new file mode 100644 index 0000000..3c025fa --- /dev/null +++ b/app/src/mg/batch/mg_mtimix_batch.pgc @@ -0,0 +1,56 @@ +/* + * mg_mtimix_batch.pgc - mg 전문유형(MTI) 구성비 산출 배치 (커서 + XA). + * + * MTI 별로 당일 전문을 묶어 유형 가짓수와 금융전문(02xx/04xx) 대 + * 망관리전문(08xx) 비중을 가른다 (전문유형 세분 통계의 일배치판). + * Usage: mg_mtimix_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_mtimix_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long pct; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_mti[8]; + long h_cnt; + EXEC SQL END DECLARE SECTION; + mg_mtimix_rec_t rec; + memset(&rec, 0, sizeof(rec)); + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE mmx CURSOR FOR + SELECT mti, count(*) FROM mg_msg_log + WHERE biz_date = :h_bizdate + GROUP BY mti ORDER BY mti; + EXEC SQL OPEN mmx; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH mmx INTO :h_mti, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE mmx; tpabort(0); return 1; } + rec.kinds++; + rec.total += h_cnt; + if (h_mti[1] == '8') rec.net_cnt += h_cnt; /* 08xx = 망관리 */ + else rec.fin_cnt += h_cnt; /* 그 외 = 금융/취소/정산 */ + printf(" [MTI %.4s] %ld건\n", h_mti, h_cnt); + } + EXEC SQL CLOSE mmx; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + pct = (rec.total > 0) ? (rec.fin_cnt * 100L) / rec.total : 0; + printf(">>> mg_mtimix_batch COMMIT: bizdate=%s 유형=%ld 총=%ld 금융=%ld(%ld%%) 망관리=%ld\n", + bizdate, rec.kinds, rec.total, rec.fin_cnt, pct, rec.net_cnt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_panaudit_batch.pgc b/app/src/mg/batch/mg_panaudit_batch.pgc new file mode 100644 index 0000000..de2c61c --- /dev/null +++ b/app/src/mg/batch/mg_panaudit_batch.pgc @@ -0,0 +1,63 @@ +/* + * mg_panaudit_batch.pgc - mg 비마스킹 PAN 적발/마스킹 배치 (행단위 커서 갱신 + XA). + * + * pan 에 '*' 가 전혀 없는(=평문 의심) 당일 전문을 커서로 적발해 + * BIN(앞6)/뒤4 보존 마스킹으로 행마다 덮어쓴다 (정보보호 일제 점검). + * Usage: mg_panaudit_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_panaudit_batch.h" + +/* 앞6/뒤4 보존, 나머지 '*' 치환 (12자리 미만은 전부 '*'). */ +static void pan_conceal(const char *in, char *out) +{ + int i, n = (int)strlen(in); + if (n > 19) n = 19; + for (i = 0; i < n; i++) + out[i] = (n >= 12 && (i < 6 || i >= n - 4)) ? in[i] : '*'; + out[n] = 0; +} + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_pan[24], h_masked[24]; + long h_id; + EXEC SQL END DECLARE SECTION; + mg_panaudit_rec_t rec; + memset(&rec, 0, sizeof(rec)); + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE pab CURSOR FOR + SELECT msg_id, pan FROM mg_msg_log + WHERE biz_date = :h_bizdate AND pan <> '' AND position('*' in pan) = 0; + EXEC SQL OPEN pab; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH pab INTO :h_id, :h_pan; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pab; tpabort(0); return 1; } + rec.inspected++; + pan_conceal(h_pan, h_masked); + EXEC SQL UPDATE mg_msg_log SET pan = :h_masked WHERE msg_id = :h_id; + if (sqlca.sqlcode < 0) { fprintf(stderr, "마스킹 FAIL msg_id=%ld [%d]\n", h_id, sqlca.sqlcode); EXEC SQL CLOSE pab; tpabort(0); return 1; } + rec.masked++; + } + EXEC SQL CLOSE pab; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_panaudit_batch COMMIT: bizdate=%s 적발=%ld건 마스킹=%ld건\n", + bizdate, rec.inspected, rec.masked); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_qagesweep_batch.pgc b/app/src/mg/batch/mg_qagesweep_batch.pgc new file mode 100644 index 0000000..b790408 --- /dev/null +++ b/app/src/mg/batch/mg_qagesweep_batch.pgc @@ -0,0 +1,50 @@ +/* + * mg_qagesweep_batch.pgc - mg 장기체류 큐 재시도 소진 스윕 배치 (2단계 갱신 + XA). + * + * 10분 넘게 대기(Q)한 큐 항목의 retry_cnt 를 올리고, 재시도 3회를 소진한 + * 항목은 실패(F)로 확정한다 (큐 체류시간 기반 단계적 포기 정책). + * Usage: mg_qagesweep_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_qagesweep_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + mg_qagesweep_rec_t rec; + memset(&rec, 0, sizeof(rec)); + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + /* 1단계: 10분 이상 체류한 대기 항목 재시도 카운트 증가 */ + EXEC SQL UPDATE mg_queue + SET retry_cnt = retry_cnt + 1, updated_at = now() + WHERE biz_date = :h_bizdate AND status = 'Q' + AND updated_at < now() - interval '10 minutes'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "재시도 가산 FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.bumped = (long)sqlca.sqlerrd[2]; + + /* 2단계: 재시도 소진(>=3) 항목 실패 확정 */ + EXEC SQL UPDATE mg_queue + SET status = 'F', updated_at = now() + WHERE biz_date = :h_bizdate AND status = 'Q' AND retry_cnt >= 3; + if (sqlca.sqlcode < 0) { fprintf(stderr, "실패 확정 FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.failed = (long)sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_qagesweep_batch COMMIT: bizdate=%s 재시도가산=%ld건 실패확정=%ld건\n", + bizdate, rec.bumped, rec.failed); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_rcdist_batch.pgc b/app/src/mg/batch/mg_rcdist_batch.pgc new file mode 100644 index 0000000..4b3dcc3 --- /dev/null +++ b/app/src/mg/batch/mg_rcdist_batch.pgc @@ -0,0 +1,58 @@ +/* + * mg_rcdist_batch.pgc - mg 응답코드 분포 리포트 배치 (커서 + XA). + * + * rc 별 GROUP BY 커서로 당일 정상(00)/거절 분포와 최빈 거절코드를 뽑아 + * 거절 코드 급증을 조기에 드러낸다 (응답코드 세분 통계의 일배치판). + * Usage: mg_rcdist_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_rcdist_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long top_cnt = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_rc[4]; + long h_cnt; + EXEC SQL END DECLARE SECTION; + mg_rcdist_rec_t rec; + memset(&rec, 0, sizeof(rec)); + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE rdb CURSOR FOR + SELECT rc, count(*) FROM mg_msg_log + WHERE biz_date = :h_bizdate + GROUP BY rc ORDER BY count(*) DESC, rc; + EXEC SQL OPEN rdb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH rdb INTO :h_rc, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE rdb; tpabort(0); return 1; } + rec.kinds++; + if (h_rc[0] == '0' && h_rc[1] == '0') { + rec.ok_cnt += h_cnt; + } else { + rec.bad_cnt += h_cnt; + if (h_cnt > top_cnt) { top_cnt = h_cnt; strncpy(rec.top_rc, h_rc, 3); rec.top_rc[3] = 0; } + } + printf(" [rc %.2s] %ld건\n", h_rc, h_cnt); + } + EXEC SQL CLOSE rdb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_rcdist_batch COMMIT: bizdate=%s 코드=%ld종 정상=%ld 거절=%ld 최빈거절=%s\n", + bizdate, rec.kinds, rec.ok_cnt, rec.bad_cnt, rec.top_rc[0] ? rec.top_rc : "-"); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_reroute_batch.pgc b/app/src/mg/batch/mg_reroute_batch.pgc new file mode 100644 index 0000000..2a010b4 --- /dev/null +++ b/app/src/mg/batch/mg_reroute_batch.pgc @@ -0,0 +1,45 @@ +/* + * mg_reroute_batch.pgc - mg 미라우팅 전문 기본룰 보충 배치 (INSERT..SELECT + XA). + * + * 당일 원장에 나타난 채널/MTI 조합 중 활성 라우팅 룰이 하나도 없는 조합을 + * 찾아 기본 목적지 ACQUIRE(우선순위 900) 룰을 자동 보충한다. + * Usage: mg_reroute_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_reroute_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + mg_reroute_rec_t rec; + memset(&rec, 0, sizeof(rec)); + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + /* 원장에 존재하나 활성 룰이 없는 채널/MTI 조합에 최후순위 기본룰 보충 */ + EXEC SQL INSERT INTO mg_route (route_id, channel, mti, dest, priority, active, biz_date) + SELECT nextval('mg_route_seq'), s.channel, s.mti, 'ACQUIRE', 900, true, CAST(:h_bizdate AS date) + FROM (SELECT DISTINCT channel, mti FROM mg_msg_log WHERE biz_date = :h_bizdate) s + WHERE NOT EXISTS ( + SELECT 1 FROM mg_route r + WHERE r.channel = s.channel AND r.mti = s.mti AND r.active = true + ); + if (sqlca.sqlcode < 0) { fprintf(stderr, "기본룰 보충 FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.added_rules = (long)sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_reroute_batch COMMIT: bizdate=%s 보충된 기본룰=%ld건\n", bizdate, rec.added_rules); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_seqsync_batch.pgc b/app/src/mg/batch/mg_seqsync_batch.pgc new file mode 100644 index 0000000..6d9db4a --- /dev/null +++ b/app/src/mg/batch/mg_seqsync_batch.pgc @@ -0,0 +1,60 @@ +/* + * mg_seqsync_batch.pgc - mg 채널 last_stan 원장 동기화 배치 (커서 + 대사 갱신 + XA). + * + * 채널마다 원장의 당일 최대 STAN 을 구해 채널 테이블의 last_stan 과 대조하고, + * 뒤처진 채널만 골라 원장 기준으로 전진 동기화한다 (순번 감사 후속 조치). + * Usage: mg_seqsync_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_seqsync_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_chan[16]; + long h_last, h_maxstan; + EXEC SQL END DECLARE SECTION; + mg_seqsync_rec_t rec; + memset(&rec, 0, sizeof(rec)); + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE ssb CURSOR FOR + SELECT c.channel, c.last_stan, coalesce(max(m.stan),0) + FROM mg_channel c + LEFT JOIN mg_msg_log m ON m.channel = c.channel AND m.biz_date = :h_bizdate + GROUP BY c.channel, c.last_stan + ORDER BY c.channel; + EXEC SQL OPEN ssb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH ssb INTO :h_chan, :h_last, :h_maxstan; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ssb; tpabort(0); return 1; } + rec.checked++; + if (h_maxstan > h_last) { /* 채널이 원장보다 뒤처짐 -> 전진 */ + EXEC SQL UPDATE mg_channel + SET last_stan = :h_maxstan, updated_at = now() + WHERE channel = :h_chan; + if (sqlca.sqlcode < 0) { fprintf(stderr, "동기화 FAIL %s [%d]\n", h_chan, sqlca.sqlcode); EXEC SQL CLOSE ssb; tpabort(0); return 1; } + rec.synced++; + printf(" [%s] last_stan %ld -> %ld\n", h_chan, h_last, h_maxstan); + } + } + EXEC SQL CLOSE ssb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_seqsync_batch COMMIT: bizdate=%s 점검=%ld채널 동기화=%ld채널\n", + bizdate, rec.checked, rec.synced); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_timeout_batch.pgc b/app/src/mg/batch/mg_timeout_batch.pgc new file mode 100644 index 0000000..b85c4c7 --- /dev/null +++ b/app/src/mg/batch/mg_timeout_batch.pgc @@ -0,0 +1,49 @@ +/* + * mg_timeout_batch.pgc - mg 응답지연 전문 일괄 타임아웃 배치 (XA 단일 갱신 배치). + * + * mg_seq(TMOUTSEC) 임계초를 읽어, 그 시간을 넘긴 수신(R) 상태 전문을 + * rc=68/N 으로 한 문장에 전이시킨다 (MG_TIMEOUT 스윕의 일배치 대량판). + * Usage: mg_timeout_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_timeout_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_sec; + EXEC SQL END DECLARE SECTION; + mg_timeout_rec_t rec; + memset(&rec, 0, sizeof(rec)); + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + h_sec = 60; /* 설정 없으면 60초 기본 */ + EXEC SQL SELECT seq_val INTO :h_sec FROM mg_seq WHERE seq_name = 'TMOUTSEC'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "TMOUTSEC 조회 FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + if (h_sec <= 0) h_sec = 60; + rec.threshold_sec = h_sec; + + EXEC SQL UPDATE mg_msg_log + SET rc = '68', status = 'N' + WHERE biz_date = :h_bizdate AND status = 'R' + AND created_at < now() - (:h_sec * interval '1 second'); + if (sqlca.sqlcode < 0) { fprintf(stderr, "타임아웃 전이 FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.expired = (long)sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_timeout_batch COMMIT: bizdate=%s 임계=%ld초 만료전이=%ld건\n", + bizdate, rec.threshold_sec, rec.expired); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/dbio/mg_chanload_batch.h b/app/src/mg/dbio/mg_chanload_batch.h new file mode 100644 index 0000000..93affc2 --- /dev/null +++ b/app/src/mg/dbio/mg_chanload_batch.h @@ -0,0 +1,10 @@ +/* + * mg_chanload_batch.h - mg mg_chanload_batch 배치 전용 카피북 (copybook). mg 채널 부하 점유율 순위 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by mg_chanload_batch.pgc. + */ +#ifndef MG_CHANLOAD_BATCH_H +#define MG_CHANLOAD_BATCH_H + +typedef struct { long channels; long grand_total; long top_load; char top_chan[16]; } mg_chanload_rec_t; + +#endif /* MG_CHANLOAD_BATCH_H */ diff --git a/app/src/mg/dbio/mg_dupscan_batch.h b/app/src/mg/dbio/mg_dupscan_batch.h new file mode 100644 index 0000000..bc30d20 --- /dev/null +++ b/app/src/mg/dbio/mg_dupscan_batch.h @@ -0,0 +1,10 @@ +/* + * mg_dupscan_batch.h - mg mg_dupscan_batch 배치 전용 카피북 (copybook). mg 중복 STAN 전문 전수 스캔 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by mg_dupscan_batch.pgc. + */ +#ifndef MG_DUPSCAN_BATCH_H +#define MG_DUPSCAN_BATCH_H + +typedef struct { long dup_groups; long dup_msgs; long worst_stan; long worst_cnt; } mg_dupscan_rec_t; + +#endif /* MG_DUPSCAN_BATCH_H */ diff --git a/app/src/mg/dbio/mg_gapfill_batch.h b/app/src/mg/dbio/mg_gapfill_batch.h new file mode 100644 index 0000000..3bcc88a --- /dev/null +++ b/app/src/mg/dbio/mg_gapfill_batch.h @@ -0,0 +1,10 @@ +/* + * mg_gapfill_batch.h - mg mg_gapfill_batch 배치 전용 카피북 (copybook). mg STAN 순번 갭 정밀 탐지 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by mg_gapfill_batch.pgc. + */ +#ifndef MG_GAPFILL_BATCH_H +#define MG_GAPFILL_BATCH_H + +typedef struct { long scanned; long gaps; long widest; } mg_gapfill_rec_t; + +#endif /* MG_GAPFILL_BATCH_H */ diff --git a/app/src/mg/dbio/mg_hourstat_batch.h b/app/src/mg/dbio/mg_hourstat_batch.h new file mode 100644 index 0000000..a01b673 --- /dev/null +++ b/app/src/mg/dbio/mg_hourstat_batch.h @@ -0,0 +1,10 @@ +/* + * mg_hourstat_batch.h - mg mg_hourstat_batch 배치 전용 카피북 (copybook). mg 시간대별 전문량 집계 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by mg_hourstat_batch.pgc. + */ +#ifndef MG_HOURSTAT_BATCH_H +#define MG_HOURSTAT_BATCH_H + +typedef struct { long hours; long peak_hour; long peak_cnt; long total; } mg_hourstat_rec_t; + +#endif /* MG_HOURSTAT_BATCH_H */ diff --git a/app/src/mg/dbio/mg_keyver_batch.h b/app/src/mg/dbio/mg_keyver_batch.h new file mode 100644 index 0000000..4b44e0e --- /dev/null +++ b/app/src/mg/dbio/mg_keyver_batch.h @@ -0,0 +1,10 @@ +/* + * mg_keyver_batch.h - mg mg_keyver_batch 배치 전용 카피북 (copybook). mg 세대키 일괄 승급/세션키 재발급 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by mg_keyver_batch.pgc. + */ +#ifndef MG_KEYVER_BATCH_H +#define MG_KEYVER_BATCH_H + +typedef struct { long new_keyver; long rekeyed; } mg_keyver_rec_t; + +#endif /* MG_KEYVER_BATCH_H */ diff --git a/app/src/mg/dbio/mg_macaudit_batch.h b/app/src/mg/dbio/mg_macaudit_batch.h new file mode 100644 index 0000000..948b268 --- /dev/null +++ b/app/src/mg/dbio/mg_macaudit_batch.h @@ -0,0 +1,10 @@ +/* + * mg_macaudit_batch.h - mg mg_macaudit_batch 배치 전용 카피북 (copybook). mg MAC 누락 전문 감사/보정 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by mg_macaudit_batch.pgc. + */ +#ifndef MG_MACAUDIT_BATCH_H +#define MG_MACAUDIT_BATCH_H + +typedef struct { long audited; long stamped; } mg_macaudit_rec_t; + +#endif /* MG_MACAUDIT_BATCH_H */ diff --git a/app/src/mg/dbio/mg_mtimix_batch.h b/app/src/mg/dbio/mg_mtimix_batch.h new file mode 100644 index 0000000..9e68839 --- /dev/null +++ b/app/src/mg/dbio/mg_mtimix_batch.h @@ -0,0 +1,10 @@ +/* + * mg_mtimix_batch.h - mg mg_mtimix_batch 배치 전용 카피북 (copybook). mg 전문유형(MTI) 구성비 산출 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by mg_mtimix_batch.pgc. + */ +#ifndef MG_MTIMIX_BATCH_H +#define MG_MTIMIX_BATCH_H + +typedef struct { long kinds; long total; long fin_cnt; long net_cnt; } mg_mtimix_rec_t; + +#endif /* MG_MTIMIX_BATCH_H */ diff --git a/app/src/mg/dbio/mg_panaudit_batch.h b/app/src/mg/dbio/mg_panaudit_batch.h new file mode 100644 index 0000000..86f2edb --- /dev/null +++ b/app/src/mg/dbio/mg_panaudit_batch.h @@ -0,0 +1,10 @@ +/* + * mg_panaudit_batch.h - mg mg_panaudit_batch 배치 전용 카피북 (copybook). mg 비마스킹 PAN 적발/마스킹 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by mg_panaudit_batch.pgc. + */ +#ifndef MG_PANAUDIT_BATCH_H +#define MG_PANAUDIT_BATCH_H + +typedef struct { long inspected; long masked; } mg_panaudit_rec_t; + +#endif /* MG_PANAUDIT_BATCH_H */ diff --git a/app/src/mg/dbio/mg_qagesweep_batch.h b/app/src/mg/dbio/mg_qagesweep_batch.h new file mode 100644 index 0000000..31654c9 --- /dev/null +++ b/app/src/mg/dbio/mg_qagesweep_batch.h @@ -0,0 +1,10 @@ +/* + * mg_qagesweep_batch.h - mg mg_qagesweep_batch 배치 전용 카피북 (copybook). mg 장기체류 큐 재시도 소진 스윕 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by mg_qagesweep_batch.pgc. + */ +#ifndef MG_QAGESWEEP_BATCH_H +#define MG_QAGESWEEP_BATCH_H + +typedef struct { long bumped; long failed; } mg_qagesweep_rec_t; + +#endif /* MG_QAGESWEEP_BATCH_H */ diff --git a/app/src/mg/dbio/mg_rcdist_batch.h b/app/src/mg/dbio/mg_rcdist_batch.h new file mode 100644 index 0000000..a8ac3ce --- /dev/null +++ b/app/src/mg/dbio/mg_rcdist_batch.h @@ -0,0 +1,10 @@ +/* + * mg_rcdist_batch.h - mg mg_rcdist_batch 배치 전용 카피북 (copybook). mg 응답코드 분포 리포트 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by mg_rcdist_batch.pgc. + */ +#ifndef MG_RCDIST_BATCH_H +#define MG_RCDIST_BATCH_H + +typedef struct { long kinds; long ok_cnt; long bad_cnt; char top_rc[4]; } mg_rcdist_rec_t; + +#endif /* MG_RCDIST_BATCH_H */ diff --git a/app/src/mg/dbio/mg_reroute_batch.h b/app/src/mg/dbio/mg_reroute_batch.h new file mode 100644 index 0000000..2544181 --- /dev/null +++ b/app/src/mg/dbio/mg_reroute_batch.h @@ -0,0 +1,10 @@ +/* + * mg_reroute_batch.h - mg mg_reroute_batch 배치 전용 카피북 (copybook). mg 미라우팅 전문 기본룰 보충 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by mg_reroute_batch.pgc. + */ +#ifndef MG_REROUTE_BATCH_H +#define MG_REROUTE_BATCH_H + +typedef struct { long added_rules; } mg_reroute_rec_t; + +#endif /* MG_REROUTE_BATCH_H */ diff --git a/app/src/mg/dbio/mg_seqsync_batch.h b/app/src/mg/dbio/mg_seqsync_batch.h new file mode 100644 index 0000000..586ee79 --- /dev/null +++ b/app/src/mg/dbio/mg_seqsync_batch.h @@ -0,0 +1,10 @@ +/* + * mg_seqsync_batch.h - mg mg_seqsync_batch 배치 전용 카피북 (copybook). mg 채널 last_stan 원장 동기화 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by mg_seqsync_batch.pgc. + */ +#ifndef MG_SEQSYNC_BATCH_H +#define MG_SEQSYNC_BATCH_H + +typedef struct { long checked; long synced; } mg_seqsync_rec_t; + +#endif /* MG_SEQSYNC_BATCH_H */ diff --git a/app/src/mg/dbio/mg_timeout_batch.h b/app/src/mg/dbio/mg_timeout_batch.h new file mode 100644 index 0000000..a7f8a9f --- /dev/null +++ b/app/src/mg/dbio/mg_timeout_batch.h @@ -0,0 +1,10 @@ +/* + * mg_timeout_batch.h - mg mg_timeout_batch 배치 전용 카피북 (copybook). mg 응답지연 전문 일괄 타임아웃 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by mg_timeout_batch.pgc. + */ +#ifndef MG_TIMEOUT_BATCH_H +#define MG_TIMEOUT_BATCH_H + +typedef struct { long threshold_sec; long expired; } mg_timeout_rec_t; + +#endif /* MG_TIMEOUT_BATCH_H */ diff --git a/app/src/mg/mg_svr.pgc b/app/src/mg/mg_svr.pgc index 63ba839..eae7bc6 100644 --- a/app/src/mg/mg_svr.pgc +++ b/app/src/mg/mg_svr.pgc @@ -2,13 +2,13 @@ * mg_svr.pgc - mg (전문게이트웨이 / message gateway) MODULE SERVER (thin dispatcher). * * The legacy "one module server, many services" pattern: this binary owns NO - * business logic. Each of mg's 30 online services now lives in its own file + * business logic. Each of mg's 75 online services now lives in its own file * (app/src/mg/svc/.pgc) with its own copybook header * (app/src/mg/svc/.h); those objects are archived into libmgsvc.a * and linked into this server. Here we only: * - include every service copybook (for the extern prototypes), * - tpopen() the ECPG XA RM, - * - tpadvertise() all 30 services from a {name, fn} table, + * - tpadvertise() all 75 services from a {name, fn} table, * - tpclose() at shutdown. * * XA: the connection is opened once by tpopen() (ECPG XA switch libndrxxaecpg.so); @@ -49,6 +49,60 @@ #include "MG_CHANSTAT.h" #include "MG_STATUS.h" #include "MG_CONFIRM.h" +/* --- 전문유형별 처리 (0100/0110/0200/0210/0400/0420/0800) --- */ +#include "MG_AUTHREQ.h" +#include "MG_AUTHRSP.h" +#include "MG_CAPTURE.h" +#include "MG_CAPRSP.h" +#include "MG_REVERSAL.h" +#include "MG_REVADVICE.h" +#include "MG_NETMGMT.h" +/* --- 채널별 어댑터 / 전문 재조립 --- */ +#include "MG_VAN1ADPT.h" +#include "MG_VAN2ADPT.h" +#include "MG_DIRECTADPT.h" +#include "MG_REASM.h" +/* --- MAC 세분 / 키 관리 --- */ +#include "MG_MACGEN.h" +#include "MG_MACVERIFY.h" +#include "MG_KEYROTATE.h" +#include "MG_KEYSTATUS.h" +/* --- 타임아웃 / 중복·순번 검출 --- */ +#include "MG_TIMEOUT.h" +#include "MG_TIMEOUTSET.h" +#include "MG_DUPCHK.h" +#include "MG_GAPSCAN.h" +#include "MG_SEQAUDIT.h" +/* --- 전문 변환 (내부<->ISO) / 라우팅 룰 관리 --- */ +#include "MG_TOISO.h" +#include "MG_FROMISO.h" +#include "MG_RTADD.h" +#include "MG_RTDROP.h" +#include "MG_RTLIST.h" +#include "MG_RTSWITCH.h" +/* --- 채널 상태 모니터/절체 --- */ +#include "MG_CHANOPEN.h" +#include "MG_CHANCLOSE.h" +#include "MG_CHANSWITCH.h" +#include "MG_CHANHEALTH.h" +/* --- 전문 통계 세분 --- */ +#include "MG_STATMTI.h" +#include "MG_STATHOUR.h" +#include "MG_STATDIR.h" +#include "MG_STATRC.h" +/* --- 오류전문 재처리 워크플로 / 트래픽 제어 --- */ +#include "MG_ERRRETRY.h" +#include "MG_ERRLIST.h" +#include "MG_ERRPURGE.h" +#include "MG_THROTTLE.h" +#include "MG_TPSCHECK.h" +/* --- 검증·마스킹 / 정산집계 전문 / 큐 모니터 --- */ +#include "MG_PANMASK.h" +#include "MG_AMTCHECK.h" +#include "MG_ADVICE.h" +#include "MG_SETTLEREQ.h" +#include "MG_SETTLERSP.h" +#include "MG_QDEPTH.h" /* ----- advertise table: {service name, function} (fn is extern from its file) */ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { @@ -67,6 +121,29 @@ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { {"MG_DEQUEUE", MG_DEQUEUE}, {"MG_RETRY", MG_RETRY}, {"MG_STATS", MG_STATS}, {"MG_CHANSTAT", MG_CHANSTAT}, {"MG_STATUS", MG_STATUS}, {"MG_CONFIRM", MG_CONFIRM}, + {"MG_AUTHREQ", MG_AUTHREQ}, {"MG_AUTHRSP", MG_AUTHRSP}, + {"MG_CAPTURE", MG_CAPTURE}, {"MG_CAPRSP", MG_CAPRSP}, + {"MG_REVERSAL", MG_REVERSAL}, {"MG_REVADVICE", MG_REVADVICE}, + {"MG_NETMGMT", MG_NETMGMT}, {"MG_VAN1ADPT", MG_VAN1ADPT}, + {"MG_VAN2ADPT", MG_VAN2ADPT}, {"MG_DIRECTADPT", MG_DIRECTADPT}, + {"MG_REASM", MG_REASM}, {"MG_MACGEN", MG_MACGEN}, + {"MG_MACVERIFY", MG_MACVERIFY}, {"MG_KEYROTATE", MG_KEYROTATE}, + {"MG_KEYSTATUS", MG_KEYSTATUS}, {"MG_TIMEOUT", MG_TIMEOUT}, + {"MG_TIMEOUTSET", MG_TIMEOUTSET}, {"MG_DUPCHK", MG_DUPCHK}, + {"MG_GAPSCAN", MG_GAPSCAN}, {"MG_SEQAUDIT", MG_SEQAUDIT}, + {"MG_TOISO", MG_TOISO}, {"MG_FROMISO", MG_FROMISO}, + {"MG_RTADD", MG_RTADD}, {"MG_RTDROP", MG_RTDROP}, + {"MG_RTLIST", MG_RTLIST}, {"MG_RTSWITCH", MG_RTSWITCH}, + {"MG_CHANOPEN", MG_CHANOPEN}, {"MG_CHANCLOSE", MG_CHANCLOSE}, + {"MG_CHANSWITCH", MG_CHANSWITCH}, {"MG_CHANHEALTH", MG_CHANHEALTH}, + {"MG_STATMTI", MG_STATMTI}, {"MG_STATHOUR", MG_STATHOUR}, + {"MG_STATDIR", MG_STATDIR}, {"MG_STATRC", MG_STATRC}, + {"MG_ERRRETRY", MG_ERRRETRY}, {"MG_ERRLIST", MG_ERRLIST}, + {"MG_ERRPURGE", MG_ERRPURGE}, {"MG_THROTTLE", MG_THROTTLE}, + {"MG_TPSCHECK", MG_TPSCHECK}, {"MG_PANMASK", MG_PANMASK}, + {"MG_AMTCHECK", MG_AMTCHECK}, {"MG_ADVICE", MG_ADVICE}, + {"MG_SETTLEREQ", MG_SETTLEREQ}, {"MG_SETTLERSP", MG_SETTLERSP}, + {"MG_QDEPTH", MG_QDEPTH}, {NULL, NULL} }; diff --git a/app/src/mg/svc/MG_ADVICE.h b/app/src/mg/svc/MG_ADVICE.h new file mode 100644 index 0000000..9962c92 --- /dev/null +++ b/app/src/mg/svc/MG_ADVICE.h @@ -0,0 +1,12 @@ +/* + * MG_ADVICE.h - mg (전문게이트웨이) 서비스 MG_ADVICE 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_ADVICE_H +#define MG_ADVICE_H + +#include "mg_svc.h" + +void MG_ADVICE(TPSVCINFO *p); + +#endif /* MG_ADVICE_H */ diff --git a/app/src/mg/svc/MG_ADVICE.pgc b/app/src/mg/svc/MG_ADVICE.pgc new file mode 100644 index 0000000..74d22e2 --- /dev/null +++ b/app/src/mg/svc/MG_ADVICE.pgc @@ -0,0 +1,39 @@ +/* + * MG_ADVICE.pgc - mg 모듈 서비스 MG_ADVICE (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_ADVICE.h" + +/* 72. MG_ADVICE - 0220 매입 어드바이스 수신: 응답이 필요 없는 통지 전문이라 + * 즉시 수용(A) 적재하고, 당일 누적 어드바이스 건수를 회신해 폭주를 감시. */ +void MG_ADVICE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char chan[16], raw[512], bizdate[16]; + long stan, msg_id, amount; + EXEC SQL BEGIN DECLARE SECTION; + char h_biz[16]; + long h_advcnt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH02"); + gets_(b, T_STR3, raw, sizeof(raw)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + amount = getl(b, T_AMOUNT); + + stan = mgdb_next_stan(); if (stan < 0) FAIL(b); + msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b); + if (mgdb_insert_msg(msg_id, stan, "0220", chan, "IN", "000000", "", amount, + "", "00", raw, "A", bizdate) < 0) FAIL(b); + + strncpy(h_biz, bizdate, sizeof(h_biz)-1); h_biz[sizeof(h_biz)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_advcnt + FROM mg_msg_log WHERE mti = '0220' AND biz_date = :h_biz; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_ID1, msg_id); + setl(b, T_COUNT, h_advcnt); + setl(b, T_RC, (h_advcnt > 1000) ? 1 : 0); /* 어드바이스 폭주 경보 */ + OK(b); +} diff --git a/app/src/mg/svc/MG_AMTCHECK.h b/app/src/mg/svc/MG_AMTCHECK.h new file mode 100644 index 0000000..f738759 --- /dev/null +++ b/app/src/mg/svc/MG_AMTCHECK.h @@ -0,0 +1,12 @@ +/* + * MG_AMTCHECK.h - mg (전문게이트웨이) 서비스 MG_AMTCHECK 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_AMTCHECK_H +#define MG_AMTCHECK_H + +#include "mg_svc.h" + +void MG_AMTCHECK(TPSVCINFO *p); + +#endif /* MG_AMTCHECK_H */ diff --git a/app/src/mg/svc/MG_AMTCHECK.pgc b/app/src/mg/svc/MG_AMTCHECK.pgc new file mode 100644 index 0000000..3ddcc5a --- /dev/null +++ b/app/src/mg/svc/MG_AMTCHECK.pgc @@ -0,0 +1,31 @@ +/* + * MG_AMTCHECK.pgc - mg 모듈 서비스 MG_AMTCHECK (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_AMTCHECK.h" + +/* 71. MG_AMTCHECK - 전문 금액/카드 사전검증: 금액 범위(0 < 금액 <= 한도)와 + * PAN Luhn 을 한 번에 검사해 게이트웨이 입구에서 불량 전문을 걸러낸다. */ +void MG_AMTCHECK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char pan[24]; + long amount, limit, rc = 0; + + amount = getl(b, T_AMOUNT); + limit = getl(b, T_AMT4); if (limit <= 0) limit = 100000000L; /* 기본 한도 1억 */ + gets_(b, T_STR5, pan, sizeof(pan)); + + if (amount <= 0) rc = 13; /* 13 = 금액 오류 */ + else if (amount > limit) rc = 61; /* 61 = 한도 초과 */ + else if (pan[0] && !acq_luhn_ok(pan)) rc = 14; /* 14 = 무효 카드번호 */ + + if (rc != 0) + userlog("MG_AMTCHECK 거절 rc=%ld 금액=%ld 한도=%ld", rc, amount, limit); + + setl(b, T_AMT4, limit); + setl(b, T_RC, rc); + if (rc != 0) FAIL(b); + OK(b); +} diff --git a/app/src/mg/svc/MG_AUTHREQ.h b/app/src/mg/svc/MG_AUTHREQ.h new file mode 100644 index 0000000..f90fea6 --- /dev/null +++ b/app/src/mg/svc/MG_AUTHREQ.h @@ -0,0 +1,12 @@ +/* + * MG_AUTHREQ.h - mg (전문게이트웨이) 서비스 MG_AUTHREQ 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_AUTHREQ_H +#define MG_AUTHREQ_H + +#include "mg_svc.h" + +void MG_AUTHREQ(TPSVCINFO *p); + +#endif /* MG_AUTHREQ_H */ diff --git a/app/src/mg/svc/MG_AUTHREQ.pgc b/app/src/mg/svc/MG_AUTHREQ.pgc new file mode 100644 index 0000000..d513496 --- /dev/null +++ b/app/src/mg/svc/MG_AUTHREQ.pgc @@ -0,0 +1,50 @@ +/* + * MG_AUTHREQ.pgc - mg 모듈 서비스 MG_AUTHREQ (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_AUTHREQ.h" + +/* ----- service-local pure helper(s) ----- */ +/* PAN 앞6/뒤4 만 남기고 '*' 마스킹 (승인요청 로그 저장용). */ +static void mg_authreq_mask(const char *pan, char *out /* >= 20 */) +{ + int i, n = (int)strlen(pan); + for (i = 0; i < n && i < 19; i++) + out[i] = (i < 6 || i >= n - 4) ? pan[i] : '*'; + out[i] = 0; +} + + +/* 31. MG_AUTHREQ - 0100 승인요청 전문 수신: 고정길이 파싱(MTI/처리코드/금액/PAN), + * PAN Luhn 검증 후 수신 원장 적재. Luhn 실패 시 rc=14(무효카드)로 기록. */ +void MG_AUTHREQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char chan[16], raw[512], bizdate[16], proc[8], pan[24], masked[24], rc[4]; + long stan, msg_id, amount, n; + + gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01"); + gets_(b, T_STR3, raw, sizeof(raw)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + n = (long)strlen(raw); + + /* 0100 고정길이: [0..3]=MTI(0100 강제) [4..9]=처리코드 [10..21]=금액 [22..37]=PAN */ + memset(proc, 0, sizeof(proc)); memset(pan, 0, sizeof(pan)); + if (n >= 10) strncpy(proc, raw + 4, 6); else strcpy(proc, "000000"); + amount = (n >= 22) ? atol(raw + 10) : getl(b, T_AMOUNT); + if (n >= 38) strncpy(pan, raw + 22, 16); + + strcpy(rc, (pan[0] && !acq_luhn_ok(pan)) ? "14" : "00"); + mg_authreq_mask(pan, masked); + + stan = mgdb_next_stan(); if (stan < 0) FAIL(b); + msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b); + if (mgdb_insert_msg(msg_id, stan, "0100", chan, "IN", proc, "", amount, + masked, rc, raw, "R", bizdate) < 0) FAIL(b); + sets_(b, T_STR2, rc); + sets_(b, T_STR5, masked); + setl(b, T_ID1, msg_id); + setl(b, T_ARG1, stan); + OK(b); +} diff --git a/app/src/mg/svc/MG_AUTHRSP.h b/app/src/mg/svc/MG_AUTHRSP.h new file mode 100644 index 0000000..e339330 --- /dev/null +++ b/app/src/mg/svc/MG_AUTHRSP.h @@ -0,0 +1,12 @@ +/* + * MG_AUTHRSP.h - mg (전문게이트웨이) 서비스 MG_AUTHRSP 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_AUTHRSP_H +#define MG_AUTHRSP_H + +#include "mg_svc.h" + +void MG_AUTHRSP(TPSVCINFO *p); + +#endif /* MG_AUTHRSP_H */ diff --git a/app/src/mg/svc/MG_AUTHRSP.pgc b/app/src/mg/svc/MG_AUTHRSP.pgc new file mode 100644 index 0000000..90fbf76 --- /dev/null +++ b/app/src/mg/svc/MG_AUTHRSP.pgc @@ -0,0 +1,34 @@ +/* + * MG_AUTHRSP.pgc - mg 모듈 서비스 MG_AUTHRSP (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_AUTHRSP.h" + +/* 32. MG_AUTHRSP - 0110 승인응답 생성: 승인한도(5천만원) 초과 시 rc=61 거절, + * 이내면 rc=00 승인. 원 0100 전문의 rc/상태를 갱신하고 0110 송신 로그 적재. */ +void MG_AUTHRSP(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char chan[16], bizdate[16], rc[4]; + long orig_id, stan, msg_id, amount; + const long AUTH_LIMIT = 50000000L; /* 건당 승인한도 5천만원 */ + + gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01"); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + orig_id = getl(b, T_ID1); + amount = getl(b, T_AMOUNT); + + strcpy(rc, (amount > AUTH_LIMIT) ? "61" : "00"); /* 61 = 한도초과 */ + + if (orig_id > 0 && mgdb_update_msg_rc(orig_id, rc, "C") < 0) FAIL(b); + + stan = mgdb_next_stan(); if (stan < 0) FAIL(b); + msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b); + if (mgdb_insert_msg(msg_id, stan, "0110", chan, "OUT", "000000", "", amount, + "", rc, "", "S", bizdate) < 0) FAIL(b); + sets_(b, T_STR2, rc); + setl(b, T_ID2, msg_id); + setl(b, T_RC, (rc[0] == '0' && rc[1] == '0') ? 0 : 1); + OK(b); +} diff --git a/app/src/mg/svc/MG_CAPRSP.h b/app/src/mg/svc/MG_CAPRSP.h new file mode 100644 index 0000000..2bed022 --- /dev/null +++ b/app/src/mg/svc/MG_CAPRSP.h @@ -0,0 +1,12 @@ +/* + * MG_CAPRSP.h - mg (전문게이트웨이) 서비스 MG_CAPRSP 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_CAPRSP_H +#define MG_CAPRSP_H + +#include "mg_svc.h" + +void MG_CAPRSP(TPSVCINFO *p); + +#endif /* MG_CAPRSP_H */ diff --git a/app/src/mg/svc/MG_CAPRSP.pgc b/app/src/mg/svc/MG_CAPRSP.pgc new file mode 100644 index 0000000..b571bc1 --- /dev/null +++ b/app/src/mg/svc/MG_CAPRSP.pgc @@ -0,0 +1,35 @@ +/* + * MG_CAPRSP.pgc - mg 모듈 서비스 MG_CAPRSP (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_CAPRSP.h" + +/* 34. MG_CAPRSP - 0210 매입응답 처리: STAN 으로 원 0200 전문을 찾아 + * 확인(C) 전이시키고 0210 송신 로그 적재. 원거래 없으면 rc=25. */ +void MG_CAPRSP(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char chan[16], bizdate[16]; + long stan, orig_id, msg_id, out_stan; + + gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH02"); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + stan = getl(b, T_ARG1); + + orig_id = mgdb_find_msg_by_stan(stan); + if (orig_id < 0) { /* 25 = 원거래 없음 */ + setl(b, T_RC, 25); + OK(b); + } + if (mgdb_update_msg_rc(orig_id, "00", "C") < 0) FAIL(b); + + out_stan = mgdb_next_stan(); if (out_stan < 0) FAIL(b); + msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b); + if (mgdb_insert_msg(msg_id, out_stan, "0210", chan, "OUT", "000000", "", 0, + "", "00", "", "S", bizdate) < 0) FAIL(b); + setl(b, T_ID1, orig_id); + setl(b, T_ID2, msg_id); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_CAPTURE.h b/app/src/mg/svc/MG_CAPTURE.h new file mode 100644 index 0000000..8a076c0 --- /dev/null +++ b/app/src/mg/svc/MG_CAPTURE.h @@ -0,0 +1,12 @@ +/* + * MG_CAPTURE.h - mg (전문게이트웨이) 서비스 MG_CAPTURE 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_CAPTURE_H +#define MG_CAPTURE_H + +#include "mg_svc.h" + +void MG_CAPTURE(TPSVCINFO *p); + +#endif /* MG_CAPTURE_H */ diff --git a/app/src/mg/svc/MG_CAPTURE.pgc b/app/src/mg/svc/MG_CAPTURE.pgc new file mode 100644 index 0000000..c5e7e5f --- /dev/null +++ b/app/src/mg/svc/MG_CAPTURE.pgc @@ -0,0 +1,35 @@ +/* + * MG_CAPTURE.pgc - mg 모듈 서비스 MG_CAPTURE (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_CAPTURE.h" + +/* 33. MG_CAPTURE - 0200 매입요청 전문 처리: MDR(bps) 수수료/정산액을 산출해 + * 수신 원장에 적재하고 수수료 내역을 회신 (전문게이트웨이 매입 접수). */ +void MG_CAPTURE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char chan[16], raw[512], bizdate[16]; + long stan, msg_id, amount, bps, fee, net; + + gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH02"); + gets_(b, T_STR3, raw, sizeof(raw)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + amount = getl(b, T_AMOUNT); + bps = getl(b, T_ARG2); if (bps <= 0) bps = 25; /* 기본 MDR 0.25% */ + + if (amount <= 0) { setl(b, T_RC, 13); OK(b); } /* 13 = 금액오류 */ + fee = acq_fee(amount, bps); + net = amount - fee; + + stan = mgdb_next_stan(); if (stan < 0) FAIL(b); + msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b); + if (mgdb_insert_msg(msg_id, stan, "0200", chan, "IN", "000000", "", amount, + "", "00", raw, "R", bizdate) < 0) FAIL(b); + setl(b, T_FEE, fee); + setl(b, T_NET, net); + setl(b, T_ID1, msg_id); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_CHANCLOSE.h b/app/src/mg/svc/MG_CHANCLOSE.h new file mode 100644 index 0000000..9af6a93 --- /dev/null +++ b/app/src/mg/svc/MG_CHANCLOSE.h @@ -0,0 +1,12 @@ +/* + * MG_CHANCLOSE.h - mg (전문게이트웨이) 서비스 MG_CHANCLOSE 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_CHANCLOSE_H +#define MG_CHANCLOSE_H + +#include "mg_svc.h" + +void MG_CHANCLOSE(TPSVCINFO *p); + +#endif /* MG_CHANCLOSE_H */ diff --git a/app/src/mg/svc/MG_CHANCLOSE.pgc b/app/src/mg/svc/MG_CHANCLOSE.pgc new file mode 100644 index 0000000..1679cab --- /dev/null +++ b/app/src/mg/svc/MG_CHANCLOSE.pgc @@ -0,0 +1,36 @@ +/* + * MG_CHANCLOSE.pgc - mg 모듈 서비스 MG_CHANCLOSE (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_CHANCLOSE.h" + +/* 58. MG_CHANCLOSE - 채널 폐쇄: 채널을 DOWN 으로 내리고 세션키를 폐기하되, + * 그 채널에 대기(Q) 중인 송신 큐 잔량을 세어 함께 회신 (폐쇄 영향도). */ +void MG_CHANCLOSE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_chan[16]; + long h_pending; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CHANNEL, h_chan, sizeof(h_chan)); if (h_chan[0] == 0) strcpy(h_chan, "CH01"); + + EXEC SQL SELECT count(*) INTO :h_pending + FROM mg_queue WHERE channel = :h_chan AND status = 'Q'; + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL UPDATE mg_channel + SET status = 'DOWN', session_key = '', updated_at = now() + WHERE channel = :h_chan; + if (sqlca.sqlcode < 0) { userlog("MG_CHANCLOSE 폐쇄 FAIL [%d]", sqlca.sqlcode); FAIL(b); } + if (sqlca.sqlerrd[2] == 0) { setl(b, T_RC, 25); FAIL(b); } /* 미등록 채널 */ + + if (h_pending > 0) + userlog("MG_CHANCLOSE %s 폐쇄: 대기 큐 %ld건 잔류", h_chan, h_pending); + sets_(b, T_STATUS, "DOWN"); + setl(b, T_COUNT, h_pending); + setl(b, T_RC, (h_pending > 0) ? 1 : 0); /* 잔량 있으면 경고 */ + OK(b); +} diff --git a/app/src/mg/svc/MG_CHANHEALTH.h b/app/src/mg/svc/MG_CHANHEALTH.h new file mode 100644 index 0000000..d1be26d --- /dev/null +++ b/app/src/mg/svc/MG_CHANHEALTH.h @@ -0,0 +1,12 @@ +/* + * MG_CHANHEALTH.h - mg (전문게이트웨이) 서비스 MG_CHANHEALTH 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_CHANHEALTH_H +#define MG_CHANHEALTH_H + +#include "mg_svc.h" + +void MG_CHANHEALTH(TPSVCINFO *p); + +#endif /* MG_CHANHEALTH_H */ diff --git a/app/src/mg/svc/MG_CHANHEALTH.pgc b/app/src/mg/svc/MG_CHANHEALTH.pgc new file mode 100644 index 0000000..d4bdad0 --- /dev/null +++ b/app/src/mg/svc/MG_CHANHEALTH.pgc @@ -0,0 +1,51 @@ +/* + * MG_CHANHEALTH.pgc - mg 모듈 서비스 MG_CHANHEALTH (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_CHANHEALTH.h" + +/* 60. MG_CHANHEALTH - 채널 종합 건강점검: 상태(UP 여부) + 당일 오류율(bps) + + * 큐 적체를 합산한 0~100 건강점수를 산출 (60 미만이면 절체 권고 rc=1). */ +void MG_CHANHEALTH(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long score, err_bps, up; + char status[16]; + EXEC SQL BEGIN DECLARE SECTION; + char h_chan[16], h_bizdate[16]; + long h_total, h_err, h_backlog; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CHANNEL, h_chan, sizeof(h_chan)); if (h_chan[0] == 0) strcpy(h_chan, "CH01"); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + if (mgdb_get_channel_status(h_chan, status) < 0) { setl(b, T_RC, 25); FAIL(b); } + up = (strcmp(status, "UP") == 0) ? 1 : 0; + + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN rc <> '00' THEN 1 ELSE 0 END),0) + INTO :h_total, :h_err + FROM mg_msg_log WHERE channel = :h_chan AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT count(*) INTO :h_backlog + FROM mg_queue WHERE channel = :h_chan AND status IN ('Q','F'); + if (sqlca.sqlcode < 0) FAIL(b); + + err_bps = (h_total > 0) ? (h_err * 10000L) / h_total : 0; + + /* 점수: 기본 100, 다운 -50, 오류율 100bps당 -10 (최대 -30), 적체 10건당 -5 (최대 -20) */ + score = 100; + if (!up) score -= 50; + score -= (err_bps / 100L) * 10L > 30 ? 30 : (err_bps / 100L) * 10L; + score -= (h_backlog / 10L) * 5L > 20 ? 20 : (h_backlog / 10L) * 5L; + if (score < 0) score = 0; + + sets_(b, T_STATUS, status); + setl(b, T_ARG3, err_bps); + setl(b, T_ARG7, h_backlog); + setl(b, T_ARG8, score); + setl(b, T_RC, (score < 60) ? 1 : 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_CHANOPEN.h b/app/src/mg/svc/MG_CHANOPEN.h new file mode 100644 index 0000000..8b5111d --- /dev/null +++ b/app/src/mg/svc/MG_CHANOPEN.h @@ -0,0 +1,12 @@ +/* + * MG_CHANOPEN.h - mg (전문게이트웨이) 서비스 MG_CHANOPEN 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_CHANOPEN_H +#define MG_CHANOPEN_H + +#include "mg_svc.h" + +void MG_CHANOPEN(TPSVCINFO *p); + +#endif /* MG_CHANOPEN_H */ diff --git a/app/src/mg/svc/MG_CHANOPEN.pgc b/app/src/mg/svc/MG_CHANOPEN.pgc new file mode 100644 index 0000000..cf33e4b --- /dev/null +++ b/app/src/mg/svc/MG_CHANOPEN.pgc @@ -0,0 +1,33 @@ +/* + * MG_CHANOPEN.pgc - mg 모듈 서비스 MG_CHANOPEN (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_CHANOPEN.h" + +/* 57. MG_CHANOPEN - 채널 개통: DOWN 채널만 UP 으로 올리고 sign-on 시각을 기록. + * 이미 UP 이면 rc=1 (이중 개통 무해화), 미등록 채널이면 rc=25. */ +void MG_CHANOPEN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char chan[16], cur[16]; + EXEC SQL BEGIN DECLARE SECTION; + char h_chan[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01"); + if (mgdb_get_channel_status(chan, cur) < 0) { setl(b, T_RC, 25); FAIL(b); } + if (strcmp(cur, "UP") == 0) { sets_(b, T_STATUS, cur); setl(b, T_RC, 1); OK(b); } + + strncpy(h_chan, chan, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0; + EXEC SQL UPDATE mg_channel + SET status = 'UP', signon_at = now(), updated_at = now() + WHERE channel = :h_chan; + if (sqlca.sqlcode < 0) { userlog("MG_CHANOPEN 개통 FAIL [%d]", sqlca.sqlcode); FAIL(b); } + + userlog("MG_CHANOPEN 채널 개통 %s (%s -> UP)", chan, cur); + sets_(b, T_STATUS, "UP"); + sets_(b, T_STR5, cur); /* 개통 전 상태 */ + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_CHANSWITCH.h b/app/src/mg/svc/MG_CHANSWITCH.h new file mode 100644 index 0000000..f1fb7c4 --- /dev/null +++ b/app/src/mg/svc/MG_CHANSWITCH.h @@ -0,0 +1,12 @@ +/* + * MG_CHANSWITCH.h - mg (전문게이트웨이) 서비스 MG_CHANSWITCH 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_CHANSWITCH_H +#define MG_CHANSWITCH_H + +#include "mg_svc.h" + +void MG_CHANSWITCH(TPSVCINFO *p); + +#endif /* MG_CHANSWITCH_H */ diff --git a/app/src/mg/svc/MG_CHANSWITCH.pgc b/app/src/mg/svc/MG_CHANSWITCH.pgc new file mode 100644 index 0000000..9682144 --- /dev/null +++ b/app/src/mg/svc/MG_CHANSWITCH.pgc @@ -0,0 +1,48 @@ +/* + * MG_CHANSWITCH.pgc - mg 모듈 서비스 MG_CHANSWITCH (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_CHANSWITCH.h" + +/* 59. MG_CHANSWITCH - 채널 절체: 주채널을 DOWN, 예비채널을 UP 으로 한 트랜잭션 + * 안에서 뒤집고, 주채널 대기 큐를 예비채널로 이관 (게이트웨이 페일오버). */ +void MG_CHANSWITCH(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char pri[16], bak[16]; + long moved; + EXEC SQL BEGIN DECLARE SECTION; + char h_pri[16], h_bak[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CHANNEL, pri, sizeof(pri)); if (pri[0] == 0) strcpy(pri, "CH01"); + gets_(b, T_STR2, bak, sizeof(bak)); if (bak[0] == 0) strcpy(bak, "CH02"); + if (strcmp(pri, bak) == 0) { setl(b, T_RC, 30); FAIL(b); } /* 자기 자신 절체 불가 */ + + strncpy(h_pri, pri, sizeof(h_pri)-1); h_pri[sizeof(h_pri)-1] = 0; + strncpy(h_bak, bak, sizeof(h_bak)-1); h_bak[sizeof(h_bak)-1] = 0; + + EXEC SQL UPDATE mg_channel SET status = 'DOWN', updated_at = now() + WHERE channel = :h_pri; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlerrd[2] == 0) { setl(b, T_RC, 25); FAIL(b); } + + EXEC SQL UPDATE mg_channel SET status = 'UP', signon_at = now(), updated_at = now() + WHERE channel = :h_bak; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlerrd[2] == 0) { setl(b, T_RC, 25); FAIL(b); } + + /* 주채널에 물려 있던 대기 전문을 예비채널로 이관 */ + EXEC SQL UPDATE mg_queue SET channel = :h_bak, updated_at = now() + WHERE channel = :h_pri AND status = 'Q'; + if (sqlca.sqlcode < 0) FAIL(b); + moved = (long)sqlca.sqlerrd[2]; + + userlog("MG_CHANSWITCH 절체 %s -> %s (큐 이관 %ld건)", pri, bak, moved); + sets_(b, T_STR5, pri); + sets_(b, T_STR2, bak); + setl(b, T_COUNT, moved); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_DIRECTADPT.h b/app/src/mg/svc/MG_DIRECTADPT.h new file mode 100644 index 0000000..c15989e --- /dev/null +++ b/app/src/mg/svc/MG_DIRECTADPT.h @@ -0,0 +1,12 @@ +/* + * MG_DIRECTADPT.h - mg (전문게이트웨이) 서비스 MG_DIRECTADPT 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_DIRECTADPT_H +#define MG_DIRECTADPT_H + +#include "mg_svc.h" + +void MG_DIRECTADPT(TPSVCINFO *p); + +#endif /* MG_DIRECTADPT_H */ diff --git a/app/src/mg/svc/MG_DIRECTADPT.pgc b/app/src/mg/svc/MG_DIRECTADPT.pgc new file mode 100644 index 0000000..d2a6084 --- /dev/null +++ b/app/src/mg/svc/MG_DIRECTADPT.pgc @@ -0,0 +1,45 @@ +/* + * MG_DIRECTADPT.pgc - mg 모듈 서비스 MG_DIRECTADPT (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_DIRECTADPT.h" + +/* ----- service-local pure helper(s) ----- */ +/* 구간 [s, s+n) 이 전부 십진 숫자인지 검사 (직연동 전문 필드 검증). */ +static int mg_direct_digits(const char *s, int n) +{ + int i; + for (i = 0; i < n; i++) + if (s[i] < '0' || s[i] > '9') return 0; + return 1; +} + + +/* 40. MG_DIRECTADPT - 직연동 채널 어댑터: 이미 내부 고정길이 포맷이므로 변환 없이 + * 길이(>=22)와 MTI(4)/처리코드(6)/금액(12) 숫자 여부만 엄격 검증. */ +void MG_DIRECTADPT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char raw[512], mti[8]; + long n, amount; + + gets_(b, T_STR3, raw, sizeof(raw)); + n = (long)strlen(raw); + + if (n < 22) { setl(b, T_RC, 31); FAIL(b); } /* 길이 부족 */ + if (!mg_direct_digits(raw, 4)) { setl(b, T_RC, 32); FAIL(b); } /* MTI 오류 */ + if (!mg_direct_digits(raw + 4, 6)) { setl(b, T_RC, 33); FAIL(b); } /* 처리코드 오류 */ + if (!mg_direct_digits(raw + 10, 12)) { setl(b, T_RC, 34); FAIL(b); } /* 금액 오류 */ + + memset(mti, 0, sizeof(mti)); + strncpy(mti, raw, 4); + amount = atol(raw + 10); + + sets_(b, T_STR1, mti); + setl(b, T_AMOUNT, amount); + sets_(b, T_CHANNEL, "CH03"); /* 직연동 = CH03 고정 매핑 */ + setl(b, T_COUNT, n); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_DUPCHK.h b/app/src/mg/svc/MG_DUPCHK.h new file mode 100644 index 0000000..66793f3 --- /dev/null +++ b/app/src/mg/svc/MG_DUPCHK.h @@ -0,0 +1,12 @@ +/* + * MG_DUPCHK.h - mg (전문게이트웨이) 서비스 MG_DUPCHK 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_DUPCHK_H +#define MG_DUPCHK_H + +#include "mg_svc.h" + +void MG_DUPCHK(TPSVCINFO *p); + +#endif /* MG_DUPCHK_H */ diff --git a/app/src/mg/svc/MG_DUPCHK.pgc b/app/src/mg/svc/MG_DUPCHK.pgc new file mode 100644 index 0000000..9636ee5 --- /dev/null +++ b/app/src/mg/svc/MG_DUPCHK.pgc @@ -0,0 +1,35 @@ +/* + * MG_DUPCHK.pgc - mg 모듈 서비스 MG_DUPCHK (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_DUPCHK.h" + +/* 48. MG_DUPCHK - 중복전문 검출: 동일 STAN 이 당일 원장에 이미 몇 건인지 세어 + * 2건 이상이면 중복(rc=94) 판정. 중복 초과분 건수를 함께 회신. */ +void MG_DUPCHK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_stan, h_cnt; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + h_stan = getl(b, T_ARG1); + if (h_stan <= 0) { setl(b, T_RC, 13); FAIL(b); } + + EXEC SQL SELECT count(*) INTO :h_cnt + FROM mg_msg_log WHERE stan = :h_stan AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + if (h_cnt > 1) { + userlog("MG_DUPCHK 중복 STAN=%ld 건수=%ld", h_stan, h_cnt); + setl(b, T_RC, 94); /* 94 = 중복 전문 */ + } else { + setl(b, T_RC, 0); + } + setl(b, T_COUNT, h_cnt); + setl(b, T_ARG2, (h_cnt > 1) ? h_cnt - 1 : 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_ERRLIST.h b/app/src/mg/svc/MG_ERRLIST.h new file mode 100644 index 0000000..12feb2a --- /dev/null +++ b/app/src/mg/svc/MG_ERRLIST.h @@ -0,0 +1,12 @@ +/* + * MG_ERRLIST.h - mg (전문게이트웨이) 서비스 MG_ERRLIST 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_ERRLIST_H +#define MG_ERRLIST_H + +#include "mg_svc.h" + +void MG_ERRLIST(TPSVCINFO *p); + +#endif /* MG_ERRLIST_H */ diff --git a/app/src/mg/svc/MG_ERRLIST.pgc b/app/src/mg/svc/MG_ERRLIST.pgc new file mode 100644 index 0000000..ecbbf72 --- /dev/null +++ b/app/src/mg/svc/MG_ERRLIST.pgc @@ -0,0 +1,33 @@ +/* + * MG_ERRLIST.pgc - mg 모듈 서비스 MG_ERRLIST (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_ERRLIST.h" + +/* 66. MG_ERRLIST - 오류전문 대기목록 요약: 당일 NACK(N) 전문의 건수, 금액합, + * 가장 오래된/최신 msg_id 를 회신 (재처리 워크플로의 조회 단계). */ +void MG_ERRLIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt, h_sum, h_first, h_last; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL SELECT count(*), coalesce(sum(amount),0), + coalesce(min(msg_id),0), coalesce(max(msg_id),0) + INTO :h_cnt, :h_sum, :h_first, :h_last + FROM mg_msg_log + WHERE biz_date = :h_bizdate AND status = 'N'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + setl(b, T_ID1, h_first); /* 가장 오래된 오류전문부터 재처리 */ + setl(b, T_ID2, h_last); + setl(b, T_RC, (h_cnt > 0) ? 1 : 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_ERRPURGE.h b/app/src/mg/svc/MG_ERRPURGE.h new file mode 100644 index 0000000..c1b14be --- /dev/null +++ b/app/src/mg/svc/MG_ERRPURGE.h @@ -0,0 +1,12 @@ +/* + * MG_ERRPURGE.h - mg (전문게이트웨이) 서비스 MG_ERRPURGE 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_ERRPURGE_H +#define MG_ERRPURGE_H + +#include "mg_svc.h" + +void MG_ERRPURGE(TPSVCINFO *p); + +#endif /* MG_ERRPURGE_H */ diff --git a/app/src/mg/svc/MG_ERRPURGE.pgc b/app/src/mg/svc/MG_ERRPURGE.pgc new file mode 100644 index 0000000..a789d80 --- /dev/null +++ b/app/src/mg/svc/MG_ERRPURGE.pgc @@ -0,0 +1,35 @@ +/* + * MG_ERRPURGE.pgc - mg 모듈 서비스 MG_ERRPURGE (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_ERRPURGE.h" + +/* 67. MG_ERRPURGE - 오류전문 폐기: 재처리를 포기한 당일 NACK(N) 전문 중 + * 무결성 실패(96)/중복(94) 등 지정 rc 만 골라 삭제 (워크플로 종결 단계). */ +void MG_ERRPURGE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char rcsel[4]; + long purged; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_rc[4]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + gets_(b, T_STR2, rcsel, sizeof(rcsel)); + if (rcsel[0] == 0) strcpy(rcsel, "96"); /* 기본: 무결성 실패 전문만 폐기 */ + strncpy(h_rc, rcsel, sizeof(h_rc)-1); h_rc[sizeof(h_rc)-1] = 0; + + if (h_rc[0] == '0' && h_rc[1] == '0') { setl(b, T_RC, 30); FAIL(b); } /* 정상 전문 폐기 금지 */ + + EXEC SQL DELETE FROM mg_msg_log + WHERE biz_date = :h_bizdate AND status = 'N' AND rc = :h_rc; + if (sqlca.sqlcode < 0) { userlog("MG_ERRPURGE 삭제 FAIL [%d]", sqlca.sqlcode); FAIL(b); } + purged = (long)sqlca.sqlerrd[2]; + + userlog("MG_ERRPURGE rc=%s 폐기 %ld건", rcsel, purged); + setl(b, T_COUNT, purged); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_ERRRETRY.h b/app/src/mg/svc/MG_ERRRETRY.h new file mode 100644 index 0000000..6a965e5 --- /dev/null +++ b/app/src/mg/svc/MG_ERRRETRY.h @@ -0,0 +1,12 @@ +/* + * MG_ERRRETRY.h - mg (전문게이트웨이) 서비스 MG_ERRRETRY 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_ERRRETRY_H +#define MG_ERRRETRY_H + +#include "mg_svc.h" + +void MG_ERRRETRY(TPSVCINFO *p); + +#endif /* MG_ERRRETRY_H */ diff --git a/app/src/mg/svc/MG_ERRRETRY.pgc b/app/src/mg/svc/MG_ERRRETRY.pgc new file mode 100644 index 0000000..a17ef99 --- /dev/null +++ b/app/src/mg/svc/MG_ERRRETRY.pgc @@ -0,0 +1,41 @@ +/* + * MG_ERRRETRY.pgc - mg 모듈 서비스 MG_ERRRETRY (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_ERRRETRY.h" + +/* 65. MG_ERRRETRY - 오류전문 단건 재처리: NACK(N) 된 전문을 rc=00/R 로 리셋하고 + * 원문 그대로 송신 큐에 재적재 (오류전문 재처리 워크플로의 실행 단계). */ +void MG_ERRRETRY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long msg_id, qid; + EXEC SQL BEGIN DECLARE SECTION; + long h_id, h_stan; + char h_status[4], h_chan[16], h_mti[8], h_raw[512], h_biz[16]; + EXEC SQL END DECLARE SECTION; + + msg_id = getl(b, T_ID1); + if (msg_id <= 0) { setl(b, T_RC, 13); FAIL(b); } + h_id = msg_id; + + EXEC SQL SELECT status, stan, channel, mti, raw_msg, to_char(biz_date,'YYYY-MM-DD') + INTO :h_status, :h_stan, :h_chan, :h_mti, :h_raw, :h_biz + FROM mg_msg_log WHERE msg_id = :h_id; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 25); FAIL(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + if (h_status[0] != 'N') { setl(b, T_RC, 1); OK(b); } /* 오류 상태 아님 = 재처리 불필요 */ + + EXEC SQL UPDATE mg_msg_log SET rc = '00', status = 'R' WHERE msg_id = :h_id; + if (sqlca.sqlcode < 0) FAIL(b); + + qid = mgdb_next_queue_id(); if (qid < 0) FAIL(b); + if (mgdb_enqueue(qid, h_stan, h_chan, h_mti, h_raw, h_biz) < 0) FAIL(b); + + userlog("MG_ERRRETRY 재처리 msg_id=%ld -> queue_id=%ld", msg_id, qid); + setl(b, T_ID2, qid); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_FROMISO.h b/app/src/mg/svc/MG_FROMISO.h new file mode 100644 index 0000000..44ea53e --- /dev/null +++ b/app/src/mg/svc/MG_FROMISO.h @@ -0,0 +1,12 @@ +/* + * MG_FROMISO.h - mg (전문게이트웨이) 서비스 MG_FROMISO 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_FROMISO_H +#define MG_FROMISO_H + +#include "mg_svc.h" + +void MG_FROMISO(TPSVCINFO *p); + +#endif /* MG_FROMISO_H */ diff --git a/app/src/mg/svc/MG_FROMISO.pgc b/app/src/mg/svc/MG_FROMISO.pgc new file mode 100644 index 0000000..1756191 --- /dev/null +++ b/app/src/mg/svc/MG_FROMISO.pgc @@ -0,0 +1,33 @@ +/* + * MG_FROMISO.pgc - mg 모듈 서비스 MG_FROMISO (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_FROMISO.h" + +/* 52. MG_FROMISO - ISO -> 내부전문 변환: 고정길이 ISO 전문을 잘라 + * 콜론 구분 내부포맷 "MTI:처리코드:금액" 으로 풀어낸다 (선행 0 제거). */ +void MG_FROMISO(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char iso[512], mti[8], proc[8], internal[64]; + long amount, n; + + gets_(b, T_STR3, iso, sizeof(iso)); + n = (long)strlen(iso); + if (n < 22) { setl(b, T_RC, 31); FAIL(b); } /* 최소 22바이트 필요 */ + + memset(mti, 0, sizeof(mti)); memset(proc, 0, sizeof(proc)); + strncpy(mti, iso, 4); + strncpy(proc, iso + 4, 6); + amount = atol(iso + 10); /* atol 이 선행 0 제거 */ + + sprintf(internal, "%.4s:%.6s:%ld", mti, proc, amount); + sets_(b, T_STR3, internal); + sets_(b, T_STR1, mti); + sets_(b, T_STR4, proc); + setl(b, T_AMOUNT, amount); + setl(b, T_COUNT, (long)strlen(internal)); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_GAPSCAN.h b/app/src/mg/svc/MG_GAPSCAN.h new file mode 100644 index 0000000..77f46c2 --- /dev/null +++ b/app/src/mg/svc/MG_GAPSCAN.h @@ -0,0 +1,12 @@ +/* + * MG_GAPSCAN.h - mg (전문게이트웨이) 서비스 MG_GAPSCAN 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_GAPSCAN_H +#define MG_GAPSCAN_H + +#include "mg_svc.h" + +void MG_GAPSCAN(TPSVCINFO *p); + +#endif /* MG_GAPSCAN_H */ diff --git a/app/src/mg/svc/MG_GAPSCAN.pgc b/app/src/mg/svc/MG_GAPSCAN.pgc new file mode 100644 index 0000000..4a8433b --- /dev/null +++ b/app/src/mg/svc/MG_GAPSCAN.pgc @@ -0,0 +1,35 @@ +/* + * MG_GAPSCAN.pgc - mg 모듈 서비스 MG_GAPSCAN (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_GAPSCAN.h" + +/* 49. MG_GAPSCAN - STAN 순번 갭 검출: 당일 최소~최대 순번 구간 폭에서 실제 + * 고유 순번 수를 빼 유실(갭) 개수를 산출. 갭 존재 시 rc=1. */ +void MG_GAPSCAN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long gaps; + EXEC SQL BEGIN DECLARE SECTION; + long h_min, h_max, h_uniq; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL SELECT coalesce(min(stan),0), coalesce(max(stan),0), count(DISTINCT stan) + INTO :h_min, :h_max, :h_uniq + FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + gaps = (h_uniq > 0) ? (h_max - h_min + 1) - h_uniq : 0; + if (gaps > 0) userlog("MG_GAPSCAN 순번 갭 %ld건 (구간 %ld..%ld)", gaps, h_min, h_max); + + setl(b, T_ARG1, h_min); + setl(b, T_ARG2, h_max); + setl(b, T_COUNT, h_uniq); + setl(b, T_ARG6, gaps); + setl(b, T_RC, (gaps > 0) ? 1 : 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_KEYROTATE.h b/app/src/mg/svc/MG_KEYROTATE.h new file mode 100644 index 0000000..cb39710 --- /dev/null +++ b/app/src/mg/svc/MG_KEYROTATE.h @@ -0,0 +1,12 @@ +/* + * MG_KEYROTATE.h - mg (전문게이트웨이) 서비스 MG_KEYROTATE 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_KEYROTATE_H +#define MG_KEYROTATE_H + +#include "mg_svc.h" + +void MG_KEYROTATE(TPSVCINFO *p); + +#endif /* MG_KEYROTATE_H */ diff --git a/app/src/mg/svc/MG_KEYROTATE.pgc b/app/src/mg/svc/MG_KEYROTATE.pgc new file mode 100644 index 0000000..d4178c1 --- /dev/null +++ b/app/src/mg/svc/MG_KEYROTATE.pgc @@ -0,0 +1,30 @@ +/* + * MG_KEYROTATE.pgc - mg 모듈 서비스 MG_KEYROTATE (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_KEYROTATE.h" + +/* 44. MG_KEYROTATE - 키 전환(교체): KEYVER 카운터를 올려 새 세대키 번호를 따고, + * 대상 채널의 세션키를 "Kvvvv-ssss" 형식으로 재발급 (키교환 후속 절차). */ +void MG_KEYROTATE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char chan[16], newkey[24]; + long kv, sess, stan; + + gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01"); + + kv = mgdb_seq_bump("KEYVER"); if (kv < 0) FAIL(b); + sess = mgdb_seq_bump("SESSION"); if (sess < 0) FAIL(b); + stan = mgdb_next_stan(); if (stan < 0) FAIL(b); + + sprintf(newkey, "K%04ld-%04ld", kv, sess); + if (mgdb_set_channel_session(chan, newkey, stan) < 0) FAIL(b); + + userlog("MG_KEYROTATE 채널=%s 키버전=%ld 세션=%ld", chan, kv, sess); + sets_(b, T_KEY1, newkey); + setl(b, T_ARG3, kv); + setl(b, T_ARG2, sess); + OK(b); +} diff --git a/app/src/mg/svc/MG_KEYSTATUS.h b/app/src/mg/svc/MG_KEYSTATUS.h new file mode 100644 index 0000000..7128cfc --- /dev/null +++ b/app/src/mg/svc/MG_KEYSTATUS.h @@ -0,0 +1,12 @@ +/* + * MG_KEYSTATUS.h - mg (전문게이트웨이) 서비스 MG_KEYSTATUS 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_KEYSTATUS_H +#define MG_KEYSTATUS_H + +#include "mg_svc.h" + +void MG_KEYSTATUS(TPSVCINFO *p); + +#endif /* MG_KEYSTATUS_H */ diff --git a/app/src/mg/svc/MG_KEYSTATUS.pgc b/app/src/mg/svc/MG_KEYSTATUS.pgc new file mode 100644 index 0000000..88135a7 --- /dev/null +++ b/app/src/mg/svc/MG_KEYSTATUS.pgc @@ -0,0 +1,37 @@ +/* + * MG_KEYSTATUS.pgc - mg 모듈 서비스 MG_KEYSTATUS (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_KEYSTATUS.h" + +/* 45. MG_KEYSTATUS - 키 상태 조회: 현재 키버전(KEYVER)과 채널의 세션키/사인온 + * 시각 유무를 함께 회신 (키 전환 필요 여부 판단용 열람 전용 서비스). */ +void MG_KEYSTATUS(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char chan[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_kv, h_has_signon; + char h_chan[16], h_skey[64]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01"); + strncpy(h_chan, chan, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0; + + h_kv = 0; + EXEC SQL SELECT seq_val INTO :h_kv FROM mg_seq WHERE seq_name = 'KEYVER'; + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT session_key, CASE WHEN signon_at IS NULL THEN 0 ELSE 1 END + INTO :h_skey, :h_has_signon + FROM mg_channel WHERE channel = :h_chan; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 25); FAIL(b); } /* 미등록 채널 */ + if (sqlca.sqlcode < 0) FAIL(b); + + sets_(b, T_KEY1, h_skey); + setl(b, T_ARG3, h_kv); + setl(b, T_ARG4, h_has_signon); + setl(b, T_RC, (h_skey[0] == 0) ? 1 : 0); /* 세션키 미발급이면 1 */ + OK(b); +} diff --git a/app/src/mg/svc/MG_MACGEN.h b/app/src/mg/svc/MG_MACGEN.h new file mode 100644 index 0000000..9e751dc --- /dev/null +++ b/app/src/mg/svc/MG_MACGEN.h @@ -0,0 +1,12 @@ +/* + * MG_MACGEN.h - mg (전문게이트웨이) 서비스 MG_MACGEN 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_MACGEN_H +#define MG_MACGEN_H + +#include "mg_svc.h" + +void MG_MACGEN(TPSVCINFO *p); + +#endif /* MG_MACGEN_H */ diff --git a/app/src/mg/svc/MG_MACGEN.pgc b/app/src/mg/svc/MG_MACGEN.pgc new file mode 100644 index 0000000..10b55ae --- /dev/null +++ b/app/src/mg/svc/MG_MACGEN.pgc @@ -0,0 +1,45 @@ +/* + * MG_MACGEN.pgc - mg 모듈 서비스 MG_MACGEN (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_MACGEN.h" + +/* ----- service-local pure helper(s) ----- */ +/* XOR-회전 8-hex MAC: 키버전을 시드로 섞어 세대별로 값이 달라진다. */ +static void mg_macgen_calc(const char *s, long keyver, char *out /* >= 9 */) +{ + unsigned long acc = 0x9E3779B9UL ^ (unsigned long)keyver; + int i; + for (i = 0; s[i]; i++) { + acc ^= (unsigned char)s[i]; + acc = (acc << 5) | (acc >> 27); /* 5비트 좌회전 */ + } + sprintf(out, "%08lX", acc & 0xFFFFFFFFUL); +} + + +/* 42. MG_MACGEN - 세대키 기반 MAC 생성: 현재 키버전(mg_seq KEYVER)을 시드로 + * XOR-회전 MAC 을 만들어 회신 (검증은 MG_MACVERIFY 가 담당). */ +void MG_MACGEN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char raw[512], mac[12]; + long keyver; + EXEC SQL BEGIN DECLARE SECTION; + long h_kv; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_STR3, raw, sizeof(raw)); + if (raw[0] == 0) { setl(b, T_RC, 30); FAIL(b); } + + EXEC SQL SELECT seq_val INTO :h_kv FROM mg_seq WHERE seq_name = 'KEYVER'; + if (sqlca.sqlcode < 0) FAIL(b); + keyver = (sqlca.sqlcode == 100) ? 0 : h_kv; + + mg_macgen_calc(raw, keyver, mac); + sets_(b, T_KEY1, mac); + setl(b, T_ARG3, keyver); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_MACVERIFY.h b/app/src/mg/svc/MG_MACVERIFY.h new file mode 100644 index 0000000..2a62901 --- /dev/null +++ b/app/src/mg/svc/MG_MACVERIFY.h @@ -0,0 +1,12 @@ +/* + * MG_MACVERIFY.h - mg (전문게이트웨이) 서비스 MG_MACVERIFY 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_MACVERIFY_H +#define MG_MACVERIFY_H + +#include "mg_svc.h" + +void MG_MACVERIFY(TPSVCINFO *p); + +#endif /* MG_MACVERIFY_H */ diff --git a/app/src/mg/svc/MG_MACVERIFY.pgc b/app/src/mg/svc/MG_MACVERIFY.pgc new file mode 100644 index 0000000..7667680 --- /dev/null +++ b/app/src/mg/svc/MG_MACVERIFY.pgc @@ -0,0 +1,50 @@ +/* + * MG_MACVERIFY.pgc - mg 모듈 서비스 MG_MACVERIFY (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_MACVERIFY.h" + +/* ----- service-local pure helper(s) ----- */ +/* MG_MAC 과 동일 계열의 가산 체크섬 (검증측 독립 구현, 4-hex). */ +static void mg_macverify_sum(const char *s, char *out /* >= 5 */) +{ + unsigned int v = 0; int i; + for (i = 0; s[i]; i++) v = (v + (unsigned char)s[i] * 31u) & 0xFFFF; + sprintf(out, "%04X", v); +} + + +/* 43. MG_MACVERIFY - 수신전문 MAC 검증: 원장의 raw_msg 로 MAC 을 재계산해 + * 저장된 mac 과 대조. 불일치면 rc=96/상태 N(무결성 실패) 전이. */ +void MG_MACVERIFY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char calc[8]; + long msg_id; + EXEC SQL BEGIN DECLARE SECTION; + long h_id; + char h_mac[16], h_raw[512]; + EXEC SQL END DECLARE SECTION; + + msg_id = getl(b, T_ID1); + if (msg_id <= 0) { setl(b, T_RC, 25); FAIL(b); } + + h_id = msg_id; + EXEC SQL SELECT mac, raw_msg INTO :h_mac, :h_raw + FROM mg_msg_log WHERE msg_id = :h_id; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 25); FAIL(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + mg_macverify_sum(h_raw, calc); + if (strncmp(h_mac, calc, 4) == 0) { + setl(b, T_RC, 0); /* MAC 일치 */ + } else { + EXEC SQL UPDATE mg_msg_log SET rc = '96', status = 'N' WHERE msg_id = :h_id; + if (sqlca.sqlcode < 0) FAIL(b); + userlog("MG_MACVERIFY 무결성 실패 msg_id=%ld 저장=%s 계산=%s", msg_id, h_mac, calc); + setl(b, T_RC, 96); + } + sets_(b, T_KEY2, calc); + OK(b); +} diff --git a/app/src/mg/svc/MG_NETMGMT.h b/app/src/mg/svc/MG_NETMGMT.h new file mode 100644 index 0000000..ccb29a8 --- /dev/null +++ b/app/src/mg/svc/MG_NETMGMT.h @@ -0,0 +1,12 @@ +/* + * MG_NETMGMT.h - mg (전문게이트웨이) 서비스 MG_NETMGMT 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_NETMGMT_H +#define MG_NETMGMT_H + +#include "mg_svc.h" + +void MG_NETMGMT(TPSVCINFO *p); + +#endif /* MG_NETMGMT_H */ diff --git a/app/src/mg/svc/MG_NETMGMT.pgc b/app/src/mg/svc/MG_NETMGMT.pgc new file mode 100644 index 0000000..7f16493 --- /dev/null +++ b/app/src/mg/svc/MG_NETMGMT.pgc @@ -0,0 +1,39 @@ +/* + * MG_NETMGMT.pgc - mg 모듈 서비스 MG_NETMGMT (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_NETMGMT.h" + +/* 37. MG_NETMGMT - 0800 망관리 전문 라우터: 처리코드 001=사인온(UP), + * 002=사인오프(DOWN), 301=에코(상태유지). 처리 후 0810 응답 MTI 회신. */ +void MG_NETMGMT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char chan[16], proc[8], bizdate[16], rc[4]; + long stan, msg_id; + + gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01"); + gets_(b, T_STR4, proc, sizeof(proc)); if (proc[0] == 0) strcpy(proc, "301"); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + + strcpy(rc, "00"); + if (strncmp(proc, "001", 3) == 0) { + if (mgdb_set_channel_status(chan, "UP") < 0) FAIL(b); + } else if (strncmp(proc, "002", 3) == 0) { + if (mgdb_set_channel_status(chan, "DOWN") < 0) FAIL(b); + } else if (strncmp(proc, "301", 3) == 0) { + /* 에코: 상태 변경 없음, 수신 자체가 생존 신호 */ + } else { + strcpy(rc, "12"); /* 12 = 무효 처리코드 */ + } + + stan = mgdb_next_stan(); if (stan < 0) FAIL(b); + msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b); + if (mgdb_insert_msg(msg_id, stan, "0800", chan, "IN", proc, "", 0, + "", rc, "", "R", bizdate) < 0) FAIL(b); + sets_(b, T_STR1, "0810"); + sets_(b, T_STR2, rc); + setl(b, T_ID1, msg_id); + OK(b); +} diff --git a/app/src/mg/svc/MG_PANMASK.h b/app/src/mg/svc/MG_PANMASK.h new file mode 100644 index 0000000..f1455db --- /dev/null +++ b/app/src/mg/svc/MG_PANMASK.h @@ -0,0 +1,12 @@ +/* + * MG_PANMASK.h - mg (전문게이트웨이) 서비스 MG_PANMASK 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_PANMASK_H +#define MG_PANMASK_H + +#include "mg_svc.h" + +void MG_PANMASK(TPSVCINFO *p); + +#endif /* MG_PANMASK_H */ diff --git a/app/src/mg/svc/MG_PANMASK.pgc b/app/src/mg/svc/MG_PANMASK.pgc new file mode 100644 index 0000000..3a1a264 --- /dev/null +++ b/app/src/mg/svc/MG_PANMASK.pgc @@ -0,0 +1,52 @@ +/* + * MG_PANMASK.pgc - mg 모듈 서비스 MG_PANMASK (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_PANMASK.h" + +/* ----- service-local pure helper(s) ----- */ +/* BIN(앞6)/뒤4 보존 마스킹. 12자리 미만 짧은 PAN 은 전체 마스킹. */ +static int mg_panmask_apply(const char *pan, char *out /* >= 20 */) +{ + int i, n = (int)strlen(pan); + if (n > 19) n = 19; + if (n < 12) { + for (i = 0; i < n; i++) out[i] = '*'; + out[n] = 0; + return 1; /* 비정상 길이 표시 */ + } + for (i = 0; i < n; i++) + out[i] = (i < 6 || i >= n - 4) ? pan[i] : '*'; + out[n] = 0; + return 0; +} + + +/* 70. MG_PANMASK - PAN 마스킹: 카드번호를 BIN/뒤4자리만 남겨 가리고, msg_id 가 + * 주어지면 원장의 pan 컬럼도 마스킹본으로 덮어쓴다 (정보보호 후처리). */ +void MG_PANMASK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char pan[24], masked[24]; + int shortpan; + EXEC SQL BEGIN DECLARE SECTION; + long h_id; + char h_pan[24]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_STR5, pan, sizeof(pan)); + if (pan[0] == 0) { setl(b, T_RC, 30); FAIL(b); } + + shortpan = mg_panmask_apply(pan, masked); + sets_(b, T_STR5, masked); + + h_id = getl(b, T_ID1); + if (h_id > 0) { + strncpy(h_pan, masked, sizeof(h_pan)-1); h_pan[sizeof(h_pan)-1] = 0; + EXEC SQL UPDATE mg_msg_log SET pan = :h_pan WHERE msg_id = :h_id; + if (sqlca.sqlcode < 0) { userlog("MG_PANMASK 원장 마스킹 FAIL [%d]", sqlca.sqlcode); FAIL(b); } + } + setl(b, T_RC, shortpan); /* 1 = 길이 비정상 PAN 이었음 */ + OK(b); +} diff --git a/app/src/mg/svc/MG_QDEPTH.h b/app/src/mg/svc/MG_QDEPTH.h new file mode 100644 index 0000000..8cc1893 --- /dev/null +++ b/app/src/mg/svc/MG_QDEPTH.h @@ -0,0 +1,12 @@ +/* + * MG_QDEPTH.h - mg (전문게이트웨이) 서비스 MG_QDEPTH 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_QDEPTH_H +#define MG_QDEPTH_H + +#include "mg_svc.h" + +void MG_QDEPTH(TPSVCINFO *p); + +#endif /* MG_QDEPTH_H */ diff --git a/app/src/mg/svc/MG_QDEPTH.pgc b/app/src/mg/svc/MG_QDEPTH.pgc new file mode 100644 index 0000000..8a9fc14 --- /dev/null +++ b/app/src/mg/svc/MG_QDEPTH.pgc @@ -0,0 +1,37 @@ +/* + * MG_QDEPTH.pgc - mg 모듈 서비스 MG_QDEPTH (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_QDEPTH.h" + +/* 75. MG_QDEPTH - 송수신 큐 깊이 조회: 대기(Q)/송신(S)/완료(D)/실패(F) 상태별 + * 건수와 최대 재시도 횟수를 한 집계로 회신 (큐 적체 대시보드). */ +void MG_QDEPTH(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_q, h_s, h_d, h_f, h_maxretry; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL SELECT + coalesce(sum(CASE WHEN status = 'Q' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN status = 'S' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN status = 'D' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN status = 'F' THEN 1 ELSE 0 END),0), + coalesce(max(retry_cnt),0) + INTO :h_q, :h_s, :h_d, :h_f, :h_maxretry + FROM mg_queue WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_ARG1, h_q); + setl(b, T_ARG2, h_s); + setl(b, T_ARG3, h_d); + setl(b, T_ARG4, h_f); + setl(b, T_ARG7, h_maxretry); + setl(b, T_RC, (h_q + h_f > 100) ? 1 : 0); /* 적체+실패 100건 초과 경보 */ + OK(b); +} diff --git a/app/src/mg/svc/MG_REASM.h b/app/src/mg/svc/MG_REASM.h new file mode 100644 index 0000000..6f245fb --- /dev/null +++ b/app/src/mg/svc/MG_REASM.h @@ -0,0 +1,12 @@ +/* + * MG_REASM.h - mg (전문게이트웨이) 서비스 MG_REASM 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_REASM_H +#define MG_REASM_H + +#include "mg_svc.h" + +void MG_REASM(TPSVCINFO *p); + +#endif /* MG_REASM_H */ diff --git a/app/src/mg/svc/MG_REASM.pgc b/app/src/mg/svc/MG_REASM.pgc new file mode 100644 index 0000000..fad5082 --- /dev/null +++ b/app/src/mg/svc/MG_REASM.pgc @@ -0,0 +1,40 @@ +/* + * MG_REASM.pgc - mg 모듈 서비스 MG_REASM (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_REASM.h" + +/* 41. MG_REASM - 분할전문 재조립: 세그먼트 T_STR5~T_STR8 (최대 4편) 을 순서대로 + * 이어붙여 원 전문을 복원. 기대 편수(T_ARG1)와 실제 편수 불일치 시 rc=35. */ +void MG_REASM(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char seg[4][128], full[512]; + long expect, got = 0, total = 0; + int i; + BFLDID segf[4]; + segf[0] = T_STR5; segf[1] = T_STR6; segf[2] = T_STR7; segf[3] = T_STR8; + + expect = getl(b, T_ARG1); if (expect <= 0 || expect > 4) expect = 4; + + full[0] = 0; + for (i = 0; i < 4; i++) { + gets_(b, segf[i], seg[i], sizeof(seg[i])); + if (seg[i][0] == 0) break; /* 첫 빈 세그먼트에서 종료 */ + got++; + total += (long)strlen(seg[i]); + strcat(full, seg[i]); + } + + if (got != expect) { /* 편수 불일치 = 재조립 실패 */ + userlog("MG_REASM 편수 불일치 기대=%ld 수신=%ld", expect, got); + setl(b, T_RC, 35); + FAIL(b); + } + sets_(b, T_STR3, full); + setl(b, T_COUNT, got); + setl(b, T_ARG2, total); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_REVADVICE.h b/app/src/mg/svc/MG_REVADVICE.h new file mode 100644 index 0000000..10f592d --- /dev/null +++ b/app/src/mg/svc/MG_REVADVICE.h @@ -0,0 +1,12 @@ +/* + * MG_REVADVICE.h - mg (전문게이트웨이) 서비스 MG_REVADVICE 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_REVADVICE_H +#define MG_REVADVICE_H + +#include "mg_svc.h" + +void MG_REVADVICE(TPSVCINFO *p); + +#endif /* MG_REVADVICE_H */ diff --git a/app/src/mg/svc/MG_REVADVICE.pgc b/app/src/mg/svc/MG_REVADVICE.pgc new file mode 100644 index 0000000..46fde6e --- /dev/null +++ b/app/src/mg/svc/MG_REVADVICE.pgc @@ -0,0 +1,41 @@ +/* + * MG_REVADVICE.pgc - mg 모듈 서비스 MG_REVADVICE (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_REVADVICE.h" + +/* 36. MG_REVADVICE - 0420 취소 어드바이스 수신: 통지성 전문이므로 무조건 수용(A), + * 적재 후 채널 last_stan 을 수신 STAN 까지 전진시켜 순번 동기 유지. */ +void MG_REVADVICE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char chan[16], bizdate[16], raw[512]; + long stan, msg_id, amount; + EXEC SQL BEGIN DECLARE SECTION; + long h_stan; + char h_chan[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01"); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + gets_(b, T_STR3, raw, sizeof(raw)); + amount = getl(b, T_AMOUNT); + + stan = getl(b, T_ARG1); + if (stan <= 0) { stan = mgdb_next_stan(); if (stan < 0) FAIL(b); } + msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b); + if (mgdb_insert_msg(msg_id, stan, "0420", chan, "IN", "170000", "", -amount, + "", "00", raw, "A", bizdate) < 0) FAIL(b); + + h_stan = stan; + strncpy(h_chan, chan, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0; + EXEC SQL UPDATE mg_channel + SET last_stan = greatest(last_stan, :h_stan), updated_at = now() + WHERE channel = :h_chan; + if (sqlca.sqlcode < 0) { userlog("MG_REVADVICE 순번동기 FAIL [%d]", sqlca.sqlcode); FAIL(b); } + + setl(b, T_ID1, msg_id); + setl(b, T_ARG1, stan); + OK(b); +} diff --git a/app/src/mg/svc/MG_REVERSAL.h b/app/src/mg/svc/MG_REVERSAL.h new file mode 100644 index 0000000..aaabdf3 --- /dev/null +++ b/app/src/mg/svc/MG_REVERSAL.h @@ -0,0 +1,12 @@ +/* + * MG_REVERSAL.h - mg (전문게이트웨이) 서비스 MG_REVERSAL 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_REVERSAL_H +#define MG_REVERSAL_H + +#include "mg_svc.h" + +void MG_REVERSAL(TPSVCINFO *p); + +#endif /* MG_REVERSAL_H */ diff --git a/app/src/mg/svc/MG_REVERSAL.pgc b/app/src/mg/svc/MG_REVERSAL.pgc new file mode 100644 index 0000000..f819cba --- /dev/null +++ b/app/src/mg/svc/MG_REVERSAL.pgc @@ -0,0 +1,39 @@ +/* + * MG_REVERSAL.pgc - mg 모듈 서비스 MG_REVERSAL (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_REVERSAL.h" + +/* 35. MG_REVERSAL - 0400 취소요청 처리: 원전문을 STAN 으로 찾아 rc=17(고객취소) + * 확정 전이 후, 음수 금액의 0400 수신 로그를 별도 적재 (취소 원장). */ +void MG_REVERSAL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char chan[16], bizdate[16]; + long stan, orig_id, msg_id, rev_stan, amount; + EXEC SQL BEGIN DECLARE SECTION; + long h_oid; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01"); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + stan = getl(b, T_ARG1); + amount = getl(b, T_AMOUNT); + + orig_id = mgdb_find_msg_by_stan(stan); + if (orig_id < 0) { setl(b, T_RC, 25); OK(b); } /* 원거래 없음 */ + + h_oid = orig_id; + EXEC SQL UPDATE mg_msg_log SET rc = '17', status = 'C' WHERE msg_id = :h_oid; + if (sqlca.sqlcode < 0) { userlog("MG_REVERSAL 원전문 취소전이 FAIL [%d]", sqlca.sqlcode); FAIL(b); } + + rev_stan = mgdb_next_stan(); if (rev_stan < 0) FAIL(b); + msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b); + if (mgdb_insert_msg(msg_id, rev_stan, "0400", chan, "IN", "170000", "", -amount, + "", "00", "", "R", bizdate) < 0) FAIL(b); + setl(b, T_ID1, orig_id); + setl(b, T_ID2, msg_id); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_RTADD.h b/app/src/mg/svc/MG_RTADD.h new file mode 100644 index 0000000..76cc4dc --- /dev/null +++ b/app/src/mg/svc/MG_RTADD.h @@ -0,0 +1,12 @@ +/* + * MG_RTADD.h - mg (전문게이트웨이) 서비스 MG_RTADD 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_RTADD_H +#define MG_RTADD_H + +#include "mg_svc.h" + +void MG_RTADD(TPSVCINFO *p); + +#endif /* MG_RTADD_H */ diff --git a/app/src/mg/svc/MG_RTADD.pgc b/app/src/mg/svc/MG_RTADD.pgc new file mode 100644 index 0000000..e2bdc9b --- /dev/null +++ b/app/src/mg/svc/MG_RTADD.pgc @@ -0,0 +1,35 @@ +/* + * MG_RTADD.pgc - mg 모듈 서비스 MG_RTADD (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_RTADD.h" + +/* 53. MG_RTADD - 라우팅 룰 등록: 채널+MTI -> 목적지 룰을 신규 채번해 추가. + * 동일 채널/MTI 활성 룰이 이미 있으면 rc=1 로 중복 등록을 알린다. */ +void MG_RTADD(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char chan[16], mti[8], dest[64], bizdate[16], old_dest[64]; + long prio, dup = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_rid; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH01"); + gets_(b, T_STR1, mti, sizeof(mti)); if (mti[0] == 0) strcpy(mti, "0200"); + gets_(b, T_STR3, dest, sizeof(dest)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + prio = getl(b, T_ARG4); if (prio <= 0) prio = 100; + if (dest[0] == 0) { setl(b, T_RC, 30); FAIL(b); } + + if (mgdb_lookup_route(chan, mti, old_dest) == 0) dup = 1; /* 기존 활성 룰 존재 */ + + EXEC SQL SELECT nextval('mg_route_seq') INTO :h_rid; + if (sqlca.sqlcode < 0) FAIL(b); + if (mgdb_insert_route(h_rid, chan, mti, dest, prio, bizdate) < 0) FAIL(b); + + setl(b, T_ID1, h_rid); + setl(b, T_RC, dup); + OK(b); +} diff --git a/app/src/mg/svc/MG_RTDROP.h b/app/src/mg/svc/MG_RTDROP.h new file mode 100644 index 0000000..2c1bf15 --- /dev/null +++ b/app/src/mg/svc/MG_RTDROP.h @@ -0,0 +1,12 @@ +/* + * MG_RTDROP.h - mg (전문게이트웨이) 서비스 MG_RTDROP 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_RTDROP_H +#define MG_RTDROP_H + +#include "mg_svc.h" + +void MG_RTDROP(TPSVCINFO *p); + +#endif /* MG_RTDROP_H */ diff --git a/app/src/mg/svc/MG_RTDROP.pgc b/app/src/mg/svc/MG_RTDROP.pgc new file mode 100644 index 0000000..394e305 --- /dev/null +++ b/app/src/mg/svc/MG_RTDROP.pgc @@ -0,0 +1,30 @@ +/* + * MG_RTDROP.pgc - mg 모듈 서비스 MG_RTDROP (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_RTDROP.h" + +/* 54. MG_RTDROP - 라우팅 룰 비활성: 채널+MTI 의 활성 룰을 전부 내려서 + * 라우팅에서 제외. 내린 룰 수를 회신하고 대상이 없으면 rc=25. */ +void MG_RTDROP(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long dropped; + EXEC SQL BEGIN DECLARE SECTION; + char h_chan[16], h_mti[8]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CHANNEL, h_chan, sizeof(h_chan)); if (h_chan[0] == 0) strcpy(h_chan, "CH01"); + gets_(b, T_STR1, h_mti, sizeof(h_mti)); if (h_mti[0] == 0) strcpy(h_mti, "0200"); + + EXEC SQL UPDATE mg_route SET active = false + WHERE channel = :h_chan AND mti = :h_mti AND active = true; + if (sqlca.sqlcode < 0) { userlog("MG_RTDROP 비활성 FAIL [%d]", sqlca.sqlcode); FAIL(b); } + dropped = (long)sqlca.sqlerrd[2]; + + if (dropped == 0) { setl(b, T_RC, 25); OK(b); } /* 내릴 룰 없음 */ + setl(b, T_COUNT, dropped); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_RTLIST.h b/app/src/mg/svc/MG_RTLIST.h new file mode 100644 index 0000000..e65f83b --- /dev/null +++ b/app/src/mg/svc/MG_RTLIST.h @@ -0,0 +1,12 @@ +/* + * MG_RTLIST.h - mg (전문게이트웨이) 서비스 MG_RTLIST 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_RTLIST_H +#define MG_RTLIST_H + +#include "mg_svc.h" + +void MG_RTLIST(TPSVCINFO *p); + +#endif /* MG_RTLIST_H */ diff --git a/app/src/mg/svc/MG_RTLIST.pgc b/app/src/mg/svc/MG_RTLIST.pgc new file mode 100644 index 0000000..64d80ca --- /dev/null +++ b/app/src/mg/svc/MG_RTLIST.pgc @@ -0,0 +1,47 @@ +/* + * MG_RTLIST.pgc - mg 모듈 서비스 MG_RTLIST (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_RTLIST.h" + +/* 55. MG_RTLIST - 라우팅 룰 열람 요약: 채널의 활성/비활성 룰 수와 최우선(최소) + * priority 의 목적지를 커서로 훑어 회신 (룰 대시보드용). */ +void MG_RTLIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long act = 0, inact = 0, best_prio = -1; + char best_dest[64]; + EXEC SQL BEGIN DECLARE SECTION; + char h_chan[16], h_dest[64]; + long h_prio, h_active; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CHANNEL, h_chan, sizeof(h_chan)); if (h_chan[0] == 0) strcpy(h_chan, "CH01"); + best_dest[0] = 0; + + EXEC SQL DECLARE rtl CURSOR FOR + SELECT dest, priority, CASE WHEN active THEN 1 ELSE 0 END + FROM mg_route WHERE channel = :h_chan ORDER BY priority; + EXEC SQL OPEN rtl; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH rtl INTO :h_dest, :h_prio, :h_active; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE rtl; FAIL(b); } + if (h_active) { + act++; + if (best_prio < 0) { best_prio = h_prio; strncpy(best_dest, h_dest, sizeof(best_dest)-1); } + } else { + inact++; + } + } + EXEC SQL CLOSE rtl; + + sets_(b, T_STR3, best_dest); + setl(b, T_ARG4, (best_prio < 0) ? 0 : best_prio); + setl(b, T_COUNT, act); + setl(b, T_ARG7, inact); + setl(b, T_RC, (act == 0) ? 1 : 0); /* 활성 룰 전무 = 경고 */ + OK(b); +} diff --git a/app/src/mg/svc/MG_RTSWITCH.h b/app/src/mg/svc/MG_RTSWITCH.h new file mode 100644 index 0000000..457c74e --- /dev/null +++ b/app/src/mg/svc/MG_RTSWITCH.h @@ -0,0 +1,12 @@ +/* + * MG_RTSWITCH.h - mg (전문게이트웨이) 서비스 MG_RTSWITCH 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_RTSWITCH_H +#define MG_RTSWITCH_H + +#include "mg_svc.h" + +void MG_RTSWITCH(TPSVCINFO *p); + +#endif /* MG_RTSWITCH_H */ diff --git a/app/src/mg/svc/MG_RTSWITCH.pgc b/app/src/mg/svc/MG_RTSWITCH.pgc new file mode 100644 index 0000000..13ea392 --- /dev/null +++ b/app/src/mg/svc/MG_RTSWITCH.pgc @@ -0,0 +1,37 @@ +/* + * MG_RTSWITCH.pgc - mg 모듈 서비스 MG_RTSWITCH (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_RTSWITCH.h" + +/* 56. MG_RTSWITCH - 라우팅 목적지 절체: 채널+MTI 활성 룰의 dest 를 새 목적지로 + * 일괄 교체 (장애 우회). 교체 전 목적지를 T_STR5 로 돌려줘 원복을 돕는다. */ +void MG_RTSWITCH(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char prev[64]; + long switched; + EXEC SQL BEGIN DECLARE SECTION; + char h_chan[16], h_mti[8], h_new[64]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CHANNEL, h_chan, sizeof(h_chan)); if (h_chan[0] == 0) strcpy(h_chan, "CH01"); + gets_(b, T_STR1, h_mti, sizeof(h_mti)); if (h_mti[0] == 0) strcpy(h_mti, "0200"); + gets_(b, T_STR3, h_new, sizeof(h_new)); + if (h_new[0] == 0) { setl(b, T_RC, 30); FAIL(b); } + + if (mgdb_lookup_route(h_chan, h_mti, prev) < 0) { setl(b, T_RC, 25); FAIL(b); } + if (strcmp(prev, h_new) == 0) { setl(b, T_RC, 1); OK(b); } /* 이미 그 목적지 */ + + EXEC SQL UPDATE mg_route SET dest = :h_new + WHERE channel = :h_chan AND mti = :h_mti AND active = true; + if (sqlca.sqlcode < 0) { userlog("MG_RTSWITCH 절체 FAIL [%d]", sqlca.sqlcode); FAIL(b); } + switched = (long)sqlca.sqlerrd[2]; + + userlog("MG_RTSWITCH %s/%s: %s -> %s (%ld룰)", h_chan, h_mti, prev, h_new, switched); + sets_(b, T_STR5, prev); + setl(b, T_COUNT, switched); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_SEQAUDIT.h b/app/src/mg/svc/MG_SEQAUDIT.h new file mode 100644 index 0000000..e9ea1da --- /dev/null +++ b/app/src/mg/svc/MG_SEQAUDIT.h @@ -0,0 +1,12 @@ +/* + * MG_SEQAUDIT.h - mg (전문게이트웨이) 서비스 MG_SEQAUDIT 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_SEQAUDIT_H +#define MG_SEQAUDIT_H + +#include "mg_svc.h" + +void MG_SEQAUDIT(TPSVCINFO *p); + +#endif /* MG_SEQAUDIT_H */ diff --git a/app/src/mg/svc/MG_SEQAUDIT.pgc b/app/src/mg/svc/MG_SEQAUDIT.pgc new file mode 100644 index 0000000..06f81ff --- /dev/null +++ b/app/src/mg/svc/MG_SEQAUDIT.pgc @@ -0,0 +1,40 @@ +/* + * MG_SEQAUDIT.pgc - mg 모듈 서비스 MG_SEQAUDIT (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_SEQAUDIT.h" + +/* 50. MG_SEQAUDIT - 채널 순번 감사: 채널이 기억하는 last_stan 과 원장의 실제 + * 최대 STAN 을 대조해 어긋난 폭(드리프트)을 회신. 어긋나면 rc=1. */ +void MG_SEQAUDIT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long drift; + EXEC SQL BEGIN DECLARE SECTION; + long h_last, h_maxstan; + char h_chan[16], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CHANNEL, h_chan, sizeof(h_chan)); if (h_chan[0] == 0) strcpy(h_chan, "CH01"); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL SELECT last_stan INTO :h_last FROM mg_channel WHERE channel = :h_chan; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 25); FAIL(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT coalesce(max(stan),0) INTO :h_maxstan + FROM mg_msg_log WHERE channel = :h_chan AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + drift = h_maxstan - h_last; + if (drift != 0) + userlog("MG_SEQAUDIT 채널=%s 드리프트=%ld (채널=%ld 원장=%ld)", + h_chan, drift, h_last, h_maxstan); + + setl(b, T_ARG1, h_last); + setl(b, T_ARG2, h_maxstan); + setl(b, T_ARG6, drift); + setl(b, T_RC, (drift != 0) ? 1 : 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_SETTLEREQ.h b/app/src/mg/svc/MG_SETTLEREQ.h new file mode 100644 index 0000000..903aafa --- /dev/null +++ b/app/src/mg/svc/MG_SETTLEREQ.h @@ -0,0 +1,12 @@ +/* + * MG_SETTLEREQ.h - mg (전문게이트웨이) 서비스 MG_SETTLEREQ 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_SETTLEREQ_H +#define MG_SETTLEREQ_H + +#include "mg_svc.h" + +void MG_SETTLEREQ(TPSVCINFO *p); + +#endif /* MG_SETTLEREQ_H */ diff --git a/app/src/mg/svc/MG_SETTLEREQ.pgc b/app/src/mg/svc/MG_SETTLEREQ.pgc new file mode 100644 index 0000000..3bd44c7 --- /dev/null +++ b/app/src/mg/svc/MG_SETTLEREQ.pgc @@ -0,0 +1,43 @@ +/* + * MG_SETTLEREQ.pgc - mg 모듈 서비스 MG_SETTLEREQ (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_SETTLEREQ.h" + +/* 73. MG_SETTLEREQ - 0500 정산집계 요청 생성: 당일 채널의 수신 0200 건수/금액을 + * 집계해 대사 원문("C:건수:합계")으로 삼은 0500 송신 전문을 적재. */ +void MG_SETTLEREQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char chan[16], bizdate[16], recon[64]; + long stan, msg_id; + EXEC SQL BEGIN DECLARE SECTION; + char h_chan[16], h_biz[16]; + long h_cnt, h_sum; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH02"); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_chan, chan, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0; + strncpy(h_biz, bizdate, sizeof(h_biz)-1); h_biz[sizeof(h_biz)-1] = 0; + + EXEC SQL SELECT count(*), coalesce(sum(amount),0) + INTO :h_cnt, :h_sum + FROM mg_msg_log + WHERE channel = :h_chan AND biz_date = :h_biz + AND mti = '0200' AND direction = 'IN'; + if (sqlca.sqlcode < 0) FAIL(b); + + sprintf(recon, "C:%ld:%ld", h_cnt, h_sum); /* 대사 원문 */ + stan = mgdb_next_stan(); if (stan < 0) FAIL(b); + msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b); + if (mgdb_insert_msg(msg_id, stan, "0500", chan, "OUT", "920000", "", h_sum, + "", "00", recon, "S", bizdate) < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + setl(b, T_ID1, msg_id); + sets_(b, T_STR3, recon); + OK(b); +} diff --git a/app/src/mg/svc/MG_SETTLERSP.h b/app/src/mg/svc/MG_SETTLERSP.h new file mode 100644 index 0000000..f4e2d41 --- /dev/null +++ b/app/src/mg/svc/MG_SETTLERSP.h @@ -0,0 +1,12 @@ +/* + * MG_SETTLERSP.h - mg (전문게이트웨이) 서비스 MG_SETTLERSP 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_SETTLERSP_H +#define MG_SETTLERSP_H + +#include "mg_svc.h" + +void MG_SETTLERSP(TPSVCINFO *p); + +#endif /* MG_SETTLERSP_H */ diff --git a/app/src/mg/svc/MG_SETTLERSP.pgc b/app/src/mg/svc/MG_SETTLERSP.pgc new file mode 100644 index 0000000..f9ebfa1 --- /dev/null +++ b/app/src/mg/svc/MG_SETTLERSP.pgc @@ -0,0 +1,50 @@ +/* + * MG_SETTLERSP.pgc - mg 모듈 서비스 MG_SETTLERSP (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_SETTLERSP.h" + +/* 74. MG_SETTLERSP - 0510 정산집계 응답 대사: 상대기관이 보낸 건수/합계를 당일 + * 자체 집계와 대조해 일치 rc=00, 불일치 rc=95(대사불일치)로 판정 적재. */ +void MG_SETTLERSP(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char chan[16], bizdate[16], rc[4]; + long their_cnt, their_sum, stan, msg_id; + EXEC SQL BEGIN DECLARE SECTION; + char h_chan[16], h_biz[16]; + long h_cnt, h_sum; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CHANNEL, chan, sizeof(chan)); if (chan[0] == 0) strcpy(chan, "CH02"); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + their_cnt = getl(b, T_COUNT); /* 상대기관 집계 건수 */ + their_sum = getl(b, T_GROSS); /* 상대기관 집계 합계 */ + + strncpy(h_chan, chan, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0; + strncpy(h_biz, bizdate, sizeof(h_biz)-1); h_biz[sizeof(h_biz)-1] = 0; + EXEC SQL SELECT count(*), coalesce(sum(amount),0) + INTO :h_cnt, :h_sum + FROM mg_msg_log + WHERE channel = :h_chan AND biz_date = :h_biz + AND mti = '0200' AND direction = 'IN'; + if (sqlca.sqlcode < 0) FAIL(b); + + strcpy(rc, (their_cnt == h_cnt && their_sum == h_sum) ? "00" : "95"); + if (rc[0] == '9') + userlog("MG_SETTLERSP 대사 불일치 채널=%s 건수 %ld/%ld 합계 %ld/%ld", + chan, their_cnt, h_cnt, their_sum, h_sum); + + stan = mgdb_next_stan(); if (stan < 0) FAIL(b); + msg_id = mgdb_next_msg_id(); if (msg_id < 0) FAIL(b); + if (mgdb_insert_msg(msg_id, stan, "0510", chan, "IN", "920000", "", their_sum, + "", rc, "", "C", bizdate) < 0) FAIL(b); + + sets_(b, T_STR2, rc); + setl(b, T_ARG1, h_cnt); + setl(b, T_AMT1, h_sum); + setl(b, T_ID1, msg_id); + setl(b, T_RC, (rc[0] == '0') ? 0 : 95); + OK(b); +} diff --git a/app/src/mg/svc/MG_STATDIR.h b/app/src/mg/svc/MG_STATDIR.h new file mode 100644 index 0000000..df6c2b4 --- /dev/null +++ b/app/src/mg/svc/MG_STATDIR.h @@ -0,0 +1,12 @@ +/* + * MG_STATDIR.h - mg (전문게이트웨이) 서비스 MG_STATDIR 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_STATDIR_H +#define MG_STATDIR_H + +#include "mg_svc.h" + +void MG_STATDIR(TPSVCINFO *p); + +#endif /* MG_STATDIR_H */ diff --git a/app/src/mg/svc/MG_STATDIR.pgc b/app/src/mg/svc/MG_STATDIR.pgc new file mode 100644 index 0000000..069bc81 --- /dev/null +++ b/app/src/mg/svc/MG_STATDIR.pgc @@ -0,0 +1,38 @@ +/* + * MG_STATDIR.pgc - mg 모듈 서비스 MG_STATDIR (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_STATDIR.h" + +/* 63. MG_STATDIR - 송수신 방향별 통계: 당일 IN/OUT 각각의 건수와 금액합, + * 그리고 수신 대비 송신 응답률(%) 을 산출 (미응답 적체 감시). */ +void MG_STATDIR(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rsp_pct; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_in_cnt, h_in_sum, h_out_cnt, h_out_sum; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL SELECT + coalesce(sum(CASE WHEN direction = 'IN' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN direction = 'IN' THEN amount ELSE 0 END),0), + coalesce(sum(CASE WHEN direction = 'OUT' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN direction = 'OUT' THEN amount ELSE 0 END),0) + INTO :h_in_cnt, :h_in_sum, :h_out_cnt, :h_out_sum + FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + rsp_pct = (h_in_cnt > 0) ? (h_out_cnt * 100L) / h_in_cnt : 100; + setl(b, T_ARG1, h_in_cnt); + setl(b, T_AMT1, h_in_sum); + setl(b, T_ARG2, h_out_cnt); + setl(b, T_AMT2, h_out_sum); + setl(b, T_ARG8, rsp_pct); + setl(b, T_RC, (rsp_pct < 90) ? 1 : 0); /* 응답률 90% 미만 = 경고 */ + OK(b); +} diff --git a/app/src/mg/svc/MG_STATHOUR.h b/app/src/mg/svc/MG_STATHOUR.h new file mode 100644 index 0000000..d39ff6f --- /dev/null +++ b/app/src/mg/svc/MG_STATHOUR.h @@ -0,0 +1,12 @@ +/* + * MG_STATHOUR.h - mg (전문게이트웨이) 서비스 MG_STATHOUR 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_STATHOUR_H +#define MG_STATHOUR_H + +#include "mg_svc.h" + +void MG_STATHOUR(TPSVCINFO *p); + +#endif /* MG_STATHOUR_H */ diff --git a/app/src/mg/svc/MG_STATHOUR.pgc b/app/src/mg/svc/MG_STATHOUR.pgc new file mode 100644 index 0000000..b8853d4 --- /dev/null +++ b/app/src/mg/svc/MG_STATHOUR.pgc @@ -0,0 +1,42 @@ +/* + * MG_STATHOUR.pgc - mg 모듈 서비스 MG_STATHOUR (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_STATHOUR.h" + +/* 62. MG_STATHOUR - 시간대별 통계: 요청 시(0~23)의 당일 건수/금액합과 함께 + * 당일 최다 유입 시간대(피크아워)를 커서 없이 집계 질의로 회신. */ +void MG_STATHOUR(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long hour; + EXEC SQL BEGIN DECLARE SECTION; + long h_hour, h_cnt, h_sum, h_peak; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + hour = getl(b, T_ARG5); + if (hour < 0 || hour > 23) hour = 0; + h_hour = hour; + + EXEC SQL SELECT count(*), coalesce(sum(amount),0) + INTO :h_cnt, :h_sum + FROM mg_msg_log + WHERE biz_date = :h_bizdate + AND extract(hour FROM created_at) = :h_hour; + if (sqlca.sqlcode < 0) FAIL(b); + + h_peak = -1; + EXEC SQL SELECT extract(hour FROM created_at)::int INTO :h_peak + FROM mg_msg_log WHERE biz_date = :h_bizdate + GROUP BY 1 ORDER BY count(*) DESC, 1 LIMIT 1; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + setl(b, T_ARG5, hour); + setl(b, T_ARG6, h_peak); /* -1 = 당일 전문 없음 */ + OK(b); +} diff --git a/app/src/mg/svc/MG_STATMTI.h b/app/src/mg/svc/MG_STATMTI.h new file mode 100644 index 0000000..ee41285 --- /dev/null +++ b/app/src/mg/svc/MG_STATMTI.h @@ -0,0 +1,12 @@ +/* + * MG_STATMTI.h - mg (전문게이트웨이) 서비스 MG_STATMTI 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_STATMTI_H +#define MG_STATMTI_H + +#include "mg_svc.h" + +void MG_STATMTI(TPSVCINFO *p); + +#endif /* MG_STATMTI_H */ diff --git a/app/src/mg/svc/MG_STATMTI.pgc b/app/src/mg/svc/MG_STATMTI.pgc new file mode 100644 index 0000000..61b487e --- /dev/null +++ b/app/src/mg/svc/MG_STATMTI.pgc @@ -0,0 +1,37 @@ +/* + * MG_STATMTI.pgc - mg 모듈 서비스 MG_STATMTI (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_STATMTI.h" + +/* 61. MG_STATMTI - 전문유형(MTI)별 당일 통계: 지정 MTI 의 건수/금액합/최대금액과 + * 전체 대비 건수 점유율(%) 을 회신 (유형별 세분 통계). */ +void MG_STATMTI(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long share; + EXEC SQL BEGIN DECLARE SECTION; + char h_mti[8], h_bizdate[16]; + long h_cnt, h_sum, h_max, h_all; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_STR1, h_mti, sizeof(h_mti)); if (h_mti[0] == 0) strcpy(h_mti, "0200"); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + EXEC SQL SELECT count(*), coalesce(sum(amount),0), coalesce(max(amount),0) + INTO :h_cnt, :h_sum, :h_max + FROM mg_msg_log WHERE mti = :h_mti AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT count(*) INTO :h_all + FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + share = (h_all > 0) ? (h_cnt * 100L) / h_all : 0; + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + setl(b, T_AMT1, h_max); + setl(b, T_ARG8, share); + OK(b); +} diff --git a/app/src/mg/svc/MG_STATRC.h b/app/src/mg/svc/MG_STATRC.h new file mode 100644 index 0000000..8b9cac5 --- /dev/null +++ b/app/src/mg/svc/MG_STATRC.h @@ -0,0 +1,12 @@ +/* + * MG_STATRC.h - mg (전문게이트웨이) 서비스 MG_STATRC 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_STATRC_H +#define MG_STATRC_H + +#include "mg_svc.h" + +void MG_STATRC(TPSVCINFO *p); + +#endif /* MG_STATRC_H */ diff --git a/app/src/mg/svc/MG_STATRC.pgc b/app/src/mg/svc/MG_STATRC.pgc new file mode 100644 index 0000000..66cae83 --- /dev/null +++ b/app/src/mg/svc/MG_STATRC.pgc @@ -0,0 +1,49 @@ +/* + * MG_STATRC.pgc - mg 모듈 서비스 MG_STATRC (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_STATRC.h" + +/* 64. MG_STATRC - 응답코드 분포 조회: 당일 정상(00)/거절 건수, 고유 거절코드 + * 가짓수, 최빈 거절코드를 커서로 훑어 회신 (거절 급증 코드 탐지). */ +void MG_STATRC(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long ok_cnt = 0, bad_cnt = 0, kinds = 0, top_cnt = 0; + char top_rc[4]; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_rc[4]; + long h_n; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + top_rc[0] = 0; + + EXEC SQL DECLARE rcd CURSOR FOR + SELECT rc, count(*) FROM mg_msg_log + WHERE biz_date = :h_bizdate + GROUP BY rc ORDER BY count(*) DESC; + EXEC SQL OPEN rcd; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH rcd INTO :h_rc, :h_n; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE rcd; FAIL(b); } + if (h_rc[0] == '0' && h_rc[1] == '0') { + ok_cnt = h_n; + } else { + bad_cnt += h_n; + kinds++; + if (h_n > top_cnt) { top_cnt = h_n; strncpy(top_rc, h_rc, sizeof(top_rc)-1); top_rc[3] = 0; } + } + } + EXEC SQL CLOSE rcd; + + setl(b, T_ARG1, ok_cnt); + setl(b, T_ARG2, bad_cnt); + setl(b, T_ARG7, kinds); + sets_(b, T_STR2, top_rc); + setl(b, T_ARG6, top_cnt); + OK(b); +} diff --git a/app/src/mg/svc/MG_THROTTLE.h b/app/src/mg/svc/MG_THROTTLE.h new file mode 100644 index 0000000..d1d0adb --- /dev/null +++ b/app/src/mg/svc/MG_THROTTLE.h @@ -0,0 +1,12 @@ +/* + * MG_THROTTLE.h - mg (전문게이트웨이) 서비스 MG_THROTTLE 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_THROTTLE_H +#define MG_THROTTLE_H + +#include "mg_svc.h" + +void MG_THROTTLE(TPSVCINFO *p); + +#endif /* MG_THROTTLE_H */ diff --git a/app/src/mg/svc/MG_THROTTLE.pgc b/app/src/mg/svc/MG_THROTTLE.pgc new file mode 100644 index 0000000..4393481 --- /dev/null +++ b/app/src/mg/svc/MG_THROTTLE.pgc @@ -0,0 +1,42 @@ +/* + * MG_THROTTLE.pgc - mg 모듈 서비스 MG_THROTTLE (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_THROTTLE.h" + +/* 68. MG_THROTTLE - 채널 트래픽 제어: 당일 수신량이 채널 한도(기본 10000건)의 + * 몇 %인지 보고, 100% 초과면 유입 차단(rc=91), 80% 이상이면 경보(rc=1). */ +void MG_THROTTLE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long limit, pct; + EXEC SQL BEGIN DECLARE SECTION; + char h_chan[16], h_bizdate[16]; + long h_cnt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CHANNEL, h_chan, sizeof(h_chan)); if (h_chan[0] == 0) strcpy(h_chan, "CH01"); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + limit = getl(b, T_ARG4); if (limit <= 0) limit = 10000; + + EXEC SQL SELECT count(*) INTO :h_cnt + FROM mg_msg_log + WHERE channel = :h_chan AND biz_date = :h_bizdate AND direction = 'IN'; + if (sqlca.sqlcode < 0) FAIL(b); + + pct = (h_cnt * 100L) / limit; + setl(b, T_COUNT, h_cnt); + setl(b, T_ARG4, limit); + setl(b, T_ARG8, pct); + + if (h_cnt >= limit) { + userlog("MG_THROTTLE 차단 채널=%s %ld/%ld건", h_chan, h_cnt, limit); + setl(b, T_RC, 91); /* 91 = 유입 차단 */ + } else if (pct >= 80) { + setl(b, T_RC, 1); /* 경보 수위 */ + } else { + setl(b, T_RC, 0); + } + OK(b); +} diff --git a/app/src/mg/svc/MG_TIMEOUT.h b/app/src/mg/svc/MG_TIMEOUT.h new file mode 100644 index 0000000..4daea3b --- /dev/null +++ b/app/src/mg/svc/MG_TIMEOUT.h @@ -0,0 +1,12 @@ +/* + * MG_TIMEOUT.h - mg (전문게이트웨이) 서비스 MG_TIMEOUT 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_TIMEOUT_H +#define MG_TIMEOUT_H + +#include "mg_svc.h" + +void MG_TIMEOUT(TPSVCINFO *p); + +#endif /* MG_TIMEOUT_H */ diff --git a/app/src/mg/svc/MG_TIMEOUT.pgc b/app/src/mg/svc/MG_TIMEOUT.pgc new file mode 100644 index 0000000..13f7475 --- /dev/null +++ b/app/src/mg/svc/MG_TIMEOUT.pgc @@ -0,0 +1,36 @@ +/* + * MG_TIMEOUT.pgc - mg 모듈 서비스 MG_TIMEOUT (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_TIMEOUT.h" + +/* 46. MG_TIMEOUT - 전문 타임아웃 스윕: 임계초(TMOUTSEC, 기본 60초)를 넘겨도 + * 아직 수신(R) 상태로 남은 전문을 rc=68(응답지연)/N 으로 일괄 전이. */ +void MG_TIMEOUT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long expired; + EXEC SQL BEGIN DECLARE SECTION; + long h_sec; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + + h_sec = 60; + EXEC SQL SELECT seq_val INTO :h_sec FROM mg_seq WHERE seq_name = 'TMOUTSEC'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_sec <= 0) h_sec = 60; + + EXEC SQL UPDATE mg_msg_log + SET rc = '68', status = 'N' + WHERE status = 'R' AND biz_date = :h_bizdate + AND created_at < now() - (:h_sec * interval '1 second'); + if (sqlca.sqlcode < 0) { userlog("MG_TIMEOUT 스윕 FAIL [%d]", sqlca.sqlcode); FAIL(b); } + expired = (long)sqlca.sqlerrd[2]; + + setl(b, T_COUNT, expired); + setl(b, T_ARG5, h_sec); + OK(b); +} diff --git a/app/src/mg/svc/MG_TIMEOUTSET.h b/app/src/mg/svc/MG_TIMEOUTSET.h new file mode 100644 index 0000000..33d835a --- /dev/null +++ b/app/src/mg/svc/MG_TIMEOUTSET.h @@ -0,0 +1,12 @@ +/* + * MG_TIMEOUTSET.h - mg (전문게이트웨이) 서비스 MG_TIMEOUTSET 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_TIMEOUTSET_H +#define MG_TIMEOUTSET_H + +#include "mg_svc.h" + +void MG_TIMEOUTSET(TPSVCINFO *p); + +#endif /* MG_TIMEOUTSET_H */ diff --git a/app/src/mg/svc/MG_TIMEOUTSET.pgc b/app/src/mg/svc/MG_TIMEOUTSET.pgc new file mode 100644 index 0000000..c46d623 --- /dev/null +++ b/app/src/mg/svc/MG_TIMEOUTSET.pgc @@ -0,0 +1,31 @@ +/* + * MG_TIMEOUTSET.pgc - mg 모듈 서비스 MG_TIMEOUTSET (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_TIMEOUTSET.h" + +/* 47. MG_TIMEOUTSET - 타임아웃 임계초 설정: 운영자가 지정한 초(5~600 범위 강제)를 + * mg_seq(TMOUTSEC) 에 upsert. MG_TIMEOUT 스윕이 이 값을 참조한다. */ +void MG_TIMEOUTSET(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_sec; + EXEC SQL END DECLARE SECTION; + long sec = getl(b, T_ARG5); + + if (sec < 5) sec = 5; /* 하한: 과도한 조기 타임아웃 방지 */ + if (sec > 600) sec = 600; /* 상한: 10분 */ + + h_sec = sec; + EXEC SQL INSERT INTO mg_seq (seq_name, seq_val) + VALUES ('TMOUTSEC', :h_sec) + ON CONFLICT (seq_name) DO UPDATE + SET seq_val = EXCLUDED.seq_val, updated_at = now(); + if (sqlca.sqlcode < 0) { userlog("MG_TIMEOUTSET upsert FAIL [%d]", sqlca.sqlcode); FAIL(b); } + + setl(b, T_ARG5, sec); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_TOISO.h b/app/src/mg/svc/MG_TOISO.h new file mode 100644 index 0000000..e2e0cd0 --- /dev/null +++ b/app/src/mg/svc/MG_TOISO.h @@ -0,0 +1,12 @@ +/* + * MG_TOISO.h - mg (전문게이트웨이) 서비스 MG_TOISO 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_TOISO_H +#define MG_TOISO_H + +#include "mg_svc.h" + +void MG_TOISO(TPSVCINFO *p); + +#endif /* MG_TOISO_H */ diff --git a/app/src/mg/svc/MG_TOISO.pgc b/app/src/mg/svc/MG_TOISO.pgc new file mode 100644 index 0000000..b584b25 --- /dev/null +++ b/app/src/mg/svc/MG_TOISO.pgc @@ -0,0 +1,44 @@ +/* + * MG_TOISO.pgc - mg 모듈 서비스 MG_TOISO (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_TOISO.h" + +/* 51. MG_TOISO - 내부전문 -> ISO 변환: 콜론 구분 내부포맷 "MTI:처리코드:금액" 을 + * 고정길이 ISO 전문으로 조립하고, 존재 필드 마스크로 1차 비트맵 hex 생성. */ +void MG_TOISO(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char in[256], mti[8], proc[8], iso[64], bmp[20]; + char *c1, *c2; + long amount; + unsigned long mask = 0; + + gets_(b, T_STR3, in, sizeof(in)); + memset(mti, 0, sizeof(mti)); memset(proc, 0, sizeof(proc)); + + c1 = strchr(in, ':'); + if (c1 == NULL) { setl(b, T_RC, 30); FAIL(b); } + *c1++ = 0; + c2 = strchr(c1, ':'); + if (c2 == NULL) { setl(b, T_RC, 30); FAIL(b); } + *c2++ = 0; + + strncpy(mti, in, 4); + strncpy(proc, c1, 6); + amount = atol(c2); + + /* 비트 3(처리코드)/4(금액) 존재 마스크 -> 16-hex 1차 비트맵 */ + if (proc[0]) mask |= (1UL << (64 - 3)); + if (amount > 0) mask |= (1UL << (64 - 4)); + sprintf(bmp, "%016lX", mask); + + sprintf(iso, "%.4s%.6s%012ld", mti, proc, amount); + sets_(b, T_STR3, iso); + sets_(b, T_STR6, bmp); + sets_(b, T_STR1, mti); + setl(b, T_AMOUNT, amount); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_TPSCHECK.h b/app/src/mg/svc/MG_TPSCHECK.h new file mode 100644 index 0000000..0c6eb59 --- /dev/null +++ b/app/src/mg/svc/MG_TPSCHECK.h @@ -0,0 +1,12 @@ +/* + * MG_TPSCHECK.h - mg (전문게이트웨이) 서비스 MG_TPSCHECK 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_TPSCHECK_H +#define MG_TPSCHECK_H + +#include "mg_svc.h" + +void MG_TPSCHECK(TPSVCINFO *p); + +#endif /* MG_TPSCHECK_H */ diff --git a/app/src/mg/svc/MG_TPSCHECK.pgc b/app/src/mg/svc/MG_TPSCHECK.pgc new file mode 100644 index 0000000..b4479f2 --- /dev/null +++ b/app/src/mg/svc/MG_TPSCHECK.pgc @@ -0,0 +1,38 @@ +/* + * MG_TPSCHECK.pgc - mg 모듈 서비스 MG_TPSCHECK (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_TPSCHECK.h" + +/* 69. MG_TPSCHECK - 순간 처리율 점검: 최근 60초 유입 전문 수로 근사 TPS(x100)를 + * 구하고 직전 60초 창과 비교한 증감까지 회신 (트래픽 급증 탐지). */ +void MG_TPSCHECK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long tps_x100, delta; + EXEC SQL BEGIN DECLARE SECTION; + long h_now_cnt, h_prev_cnt; + EXEC SQL END DECLARE SECTION; + + EXEC SQL SELECT count(*) INTO :h_now_cnt + FROM mg_msg_log + WHERE created_at >= now() - interval '60 seconds'; + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT count(*) INTO :h_prev_cnt + FROM mg_msg_log + WHERE created_at >= now() - interval '120 seconds' + AND created_at < now() - interval '60 seconds'; + if (sqlca.sqlcode < 0) FAIL(b); + + tps_x100 = (h_now_cnt * 100L) / 60L; + delta = h_now_cnt - h_prev_cnt; + + setl(b, T_ARG1, h_now_cnt); + setl(b, T_ARG2, h_prev_cnt); + setl(b, T_ARG8, tps_x100); + setl(b, T_ARG6, delta); + setl(b, T_RC, (h_prev_cnt > 0 && h_now_cnt > h_prev_cnt * 3) ? 1 : 0); /* 3배 급증 경보 */ + OK(b); +} diff --git a/app/src/mg/svc/MG_VAN1ADPT.h b/app/src/mg/svc/MG_VAN1ADPT.h new file mode 100644 index 0000000..0ea8bd1 --- /dev/null +++ b/app/src/mg/svc/MG_VAN1ADPT.h @@ -0,0 +1,12 @@ +/* + * MG_VAN1ADPT.h - mg (전문게이트웨이) 서비스 MG_VAN1ADPT 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_VAN1ADPT_H +#define MG_VAN1ADPT_H + +#include "mg_svc.h" + +void MG_VAN1ADPT(TPSVCINFO *p); + +#endif /* MG_VAN1ADPT_H */ diff --git a/app/src/mg/svc/MG_VAN1ADPT.pgc b/app/src/mg/svc/MG_VAN1ADPT.pgc new file mode 100644 index 0000000..f6ba461 --- /dev/null +++ b/app/src/mg/svc/MG_VAN1ADPT.pgc @@ -0,0 +1,39 @@ +/* + * MG_VAN1ADPT.pgc - mg 모듈 서비스 MG_VAN1ADPT (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_VAN1ADPT.h" + +/* 38. MG_VAN1ADPT - VAN1 채널 어댑터: 파이프 구분 전문 "V1|MTI|금액|처리코드" 를 + * 내부 고정길이 전문으로 정규화. 헤더 V1 검증 실패 시 rc=30(포맷오류). */ +void MG_VAN1ADPT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char raw[512], mti[8], proc[8], out[64]; + char *t1, *t2, *t3; + long amount = 0; + + gets_(b, T_STR3, raw, sizeof(raw)); + if (strncmp(raw, "V1|", 3) != 0) { setl(b, T_RC, 30); FAIL(b); } + + /* "V1|" 이후를 파이프 단위로 3 토큰 절단 (MTI | 금액 | 처리코드) */ + memset(mti, 0, sizeof(mti)); memset(proc, 0, sizeof(proc)); + t1 = raw + 3; + t2 = strchr(t1, '|'); + if (t2 == NULL) { setl(b, T_RC, 30); FAIL(b); } + *t2++ = 0; + t3 = strchr(t2, '|'); + if (t3 != NULL) { *t3++ = 0; strncpy(proc, t3, 6); } + else strcpy(proc, "000000"); + strncpy(mti, t1, 4); + amount = atol(t2); + + sprintf(out, "%.4s%.6s%012ld", mti, proc, amount); + sets_(b, T_STR3, out); + sets_(b, T_STR1, mti); + setl(b, T_AMOUNT, amount); + sets_(b, T_CHANNEL, "CH01"); /* VAN1 = CH01 고정 매핑 */ + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mg/svc/MG_VAN2ADPT.h b/app/src/mg/svc/MG_VAN2ADPT.h new file mode 100644 index 0000000..a753086 --- /dev/null +++ b/app/src/mg/svc/MG_VAN2ADPT.h @@ -0,0 +1,12 @@ +/* + * MG_VAN2ADPT.h - mg (전문게이트웨이) 서비스 MG_VAN2ADPT 카피북 (copybook / record header). + * Split out of the former inline mg_svr.pgc; advertised by mg_svr. + */ +#ifndef MG_VAN2ADPT_H +#define MG_VAN2ADPT_H + +#include "mg_svc.h" + +void MG_VAN2ADPT(TPSVCINFO *p); + +#endif /* MG_VAN2ADPT_H */ diff --git a/app/src/mg/svc/MG_VAN2ADPT.pgc b/app/src/mg/svc/MG_VAN2ADPT.pgc new file mode 100644 index 0000000..df1b4d7 --- /dev/null +++ b/app/src/mg/svc/MG_VAN2ADPT.pgc @@ -0,0 +1,45 @@ +/* + * MG_VAN2ADPT.pgc - mg 모듈 서비스 MG_VAN2ADPT (one service = one file). + * Split out of the former inline mg_svr.pgc; runs on the caller's XA + * branch (no EXEC SQL CONNECT). Advertised by mg_svr at tpsvrinit. + */ +#include "MG_VAN2ADPT.h" + +/* 39. MG_VAN2ADPT - VAN2 채널 어댑터: 키=값 세미콜론 전문 "M=0200;A=1000;P=000000" + * 을 키 스캔 방식으로 해석해 내부 고정길이 전문으로 정규화. */ +void MG_VAN2ADPT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char raw[512], mti[8], proc[8], out[64]; + char *cur, *next; + long amount = 0, seen = 0; + + gets_(b, T_STR3, raw, sizeof(raw)); + memset(mti, 0, sizeof(mti)); memset(proc, 0, sizeof(proc)); + strcpy(mti, "0200"); strcpy(proc, "000000"); + + /* 세미콜론으로 자르며 "키=값" 을 하나씩 소화 (M=MTI, A=금액, P=처리코드) */ + cur = raw; + while (cur && *cur) { + next = strchr(cur, ';'); + if (next) *next++ = 0; + if (cur[1] == '=') { + switch (cur[0]) { + case 'M': strncpy(mti, cur + 2, 4); seen |= 1; break; + case 'A': amount = atol(cur + 2); seen |= 2; break; + case 'P': strncpy(proc, cur + 2, 6); seen |= 4; break; + default: break; /* 모르는 키는 무시 */ + } + } + cur = next; + } + if ((seen & 3) != 3) { setl(b, T_RC, 30); FAIL(b); } /* M,A 필수 */ + + sprintf(out, "%.4s%.6s%012ld", mti, proc, amount); + sets_(b, T_STR3, out); + sets_(b, T_STR1, mti); + setl(b, T_AMOUNT, amount); + sets_(b, T_CHANNEL, "CH02"); /* VAN2 = CH02 고정 매핑 */ + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/batch/mm_acct_reverify_batch.pgc b/app/src/mm/batch/mm_acct_reverify_batch.pgc new file mode 100644 index 0000000..41915b6 --- /dev/null +++ b/app/src/mm/batch/mm_acct_reverify_batch.pgc @@ -0,0 +1,63 @@ +/* + * mm_acct_reverify_batch.pgc - 정산계좌 정기 재검증 배치 (전수 검증 이력 적재 + XA). + * + * 활성 가맹점의 정산계좌를 커서로 전수 재검증하여 결과(OK/EMPTY/FORMAT)를 + * 가맹점별 ACCT_VRF 검증 이력으로 남긴다 (온라인 MM_ACCT_VERIFY 와 동일 판정 기준). + * 전 구간 ONE global XA transaction. + * host 변수는 전용 카피북 mm_acct_reverify_cpy.h 를 EXEC SQL INCLUDE. + * Usage: mm_acct_reverify_batch + */ +#include +#include +#include +#include +#include "mm_dbio.h" + +static const char *acct_verdict(const char *acct) +{ + size_t i, len = strlen(acct); + if (len == 0) return "EMPTY"; + if (len < 10 || len > 16) return "FORMAT"; + for (i = 0; i < len; i++) + if (acct[i] < '0' || acct[i] > '9') return "FORMAT"; + return "OK"; +} + +int main(int argc, char **argv) +{ + long total = 0, failed = 0; + const char *result; + (void)argc; (void)argv; + + EXEC SQL INCLUDE mm_acct_reverify_cpy; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE arv CURSOR FOR + SELECT merchant_id, settle_acct FROM mm_merchant_ext + WHERE status = 'ACTIVE' + ORDER BY merchant_id; + EXEC SQL OPEN arv; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH arv INTO :b_merch, :b_acct; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE arv; tpabort(0); return 1; } + result = acct_verdict(b_acct); + if (mmdb_insert_merch_hist(b_merch, "ACCT_VRF", "", result, "정기 배치 재검증") < 0) { + EXEC SQL CLOSE arv; tpabort(0); return 1; + } + total++; + if (strcmp(result, "OK") != 0) failed++; + } + EXEC SQL CLOSE arv; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_acct_reverify_batch COMMIT: 재검증=%ld건 불량=%ld건\n", total, failed); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_acct_reverify_cpy.h b/app/src/mm/batch/mm_acct_reverify_cpy.h new file mode 100644 index 0000000..aa9bdbf --- /dev/null +++ b/app/src/mm/batch/mm_acct_reverify_cpy.h @@ -0,0 +1,5 @@ +/* mm_acct_reverify_cpy.h - mm_acct_reverify_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char b_merch[64]; +char b_acct[40]; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/mm/batch/mm_apply_expire_batch.pgc b/app/src/mm/batch/mm_apply_expire_batch.pgc new file mode 100644 index 0000000..c564738 --- /dev/null +++ b/app/src/mm/batch/mm_apply_expire_batch.pgc @@ -0,0 +1,55 @@ +/* + * mm_apply_expire_batch.pgc - 장기 미처리 가입신청 자동반려 배치 (심사 워크플로 + XA). + * + * APPLIED/REVIEW 상태로 기한을 넘긴 신청 건을 커서로 훑어 REJECTED 로 전환하고 + * 자동반려 사유를 이력(REJECT)으로 남긴다. 전 구간 ONE global XA transaction. + * host 변수는 전용 카피북 mm_apply_expire_cpy.h 를 EXEC SQL INCLUDE. + * Usage: mm_apply_expire_batch [DAYS=30] + */ +#include +#include +#include +#include +#include "mm_dbio.h" + +int main(int argc, char **argv) +{ + long expired = 0; + + EXEC SQL INCLUDE mm_apply_expire_cpy; + + b_days = (argc > 1) ? atol(argv[1]) : 30; + if (b_days < 1) b_days = 30; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE aex CURSOR FOR + SELECT merchant_id, status FROM mm_merchant_ext + WHERE status IN ('APPLIED', 'REVIEW') + AND updated_at < now() - :b_days * interval '1 day' + ORDER BY merchant_id; + EXEC SQL OPEN aex; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH aex INTO :b_merch, :b_stat; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE aex; tpabort(0); return 1; } + EXEC SQL UPDATE mm_merchant_ext SET status = 'REJECTED', updated_at = now() + WHERE merchant_id = :b_merch; + if (sqlca.sqlcode < 0) { fprintf(stderr, "UPDATE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE aex; tpabort(0); return 1; } + if (mmdb_insert_merch_hist(b_merch, "REJECT", b_stat, "REJECTED", "심사기한 초과 자동반려") < 0) { + EXEC SQL CLOSE aex; tpabort(0); return 1; + } + expired++; + } + EXEC SQL CLOSE aex; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_apply_expire_batch COMMIT: 기한=%ld일 자동반려=%ld건\n", b_days, expired); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_apply_expire_cpy.h b/app/src/mm/batch/mm_apply_expire_cpy.h new file mode 100644 index 0000000..1079bce --- /dev/null +++ b/app/src/mm/batch/mm_apply_expire_cpy.h @@ -0,0 +1,6 @@ +/* mm_apply_expire_cpy.h - mm_apply_expire_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +long b_days; +char b_merch[64]; +char b_stat[16]; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/mm/batch/mm_audit_compact_batch.pgc b/app/src/mm/batch/mm_audit_compact_batch.pgc new file mode 100644 index 0000000..7eed509 --- /dev/null +++ b/app/src/mm/batch/mm_audit_compact_batch.pgc @@ -0,0 +1,65 @@ +/* + * mm_audit_compact_batch.pgc - 감사 이력 압축 배치 (요약행 생성 후 원본 삭제 + XA). + * + * 보존일수를 넘긴 가맹점별 이력이 임계 건수를 초과하면 건수를 담은 AUDIT_SUM + * 요약행 1건을 남기고 해당 구간의 원본 이력을 삭제해 mm_merch_hist 를 압축한다. + * (AUDIT_SUM 자신은 삭제 대상에서 제외.) 전 구간 ONE global XA transaction. + * host 변수는 전용 카피북 mm_audit_compact_cpy.h 를 EXEC SQL INCLUDE. + * Usage: mm_audit_compact_batch [DAYS=365] [KEEP_THRESHOLD=10] + */ +#include +#include +#include +#include +#include "mm_dbio.h" + +int main(int argc, char **argv) +{ + long merchants = 0, removed = 0; + char summary[96]; + + EXEC SQL INCLUDE mm_audit_compact_cpy; + + b_days = (argc > 1) ? atol(argv[1]) : 365; + b_keep = (argc > 2) ? atol(argv[2]) : 10; + if (b_days < 30) b_days = 365; + if (b_keep < 1) b_keep = 10; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE acp CURSOR FOR + SELECT merchant_id, count(*) FROM mm_merch_hist + WHERE created_at < now() - :b_days * interval '1 day' + AND action <> 'AUDIT_SUM' + GROUP BY merchant_id + HAVING count(*) > :b_keep + ORDER BY merchant_id; + EXEC SQL OPEN acp; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH acp INTO :b_merch, :b_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE acp; tpabort(0); return 1; } + snprintf(summary, sizeof(summary), "보존기한 경과 이력 %ld건 압축 요약", b_cnt); + if (mmdb_insert_merch_hist(b_merch, "AUDIT_SUM", "", "", summary) < 0) { + EXEC SQL CLOSE acp; tpabort(0); return 1; + } + EXEC SQL DELETE FROM mm_merch_hist + WHERE merchant_id = :b_merch + AND created_at < now() - :b_days * interval '1 day' + AND action <> 'AUDIT_SUM'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "DELETE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE acp; tpabort(0); return 1; } + removed += sqlca.sqlerrd[2]; + merchants++; + } + EXEC SQL CLOSE acp; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_audit_compact_batch COMMIT: 압축 가맹점=%ld 삭제 이력=%ld건\n", merchants, removed); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_audit_compact_cpy.h b/app/src/mm/batch/mm_audit_compact_cpy.h new file mode 100644 index 0000000..1952130 --- /dev/null +++ b/app/src/mm/batch/mm_audit_compact_cpy.h @@ -0,0 +1,7 @@ +/* mm_audit_compact_cpy.h - mm_audit_compact_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +long b_days; +long b_keep; +char b_merch[64]; +long b_cnt; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/mm/batch/mm_bizno_audit_batch.pgc b/app/src/mm/batch/mm_bizno_audit_batch.pgc new file mode 100644 index 0000000..f3b52b6 --- /dev/null +++ b/app/src/mm/batch/mm_bizno_audit_batch.pgc @@ -0,0 +1,68 @@ +/* + * mm_bizno_audit_batch.pgc - 사업자등록번호 전수 점검 배치 (형식+검증공식 + XA). + * + * 활성 가맹점의 사업자번호를 커서로 전수 검사한다: 10자리 숫자 형식과 + * 국세청 검증공식(가중치 1,3,7,1,3,7,1,3,5) checksum. 위반 건은 BIZNO_ERR + * 이력으로 적재한다. 전 구간 ONE global XA transaction. + * host 변수는 전용 카피북 mm_bizno_audit_cpy.h 를 EXEC SQL INCLUDE. + * Usage: mm_bizno_audit_batch + */ +#include +#include +#include +#include +#include "mm_dbio.h" + +/* 0=정상, 1=형식 오류, 2=검증숫자 불일치 */ +static int bizno_verdict(const char *no) +{ + static const int w[9] = { 1, 3, 7, 1, 3, 7, 1, 3, 5 }; + int i, sum = 0; + if (strlen(no) != 10) return 1; + for (i = 0; i < 10; i++) if (no[i] < '0' || no[i] > '9') return 1; + for (i = 0; i < 9; i++) sum += (no[i] - '0') * w[i]; + sum += ((no[8] - '0') * 5) / 10; + return ((10 - sum % 10) % 10) == (no[9] - '0') ? 0 : 2; +} + +int main(int argc, char **argv) +{ + long checked = 0, bad = 0; + int verdict; + (void)argc; (void)argv; + + EXEC SQL INCLUDE mm_bizno_audit_cpy; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE bza CURSOR FOR + SELECT merchant_id, biz_reg_no FROM mm_merchant_ext + WHERE status = 'ACTIVE' AND biz_reg_no <> '' + ORDER BY merchant_id; + EXEC SQL OPEN bza; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH bza INTO :b_merch, :b_bizno; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE bza; tpabort(0); return 1; } + checked++; + verdict = bizno_verdict(b_bizno); + if (verdict != 0) { + if (mmdb_insert_merch_hist(b_merch, "BIZNO_ERR", "", b_bizno, + verdict == 1 ? "사업자번호 형식 오류" : "사업자번호 검증숫자 불일치") < 0) { + EXEC SQL CLOSE bza; tpabort(0); return 1; + } + bad++; + } + } + EXEC SQL CLOSE bza; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_bizno_audit_batch COMMIT: 점검=%ld건 위반=%ld건\n", checked, bad); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_bizno_audit_cpy.h b/app/src/mm/batch/mm_bizno_audit_cpy.h new file mode 100644 index 0000000..a043894 --- /dev/null +++ b/app/src/mm/batch/mm_bizno_audit_cpy.h @@ -0,0 +1,5 @@ +/* mm_bizno_audit_cpy.h - mm_bizno_audit_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char b_merch[64]; +char b_bizno[16]; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/mm/batch/mm_close_sweep_batch.pgc b/app/src/mm/batch/mm_close_sweep_batch.pgc new file mode 100644 index 0000000..e064afc --- /dev/null +++ b/app/src/mm/batch/mm_close_sweep_batch.pgc @@ -0,0 +1,47 @@ +/* + * mm_close_sweep_batch.pgc - 폐업 가맹점 부속 마스터 정리 배치 (IN-subquery 일괄 + XA). + * + * DELETED 가맹점에 딸린 부속 데이터를 일괄 정리한다: + * 미폐기 단말 SCRAPPED 전환, 수수료율 행 삭제, 한도 행 삭제. + * 커서 없이 서브쿼리 3방으로 처리. 전 구간 ONE global XA transaction. + * host 변수는 전용 카피북 mm_close_sweep_cpy.h 를 EXEC SQL INCLUDE. + * Usage: mm_close_sweep_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + + EXEC SQL INCLUDE mm_close_sweep_cpy; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL UPDATE mm_terminal SET status = 'SCRAPPED' + WHERE status <> 'SCRAPPED' + AND merchant_id IN (SELECT merchant_id FROM mm_merchant_ext WHERE status = 'DELETED'); + if (sqlca.sqlcode < 0) { fprintf(stderr, "TERM FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + b_terms = sqlca.sqlerrd[2]; + + EXEC SQL DELETE FROM mm_fee_rate + WHERE merchant_id IN (SELECT merchant_id FROM mm_merchant_ext WHERE status = 'DELETED'); + if (sqlca.sqlcode < 0) { fprintf(stderr, "FEE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + b_fees = sqlca.sqlerrd[2]; + + EXEC SQL DELETE FROM mm_limit + WHERE merchant_id IN (SELECT merchant_id FROM mm_merchant_ext WHERE status = 'DELETED'); + if (sqlca.sqlcode < 0) { fprintf(stderr, "LIMIT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + b_limits = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_close_sweep_batch COMMIT: 단말 폐기=%ld 요율 삭제=%ld 한도 삭제=%ld\n", + b_terms, b_fees, b_limits); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_close_sweep_cpy.h b/app/src/mm/batch/mm_close_sweep_cpy.h new file mode 100644 index 0000000..a8f532a --- /dev/null +++ b/app/src/mm/batch/mm_close_sweep_cpy.h @@ -0,0 +1,6 @@ +/* mm_close_sweep_cpy.h - mm_close_sweep_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +long b_terms; +long b_fees; +long b_limits; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/mm/batch/mm_codemap_gc_batch.pgc b/app/src/mm/batch/mm_codemap_gc_batch.pgc new file mode 100644 index 0000000..1081286 --- /dev/null +++ b/app/src/mm/batch/mm_codemap_gc_batch.pgc @@ -0,0 +1,46 @@ +/* + * mm_codemap_gc_batch.pgc - 비활성 코드 매핑 정리(GC) 배치 (집합 DELETE 2단 + XA). + * + * (1) MAP_* 네임스페이스의 INACTIVE 매핑 행을 즉시 삭제하고, + * (2) MERCH_REL 관계의 INACTIVE 행은 보존일수 경과분만 삭제한다. + * 커서 없이 집합 DELETE 두 번으로 처리, 삭제 건수는 sqlca.sqlerrd 로 집계. + * 전 구간 ONE global XA transaction. + * host 변수는 전용 카피북 mm_codemap_gc_cpy.h 를 EXEC SQL INCLUDE. + * Usage: mm_codemap_gc_batch [REL_KEEP_DAYS=90] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long map_gone, rel_gone; + + EXEC SQL INCLUDE mm_codemap_gc_cpy; + + b_days = (argc > 1) ? atol(argv[1]) : 90; + if (b_days < 1) b_days = 90; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DELETE FROM mm_code + WHERE substr(code_group, 1, 4) = 'MAP_' AND status = 'INACTIVE'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "DELETE(MAP) FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + map_gone = sqlca.sqlerrd[2]; + + EXEC SQL DELETE FROM mm_code + WHERE code_group = 'MERCH_REL' AND status = 'INACTIVE' + AND created_at < now() - :b_days * interval '1 day'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "DELETE(REL) FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rel_gone = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_codemap_gc_batch COMMIT: 매핑 삭제=%ld건 관계 삭제=%ld건 (보존 %ld일)\n", + map_gone, rel_gone, b_days); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_codemap_gc_cpy.h b/app/src/mm/batch/mm_codemap_gc_cpy.h new file mode 100644 index 0000000..66fd52b --- /dev/null +++ b/app/src/mm/batch/mm_codemap_gc_cpy.h @@ -0,0 +1,4 @@ +/* mm_codemap_gc_cpy.h - mm_codemap_gc_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +long b_days; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/mm/batch/mm_consist_report_batch.pgc b/app/src/mm/batch/mm_consist_report_batch.pgc new file mode 100644 index 0000000..81c449b --- /dev/null +++ b/app/src/mm/batch/mm_consist_report_batch.pgc @@ -0,0 +1,63 @@ +/* + * mm_consist_report_batch.pgc - 마스터 정합성 종합 리포트 배치 (read-only 점검 5종 + XA). + * + * 마스터 전반의 불일치를 한 트랜잭션 스냅샷에서 집계해 리포트로 출력한다: + * 고아 단말 / 요율 없는 활성 가맹점 / 한도 없는 활성 가맹점 / 중복 사업자번호 / + * 심사 적체(APPLIED+REVIEW). 불일치가 하나라도 있으면 exit 2 (모니터링 연동용). + * host 변수는 전용 카피북 mm_consist_report_cpy.h 를 EXEC SQL INCLUDE. + * Usage: mm_consist_report_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long faults; + (void)argc; (void)argv; + + EXEC SQL INCLUDE mm_consist_report_cpy; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*) INTO :r_orphan_term FROM mm_terminal t + WHERE NOT EXISTS (SELECT 1 FROM mm_merchant_ext m WHERE m.merchant_id = t.merchant_id); + if (sqlca.sqlcode < 0) { fprintf(stderr, "CHK1 FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + + EXEC SQL SELECT count(*) INTO :r_no_fee FROM mm_merchant_ext m + WHERE m.status = 'ACTIVE' + AND NOT EXISTS (SELECT 1 FROM mm_fee_rate f WHERE f.merchant_id = m.merchant_id); + if (sqlca.sqlcode < 0) { fprintf(stderr, "CHK2 FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + + EXEC SQL SELECT count(*) INTO :r_no_limit FROM mm_merchant_ext m + WHERE m.status = 'ACTIVE' + AND NOT EXISTS (SELECT 1 FROM mm_limit l WHERE l.merchant_id = m.merchant_id); + if (sqlca.sqlcode < 0) { fprintf(stderr, "CHK3 FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + + EXEC SQL SELECT count(*) INTO :r_dup_bizno FROM ( + SELECT biz_reg_no FROM mm_merchant_ext + WHERE biz_reg_no <> '' AND status <> 'DELETED' + GROUP BY biz_reg_no HAVING count(*) > 1) d; + if (sqlca.sqlcode < 0) { fprintf(stderr, "CHK4 FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + + EXEC SQL SELECT count(*) INTO :r_backlog FROM mm_merchant_ext + WHERE status IN ('APPLIED', 'REVIEW'); + if (sqlca.sqlcode < 0) { fprintf(stderr, "CHK5 FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + + faults = r_orphan_term + r_no_fee + r_no_limit + r_dup_bizno; + printf("=== mm 마스터 정합성 리포트 ===\n"); + printf("고아 단말 : %ld\n", r_orphan_term); + printf("요율 없는 활성 가맹점: %ld\n", r_no_fee); + printf("한도 없는 활성 가맹점: %ld\n", r_no_limit); + printf("중복 사업자번호 : %ld\n", r_dup_bizno); + printf("심사 적체(참고) : %ld\n", r_backlog); + printf(">>> mm_consist_report_batch DONE: 불일치=%ld\n", faults); + + tpclose(); tpterm(); + return faults > 0 ? 2 : 0; +} diff --git a/app/src/mm/batch/mm_consist_report_cpy.h b/app/src/mm/batch/mm_consist_report_cpy.h new file mode 100644 index 0000000..29a2974 --- /dev/null +++ b/app/src/mm/batch/mm_consist_report_cpy.h @@ -0,0 +1,8 @@ +/* mm_consist_report_cpy.h - mm_consist_report_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +long r_orphan_term; +long r_no_fee; +long r_no_limit; +long r_dup_bizno; +long r_backlog; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/mm/batch/mm_contract_renew_batch.pgc b/app/src/mm/batch/mm_contract_renew_batch.pgc new file mode 100644 index 0000000..57a97f8 --- /dev/null +++ b/app/src/mm/batch/mm_contract_renew_batch.pgc @@ -0,0 +1,62 @@ +/* + * mm_contract_renew_batch.pgc - 만기 도래 가맹 계약 자동갱신 배치 (GROUP BY 커서 + XA). + * + * 가맹점별 최종 계약 만기(after_val 최대값)가 오늘 이하인 건을 커서로 훑어 + * 만기일 기준 개월수 연장한 새 CONTRACT 이력을 적재한다 (해지 이력이 뒤따르는 + * 가맹점은 갱신 대상에서 제외). 전 구간 ONE global XA transaction. + * host 변수는 전용 카피북 mm_contract_renew_cpy.h 를 EXEC SQL INCLUDE. + * Usage: mm_contract_renew_batch [MONTHS=12] + */ +#include +#include +#include +#include +#include "mm_dbio.h" + +int main(int argc, char **argv) +{ + long renewed = 0; + + EXEC SQL INCLUDE mm_contract_renew_cpy; + + b_mon = (argc > 1) ? atol(argv[1]) : 12; + if (b_mon < 1 || b_mon > 120) b_mon = 12; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE crn CURSOR FOR + SELECT h.merchant_id, max(h.after_val) + FROM mm_merch_hist h + JOIN mm_merchant_ext m ON m.merchant_id = h.merchant_id AND m.status = 'ACTIVE' + WHERE h.action = 'CONTRACT' + AND NOT EXISTS (SELECT 1 FROM mm_merch_hist e + WHERE e.merchant_id = h.merchant_id AND e.action = 'CONT_END' + AND e.hist_id > h.hist_id) + GROUP BY h.merchant_id + HAVING max(h.after_val) <= to_char(current_date, 'YYYYMMDD') + ORDER BY h.merchant_id; + EXEC SQL OPEN crn; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH crn INTO :b_merch, :b_old; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE crn; tpabort(0); return 1; } + EXEC SQL SELECT to_char(to_date(:b_old, 'YYYYMMDD') + :b_mon * interval '1 month', 'YYYYMMDD') + INTO :b_new; + if (sqlca.sqlcode < 0) { fprintf(stderr, "CALC FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE crn; tpabort(0); return 1; } + if (mmdb_insert_merch_hist(b_merch, "CONTRACT", b_old, b_new, "만기 도래 배치 자동갱신") < 0) { + EXEC SQL CLOSE crn; tpabort(0); return 1; + } + renewed++; + } + EXEC SQL CLOSE crn; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_contract_renew_batch COMMIT: 연장=%ld개월 갱신=%ld건\n", b_mon, renewed); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_contract_renew_cpy.h b/app/src/mm/batch/mm_contract_renew_cpy.h new file mode 100644 index 0000000..be1b018 --- /dev/null +++ b/app/src/mm/batch/mm_contract_renew_cpy.h @@ -0,0 +1,7 @@ +/* mm_contract_renew_cpy.h - mm_contract_renew_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +long b_mon; +char b_merch[64]; +char b_old[16]; +char b_new[16]; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/mm/batch/mm_grade_demote_batch.pgc b/app/src/mm/batch/mm_grade_demote_batch.pgc new file mode 100644 index 0000000..af9adae --- /dev/null +++ b/app/src/mm/batch/mm_grade_demote_batch.pgc @@ -0,0 +1,76 @@ +/* + * mm_grade_demote_batch.pgc - 매출 미달 가맹점 등급 강등 배치 (LEFT JOIN 집계 커서 + XA). + * + * 최근 N일 매출 합계가 현재 등급의 유지 기준(S>=5억, A>=1억, B>=1천만)에 미달하는 + * 활성 가맹점을 한 단계씩 강등(S->A->B->C)하고 GRADE_DEM 이력을 남긴다. + * 승격은 하지 않는다 (승격은 mm_grade_recalc_batch 소관). ONE global XA transaction. + * host 변수는 전용 카피북 mm_grade_demote_cpy.h 를 EXEC SQL INCLUDE. + * Usage: mm_grade_demote_batch [DAYS=90] + */ +#include +#include +#include +#include +#include "mm_dbio.h" + +/* 등급 유지 최소 매출 (원). 미달 시 한 단계 강등 */ +static long keep_floor(const char *grade) +{ + if (grade[0] == 'S') return 500000000L; + if (grade[0] == 'A') return 100000000L; + if (grade[0] == 'B') return 10000000L; + return 0; +} + +static const char *one_down(const char *grade) +{ + if (grade[0] == 'S') return "A"; + if (grade[0] == 'A') return "B"; + return "C"; +} + +int main(int argc, char **argv) +{ + long demoted = 0; + + EXEC SQL INCLUDE mm_grade_demote_cpy; + + b_days = (argc > 1) ? atol(argv[1]) : 90; + if (b_days < 1) b_days = 90; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE gdm CURSOR FOR + SELECT me.merchant_id, me.grade, coalesce(sum(ms.gross_amount), 0) + FROM mm_merchant_ext me + LEFT JOIN merchant_summary ms + ON ms.merchant_id = me.merchant_id + AND ms.biz_date >= current_date - :b_days + WHERE me.status = 'ACTIVE' AND me.grade IN ('S', 'A', 'B') + GROUP BY me.merchant_id, me.grade + ORDER BY me.merchant_id; + EXEC SQL OPEN gdm; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH gdm INTO :b_merch, :b_grade, :b_gross; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE gdm; tpabort(0); return 1; } + if (b_gross >= keep_floor(b_grade)) continue; + strncpy(b_next, one_down(b_grade), sizeof(b_next)-1); b_next[sizeof(b_next)-1] = 0; + if (mmdb_update_merchant_grade(b_merch, b_next) < 0) { EXEC SQL CLOSE gdm; tpabort(0); return 1; } + if (mmdb_insert_merch_hist(b_merch, "GRADE_DEM", b_grade, b_next, "매출 미달 배치 강등") < 0) { + EXEC SQL CLOSE gdm; tpabort(0); return 1; + } + demoted++; + } + EXEC SQL CLOSE gdm; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_grade_demote_batch COMMIT: 기간=%ld일 강등=%ld건\n", b_days, demoted); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_grade_demote_cpy.h b/app/src/mm/batch/mm_grade_demote_cpy.h new file mode 100644 index 0000000..133ebea --- /dev/null +++ b/app/src/mm/batch/mm_grade_demote_cpy.h @@ -0,0 +1,8 @@ +/* mm_grade_demote_cpy.h - mm_grade_demote_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +long b_days; +char b_merch[64]; +char b_grade[8]; +long b_gross; +char b_next[8]; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/mm/batch/mm_mcc_reclass_batch.pgc b/app/src/mm/batch/mm_mcc_reclass_batch.pgc new file mode 100644 index 0000000..314186e --- /dev/null +++ b/app/src/mm/batch/mm_mcc_reclass_batch.pgc @@ -0,0 +1,56 @@ +/* + * mm_mcc_reclass_batch.pgc - MCC 위험도 재분류 배치 (업종별 폐업률 기반 + XA). + * + * MCC 별 소속 가맹점의 폐업(DELETED) 비율로 위험도를 재산정한다: + * 신위험도 = 1 + (폐업수 * 4 / 전체수), 1~5 범위. 기존 값과 다를 때만 UPDATE. + * 전 구간 ONE global XA transaction. + * host 변수는 전용 카피북 mm_mcc_reclass_cpy.h 를 EXEC SQL INCLUDE. + * Usage: mm_mcc_reclass_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long scanned = 0, changed = 0; + (void)argc; (void)argv; + + EXEC SQL INCLUDE mm_mcc_reclass_cpy; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE mrc CURSOR FOR + SELECT c.mcc, c.risk_level, count(me.merchant_id), + coalesce(sum(CASE WHEN me.status = 'DELETED' THEN 1 ELSE 0 END), 0) + FROM mm_mcc c + JOIN mm_merchant_ext me ON me.mcc = c.mcc + GROUP BY c.mcc, c.risk_level + ORDER BY c.mcc; + EXEC SQL OPEN mrc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH mrc INTO :b_mcc, :b_risk, :b_total, :b_closed; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE mrc; tpabort(0); return 1; } + scanned++; + if (b_total <= 0) continue; + b_newrisk = 1 + (b_closed * 4) / b_total; + if (b_newrisk > 5) b_newrisk = 5; + if (b_newrisk == b_risk) continue; + EXEC SQL UPDATE mm_mcc SET risk_level = :b_newrisk WHERE mcc = :b_mcc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "UPDATE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE mrc; tpabort(0); return 1; } + changed++; + } + EXEC SQL CLOSE mrc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_mcc_reclass_batch COMMIT: 점검 MCC=%ld 위험도 변경=%ld건\n", scanned, changed); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_mcc_reclass_cpy.h b/app/src/mm/batch/mm_mcc_reclass_cpy.h new file mode 100644 index 0000000..56b5a65 --- /dev/null +++ b/app/src/mm/batch/mm_mcc_reclass_cpy.h @@ -0,0 +1,8 @@ +/* mm_mcc_reclass_cpy.h - mm_mcc_reclass_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char b_mcc[8]; +long b_total; +long b_closed; +long b_risk; +long b_newrisk; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/mm/batch/mm_partner_sync_batch.pgc b/app/src/mm/batch/mm_partner_sync_batch.pgc new file mode 100644 index 0000000..e6113c5 --- /dev/null +++ b/app/src/mm/batch/mm_partner_sync_batch.pgc @@ -0,0 +1,58 @@ +/* + * mm_partner_sync_batch.pgc - 제휴사 마스터-발급사 동기화 배치 (upsert 커서 + XA). + * + * 활성 발급사(mm_issuer)를 커서로 훑어 제휴사 코드(PARTNER 코드그룹)로 upsert 하고, + * 발급사 마스터에서 사라졌거나 비활성인 제휴사 코드는 INACTIVE 로 내린다. + * 전 구간 ONE global XA transaction. + * host 변수는 전용 카피북 mm_partner_sync_cpy.h 를 EXEC SQL INCLUDE. + * Usage: mm_partner_sync_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long synced = 0, retired; + (void)argc; (void)argv; + + EXEC SQL INCLUDE mm_partner_sync_cpy; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE psy CURSOR FOR + SELECT issuer_cd, issuer_name FROM mm_issuer + WHERE status = 'ACTIVE' ORDER BY issuer_cd; + EXEC SQL OPEN psy; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH psy INTO :b_cd, :b_nm; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE psy; tpabort(0); return 1; } + EXEC SQL INSERT INTO mm_code (code_group, code, code_name, status) + VALUES ('PARTNER', :b_cd, :b_nm, 'ACTIVE') + ON CONFLICT (code_group, code) + DO UPDATE SET code_name = EXCLUDED.code_name, status = 'ACTIVE'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "UPSERT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE psy; tpabort(0); return 1; } + synced++; + } + EXEC SQL CLOSE psy; + + /* 발급사 마스터에 없는(또는 비활성) 제휴사 코드는 은퇴 처리 */ + EXEC SQL UPDATE mm_code SET status = 'INACTIVE' + WHERE code_group = 'PARTNER' AND status = 'ACTIVE' + AND NOT EXISTS (SELECT 1 FROM mm_issuer i + WHERE i.issuer_cd = mm_code.code AND i.status = 'ACTIVE'); + if (sqlca.sqlcode < 0) { fprintf(stderr, "RETIRE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + retired = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_partner_sync_batch COMMIT: 동기화=%ld건 은퇴=%ld건\n", synced, retired); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_partner_sync_cpy.h b/app/src/mm/batch/mm_partner_sync_cpy.h new file mode 100644 index 0000000..d71fc9d --- /dev/null +++ b/app/src/mm/batch/mm_partner_sync_cpy.h @@ -0,0 +1,5 @@ +/* mm_partner_sync_cpy.h - mm_partner_sync_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char b_cd[16]; +char b_nm[64]; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/mm/batch/mm_rel_orphan_batch.pgc b/app/src/mm/batch/mm_rel_orphan_batch.pgc new file mode 100644 index 0000000..5ad2670 --- /dev/null +++ b/app/src/mm/batch/mm_rel_orphan_batch.pgc @@ -0,0 +1,55 @@ +/* + * mm_rel_orphan_batch.pgc - 대표-하위 가맹점 관계 정합성 정리 배치 (NOT EXISTS 커서 + XA). + * + * 대표가맹점(code_name)이 더 이상 활성이 아닌 MERCH_REL 관계 행을 커서로 찾아 + * INACTIVE 로 내리고 하위가맹점 이력(REL_UNLINK)을 남긴다. + * 전 구간 ONE global XA transaction. + * host 변수는 전용 카피북 mm_rel_orphan_cpy.h 를 EXEC SQL INCLUDE. + * Usage: mm_rel_orphan_batch + */ +#include +#include +#include +#include +#include "mm_dbio.h" + +int main(int argc, char **argv) +{ + long cleaned = 0; + (void)argc; (void)argv; + + EXEC SQL INCLUDE mm_rel_orphan_cpy; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE rox CURSOR FOR + SELECT c.code, c.code_name FROM mm_code c + WHERE c.code_group = 'MERCH_REL' AND c.status = 'ACTIVE' + AND NOT EXISTS (SELECT 1 FROM mm_merchant_ext m + WHERE m.merchant_id = c.code_name AND m.status = 'ACTIVE') + ORDER BY c.code; + EXEC SQL OPEN rox; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH rox INTO :b_child, :b_parent; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE rox; tpabort(0); return 1; } + EXEC SQL UPDATE mm_code SET status = 'INACTIVE' + WHERE code_group = 'MERCH_REL' AND code = :b_child; + if (sqlca.sqlcode < 0) { fprintf(stderr, "UPDATE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE rox; tpabort(0); return 1; } + if (mmdb_insert_merch_hist(b_child, "REL_UNLINK", b_parent, "", "대표가맹점 비활성 배치 해제") < 0) { + EXEC SQL CLOSE rox; tpabort(0); return 1; + } + cleaned++; + } + EXEC SQL CLOSE rox; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_rel_orphan_batch COMMIT: 관계 해제=%ld건\n", cleaned); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_rel_orphan_cpy.h b/app/src/mm/batch/mm_rel_orphan_cpy.h new file mode 100644 index 0000000..adcf7f4 --- /dev/null +++ b/app/src/mm/batch/mm_rel_orphan_cpy.h @@ -0,0 +1,5 @@ +/* mm_rel_orphan_cpy.h - mm_rel_orphan_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char b_child[64]; +char b_parent[64]; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/mm/batch/mm_term_scrap_batch.pgc b/app/src/mm/batch/mm_term_scrap_batch.pgc new file mode 100644 index 0000000..96fba76 --- /dev/null +++ b/app/src/mm/batch/mm_term_scrap_batch.pgc @@ -0,0 +1,54 @@ +/* + * mm_term_scrap_batch.pgc - 장기 정지 단말 일괄 폐기 배치 (커서 + UPDATE + 이력 + XA). + * + * SUSPENDED 상태로 보존일수 이상 경과한 단말을 커서로 훑어 SCRAPPED 로 전환하고, + * 소속 가맹점 이력(TERM_SCRAP)을 남긴다. 전 구간 ONE global XA transaction. + * host 변수는 전용 카피북 mm_term_scrap_cpy.h 를 EXEC SQL INCLUDE. + * Usage: mm_term_scrap_batch [DAYS=180] + */ +#include +#include +#include +#include +#include "mm_dbio.h" + +int main(int argc, char **argv) +{ + long scrapped = 0; + + EXEC SQL INCLUDE mm_term_scrap_cpy; + + b_days = (argc > 1) ? atol(argv[1]) : 180; + if (b_days < 1) b_days = 180; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE tsc CURSOR FOR + SELECT terminal_id, merchant_id FROM mm_terminal + WHERE status = 'SUSPENDED' + AND created_at < now() - :b_days * interval '1 day' + ORDER BY terminal_id; + EXEC SQL OPEN tsc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH tsc INTO :b_term, :b_merch; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE tsc; tpabort(0); return 1; } + EXEC SQL UPDATE mm_terminal SET status = 'SCRAPPED' WHERE terminal_id = :b_term; + if (sqlca.sqlcode < 0) { fprintf(stderr, "UPDATE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE tsc; tpabort(0); return 1; } + if (mmdb_insert_merch_hist(b_merch, "TERM_SCRAP", "SUSPENDED", "SCRAPPED", "장기 정지 단말 배치 폐기") < 0) { + EXEC SQL CLOSE tsc; tpabort(0); return 1; + } + scrapped++; + } + EXEC SQL CLOSE tsc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_term_scrap_batch COMMIT: 기준=%ld일 폐기=%ld대\n", b_days, scrapped); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_term_scrap_cpy.h b/app/src/mm/batch/mm_term_scrap_cpy.h new file mode 100644 index 0000000..77fc3b5 --- /dev/null +++ b/app/src/mm/batch/mm_term_scrap_cpy.h @@ -0,0 +1,6 @@ +/* mm_term_scrap_cpy.h - mm_term_scrap_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +long b_days; +char b_term[64]; +char b_merch[64]; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/mm/mm_svr.pgc b/app/src/mm/mm_svr.pgc index 0f90996..27092bb 100644 --- a/app/src/mm/mm_svr.pgc +++ b/app/src/mm/mm_svr.pgc @@ -2,7 +2,7 @@ * mm_svr.pgc - mm (마스터/master) MODULE SERVER (thin dispatcher). * * The legacy "one module server, many services" pattern: this binary owns NO - * business logic. Each of mm's ~30 online services now lives in its own file + * business logic. Each of mm's ~75 online services now lives in its own file * (app/src/mm/svc/.pgc) with its own copybook header * (app/src/mm/svc/.h); those objects are archived into libmmsvc.a and * linked into this server. Here we only include every service copybook, tpopen() @@ -46,6 +46,51 @@ #include "MM_CODE_CACHE.h" #include "MM_STATUS.h" #include "MM_CONFIRM.h" +#include "MM_TERM_MOVE.h" +#include "MM_TERM_SCRAP.h" +#include "MM_TERM_SUSPEND.h" +#include "MM_TERM_CNT.h" +#include "MM_APPLY_REG.h" +#include "MM_APPLY_REVIEW.h" +#include "MM_APPLY_APPROVE.h" +#include "MM_APPLY_REJECT.h" +#include "MM_APPLY_STATUS.h" +#include "MM_CONTRACT_NEW.h" +#include "MM_CONTRACT_RENEW.h" +#include "MM_CONTRACT_END.h" +#include "MM_CONTRACT_INQ.h" +#include "MM_PARTNER_REG.h" +#include "MM_PARTNER_INQ.h" +#include "MM_PARTNER_STATUS.h" +#include "MM_REL_LINK.h" +#include "MM_REL_UNLINK.h" +#include "MM_REL_LIST.h" +#include "MM_MCC_UPD.h" +#include "MM_MCC_INQ.h" +#include "MM_MCC_RISKLIST.h" +#include "MM_RATE_REQ.h" +#include "MM_RATE_APPROVE.h" +#include "MM_RATE_REJECT.h" +#include "MM_MERCH_PAUSE.h" +#include "MM_MERCH_RESUME.h" +#include "MM_MERCH_CLOSE.h" +#include "MM_ACCT_VERIFY.h" +#include "MM_ACCT_VRF_HIST.h" +#include "MM_ACCT_VRF_STAT.h" +#include "MM_GRADE_PREVIEW.h" +#include "MM_GRADE_ADJUST.h" +#include "MM_GRADE_DIST.h" +#include "MM_AUDIT_TRAIL.h" +#include "MM_AUDIT_RECENT.h" +#include "MM_AUDIT_PURGE.h" +#include "MM_CHK_TERM.h" +#include "MM_CHK_FEERATE.h" +#include "MM_CHK_LIMIT.h" +#include "MM_CODEMAP_SET.h" +#include "MM_CODEMAP_GET.h" +#include "MM_CODEMAP_DEL.h" +#include "MM_BIZNO_CHECK.h" +#include "MM_BIZNO_UPD.h" /* ----- advertise table: {service name, function} (fn is extern from its file) */ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { @@ -79,6 +124,51 @@ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { {"MM_CODE_CACHE", MM_CODE_CACHE}, {"MM_STATUS", MM_STATUS}, {"MM_CONFIRM", MM_CONFIRM}, + {"MM_TERM_MOVE", MM_TERM_MOVE}, + {"MM_TERM_SCRAP", MM_TERM_SCRAP}, + {"MM_TERM_SUSPEND", MM_TERM_SUSPEND}, + {"MM_TERM_CNT", MM_TERM_CNT}, + {"MM_APPLY_REG", MM_APPLY_REG}, + {"MM_APPLY_REVIEW", MM_APPLY_REVIEW}, + {"MM_APPLY_APPROVE", MM_APPLY_APPROVE}, + {"MM_APPLY_REJECT", MM_APPLY_REJECT}, + {"MM_APPLY_STATUS", MM_APPLY_STATUS}, + {"MM_CONTRACT_NEW", MM_CONTRACT_NEW}, + {"MM_CONTRACT_RENEW", MM_CONTRACT_RENEW}, + {"MM_CONTRACT_END", MM_CONTRACT_END}, + {"MM_CONTRACT_INQ", MM_CONTRACT_INQ}, + {"MM_PARTNER_REG", MM_PARTNER_REG}, + {"MM_PARTNER_INQ", MM_PARTNER_INQ}, + {"MM_PARTNER_STATUS", MM_PARTNER_STATUS}, + {"MM_REL_LINK", MM_REL_LINK}, + {"MM_REL_UNLINK", MM_REL_UNLINK}, + {"MM_REL_LIST", MM_REL_LIST}, + {"MM_MCC_UPD", MM_MCC_UPD}, + {"MM_MCC_INQ", MM_MCC_INQ}, + {"MM_MCC_RISKLIST", MM_MCC_RISKLIST}, + {"MM_RATE_REQ", MM_RATE_REQ}, + {"MM_RATE_APPROVE", MM_RATE_APPROVE}, + {"MM_RATE_REJECT", MM_RATE_REJECT}, + {"MM_MERCH_PAUSE", MM_MERCH_PAUSE}, + {"MM_MERCH_RESUME", MM_MERCH_RESUME}, + {"MM_MERCH_CLOSE", MM_MERCH_CLOSE}, + {"MM_ACCT_VERIFY", MM_ACCT_VERIFY}, + {"MM_ACCT_VRF_HIST", MM_ACCT_VRF_HIST}, + {"MM_ACCT_VRF_STAT", MM_ACCT_VRF_STAT}, + {"MM_GRADE_PREVIEW", MM_GRADE_PREVIEW}, + {"MM_GRADE_ADJUST", MM_GRADE_ADJUST}, + {"MM_GRADE_DIST", MM_GRADE_DIST}, + {"MM_AUDIT_TRAIL", MM_AUDIT_TRAIL}, + {"MM_AUDIT_RECENT", MM_AUDIT_RECENT}, + {"MM_AUDIT_PURGE", MM_AUDIT_PURGE}, + {"MM_CHK_TERM", MM_CHK_TERM}, + {"MM_CHK_FEERATE", MM_CHK_FEERATE}, + {"MM_CHK_LIMIT", MM_CHK_LIMIT}, + {"MM_CODEMAP_SET", MM_CODEMAP_SET}, + {"MM_CODEMAP_GET", MM_CODEMAP_GET}, + {"MM_CODEMAP_DEL", MM_CODEMAP_DEL}, + {"MM_BIZNO_CHECK", MM_BIZNO_CHECK}, + {"MM_BIZNO_UPD", MM_BIZNO_UPD}, {NULL, NULL} }; diff --git a/app/src/mm/svc/MM_ACCT_VERIFY.h b/app/src/mm/svc/MM_ACCT_VERIFY.h new file mode 100644 index 0000000..23b692d --- /dev/null +++ b/app/src/mm/svc/MM_ACCT_VERIFY.h @@ -0,0 +1,12 @@ +/* + * MM_ACCT_VERIFY.h - mm 서비스 MM_ACCT_VERIFY 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STR1(검증 결과), T_RC + */ +#ifndef MM_ACCT_VERIFY_H +#define MM_ACCT_VERIFY_H + +#include "mm_svc.h" + +void MM_ACCT_VERIFY(TPSVCINFO *p); + +#endif /* MM_ACCT_VERIFY_H */ diff --git a/app/src/mm/svc/MM_ACCT_VERIFY.pgc b/app/src/mm/svc/MM_ACCT_VERIFY.pgc new file mode 100644 index 0000000..ea814e8 --- /dev/null +++ b/app/src/mm/svc/MM_ACCT_VERIFY.pgc @@ -0,0 +1,42 @@ +/* + * MM_ACCT_VERIFY.pgc - mm 모듈 서비스 MM_ACCT_VERIFY (one service = one file). + * 기능: 정산계좌 검증 실행 (형식 검사 + ACCT_VRF 검증 이력 적재). + * 입력: T_MERCHANT. 출력: T_STR1=결과(OK/EMPTY/FORMAT), T_RC=0정상 1미존재 2계좌미등록 3형식오류. + * 검증 결과는 성공/실패 모두 이력으로 남긴다 (감사 추적용). + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_ACCT_VERIFY.h" + +void MM_ACCT_VERIFY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64]; + const char *result; + long rc = 0; + size_t i, len; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_acct[40]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT settle_acct INTO :h_acct FROM mm_merchant_ext + WHERE merchant_id = :h_merch; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + len = strlen(h_acct); + if (len == 0) { result = "EMPTY"; rc = 2; } + else { + result = "OK"; + if (len < 10 || len > 16) { result = "FORMAT"; rc = 3; } + else for (i = 0; i < len; i++) + if (h_acct[i] < '0' || h_acct[i] > '9') { result = "FORMAT"; rc = 3; break; } + } + + if (mmdb_insert_merch_hist(h_merch, "ACCT_VRF", "", result, "정산계좌 검증 수행") < 0) FAIL(b); + Bchg(b, T_STR1, 0, (char *)result, 0L); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/mm/svc/MM_ACCT_VRF_HIST.h b/app/src/mm/svc/MM_ACCT_VRF_HIST.h new file mode 100644 index 0000000..38f5d4f --- /dev/null +++ b/app/src/mm/svc/MM_ACCT_VRF_HIST.h @@ -0,0 +1,12 @@ +/* + * MM_ACCT_VRF_HIST.h - mm 서비스 MM_ACCT_VRF_HIST 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_COUNT, T_STR1(최근 결과), T_STR2(최근 검증일), T_RC + */ +#ifndef MM_ACCT_VRF_HIST_H +#define MM_ACCT_VRF_HIST_H + +#include "mm_svc.h" + +void MM_ACCT_VRF_HIST(TPSVCINFO *p); + +#endif /* MM_ACCT_VRF_HIST_H */ diff --git a/app/src/mm/svc/MM_ACCT_VRF_HIST.pgc b/app/src/mm/svc/MM_ACCT_VRF_HIST.pgc new file mode 100644 index 0000000..c13d823 --- /dev/null +++ b/app/src/mm/svc/MM_ACCT_VRF_HIST.pgc @@ -0,0 +1,39 @@ +/* + * MM_ACCT_VRF_HIST.pgc - mm 모듈 서비스 MM_ACCT_VRF_HIST (one service = one file). + * 기능: 정산계좌 검증 이력 조회 (누적 횟수 + 최근 결과/일시). + * 입력: T_MERCHANT. 출력: T_COUNT, T_STR1=최근 결과, T_STR2=최근 검증일(YYYYMMDD). + * RC: 1=검증 이력 없음. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_ACCT_VRF_HIST.h" + +void MM_ACCT_VRF_HIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64]; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_last[16], h_when[16]; + long h_cnt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT count(*) INTO :h_cnt FROM mm_merch_hist + WHERE merchant_id = :h_merch AND action = 'ACCT_VRF'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt == 0) { setl(b, T_RC, 1); OK(b); } + + EXEC SQL SELECT after_val, to_char(created_at, 'YYYYMMDD') + INTO :h_last, :h_when + FROM mm_merch_hist + WHERE merchant_id = :h_merch AND action = 'ACCT_VRF' + ORDER BY hist_id DESC LIMIT 1; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + Bchg(b, T_STR1, 0, h_last, 0L); + Bchg(b, T_STR2, 0, h_when, 0L); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_ACCT_VRF_STAT.h b/app/src/mm/svc/MM_ACCT_VRF_STAT.h new file mode 100644 index 0000000..93236da --- /dev/null +++ b/app/src/mm/svc/MM_ACCT_VRF_STAT.h @@ -0,0 +1,12 @@ +/* + * MM_ACCT_VRF_STAT.h - mm 서비스 MM_ACCT_VRF_STAT 카피북 (copybook / record header). + * 사용 UBF 필드: T_COUNT(총 검증), T_ARG1(검증 가맹점 수), T_ARG2(실패 건), T_RC + */ +#ifndef MM_ACCT_VRF_STAT_H +#define MM_ACCT_VRF_STAT_H + +#include "mm_svc.h" + +void MM_ACCT_VRF_STAT(TPSVCINFO *p); + +#endif /* MM_ACCT_VRF_STAT_H */ diff --git a/app/src/mm/svc/MM_ACCT_VRF_STAT.pgc b/app/src/mm/svc/MM_ACCT_VRF_STAT.pgc new file mode 100644 index 0000000..2a50664 --- /dev/null +++ b/app/src/mm/svc/MM_ACCT_VRF_STAT.pgc @@ -0,0 +1,28 @@ +/* + * MM_ACCT_VRF_STAT.pgc - mm 모듈 서비스 MM_ACCT_VRF_STAT (one service = one file). + * 기능: 정산계좌 검증 전사 통계 (총 검증 수, 검증 대상 가맹점 수, 실패 건수). + * 출력: T_COUNT, T_ARG1, T_ARG2. 실패 = 결과가 OK가 아닌 검증. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_ACCT_VRF_STAT.h" + +void MM_ACCT_VRF_STAT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_tot, h_merch, h_bad; + EXEC SQL END DECLARE SECTION; + + EXEC SQL SELECT count(*), + count(DISTINCT merchant_id), + coalesce(sum(CASE WHEN after_val <> 'OK' THEN 1 ELSE 0 END), 0) + INTO :h_tot, :h_merch, :h_bad + FROM mm_merch_hist WHERE action = 'ACCT_VRF'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_tot); + setl(b, T_ARG1, h_merch); + setl(b, T_ARG2, h_bad); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_APPLY_APPROVE.h b/app/src/mm/svc/MM_APPLY_APPROVE.h new file mode 100644 index 0000000..015a630 --- /dev/null +++ b/app/src/mm/svc/MM_APPLY_APPROVE.h @@ -0,0 +1,12 @@ +/* + * MM_APPLY_APPROVE.h - mm 서비스 MM_APPLY_APPROVE 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_ARG1(기본 수수료율 bps), T_RC + */ +#ifndef MM_APPLY_APPROVE_H +#define MM_APPLY_APPROVE_H + +#include "mm_svc.h" + +void MM_APPLY_APPROVE(TPSVCINFO *p); + +#endif /* MM_APPLY_APPROVE_H */ diff --git a/app/src/mm/svc/MM_APPLY_APPROVE.pgc b/app/src/mm/svc/MM_APPLY_APPROVE.pgc new file mode 100644 index 0000000..5e8ac87 --- /dev/null +++ b/app/src/mm/svc/MM_APPLY_APPROVE.pgc @@ -0,0 +1,43 @@ +/* + * MM_APPLY_APPROVE.pgc - mm 모듈 서비스 MM_APPLY_APPROVE (one service = one file). + * 기능: 가입 심사 승인 (REVIEW -> ACTIVE, 기본 수수료율/한도 자동 개설). + * 입력: T_MERCHANT, T_ARG1=기본 수수료율 bps(없으면 25). + * RC: 1=가맹점 없음, 2=REVIEW 상태가 아님. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_APPLY_APPROVE.h" + +void MM_APPLY_APPROVE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64]; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_stat[16]; + long h_bps; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + h_bps = getl(b, T_ARG1); + if (h_bps <= 0 || h_bps > 500) h_bps = 25; + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT status INTO :h_stat FROM mm_merchant_ext WHERE merchant_id = :h_merch; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + if (strcmp(h_stat, "REVIEW") != 0) { setl(b, T_RC, 2); OK(b); } + + EXEC SQL UPDATE mm_merchant_ext SET status = 'ACTIVE', updated_at = now() + WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0) FAIL(b); + /* 승인과 동시에 기본 수수료율/한도 레코드 개설 (이미 있으면 유지) */ + EXEC SQL INSERT INTO mm_fee_rate (merchant_id, card_type, rate_bps) + VALUES (:h_merch, 'CREDIT', :h_bps) + ON CONFLICT (merchant_id, card_type) DO NOTHING; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO mm_limit (merchant_id) VALUES (:h_merch) + ON CONFLICT (merchant_id) DO NOTHING; + if (sqlca.sqlcode < 0) FAIL(b); + if (mmdb_insert_merch_hist(h_merch, "APPROVE", "REVIEW", "ACTIVE", "심사 승인, 기본 요율/한도 개설") < 0) FAIL(b); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_APPLY_REG.h b/app/src/mm/svc/MM_APPLY_REG.h new file mode 100644 index 0000000..e577c54 --- /dev/null +++ b/app/src/mm/svc/MM_APPLY_REG.h @@ -0,0 +1,12 @@ +/* + * MM_APPLY_REG.h - mm 서비스 MM_APPLY_REG 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STR1(상호), T_STR2(사업자번호), T_STR3(MCC), T_RC + */ +#ifndef MM_APPLY_REG_H +#define MM_APPLY_REG_H + +#include "mm_svc.h" + +void MM_APPLY_REG(TPSVCINFO *p); + +#endif /* MM_APPLY_REG_H */ diff --git a/app/src/mm/svc/MM_APPLY_REG.pgc b/app/src/mm/svc/MM_APPLY_REG.pgc new file mode 100644 index 0000000..66a942d --- /dev/null +++ b/app/src/mm/svc/MM_APPLY_REG.pgc @@ -0,0 +1,45 @@ +/* + * MM_APPLY_REG.pgc - mm 모듈 서비스 MM_APPLY_REG (one service = one file). + * 기능: 가맹점 가입 신청 접수 (심사 워크플로 시작, 상태 APPLIED). + * 입력: T_MERCHANT, T_STR1=상호, T_STR2=사업자번호(10자리), T_STR3=MCC. + * RC: 1=이미 존재하는 가맹점, 2=사업자번호 형식 오류, 3=필수값 누락. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_APPLY_REG.h" + +void MM_APPLY_REG(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], name[64], bizno[16], mcc[8]; + int i; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_name[64], h_bizno[16], h_mcc[8]; + long h_dup; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_STR1, name, sizeof(name)); + gets_(b, T_STR2, bizno, sizeof(bizno)); + gets_(b, T_STR3, mcc, sizeof(mcc)); + if (mcc[0] == 0) strcpy(mcc, "0000"); + if (merch[0] == 0 || name[0] == 0) { setl(b, T_RC, 3); OK(b); } + if (strlen(bizno) != 10) { setl(b, T_RC, 2); OK(b); } + for (i = 0; i < 10; i++) + if (bizno[i] < '0' || bizno[i] > '9') { setl(b, T_RC, 2); OK(b); } + + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_name, name, sizeof(h_name)-1); h_name[sizeof(h_name)-1] = 0; + strncpy(h_bizno, bizno, sizeof(h_bizno)-1); h_bizno[sizeof(h_bizno)-1] = 0; + strncpy(h_mcc, mcc, sizeof(h_mcc)-1); h_mcc[sizeof(h_mcc)-1] = 0; + + EXEC SQL SELECT count(*) INTO :h_dup FROM mm_merchant_ext WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_dup > 0) { setl(b, T_RC, 1); OK(b); } + + EXEC SQL INSERT INTO mm_merchant_ext (merchant_id, name, biz_reg_no, mcc, status) + VALUES (:h_merch, :h_name, :h_bizno, :h_mcc, 'APPLIED'); + if (sqlca.sqlcode < 0) FAIL(b); + if (mmdb_insert_merch_hist(h_merch, "APPLY", "", "APPLIED", "가입 신청 접수") < 0) FAIL(b); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_APPLY_REJECT.h b/app/src/mm/svc/MM_APPLY_REJECT.h new file mode 100644 index 0000000..68c1a74 --- /dev/null +++ b/app/src/mm/svc/MM_APPLY_REJECT.h @@ -0,0 +1,12 @@ +/* + * MM_APPLY_REJECT.h - mm 서비스 MM_APPLY_REJECT 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_REASON, T_RC + */ +#ifndef MM_APPLY_REJECT_H +#define MM_APPLY_REJECT_H + +#include "mm_svc.h" + +void MM_APPLY_REJECT(TPSVCINFO *p); + +#endif /* MM_APPLY_REJECT_H */ diff --git a/app/src/mm/svc/MM_APPLY_REJECT.pgc b/app/src/mm/svc/MM_APPLY_REJECT.pgc new file mode 100644 index 0000000..0ff5a17 --- /dev/null +++ b/app/src/mm/svc/MM_APPLY_REJECT.pgc @@ -0,0 +1,36 @@ +/* + * MM_APPLY_REJECT.pgc - mm 모듈 서비스 MM_APPLY_REJECT (one service = one file). + * 기능: 가입 심사 반려 (APPLIED/REVIEW -> REJECTED, 반려 사유 필수). + * 입력: T_MERCHANT, T_REASON=반려 사유(필수). + * RC: 1=가맹점 없음, 2=심사중 상태가 아님, 3=반려 사유 누락. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_APPLY_REJECT.h" + +void MM_APPLY_REJECT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], reason[128]; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_stat[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_REASON, reason, sizeof(reason)); + if (reason[0] == 0) { setl(b, T_RC, 3); OK(b); } + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT status INTO :h_stat FROM mm_merchant_ext WHERE merchant_id = :h_merch; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + if (strcmp(h_stat, "APPLIED") != 0 && strcmp(h_stat, "REVIEW") != 0) { + setl(b, T_RC, 2); OK(b); + } + + EXEC SQL UPDATE mm_merchant_ext SET status = 'REJECTED', updated_at = now() + WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0) FAIL(b); + if (mmdb_insert_merch_hist(h_merch, "REJECT", h_stat, "REJECTED", reason) < 0) FAIL(b); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_APPLY_REVIEW.h b/app/src/mm/svc/MM_APPLY_REVIEW.h new file mode 100644 index 0000000..cd1e1cc --- /dev/null +++ b/app/src/mm/svc/MM_APPLY_REVIEW.h @@ -0,0 +1,12 @@ +/* + * MM_APPLY_REVIEW.h - mm 서비스 MM_APPLY_REVIEW 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_ARG1(MCC 등록 여부), T_RC + */ +#ifndef MM_APPLY_REVIEW_H +#define MM_APPLY_REVIEW_H + +#include "mm_svc.h" + +void MM_APPLY_REVIEW(TPSVCINFO *p); + +#endif /* MM_APPLY_REVIEW_H */ diff --git a/app/src/mm/svc/MM_APPLY_REVIEW.pgc b/app/src/mm/svc/MM_APPLY_REVIEW.pgc new file mode 100644 index 0000000..38feea3 --- /dev/null +++ b/app/src/mm/svc/MM_APPLY_REVIEW.pgc @@ -0,0 +1,40 @@ +/* + * MM_APPLY_REVIEW.pgc - mm 모듈 서비스 MM_APPLY_REVIEW (one service = one file). + * 기능: 가입 신청 심사 개시 (APPLIED -> REVIEW, MCC 유효성 사전점검). + * 입력: T_MERCHANT. 출력: T_ARG1=신청 MCC의 마스터 등록 여부(1/0). + * RC: 1=가맹점 없음, 2=APPLIED 상태가 아님. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_APPLY_REVIEW.h" + +void MM_APPLY_REVIEW(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], memo[128]; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_stat[16], h_mcc[8]; + long h_mccok; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT status, mcc INTO :h_stat, :h_mcc + FROM mm_merchant_ext WHERE merchant_id = :h_merch; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + if (strcmp(h_stat, "APPLIED") != 0) { setl(b, T_RC, 2); OK(b); } + + EXEC SQL SELECT count(*) INTO :h_mccok FROM mm_mcc WHERE mcc = :h_mcc; + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL UPDATE mm_merchant_ext SET status = 'REVIEW', updated_at = now() + WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0) FAIL(b); + snprintf(memo, sizeof(memo), "심사 개시 (MCC %s %s)", h_mcc, + h_mccok > 0 ? "등록됨" : "미등록"); + if (mmdb_insert_merch_hist(h_merch, "REVIEW", "APPLIED", "REVIEW", memo) < 0) FAIL(b); + setl(b, T_ARG1, h_mccok > 0 ? 1 : 0); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_APPLY_STATUS.h b/app/src/mm/svc/MM_APPLY_STATUS.h new file mode 100644 index 0000000..657fb30 --- /dev/null +++ b/app/src/mm/svc/MM_APPLY_STATUS.h @@ -0,0 +1,12 @@ +/* + * MM_APPLY_STATUS.h - mm 서비스 MM_APPLY_STATUS 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STATUS, T_ARG1(심사 단계), T_COUNT, T_RC + */ +#ifndef MM_APPLY_STATUS_H +#define MM_APPLY_STATUS_H + +#include "mm_svc.h" + +void MM_APPLY_STATUS(TPSVCINFO *p); + +#endif /* MM_APPLY_STATUS_H */ diff --git a/app/src/mm/svc/MM_APPLY_STATUS.pgc b/app/src/mm/svc/MM_APPLY_STATUS.pgc new file mode 100644 index 0000000..a929372 --- /dev/null +++ b/app/src/mm/svc/MM_APPLY_STATUS.pgc @@ -0,0 +1,42 @@ +/* + * MM_APPLY_STATUS.pgc - mm 모듈 서비스 MM_APPLY_STATUS (one service = one file). + * 기능: 가입 심사 진행 상태 조회 (단계 코드 + 워크플로 이력 건수). + * 입력: T_MERCHANT. 출력: T_STATUS=현재 상태, T_ARG1=단계(1접수 2심사 3승인 9반려), T_COUNT=워크플로 이력 수. + * RC: 1=가맹점 없음. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_APPLY_STATUS.h" + +void MM_APPLY_STATUS(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64]; + long stage = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_stat[16]; + long h_cnt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT status INTO :h_stat FROM mm_merchant_ext WHERE merchant_id = :h_merch; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + if (strcmp(h_stat, "APPLIED") == 0) stage = 1; + else if (strcmp(h_stat, "REVIEW") == 0) stage = 2; + else if (strcmp(h_stat, "ACTIVE") == 0) stage = 3; + else if (strcmp(h_stat, "REJECTED") == 0) stage = 9; + + EXEC SQL SELECT count(*) INTO :h_cnt FROM mm_merch_hist + WHERE merchant_id = :h_merch + AND action IN ('APPLY', 'REVIEW', 'APPROVE', 'REJECT'); + if (sqlca.sqlcode < 0) FAIL(b); + + Bchg(b, T_STATUS, 0, h_stat, 0L); + setl(b, T_ARG1, stage); + setl(b, T_COUNT, h_cnt); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_AUDIT_PURGE.h b/app/src/mm/svc/MM_AUDIT_PURGE.h new file mode 100644 index 0000000..170a555 --- /dev/null +++ b/app/src/mm/svc/MM_AUDIT_PURGE.h @@ -0,0 +1,12 @@ +/* + * MM_AUDIT_PURGE.h - mm 서비스 MM_AUDIT_PURGE 카피북 (copybook / record header). + * 사용 UBF 필드: T_ARG1(보존일수), T_COUNT(삭제 건수), T_RC + */ +#ifndef MM_AUDIT_PURGE_H +#define MM_AUDIT_PURGE_H + +#include "mm_svc.h" + +void MM_AUDIT_PURGE(TPSVCINFO *p); + +#endif /* MM_AUDIT_PURGE_H */ diff --git a/app/src/mm/svc/MM_AUDIT_PURGE.pgc b/app/src/mm/svc/MM_AUDIT_PURGE.pgc new file mode 100644 index 0000000..7c01256 --- /dev/null +++ b/app/src/mm/svc/MM_AUDIT_PURGE.pgc @@ -0,0 +1,36 @@ +/* + * MM_AUDIT_PURGE.pgc - mm 모듈 서비스 MM_AUDIT_PURGE (one service = one file). + * 기능: 감사 이력 보존기한 경과분 삭제 (삭제 내역 자체도 감사 기록으로 남김). + * 입력: T_ARG1=보존일수(기본 365, 최소 30). 출력: T_COUNT=삭제 건수. + * RC: 3=보존일수 30일 미만 불가. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_AUDIT_PURGE.h" + +void MM_AUDIT_PURGE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char memo[96]; + long purged; + EXEC SQL BEGIN DECLARE SECTION; + long h_days; + EXEC SQL END DECLARE SECTION; + + h_days = getl(b, T_ARG1); + if (h_days == 0) h_days = 365; + if (h_days < 30) { setl(b, T_RC, 3); OK(b); } + + EXEC SQL DELETE FROM mm_merch_hist + WHERE created_at < now() - :h_days * interval '1 day' + AND action <> 'AUDIT_PRG'; + if (sqlca.sqlcode < 0) FAIL(b); + purged = sqlca.sqlerrd[2]; + + if (purged > 0) { + snprintf(memo, sizeof(memo), "보존기한 %ld일 경과 이력 %ld건 삭제", h_days, purged); + if (mmdb_insert_merch_hist("*", "AUDIT_PRG", "", "", memo) < 0) FAIL(b); + } + setl(b, T_COUNT, purged); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_AUDIT_RECENT.h b/app/src/mm/svc/MM_AUDIT_RECENT.h new file mode 100644 index 0000000..bc6b0ca --- /dev/null +++ b/app/src/mm/svc/MM_AUDIT_RECENT.h @@ -0,0 +1,12 @@ +/* + * MM_AUDIT_RECENT.h - mm 서비스 MM_AUDIT_RECENT 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STR1(액션), T_STR2(변경 후 값), T_MSG(메모), T_BIZDATE(변경일), T_RC + */ +#ifndef MM_AUDIT_RECENT_H +#define MM_AUDIT_RECENT_H + +#include "mm_svc.h" + +void MM_AUDIT_RECENT(TPSVCINFO *p); + +#endif /* MM_AUDIT_RECENT_H */ diff --git a/app/src/mm/svc/MM_AUDIT_RECENT.pgc b/app/src/mm/svc/MM_AUDIT_RECENT.pgc new file mode 100644 index 0000000..05fd872 --- /dev/null +++ b/app/src/mm/svc/MM_AUDIT_RECENT.pgc @@ -0,0 +1,35 @@ +/* + * MM_AUDIT_RECENT.pgc - mm 모듈 서비스 MM_AUDIT_RECENT (one service = one file). + * 기능: 가맹점 최근 변경 감사 조회 (최신 이력 1건: 액션/변경값/메모/일자). + * 입력: T_MERCHANT. 출력: T_STR1, T_STR2, T_MSG, T_BIZDATE. + * RC: 1=변경 이력 없음. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_AUDIT_RECENT.h" + +void MM_AUDIT_RECENT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64]; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_act[32], h_after[128], h_memo[128], h_when[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT action, after_val, memo, to_char(created_at, 'YYYYMMDD') + INTO :h_act, :h_after, :h_memo, :h_when + FROM mm_merch_hist + WHERE merchant_id = :h_merch + ORDER BY hist_id DESC LIMIT 1; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + Bchg(b, T_STR1, 0, h_act, 0L); + Bchg(b, T_STR2, 0, h_after, 0L); + Bchg(b, T_MSG, 0, h_memo, 0L); + Bchg(b, T_BIZDATE, 0, h_when, 0L); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_AUDIT_TRAIL.h b/app/src/mm/svc/MM_AUDIT_TRAIL.h new file mode 100644 index 0000000..b204fea --- /dev/null +++ b/app/src/mm/svc/MM_AUDIT_TRAIL.h @@ -0,0 +1,12 @@ +/* + * MM_AUDIT_TRAIL.h - mm 서비스 MM_AUDIT_TRAIL 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STR1(action 필터), T_COUNT, T_ARG1(액션 종류 수), T_RC + */ +#ifndef MM_AUDIT_TRAIL_H +#define MM_AUDIT_TRAIL_H + +#include "mm_svc.h" + +void MM_AUDIT_TRAIL(TPSVCINFO *p); + +#endif /* MM_AUDIT_TRAIL_H */ diff --git a/app/src/mm/svc/MM_AUDIT_TRAIL.pgc b/app/src/mm/svc/MM_AUDIT_TRAIL.pgc new file mode 100644 index 0000000..6b07776 --- /dev/null +++ b/app/src/mm/svc/MM_AUDIT_TRAIL.pgc @@ -0,0 +1,35 @@ +/* + * MM_AUDIT_TRAIL.pgc - mm 모듈 서비스 MM_AUDIT_TRAIL (one service = one file). + * 기능: 가맹점 마스터 변경 감사 추적 (액션 필터별 이력 건수 + 발생 액션 종류 수). + * 입력: T_MERCHANT, T_STR1=액션 필터(빈값이면 전체). + * 출력: T_COUNT=이력 건수, T_ARG1=서로 다른 액션 종류 수. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_AUDIT_TRAIL.h" + +void MM_AUDIT_TRAIL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], act[32]; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_act[32]; + long h_cnt, h_kinds; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_STR1, act, sizeof(act)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_act, act, sizeof(h_act)-1); h_act[sizeof(h_act)-1] = 0; + + EXEC SQL SELECT count(*), count(DISTINCT action) + INTO :h_cnt, :h_kinds + FROM mm_merch_hist + WHERE merchant_id = :h_merch + AND (:h_act = '' OR action = :h_act); + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + setl(b, T_ARG1, h_kinds); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_BIZNO_CHECK.h b/app/src/mm/svc/MM_BIZNO_CHECK.h new file mode 100644 index 0000000..fe9b33c --- /dev/null +++ b/app/src/mm/svc/MM_BIZNO_CHECK.h @@ -0,0 +1,12 @@ +/* + * MM_BIZNO_CHECK.h - mm 서비스 MM_BIZNO_CHECK 카피북 (copybook / record header). + * 사용 UBF 필드: T_STR2(사업자번호), T_COUNT(중복 사용 가맹점 수), T_RC + */ +#ifndef MM_BIZNO_CHECK_H +#define MM_BIZNO_CHECK_H + +#include "mm_svc.h" + +void MM_BIZNO_CHECK(TPSVCINFO *p); + +#endif /* MM_BIZNO_CHECK_H */ diff --git a/app/src/mm/svc/MM_BIZNO_CHECK.pgc b/app/src/mm/svc/MM_BIZNO_CHECK.pgc new file mode 100644 index 0000000..1122a15 --- /dev/null +++ b/app/src/mm/svc/MM_BIZNO_CHECK.pgc @@ -0,0 +1,44 @@ +/* + * MM_BIZNO_CHECK.pgc - mm 모듈 서비스 MM_BIZNO_CHECK (one service = one file). + * 기능: 사업자등록번호 검증 (10자리 형식 + 국세청 검증공식 checksum + 마스터 중복 사용 확인). + * checksum: 가중치 1,3,7,1,3,7,1,3,5 곱 합 + floor(9번째*5/10), (10-합%10)%10 == 검증숫자. + * 입력: T_STR2=사업자번호. 출력: T_COUNT=해당 번호를 쓰는 기존 가맹점 수. + * RC: 0=유효, 2=형식 오류, 3=검증숫자 불일치. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_BIZNO_CHECK.h" + +static int bizno_checksum_ok(const char *no) +{ + static const int w[9] = { 1, 3, 7, 1, 3, 7, 1, 3, 5 }; + int i, sum = 0; + for (i = 0; i < 9; i++) sum += (no[i] - '0') * w[i]; + sum += ((no[8] - '0') * 5) / 10; + return ((10 - sum % 10) % 10) == (no[9] - '0'); +} + +void MM_BIZNO_CHECK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizno[16]; + int i; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizno[16]; + long h_used; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_STR2, bizno, sizeof(bizno)); + if (strlen(bizno) != 10) { setl(b, T_RC, 2); OK(b); } + for (i = 0; i < 10; i++) + if (bizno[i] < '0' || bizno[i] > '9') { setl(b, T_RC, 2); OK(b); } + if (!bizno_checksum_ok(bizno)) { setl(b, T_RC, 3); OK(b); } + + strncpy(h_bizno, bizno, sizeof(h_bizno)-1); h_bizno[sizeof(h_bizno)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_used FROM mm_merchant_ext + WHERE biz_reg_no = :h_bizno AND status <> 'DELETED'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_used); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_BIZNO_UPD.h b/app/src/mm/svc/MM_BIZNO_UPD.h new file mode 100644 index 0000000..7c13d21 --- /dev/null +++ b/app/src/mm/svc/MM_BIZNO_UPD.h @@ -0,0 +1,12 @@ +/* + * MM_BIZNO_UPD.h - mm 서비스 MM_BIZNO_UPD 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STR2(신규 사업자번호), T_STR1(기존 번호), T_RC + */ +#ifndef MM_BIZNO_UPD_H +#define MM_BIZNO_UPD_H + +#include "mm_svc.h" + +void MM_BIZNO_UPD(TPSVCINFO *p); + +#endif /* MM_BIZNO_UPD_H */ diff --git a/app/src/mm/svc/MM_BIZNO_UPD.pgc b/app/src/mm/svc/MM_BIZNO_UPD.pgc new file mode 100644 index 0000000..2e02a29 --- /dev/null +++ b/app/src/mm/svc/MM_BIZNO_UPD.pgc @@ -0,0 +1,46 @@ +/* + * MM_BIZNO_UPD.pgc - mm 모듈 서비스 MM_BIZNO_UPD (one service = one file). + * 기능: 사업자등록번호 변경 (형식 검사 + 타 가맹점 중복 차단 + 변경 이력). + * 입력: T_MERCHANT, T_STR2=신규 번호. 출력: T_STR1=기존 번호. + * RC: 1=가맹점 없음, 2=형식 오류, 4=타 가맹점이 이미 사용, 5=기존 번호와 동일. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_BIZNO_UPD.h" + +void MM_BIZNO_UPD(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizno[16]; + int i; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_new[16], h_old[16]; + long h_other; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_STR2, bizno, sizeof(bizno)); + if (strlen(bizno) != 10) { setl(b, T_RC, 2); OK(b); } + for (i = 0; i < 10; i++) + if (bizno[i] < '0' || bizno[i] > '9') { setl(b, T_RC, 2); OK(b); } + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_new, bizno, sizeof(h_new)-1); h_new[sizeof(h_new)-1] = 0; + + EXEC SQL SELECT biz_reg_no INTO :h_old FROM mm_merchant_ext + WHERE merchant_id = :h_merch; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + if (strcmp(h_old, h_new) == 0) { setl(b, T_RC, 5); OK(b); } + + EXEC SQL SELECT count(*) INTO :h_other FROM mm_merchant_ext + WHERE biz_reg_no = :h_new AND merchant_id <> :h_merch AND status <> 'DELETED'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_other > 0) { setl(b, T_RC, 4); OK(b); } + + EXEC SQL UPDATE mm_merchant_ext SET biz_reg_no = :h_new, updated_at = now() + WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0) FAIL(b); + if (mmdb_insert_merch_hist(h_merch, "BIZNO", h_old, h_new, "사업자번호 변경") < 0) FAIL(b); + Bchg(b, T_STR1, 0, h_old, 0L); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_CHK_FEERATE.h b/app/src/mm/svc/MM_CHK_FEERATE.h new file mode 100644 index 0000000..2c9c61d --- /dev/null +++ b/app/src/mm/svc/MM_CHK_FEERATE.h @@ -0,0 +1,12 @@ +/* + * MM_CHK_FEERATE.h - mm 서비스 MM_CHK_FEERATE 카피북 (copybook / record header). + * 사용 UBF 필드: T_COUNT(요율 미설정), T_ARG1(범위 이탈 요율), T_ARG2(고아 요율), T_RC + */ +#ifndef MM_CHK_FEERATE_H +#define MM_CHK_FEERATE_H + +#include "mm_svc.h" + +void MM_CHK_FEERATE(TPSVCINFO *p); + +#endif /* MM_CHK_FEERATE_H */ diff --git a/app/src/mm/svc/MM_CHK_FEERATE.pgc b/app/src/mm/svc/MM_CHK_FEERATE.pgc new file mode 100644 index 0000000..1eb488e --- /dev/null +++ b/app/src/mm/svc/MM_CHK_FEERATE.pgc @@ -0,0 +1,37 @@ +/* + * MM_CHK_FEERATE.pgc - mm 모듈 서비스 MM_CHK_FEERATE (one service = one file). + * 기능: 수수료율 마스터 정합성 점검 (활성 가맹점 요율 누락 / bps 범위 이탈 / 고아 요율). + * 출력: T_COUNT=요율 없는 활성 가맹점, T_ARG1=bps 0 이하 또는 500 초과, T_ARG2=가맹점 없는 요율. + * T_RC=0 정상 / 9 불일치 존재. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_CHK_FEERATE.h" + +void MM_CHK_FEERATE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_missing, h_range, h_orphan; + EXEC SQL END DECLARE SECTION; + + EXEC SQL SELECT count(*) INTO :h_missing FROM mm_merchant_ext m + WHERE m.status = 'ACTIVE' + AND NOT EXISTS (SELECT 1 FROM mm_fee_rate f + WHERE f.merchant_id = m.merchant_id); + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT count(*) INTO :h_range FROM mm_fee_rate + WHERE rate_bps <= 0 OR rate_bps > 500; + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT count(*) INTO :h_orphan FROM mm_fee_rate f + WHERE NOT EXISTS (SELECT 1 FROM mm_merchant_ext m + WHERE m.merchant_id = f.merchant_id); + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_missing); + setl(b, T_ARG1, h_range); + setl(b, T_ARG2, h_orphan); + setl(b, T_RC, (h_missing + h_range + h_orphan) > 0 ? 9 : 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_CHK_LIMIT.h b/app/src/mm/svc/MM_CHK_LIMIT.h new file mode 100644 index 0000000..80bb80b --- /dev/null +++ b/app/src/mm/svc/MM_CHK_LIMIT.h @@ -0,0 +1,12 @@ +/* + * MM_CHK_LIMIT.h - mm 서비스 MM_CHK_LIMIT 카피북 (copybook / record header). + * 사용 UBF 필드: T_COUNT(한도 미설정), T_ARG1(한도 역전), T_RC + */ +#ifndef MM_CHK_LIMIT_H +#define MM_CHK_LIMIT_H + +#include "mm_svc.h" + +void MM_CHK_LIMIT(TPSVCINFO *p); + +#endif /* MM_CHK_LIMIT_H */ diff --git a/app/src/mm/svc/MM_CHK_LIMIT.pgc b/app/src/mm/svc/MM_CHK_LIMIT.pgc new file mode 100644 index 0000000..0d90176 --- /dev/null +++ b/app/src/mm/svc/MM_CHK_LIMIT.pgc @@ -0,0 +1,31 @@ +/* + * MM_CHK_LIMIT.pgc - mm 모듈 서비스 MM_CHK_LIMIT (one service = one file). + * 기능: 한도 마스터 정합성 점검 (활성 가맹점 한도 누락 + 한도 체계 역전 검출). + * 역전 = 건당한도 > 일한도 또는 일한도 > 월한도. + * 출력: T_COUNT=한도 없는 활성 가맹점, T_ARG1=역전 한도 행. T_RC=0 정상 / 9 불일치 존재. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_CHK_LIMIT.h" + +void MM_CHK_LIMIT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_missing, h_invert; + EXEC SQL END DECLARE SECTION; + + EXEC SQL SELECT count(*) INTO :h_missing FROM mm_merchant_ext m + WHERE m.status = 'ACTIVE' + AND NOT EXISTS (SELECT 1 FROM mm_limit l + WHERE l.merchant_id = m.merchant_id); + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT count(*) INTO :h_invert FROM mm_limit + WHERE per_txn_limit > daily_limit OR daily_limit > monthly_limit; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_missing); + setl(b, T_ARG1, h_invert); + setl(b, T_RC, (h_missing + h_invert) > 0 ? 9 : 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_CHK_TERM.h b/app/src/mm/svc/MM_CHK_TERM.h new file mode 100644 index 0000000..4205782 --- /dev/null +++ b/app/src/mm/svc/MM_CHK_TERM.h @@ -0,0 +1,12 @@ +/* + * MM_CHK_TERM.h - mm 서비스 MM_CHK_TERM 카피북 (copybook / record header). + * 사용 UBF 필드: T_COUNT(고아 단말), T_ARG1(폐업 가맹점 가동 단말), T_RC + */ +#ifndef MM_CHK_TERM_H +#define MM_CHK_TERM_H + +#include "mm_svc.h" + +void MM_CHK_TERM(TPSVCINFO *p); + +#endif /* MM_CHK_TERM_H */ diff --git a/app/src/mm/svc/MM_CHK_TERM.pgc b/app/src/mm/svc/MM_CHK_TERM.pgc new file mode 100644 index 0000000..47899c2 --- /dev/null +++ b/app/src/mm/svc/MM_CHK_TERM.pgc @@ -0,0 +1,30 @@ +/* + * MM_CHK_TERM.pgc - mm 모듈 서비스 MM_CHK_TERM (one service = one file). + * 기능: 단말 마스터 정합성 점검 (소속 가맹점 없는 고아 단말 + 폐업 가맹점의 가동 단말). + * 출력: T_COUNT=고아 단말 수, T_ARG1=폐업 가맹점 소속 미폐기 단말 수, T_RC=0 정상 / 9 불일치 존재. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_CHK_TERM.h" + +void MM_CHK_TERM(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_orphan, h_zombie; + EXEC SQL END DECLARE SECTION; + + EXEC SQL SELECT count(*) INTO :h_orphan FROM mm_terminal t + WHERE NOT EXISTS (SELECT 1 FROM mm_merchant_ext m + WHERE m.merchant_id = t.merchant_id); + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT count(*) INTO :h_zombie FROM mm_terminal t + JOIN mm_merchant_ext m ON m.merchant_id = t.merchant_id + WHERE m.status = 'DELETED' AND t.status <> 'SCRAPPED'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_orphan); + setl(b, T_ARG1, h_zombie); + setl(b, T_RC, (h_orphan + h_zombie) > 0 ? 9 : 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_CODEMAP_DEL.h b/app/src/mm/svc/MM_CODEMAP_DEL.h new file mode 100644 index 0000000..9fad374 --- /dev/null +++ b/app/src/mm/svc/MM_CODEMAP_DEL.h @@ -0,0 +1,12 @@ +/* + * MM_CODEMAP_DEL.h - mm 서비스 MM_CODEMAP_DEL 카피북 (copybook / record header). + * 사용 UBF 필드: T_KEY1(네임스페이스), T_STR1(원본 코드; 빈값=네임스페이스 전체), T_COUNT, T_RC + */ +#ifndef MM_CODEMAP_DEL_H +#define MM_CODEMAP_DEL_H + +#include "mm_svc.h" + +void MM_CODEMAP_DEL(TPSVCINFO *p); + +#endif /* MM_CODEMAP_DEL_H */ diff --git a/app/src/mm/svc/MM_CODEMAP_DEL.pgc b/app/src/mm/svc/MM_CODEMAP_DEL.pgc new file mode 100644 index 0000000..c109df0 --- /dev/null +++ b/app/src/mm/svc/MM_CODEMAP_DEL.pgc @@ -0,0 +1,37 @@ +/* + * MM_CODEMAP_DEL.pgc - mm 모듈 서비스 MM_CODEMAP_DEL (one service = one file). + * 기능: 코드 매핑 삭제 (단건 또는 네임스페이스 전체 일괄 삭제). + * 입력: T_KEY1=네임스페이스, T_STR1=원본 코드(빈값이면 네임스페이스 전체 삭제). + * 출력: T_COUNT=삭제 건수. RC: 1=대상 없음, 3=네임스페이스 누락. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_CODEMAP_DEL.h" + +void MM_CODEMAP_DEL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char ns[16], src[32]; + long ndel; + EXEC SQL BEGIN DECLARE SECTION; + char h_grp[24], h_src[32]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, ns, sizeof(ns)); + gets_(b, T_STR1, src, sizeof(src)); + if (ns[0] == 0) { setl(b, T_RC, 3); OK(b); } + snprintf(h_grp, sizeof(h_grp), "MAP_%s", ns); + strncpy(h_src, src, sizeof(h_src)-1); h_src[sizeof(h_src)-1] = 0; + + if (h_src[0] == 0) { + EXEC SQL DELETE FROM mm_code WHERE code_group = :h_grp; + } else { + EXEC SQL DELETE FROM mm_code WHERE code_group = :h_grp AND code = :h_src; + } + if (sqlca.sqlcode < 0) FAIL(b); + ndel = sqlca.sqlerrd[2]; + if (ndel == 0) { setl(b, T_RC, 1); OK(b); } + + setl(b, T_COUNT, ndel); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_CODEMAP_GET.h b/app/src/mm/svc/MM_CODEMAP_GET.h new file mode 100644 index 0000000..47e54ad --- /dev/null +++ b/app/src/mm/svc/MM_CODEMAP_GET.h @@ -0,0 +1,12 @@ +/* + * MM_CODEMAP_GET.h - mm 서비스 MM_CODEMAP_GET 카피북 (copybook / record header). + * 사용 UBF 필드: T_KEY1(네임스페이스), T_STR1(원본 코드), T_STR2(변환 결과), T_RC + */ +#ifndef MM_CODEMAP_GET_H +#define MM_CODEMAP_GET_H + +#include "mm_svc.h" + +void MM_CODEMAP_GET(TPSVCINFO *p); + +#endif /* MM_CODEMAP_GET_H */ diff --git a/app/src/mm/svc/MM_CODEMAP_GET.pgc b/app/src/mm/svc/MM_CODEMAP_GET.pgc new file mode 100644 index 0000000..27b3ecf --- /dev/null +++ b/app/src/mm/svc/MM_CODEMAP_GET.pgc @@ -0,0 +1,36 @@ +/* + * MM_CODEMAP_GET.pgc - mm 모듈 서비스 MM_CODEMAP_GET (one service = one file). + * 기능: 코드 매핑 변환 조회 (네임스페이스의 원본 코드를 대상 코드로 변환). + * 입력: T_KEY1=네임스페이스, T_STR1=원본 코드. 출력: T_STR2=변환된 대상 코드. + * RC: 1=활성 매핑 없음 (이때 T_STR2에는 원본 코드를 그대로 반환 - passthrough). + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_CODEMAP_GET.h" + +void MM_CODEMAP_GET(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char ns[16], src[32]; + EXEC SQL BEGIN DECLARE SECTION; + char h_grp[24], h_src[32], h_dst[32]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, ns, sizeof(ns)); + gets_(b, T_STR1, src, sizeof(src)); + snprintf(h_grp, sizeof(h_grp), "MAP_%s", ns); + strncpy(h_src, src, sizeof(h_src)-1); h_src[sizeof(h_src)-1] = 0; + + EXEC SQL SELECT code_name INTO :h_dst FROM mm_code + WHERE code_group = :h_grp AND code = :h_src AND status = 'ACTIVE'; + if (sqlca.sqlcode == 100) { + /* 매핑이 없으면 원본 코드 그대로 통과 */ + Bchg(b, T_STR2, 0, h_src, 0L); + setl(b, T_RC, 1); + OK(b); + } + if (sqlca.sqlcode < 0) FAIL(b); + + Bchg(b, T_STR2, 0, h_dst, 0L); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_CODEMAP_SET.h b/app/src/mm/svc/MM_CODEMAP_SET.h new file mode 100644 index 0000000..dcb0ffa --- /dev/null +++ b/app/src/mm/svc/MM_CODEMAP_SET.h @@ -0,0 +1,12 @@ +/* + * MM_CODEMAP_SET.h - mm 서비스 MM_CODEMAP_SET 카피북 (copybook / record header). + * 사용 UBF 필드: T_KEY1(매핑 네임스페이스), T_STR1(원본 코드), T_STR2(대상 코드), T_RC + */ +#ifndef MM_CODEMAP_SET_H +#define MM_CODEMAP_SET_H + +#include "mm_svc.h" + +void MM_CODEMAP_SET(TPSVCINFO *p); + +#endif /* MM_CODEMAP_SET_H */ diff --git a/app/src/mm/svc/MM_CODEMAP_SET.pgc b/app/src/mm/svc/MM_CODEMAP_SET.pgc new file mode 100644 index 0000000..166e25a --- /dev/null +++ b/app/src/mm/svc/MM_CODEMAP_SET.pgc @@ -0,0 +1,33 @@ +/* + * MM_CODEMAP_SET.pgc - mm 모듈 서비스 MM_CODEMAP_SET (one service = one file). + * 기능: 코드 매핑 등록/갱신 (외부코드->내부코드 변환표, MAP_<네임스페이스> 코드그룹 upsert). + * 입력: T_KEY1=네임스페이스(예: VAN1), T_STR1=원본 코드, T_STR2=대상 코드. + * RC: 3=필수값 누락. 재등록 시 대상 코드 교체 + 상태 ACTIVE 복원. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_CODEMAP_SET.h" + +void MM_CODEMAP_SET(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char ns[16], src[32], dst[32]; + EXEC SQL BEGIN DECLARE SECTION; + char h_grp[24], h_src[32], h_dst[32]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, ns, sizeof(ns)); + gets_(b, T_STR1, src, sizeof(src)); + gets_(b, T_STR2, dst, sizeof(dst)); + if (ns[0] == 0 || src[0] == 0 || dst[0] == 0) { setl(b, T_RC, 3); OK(b); } + snprintf(h_grp, sizeof(h_grp), "MAP_%s", ns); + strncpy(h_src, src, sizeof(h_src)-1); h_src[sizeof(h_src)-1] = 0; + strncpy(h_dst, dst, sizeof(h_dst)-1); h_dst[sizeof(h_dst)-1] = 0; + + EXEC SQL INSERT INTO mm_code (code_group, code, code_name, status) + VALUES (:h_grp, :h_src, :h_dst, 'ACTIVE') + ON CONFLICT (code_group, code) + DO UPDATE SET code_name = EXCLUDED.code_name, status = 'ACTIVE'; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_CONTRACT_END.h b/app/src/mm/svc/MM_CONTRACT_END.h new file mode 100644 index 0000000..7c21327 --- /dev/null +++ b/app/src/mm/svc/MM_CONTRACT_END.h @@ -0,0 +1,12 @@ +/* + * MM_CONTRACT_END.h - mm 서비스 MM_CONTRACT_END 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_REASON, T_RC + */ +#ifndef MM_CONTRACT_END_H +#define MM_CONTRACT_END_H + +#include "mm_svc.h" + +void MM_CONTRACT_END(TPSVCINFO *p); + +#endif /* MM_CONTRACT_END_H */ diff --git a/app/src/mm/svc/MM_CONTRACT_END.pgc b/app/src/mm/svc/MM_CONTRACT_END.pgc new file mode 100644 index 0000000..9e85c63 --- /dev/null +++ b/app/src/mm/svc/MM_CONTRACT_END.pgc @@ -0,0 +1,43 @@ +/* + * MM_CONTRACT_END.pgc - mm 모듈 서비스 MM_CONTRACT_END (one service = one file). + * 기능: 가맹 계약 해지 (CONT_END 이력 + 가맹점 상태 HOLD 전환). + * 입력: T_MERCHANT, T_REASON=해지 사유(필수). + * RC: 1=체결된 계약 없음, 2=이미 해지됨, 3=해지 사유 누락. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_CONTRACT_END.h" + +void MM_CONTRACT_END(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], reason[128]; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_end[16]; + long h_ended; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_REASON, reason, sizeof(reason)); + if (reason[0] == 0) { setl(b, T_RC, 3); OK(b); } + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT after_val INTO :h_end FROM mm_merch_hist + WHERE merchant_id = :h_merch AND action = 'CONTRACT' + ORDER BY hist_id DESC LIMIT 1; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT count(*) INTO :h_ended FROM mm_merch_hist + WHERE merchant_id = :h_merch AND action = 'CONT_END' + AND hist_id > (SELECT max(hist_id) FROM mm_merch_hist + WHERE merchant_id = :h_merch AND action = 'CONTRACT'); + if (sqlca.sqlcode < 0) FAIL(b); + if (h_ended > 0) { setl(b, T_RC, 2); OK(b); } + + EXEC SQL UPDATE mm_merchant_ext SET status = 'HOLD', updated_at = now() + WHERE merchant_id = :h_merch AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) FAIL(b); + if (mmdb_insert_merch_hist(h_merch, "CONT_END", h_end, "", reason) < 0) FAIL(b); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_CONTRACT_INQ.h b/app/src/mm/svc/MM_CONTRACT_INQ.h new file mode 100644 index 0000000..21bd514 --- /dev/null +++ b/app/src/mm/svc/MM_CONTRACT_INQ.h @@ -0,0 +1,12 @@ +/* + * MM_CONTRACT_INQ.h - mm 서비스 MM_CONTRACT_INQ 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_COUNT(체결 횟수), T_STR1(최종 만기), T_ARG1(유효 여부), T_RC + */ +#ifndef MM_CONTRACT_INQ_H +#define MM_CONTRACT_INQ_H + +#include "mm_svc.h" + +void MM_CONTRACT_INQ(TPSVCINFO *p); + +#endif /* MM_CONTRACT_INQ_H */ diff --git a/app/src/mm/svc/MM_CONTRACT_INQ.pgc b/app/src/mm/svc/MM_CONTRACT_INQ.pgc new file mode 100644 index 0000000..dfb5142 --- /dev/null +++ b/app/src/mm/svc/MM_CONTRACT_INQ.pgc @@ -0,0 +1,37 @@ +/* + * MM_CONTRACT_INQ.pgc - mm 모듈 서비스 MM_CONTRACT_INQ (one service = one file). + * 기능: 가맹 계약 현황 조회 (체결/갱신 횟수, 최종 만기일, 유효 여부). + * 입력: T_MERCHANT. 출력: T_COUNT=체결·갱신 누계, T_STR1=최종 만기, T_ARG1=1이면 만기 미도래. + * RC: 1=계약 이력 없음. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_CONTRACT_INQ.h" + +void MM_CONTRACT_INQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64]; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_end[16], h_today[16]; + long h_cnt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT count(*), coalesce(max(after_val), '') + INTO :h_cnt, :h_end + FROM mm_merch_hist + WHERE merchant_id = :h_merch AND action = 'CONTRACT'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt == 0) { setl(b, T_RC, 1); OK(b); } + + EXEC SQL SELECT to_char(current_date, 'YYYYMMDD') INTO :h_today; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + Bchg(b, T_STR1, 0, h_end, 0L); + setl(b, T_ARG1, strcmp(h_end, h_today) > 0 ? 1 : 0); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_CONTRACT_NEW.h b/app/src/mm/svc/MM_CONTRACT_NEW.h new file mode 100644 index 0000000..c1f8aed --- /dev/null +++ b/app/src/mm/svc/MM_CONTRACT_NEW.h @@ -0,0 +1,12 @@ +/* + * MM_CONTRACT_NEW.h - mm 서비스 MM_CONTRACT_NEW 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_ARG1(계약기간 개월), T_STR1(만기일), T_RC + */ +#ifndef MM_CONTRACT_NEW_H +#define MM_CONTRACT_NEW_H + +#include "mm_svc.h" + +void MM_CONTRACT_NEW(TPSVCINFO *p); + +#endif /* MM_CONTRACT_NEW_H */ diff --git a/app/src/mm/svc/MM_CONTRACT_NEW.pgc b/app/src/mm/svc/MM_CONTRACT_NEW.pgc new file mode 100644 index 0000000..3d79979 --- /dev/null +++ b/app/src/mm/svc/MM_CONTRACT_NEW.pgc @@ -0,0 +1,44 @@ +/* + * MM_CONTRACT_NEW.pgc - mm 모듈 서비스 MM_CONTRACT_NEW (one service = one file). + * 기능: 가맹 계약 체결 (기간 개월수로 만기일 산출, CONTRACT 이력 기록). + * 입력: T_MERCHANT, T_ARG1=계약기간 개월(1~120, 기본 12). 출력: T_STR1=만기일(YYYYMMDD). + * RC: 1=활성 가맹점 아님, 3=기간 범위 오류, 4=미만기 계약 이미 존재. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_CONTRACT_NEW.h" + +void MM_CONTRACT_NEW(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64]; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_end[16]; + long h_mon, h_alive, h_open; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + h_mon = getl(b, T_ARG1); + if (h_mon == 0) h_mon = 12; + if (h_mon < 1 || h_mon > 120) { setl(b, T_RC, 3); OK(b); } + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT count(*) INTO :h_alive FROM mm_merchant_ext + WHERE merchant_id = :h_merch AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_alive == 0) { setl(b, T_RC, 1); OK(b); } + + /* 만기 미도래 계약이 남아 있으면 신규 체결 불가 */ + EXEC SQL SELECT count(*) INTO :h_open FROM mm_merch_hist + WHERE merchant_id = :h_merch AND action = 'CONTRACT' + AND after_val > to_char(current_date, 'YYYYMMDD'); + if (sqlca.sqlcode < 0) FAIL(b); + if (h_open > 0) { setl(b, T_RC, 4); OK(b); } + + EXEC SQL SELECT to_char(current_date + :h_mon * interval '1 month', 'YYYYMMDD') + INTO :h_end; + if (sqlca.sqlcode < 0) FAIL(b); + if (mmdb_insert_merch_hist(h_merch, "CONTRACT", "", h_end, "가맹 계약 체결") < 0) FAIL(b); + Bchg(b, T_STR1, 0, h_end, 0L); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_CONTRACT_RENEW.h b/app/src/mm/svc/MM_CONTRACT_RENEW.h new file mode 100644 index 0000000..456b74e --- /dev/null +++ b/app/src/mm/svc/MM_CONTRACT_RENEW.h @@ -0,0 +1,12 @@ +/* + * MM_CONTRACT_RENEW.h - mm 서비스 MM_CONTRACT_RENEW 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_ARG1(연장 개월), T_STR1(기존 만기), T_STR2(신규 만기), T_RC + */ +#ifndef MM_CONTRACT_RENEW_H +#define MM_CONTRACT_RENEW_H + +#include "mm_svc.h" + +void MM_CONTRACT_RENEW(TPSVCINFO *p); + +#endif /* MM_CONTRACT_RENEW_H */ diff --git a/app/src/mm/svc/MM_CONTRACT_RENEW.pgc b/app/src/mm/svc/MM_CONTRACT_RENEW.pgc new file mode 100644 index 0000000..2ed81af --- /dev/null +++ b/app/src/mm/svc/MM_CONTRACT_RENEW.pgc @@ -0,0 +1,47 @@ +/* + * MM_CONTRACT_RENEW.pgc - mm 모듈 서비스 MM_CONTRACT_RENEW (one service = one file). + * 기능: 가맹 계약 갱신 (직전 만기일 기준 개월수 연장). + * 입력: T_MERCHANT, T_ARG1=연장 개월(기본 12). 출력: T_STR1=기존 만기, T_STR2=신규 만기. + * RC: 1=체결된 계약 없음, 2=해지된 계약(갱신 불가), 3=기간 범위 오류. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_CONTRACT_RENEW.h" + +void MM_CONTRACT_RENEW(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64]; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_old[16], h_new[16]; + long h_mon, h_ended; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + h_mon = getl(b, T_ARG1); + if (h_mon == 0) h_mon = 12; + if (h_mon < 1 || h_mon > 120) { setl(b, T_RC, 3); OK(b); } + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT after_val INTO :h_old FROM mm_merch_hist + WHERE merchant_id = :h_merch AND action = 'CONTRACT' + ORDER BY hist_id DESC LIMIT 1; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + /* 만기 이후 해지 이력이 있으면 갱신 불가 */ + EXEC SQL SELECT count(*) INTO :h_ended FROM mm_merch_hist + WHERE merchant_id = :h_merch AND action = 'CONT_END' + AND hist_id > (SELECT max(hist_id) FROM mm_merch_hist + WHERE merchant_id = :h_merch AND action = 'CONTRACT'); + if (sqlca.sqlcode < 0) FAIL(b); + if (h_ended > 0) { setl(b, T_RC, 2); OK(b); } + + EXEC SQL SELECT to_char(to_date(:h_old, 'YYYYMMDD') + :h_mon * interval '1 month', 'YYYYMMDD') + INTO :h_new; + if (sqlca.sqlcode < 0) FAIL(b); + if (mmdb_insert_merch_hist(h_merch, "CONTRACT", h_old, h_new, "계약 갱신 연장") < 0) FAIL(b); + Bchg(b, T_STR1, 0, h_old, 0L); + Bchg(b, T_STR2, 0, h_new, 0L); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_GRADE_ADJUST.h b/app/src/mm/svc/MM_GRADE_ADJUST.h new file mode 100644 index 0000000..0eb2e8f --- /dev/null +++ b/app/src/mm/svc/MM_GRADE_ADJUST.h @@ -0,0 +1,12 @@ +/* + * MM_GRADE_ADJUST.h - mm 서비스 MM_GRADE_ADJUST 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STR2(조정 등급), T_REASON(필수), T_STR1(이전 등급), T_RC + */ +#ifndef MM_GRADE_ADJUST_H +#define MM_GRADE_ADJUST_H + +#include "mm_svc.h" + +void MM_GRADE_ADJUST(TPSVCINFO *p); + +#endif /* MM_GRADE_ADJUST_H */ diff --git a/app/src/mm/svc/MM_GRADE_ADJUST.pgc b/app/src/mm/svc/MM_GRADE_ADJUST.pgc new file mode 100644 index 0000000..ebc4bd7 --- /dev/null +++ b/app/src/mm/svc/MM_GRADE_ADJUST.pgc @@ -0,0 +1,38 @@ +/* + * MM_GRADE_ADJUST.pgc - mm 모듈 서비스 MM_GRADE_ADJUST (one service = one file). + * 기능: 가맹점 등급 수동 보정 (산정 결과 예외 조정, 조정 사유 필수 감사 기록). + * 입력: T_MERCHANT, T_STR2=조정 등급(S/A/B/C), T_REASON=조정 사유(필수). + * 출력: T_STR1=이전 등급. RC: 1=가맹점 없음, 2=동일 등급, 3=등급값 오류, 4=사유 누락. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_GRADE_ADJUST.h" + +void MM_GRADE_ADJUST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], grade[8], reason[128]; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_old[8], h_new[8]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_STR2, grade, sizeof(grade)); + gets_(b, T_REASON, reason, sizeof(reason)); + if (strlen(grade) != 1 || strchr("SABC", grade[0]) == NULL) { setl(b, T_RC, 3); OK(b); } + if (reason[0] == 0) { setl(b, T_RC, 4); OK(b); } + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_new, grade, sizeof(h_new)-1); h_new[sizeof(h_new)-1] = 0; + + EXEC SQL SELECT grade INTO :h_old FROM mm_merchant_ext WHERE merchant_id = :h_merch; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + if (strcmp(h_old, h_new) == 0) { setl(b, T_RC, 2); OK(b); } + + EXEC SQL UPDATE mm_merchant_ext SET grade = :h_new, updated_at = now() + WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0) FAIL(b); + if (mmdb_insert_merch_hist(h_merch, "GRADE_ADJ", h_old, h_new, reason) < 0) FAIL(b); + Bchg(b, T_STR1, 0, h_old, 0L); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_GRADE_DIST.h b/app/src/mm/svc/MM_GRADE_DIST.h new file mode 100644 index 0000000..8920911 --- /dev/null +++ b/app/src/mm/svc/MM_GRADE_DIST.h @@ -0,0 +1,12 @@ +/* + * MM_GRADE_DIST.h - mm 서비스 MM_GRADE_DIST 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE(기준일), T_ARG1~T_ARG4(S/A/B/C 분포), T_COUNT(합계), T_RC + */ +#ifndef MM_GRADE_DIST_H +#define MM_GRADE_DIST_H + +#include "mm_svc.h" + +void MM_GRADE_DIST(TPSVCINFO *p); + +#endif /* MM_GRADE_DIST_H */ diff --git a/app/src/mm/svc/MM_GRADE_DIST.pgc b/app/src/mm/svc/MM_GRADE_DIST.pgc new file mode 100644 index 0000000..4778d28 --- /dev/null +++ b/app/src/mm/svc/MM_GRADE_DIST.pgc @@ -0,0 +1,42 @@ +/* + * MM_GRADE_DIST.pgc - mm 모듈 서비스 MM_GRADE_DIST (one service = one file). + * 기능: 가맹점 등급 분포 조회 (최근 등급 통계일 기준 S/A/B/C 세분 현황). + * 출력: T_BIZDATE=통계 기준일, T_ARG1=S, T_ARG2=A, T_ARG3=B, T_ARG4=C, T_COUNT=합계. + * RC: 1=통계 미생성 (mm_stat_batch 선행 필요). + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_GRADE_DIST.h" + +void MM_GRADE_DIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_date[16]; + long h_s, h_a, h_b, h_c, h_rows; + EXEC SQL END DECLARE SECTION; + + EXEC SQL SELECT count(*) INTO :h_rows FROM mm_grade_stat; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_rows == 0) { setl(b, T_RC, 1); OK(b); } + + EXEC SQL SELECT to_char(max(stat_date), 'YYYYMMDD') INTO :h_date FROM mm_grade_stat; + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT coalesce(sum(CASE WHEN grade = 'S' THEN merch_cnt ELSE 0 END), 0), + coalesce(sum(CASE WHEN grade = 'A' THEN merch_cnt ELSE 0 END), 0), + coalesce(sum(CASE WHEN grade = 'B' THEN merch_cnt ELSE 0 END), 0), + coalesce(sum(CASE WHEN grade = 'C' THEN merch_cnt ELSE 0 END), 0) + INTO :h_s, :h_a, :h_b, :h_c + FROM mm_grade_stat + WHERE stat_date = (SELECT max(stat_date) FROM mm_grade_stat); + if (sqlca.sqlcode < 0) FAIL(b); + + Bchg(b, T_BIZDATE, 0, h_date, 0L); + setl(b, T_ARG1, h_s); + setl(b, T_ARG2, h_a); + setl(b, T_ARG3, h_b); + setl(b, T_ARG4, h_c); + setl(b, T_COUNT, h_s + h_a + h_b + h_c); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_GRADE_PREVIEW.h b/app/src/mm/svc/MM_GRADE_PREVIEW.h new file mode 100644 index 0000000..3d7d857 --- /dev/null +++ b/app/src/mm/svc/MM_GRADE_PREVIEW.h @@ -0,0 +1,12 @@ +/* + * MM_GRADE_PREVIEW.h - mm 서비스 MM_GRADE_PREVIEW 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STR1(현재 등급), T_STR2(산정 등급), T_GROSS, T_ARG1(변경 필요), T_RC + */ +#ifndef MM_GRADE_PREVIEW_H +#define MM_GRADE_PREVIEW_H + +#include "mm_svc.h" + +void MM_GRADE_PREVIEW(TPSVCINFO *p); + +#endif /* MM_GRADE_PREVIEW_H */ diff --git a/app/src/mm/svc/MM_GRADE_PREVIEW.pgc b/app/src/mm/svc/MM_GRADE_PREVIEW.pgc new file mode 100644 index 0000000..e61b423 --- /dev/null +++ b/app/src/mm/svc/MM_GRADE_PREVIEW.pgc @@ -0,0 +1,43 @@ +/* + * MM_GRADE_PREVIEW.pgc - mm 모듈 서비스 MM_GRADE_PREVIEW (one service = one file). + * 기능: 가맹점 등급 산정 시뮬레이션 (누적 매출로 등급 산출, 마스터는 불변). + * 임계: S>=10억, A>=3억, B>=5천만, 그 외 C (배치 재산정과 동일 기준). + * 입력: T_MERCHANT. 출력: T_STR1=현재, T_STR2=산정, T_GROSS=누적 매출, T_ARG1=1이면 등급 변경 대상. + * RC: 1=가맹점 없음. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_GRADE_PREVIEW.h" + +void MM_GRADE_PREVIEW(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64]; + const char *calc; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_cur[8]; + long h_gross; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT grade INTO :h_cur FROM mm_merchant_ext WHERE merchant_id = :h_merch; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT coalesce(sum(gross_amount), 0) INTO :h_gross + FROM merchant_summary WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0) FAIL(b); + + if (h_gross >= 1000000000L) calc = "S"; + else if (h_gross >= 300000000L) calc = "A"; + else if (h_gross >= 50000000L) calc = "B"; + else calc = "C"; + + Bchg(b, T_STR1, 0, h_cur, 0L); + Bchg(b, T_STR2, 0, (char *)calc, 0L); + setl(b, T_GROSS, h_gross); + setl(b, T_ARG1, strcmp(h_cur, calc) != 0 ? 1 : 0); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_MCC_INQ.h b/app/src/mm/svc/MM_MCC_INQ.h new file mode 100644 index 0000000..450814e --- /dev/null +++ b/app/src/mm/svc/MM_MCC_INQ.h @@ -0,0 +1,12 @@ +/* + * MM_MCC_INQ.h - mm 서비스 MM_MCC_INQ 카피북 (copybook / record header). + * 사용 UBF 필드: T_CATEGORY(MCC), T_STR1(명칭), T_ARG1(위험도), T_COUNT(소속 가맹점 수), T_RC + */ +#ifndef MM_MCC_INQ_H +#define MM_MCC_INQ_H + +#include "mm_svc.h" + +void MM_MCC_INQ(TPSVCINFO *p); + +#endif /* MM_MCC_INQ_H */ diff --git a/app/src/mm/svc/MM_MCC_INQ.pgc b/app/src/mm/svc/MM_MCC_INQ.pgc new file mode 100644 index 0000000..68b9f27 --- /dev/null +++ b/app/src/mm/svc/MM_MCC_INQ.pgc @@ -0,0 +1,36 @@ +/* + * MM_MCC_INQ.pgc - mm 모듈 서비스 MM_MCC_INQ (one service = one file). + * 기능: MCC 업종 조회 (명칭/위험도 + 해당 업종 활성 가맹점 수). + * 입력: T_CATEGORY=MCC. 출력: T_STR1, T_ARG1, T_COUNT. + * RC: 1=미등록 MCC. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_MCC_INQ.h" + +void MM_MCC_INQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char mcc[8]; + EXEC SQL BEGIN DECLARE SECTION; + char h_mcc[8], h_name[64]; + long h_risk, h_merch; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CATEGORY, mcc, sizeof(mcc)); + strncpy(h_mcc, mcc, sizeof(h_mcc)-1); h_mcc[sizeof(h_mcc)-1] = 0; + + EXEC SQL SELECT mcc_name, risk_level INTO :h_name, :h_risk + FROM mm_mcc WHERE mcc = :h_mcc; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT count(*) INTO :h_merch FROM mm_merchant_ext + WHERE mcc = :h_mcc AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) FAIL(b); + + Bchg(b, T_STR1, 0, h_name, 0L); + setl(b, T_ARG1, h_risk); + setl(b, T_COUNT, h_merch); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_MCC_RISKLIST.h b/app/src/mm/svc/MM_MCC_RISKLIST.h new file mode 100644 index 0000000..42309d6 --- /dev/null +++ b/app/src/mm/svc/MM_MCC_RISKLIST.h @@ -0,0 +1,12 @@ +/* + * MM_MCC_RISKLIST.h - mm 서비스 MM_MCC_RISKLIST 카피북 (copybook / record header). + * 사용 UBF 필드: T_ARG1(최소 위험도), T_COUNT(MCC 수), T_ARG2(최고 위험도), T_ARG3(소속 가맹점 수), T_RC + */ +#ifndef MM_MCC_RISKLIST_H +#define MM_MCC_RISKLIST_H + +#include "mm_svc.h" + +void MM_MCC_RISKLIST(TPSVCINFO *p); + +#endif /* MM_MCC_RISKLIST_H */ diff --git a/app/src/mm/svc/MM_MCC_RISKLIST.pgc b/app/src/mm/svc/MM_MCC_RISKLIST.pgc new file mode 100644 index 0000000..6655e0c --- /dev/null +++ b/app/src/mm/svc/MM_MCC_RISKLIST.pgc @@ -0,0 +1,36 @@ +/* + * MM_MCC_RISKLIST.pgc - mm 모듈 서비스 MM_MCC_RISKLIST (one service = one file). + * 기능: 고위험 MCC 현황 (기준 위험도 이상 MCC 수, 최고 위험도, 소속 활성 가맹점 수). + * 입력: T_ARG1=최소 위험도(기본 3). 출력: T_COUNT, T_ARG2, T_ARG3. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_MCC_RISKLIST.h" + +void MM_MCC_RISKLIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_min, h_cnt, h_max, h_merch; + EXEC SQL END DECLARE SECTION; + + h_min = getl(b, T_ARG1); + if (h_min <= 0) h_min = 3; + + EXEC SQL SELECT count(*), coalesce(max(risk_level), 0) + INTO :h_cnt, :h_max + FROM mm_mcc WHERE risk_level >= :h_min; + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL SELECT count(*) INTO :h_merch + FROM mm_merchant_ext me + WHERE me.status = 'ACTIVE' + AND EXISTS (SELECT 1 FROM mm_mcc c + WHERE c.mcc = me.mcc AND c.risk_level >= :h_min); + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + setl(b, T_ARG2, h_max); + setl(b, T_ARG3, h_merch); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_MCC_UPD.h b/app/src/mm/svc/MM_MCC_UPD.h new file mode 100644 index 0000000..02bf9e5 --- /dev/null +++ b/app/src/mm/svc/MM_MCC_UPD.h @@ -0,0 +1,12 @@ +/* + * MM_MCC_UPD.h - mm 서비스 MM_MCC_UPD 카피북 (copybook / record header). + * 사용 UBF 필드: T_CATEGORY(MCC), T_STR1(명칭), T_ARG1(위험도), T_RC + */ +#ifndef MM_MCC_UPD_H +#define MM_MCC_UPD_H + +#include "mm_svc.h" + +void MM_MCC_UPD(TPSVCINFO *p); + +#endif /* MM_MCC_UPD_H */ diff --git a/app/src/mm/svc/MM_MCC_UPD.pgc b/app/src/mm/svc/MM_MCC_UPD.pgc new file mode 100644 index 0000000..87821e2 --- /dev/null +++ b/app/src/mm/svc/MM_MCC_UPD.pgc @@ -0,0 +1,37 @@ +/* + * MM_MCC_UPD.pgc - mm 모듈 서비스 MM_MCC_UPD (one service = one file). + * 기능: MCC 업종 마스터 변경 (명칭 선택 변경 + 위험도 1~5 재설정). + * 입력: T_CATEGORY=MCC, T_STR1=명칭(빈값이면 유지), T_ARG1=위험도(1~5). + * RC: 1=미등록 MCC, 3=위험도 범위 오류. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_MCC_UPD.h" + +void MM_MCC_UPD(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char mcc[8], name[64]; + EXEC SQL BEGIN DECLARE SECTION; + char h_mcc[8], h_name[64]; + long h_risk, h_found; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_CATEGORY, mcc, sizeof(mcc)); + gets_(b, T_STR1, name, sizeof(name)); + h_risk = getl(b, T_ARG1); + if (h_risk < 1 || h_risk > 5) { setl(b, T_RC, 3); OK(b); } + strncpy(h_mcc, mcc, sizeof(h_mcc)-1); h_mcc[sizeof(h_mcc)-1] = 0; + strncpy(h_name, name, sizeof(h_name)-1); h_name[sizeof(h_name)-1] = 0; + + EXEC SQL SELECT count(*) INTO :h_found FROM mm_mcc WHERE mcc = :h_mcc; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_found == 0) { setl(b, T_RC, 1); OK(b); } + + EXEC SQL UPDATE mm_mcc + SET mcc_name = CASE WHEN :h_name = '' THEN mcc_name ELSE :h_name END, + risk_level = :h_risk + WHERE mcc = :h_mcc; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_MERCH_CLOSE.h b/app/src/mm/svc/MM_MERCH_CLOSE.h new file mode 100644 index 0000000..e46fbe8 --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_CLOSE.h @@ -0,0 +1,12 @@ +/* + * MM_MERCH_CLOSE.h - mm 서비스 MM_MERCH_CLOSE 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_REASON, T_COUNT(폐기 단말 수), T_RC + */ +#ifndef MM_MERCH_CLOSE_H +#define MM_MERCH_CLOSE_H + +#include "mm_svc.h" + +void MM_MERCH_CLOSE(TPSVCINFO *p); + +#endif /* MM_MERCH_CLOSE_H */ diff --git a/app/src/mm/svc/MM_MERCH_CLOSE.pgc b/app/src/mm/svc/MM_MERCH_CLOSE.pgc new file mode 100644 index 0000000..9a98098 --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_CLOSE.pgc @@ -0,0 +1,46 @@ +/* + * MM_MERCH_CLOSE.pgc - mm 모듈 서비스 MM_MERCH_CLOSE (one service = one file). + * 기능: 가맹점 폐업 처리 (DELETED 전환 + 전체 단말 폐기 + 하위 관계 해제). + * 입력: T_MERCHANT, T_REASON=폐업 사유(없으면 '폐업 신고'). 출력: T_COUNT=폐기된 단말 수. + * RC: 1=가맹점 없음, 2=이미 폐업됨. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_MERCH_CLOSE.h" + +void MM_MERCH_CLOSE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], reason[128], memo[160]; + long nterm; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_stat[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_REASON, reason, sizeof(reason)); + if (reason[0] == 0) strcpy(reason, "폐업 신고"); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT status INTO :h_stat FROM mm_merchant_ext WHERE merchant_id = :h_merch; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + if (strcmp(h_stat, "DELETED") == 0) { setl(b, T_RC, 2); OK(b); } + + EXEC SQL UPDATE mm_merchant_ext SET status = 'DELETED', updated_at = now() + WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL UPDATE mm_terminal SET status = 'SCRAPPED' + WHERE merchant_id = :h_merch AND status <> 'SCRAPPED'; + if (sqlca.sqlcode < 0) FAIL(b); + nterm = sqlca.sqlerrd[2]; + /* 대표-하위 관계도 함께 해제 (이 가맹점이 하위이거나 대표인 경우 모두) */ + EXEC SQL UPDATE mm_code SET status = 'INACTIVE' + WHERE code_group = 'MERCH_REL' + AND (code = :h_merch OR code_name = :h_merch) AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) FAIL(b); + snprintf(memo, sizeof(memo), "%s (단말 %ld대 폐기)", reason, nterm); + if (mmdb_insert_merch_hist(h_merch, "CLOSE", h_stat, "DELETED", memo) < 0) FAIL(b); + setl(b, T_COUNT, nterm); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_MERCH_PAUSE.h b/app/src/mm/svc/MM_MERCH_PAUSE.h new file mode 100644 index 0000000..10c9924 --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_PAUSE.h @@ -0,0 +1,12 @@ +/* + * MM_MERCH_PAUSE.h - mm 서비스 MM_MERCH_PAUSE 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_REASON, T_COUNT(정지된 단말 수), T_RC + */ +#ifndef MM_MERCH_PAUSE_H +#define MM_MERCH_PAUSE_H + +#include "mm_svc.h" + +void MM_MERCH_PAUSE(TPSVCINFO *p); + +#endif /* MM_MERCH_PAUSE_H */ diff --git a/app/src/mm/svc/MM_MERCH_PAUSE.pgc b/app/src/mm/svc/MM_MERCH_PAUSE.pgc new file mode 100644 index 0000000..46289db --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_PAUSE.pgc @@ -0,0 +1,40 @@ +/* + * MM_MERCH_PAUSE.pgc - mm 모듈 서비스 MM_MERCH_PAUSE (one service = one file). + * 기능: 가맹점 휴업 처리 (ACTIVE -> HOLD, 가동 단말 일괄 SUSPENDED). + * 입력: T_MERCHANT, T_REASON=휴업 사유(없으면 '자진 휴업'). 출력: T_COUNT=정지된 단말 수. + * RC: 1=가맹점 없음, 2=활성 상태가 아님. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_MERCH_PAUSE.h" + +void MM_MERCH_PAUSE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], reason[128]; + long nterm; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_stat[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_REASON, reason, sizeof(reason)); + if (reason[0] == 0) strcpy(reason, "자진 휴업"); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT status INTO :h_stat FROM mm_merchant_ext WHERE merchant_id = :h_merch; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + if (strcmp(h_stat, "ACTIVE") != 0) { setl(b, T_RC, 2); OK(b); } + + EXEC SQL UPDATE mm_merchant_ext SET status = 'HOLD', updated_at = now() + WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL UPDATE mm_terminal SET status = 'SUSPENDED' + WHERE merchant_id = :h_merch AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) FAIL(b); + nterm = sqlca.sqlerrd[2]; + if (mmdb_insert_merch_hist(h_merch, "PAUSE", "ACTIVE", "HOLD", reason) < 0) FAIL(b); + setl(b, T_COUNT, nterm); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_MERCH_RESUME.h b/app/src/mm/svc/MM_MERCH_RESUME.h new file mode 100644 index 0000000..50f65b0 --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_RESUME.h @@ -0,0 +1,12 @@ +/* + * MM_MERCH_RESUME.h - mm 서비스 MM_MERCH_RESUME 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_COUNT(재가동 단말 수), T_RC + */ +#ifndef MM_MERCH_RESUME_H +#define MM_MERCH_RESUME_H + +#include "mm_svc.h" + +void MM_MERCH_RESUME(TPSVCINFO *p); + +#endif /* MM_MERCH_RESUME_H */ diff --git a/app/src/mm/svc/MM_MERCH_RESUME.pgc b/app/src/mm/svc/MM_MERCH_RESUME.pgc new file mode 100644 index 0000000..be39570 --- /dev/null +++ b/app/src/mm/svc/MM_MERCH_RESUME.pgc @@ -0,0 +1,38 @@ +/* + * MM_MERCH_RESUME.pgc - mm 모듈 서비스 MM_MERCH_RESUME (one service = one file). + * 기능: 가맹점 영업 재개 (HOLD -> ACTIVE, 정지 단말 재가동). + * 입력: T_MERCHANT. 출력: T_COUNT=재가동된 단말 수. + * RC: 1=가맹점 없음, 2=휴업 상태가 아님. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_MERCH_RESUME.h" + +void MM_MERCH_RESUME(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64]; + long nterm; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_stat[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT status INTO :h_stat FROM mm_merchant_ext WHERE merchant_id = :h_merch; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + if (strcmp(h_stat, "HOLD") != 0) { setl(b, T_RC, 2); OK(b); } + + EXEC SQL UPDATE mm_merchant_ext SET status = 'ACTIVE', updated_at = now() + WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL UPDATE mm_terminal SET status = 'ACTIVE' + WHERE merchant_id = :h_merch AND status = 'SUSPENDED'; + if (sqlca.sqlcode < 0) FAIL(b); + nterm = sqlca.sqlerrd[2]; + if (mmdb_insert_merch_hist(h_merch, "RESUME", "HOLD", "ACTIVE", "영업 재개") < 0) FAIL(b); + setl(b, T_COUNT, nterm); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_PARTNER_INQ.h b/app/src/mm/svc/MM_PARTNER_INQ.h new file mode 100644 index 0000000..4550747 --- /dev/null +++ b/app/src/mm/svc/MM_PARTNER_INQ.h @@ -0,0 +1,12 @@ +/* + * MM_PARTNER_INQ.h - mm 서비스 MM_PARTNER_INQ 카피북 (copybook / record header). + * 사용 UBF 필드: T_KEY1, T_STR1(제휴사명), T_STATUS, T_ARG1(표시순서), T_RC + */ +#ifndef MM_PARTNER_INQ_H +#define MM_PARTNER_INQ_H + +#include "mm_svc.h" + +void MM_PARTNER_INQ(TPSVCINFO *p); + +#endif /* MM_PARTNER_INQ_H */ diff --git a/app/src/mm/svc/MM_PARTNER_INQ.pgc b/app/src/mm/svc/MM_PARTNER_INQ.pgc new file mode 100644 index 0000000..b7890f2 --- /dev/null +++ b/app/src/mm/svc/MM_PARTNER_INQ.pgc @@ -0,0 +1,32 @@ +/* + * MM_PARTNER_INQ.pgc - mm 모듈 서비스 MM_PARTNER_INQ (one service = one file). + * 기능: 제휴사 마스터 단건 조회 (명칭/상태/표시순서). + * 입력: T_KEY1=제휴사 코드. 출력: T_STR1, T_STATUS, T_ARG1. + * RC: 1=미등록 제휴사. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_PARTNER_INQ.h" + +void MM_PARTNER_INQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char code[16]; + EXEC SQL BEGIN DECLARE SECTION; + char h_code[16], h_name[64], h_stat[16]; + long h_sort; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, code, sizeof(code)); + strncpy(h_code, code, sizeof(h_code)-1); h_code[sizeof(h_code)-1] = 0; + + EXEC SQL SELECT code_name, status, sort_order INTO :h_name, :h_stat, :h_sort + FROM mm_code WHERE code_group = 'PARTNER' AND code = :h_code; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + Bchg(b, T_STR1, 0, h_name, 0L); + Bchg(b, T_STATUS, 0, h_stat, 0L); + setl(b, T_ARG1, h_sort); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_PARTNER_REG.h b/app/src/mm/svc/MM_PARTNER_REG.h new file mode 100644 index 0000000..79e361f --- /dev/null +++ b/app/src/mm/svc/MM_PARTNER_REG.h @@ -0,0 +1,12 @@ +/* + * MM_PARTNER_REG.h - mm 서비스 MM_PARTNER_REG 카피북 (copybook / record header). + * 사용 UBF 필드: T_KEY1(제휴사 코드), T_STR1(제휴사명), T_ARG1(표시순서), T_RC + */ +#ifndef MM_PARTNER_REG_H +#define MM_PARTNER_REG_H + +#include "mm_svc.h" + +void MM_PARTNER_REG(TPSVCINFO *p); + +#endif /* MM_PARTNER_REG_H */ diff --git a/app/src/mm/svc/MM_PARTNER_REG.pgc b/app/src/mm/svc/MM_PARTNER_REG.pgc new file mode 100644 index 0000000..d1f2641 --- /dev/null +++ b/app/src/mm/svc/MM_PARTNER_REG.pgc @@ -0,0 +1,33 @@ +/* + * MM_PARTNER_REG.pgc - mm 모듈 서비스 MM_PARTNER_REG (one service = one file). + * 기능: 제휴사 마스터 등록/갱신 (공통코드 PARTNER 그룹 upsert). + * 입력: T_KEY1=제휴사 코드, T_STR1=제휴사명, T_ARG1=표시순서. + * RC: 3=필수값 누락. 재등록 시 명칭/순서 갱신 (기존 상태 유지). + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_PARTNER_REG.h" + +void MM_PARTNER_REG(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char code[16], name[64]; + EXEC SQL BEGIN DECLARE SECTION; + char h_code[16], h_name[64]; + long h_sort; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, code, sizeof(code)); + gets_(b, T_STR1, name, sizeof(name)); + h_sort = getl(b, T_ARG1); + if (code[0] == 0 || name[0] == 0) { setl(b, T_RC, 3); OK(b); } + strncpy(h_code, code, sizeof(h_code)-1); h_code[sizeof(h_code)-1] = 0; + strncpy(h_name, name, sizeof(h_name)-1); h_name[sizeof(h_name)-1] = 0; + + EXEC SQL INSERT INTO mm_code (code_group, code, code_name, sort_order) + VALUES ('PARTNER', :h_code, :h_name, :h_sort) + ON CONFLICT (code_group, code) + DO UPDATE SET code_name = EXCLUDED.code_name, sort_order = EXCLUDED.sort_order; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_PARTNER_STATUS.h b/app/src/mm/svc/MM_PARTNER_STATUS.h new file mode 100644 index 0000000..845b275 --- /dev/null +++ b/app/src/mm/svc/MM_PARTNER_STATUS.h @@ -0,0 +1,12 @@ +/* + * MM_PARTNER_STATUS.h - mm 서비스 MM_PARTNER_STATUS 카피북 (copybook / record header). + * 사용 UBF 필드: T_KEY1, T_STATUS(ACTIVE/INACTIVE), T_RC + */ +#ifndef MM_PARTNER_STATUS_H +#define MM_PARTNER_STATUS_H + +#include "mm_svc.h" + +void MM_PARTNER_STATUS(TPSVCINFO *p); + +#endif /* MM_PARTNER_STATUS_H */ diff --git a/app/src/mm/svc/MM_PARTNER_STATUS.pgc b/app/src/mm/svc/MM_PARTNER_STATUS.pgc new file mode 100644 index 0000000..59ecbd8 --- /dev/null +++ b/app/src/mm/svc/MM_PARTNER_STATUS.pgc @@ -0,0 +1,32 @@ +/* + * MM_PARTNER_STATUS.pgc - mm 모듈 서비스 MM_PARTNER_STATUS (one service = one file). + * 기능: 제휴사 사용 상태 변경 (ACTIVE/INACTIVE 만 허용). + * 입력: T_KEY1=제휴사 코드, T_STATUS=변경할 상태. + * RC: 1=미등록 제휴사, 3=허용되지 않는 상태값. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_PARTNER_STATUS.h" + +void MM_PARTNER_STATUS(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char code[16], stat[16]; + EXEC SQL BEGIN DECLARE SECTION; + char h_code[16], h_stat[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, code, sizeof(code)); + gets_(b, T_STATUS, stat, sizeof(stat)); + if (strcmp(stat, "ACTIVE") != 0 && strcmp(stat, "INACTIVE") != 0) { + setl(b, T_RC, 3); OK(b); + } + strncpy(h_code, code, sizeof(h_code)-1); h_code[sizeof(h_code)-1] = 0; + strncpy(h_stat, stat, sizeof(h_stat)-1); h_stat[sizeof(h_stat)-1] = 0; + + EXEC SQL UPDATE mm_code SET status = :h_stat + WHERE code_group = 'PARTNER' AND code = :h_code; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlerrd[2] == 0) { setl(b, T_RC, 1); OK(b); } + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_RATE_APPROVE.h b/app/src/mm/svc/MM_RATE_APPROVE.h new file mode 100644 index 0000000..c6c5c02 --- /dev/null +++ b/app/src/mm/svc/MM_RATE_APPROVE.h @@ -0,0 +1,12 @@ +/* + * MM_RATE_APPROVE.h - mm 서비스 MM_RATE_APPROVE 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_AMT1(적용 bps), T_STR2(카드종류), T_RC + */ +#ifndef MM_RATE_APPROVE_H +#define MM_RATE_APPROVE_H + +#include "mm_svc.h" + +void MM_RATE_APPROVE(TPSVCINFO *p); + +#endif /* MM_RATE_APPROVE_H */ diff --git a/app/src/mm/svc/MM_RATE_APPROVE.pgc b/app/src/mm/svc/MM_RATE_APPROVE.pgc new file mode 100644 index 0000000..2ac335e --- /dev/null +++ b/app/src/mm/svc/MM_RATE_APPROVE.pgc @@ -0,0 +1,47 @@ +/* + * MM_RATE_APPROVE.pgc - mm 모듈 서비스 MM_RATE_APPROVE (one service = one file). + * 기능: 수수료율 변경 승인 (대기 요청을 실제 요율 마스터에 반영, RATE_APV 이력). + * 입력: T_MERCHANT. 출력: T_STR2=카드종류, T_AMT1=적용 bps. + * RC: 1=대기중 요청 없음, 5=요청 데이터 형식 오류. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_RATE_APPROVE.h" + +void MM_RATE_APPROVE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], card[16]; + char *colon; + long bps; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_req[32]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + /* 최신 미처리 RATE_REQ 를 찾는다 (형식 "CARD:bps") */ + EXEC SQL SELECT r.after_val INTO :h_req FROM mm_merch_hist r + WHERE r.merchant_id = :h_merch AND r.action = 'RATE_REQ' + AND NOT EXISTS (SELECT 1 FROM mm_merch_hist d + WHERE d.merchant_id = r.merchant_id + AND d.action IN ('RATE_APV', 'RATE_REJ') + AND d.hist_id > r.hist_id) + ORDER BY r.hist_id DESC LIMIT 1; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + colon = strchr(h_req, ':'); + if (colon == NULL) { setl(b, T_RC, 5); OK(b); } + *colon = 0; + strncpy(card, h_req, sizeof(card)-1); card[sizeof(card)-1] = 0; + bps = atol(colon + 1); + if (bps < 1 || bps > 500) { setl(b, T_RC, 5); OK(b); } + + if (mmdb_set_fee_rate(h_merch, card, bps) < 0) FAIL(b); + if (mmdb_insert_merch_hist(h_merch, "RATE_APV", "", colon + 1, "요율 변경 승인 반영") < 0) FAIL(b); + Bchg(b, T_STR2, 0, card, 0L); + setl(b, T_AMT1, bps); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_RATE_REJECT.h b/app/src/mm/svc/MM_RATE_REJECT.h new file mode 100644 index 0000000..3431ce0 --- /dev/null +++ b/app/src/mm/svc/MM_RATE_REJECT.h @@ -0,0 +1,12 @@ +/* + * MM_RATE_REJECT.h - mm 서비스 MM_RATE_REJECT 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_REASON, T_STR1(반려된 요청), T_RC + */ +#ifndef MM_RATE_REJECT_H +#define MM_RATE_REJECT_H + +#include "mm_svc.h" + +void MM_RATE_REJECT(TPSVCINFO *p); + +#endif /* MM_RATE_REJECT_H */ diff --git a/app/src/mm/svc/MM_RATE_REJECT.pgc b/app/src/mm/svc/MM_RATE_REJECT.pgc new file mode 100644 index 0000000..7c28c7d --- /dev/null +++ b/app/src/mm/svc/MM_RATE_REJECT.pgc @@ -0,0 +1,37 @@ +/* + * MM_RATE_REJECT.pgc - mm 모듈 서비스 MM_RATE_REJECT (one service = one file). + * 기능: 수수료율 변경 반려 (대기 요청을 반려 처리, 요율 마스터는 불변). + * 입력: T_MERCHANT, T_REASON=반려 사유(필수). 출력: T_STR1=반려된 요청 내용. + * RC: 1=대기중 요청 없음, 3=반려 사유 누락. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_RATE_REJECT.h" + +void MM_RATE_REJECT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], reason[128]; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_req[32]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_REASON, reason, sizeof(reason)); + if (reason[0] == 0) { setl(b, T_RC, 3); OK(b); } + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT r.after_val INTO :h_req FROM mm_merch_hist r + WHERE r.merchant_id = :h_merch AND r.action = 'RATE_REQ' + AND NOT EXISTS (SELECT 1 FROM mm_merch_hist d + WHERE d.merchant_id = r.merchant_id + AND d.action IN ('RATE_APV', 'RATE_REJ') + AND d.hist_id > r.hist_id) + ORDER BY r.hist_id DESC LIMIT 1; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + if (mmdb_insert_merch_hist(h_merch, "RATE_REJ", h_req, "", reason) < 0) FAIL(b); + Bchg(b, T_STR1, 0, h_req, 0L); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_RATE_REQ.h b/app/src/mm/svc/MM_RATE_REQ.h new file mode 100644 index 0000000..19d5807 --- /dev/null +++ b/app/src/mm/svc/MM_RATE_REQ.h @@ -0,0 +1,12 @@ +/* + * MM_RATE_REQ.h - mm 서비스 MM_RATE_REQ 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_STR2(카드종류), T_ARG1(요청 bps), T_REASON, T_AMT1(현재 bps), T_RC + */ +#ifndef MM_RATE_REQ_H +#define MM_RATE_REQ_H + +#include "mm_svc.h" + +void MM_RATE_REQ(TPSVCINFO *p); + +#endif /* MM_RATE_REQ_H */ diff --git a/app/src/mm/svc/MM_RATE_REQ.pgc b/app/src/mm/svc/MM_RATE_REQ.pgc new file mode 100644 index 0000000..79df903 --- /dev/null +++ b/app/src/mm/svc/MM_RATE_REQ.pgc @@ -0,0 +1,51 @@ +/* + * MM_RATE_REQ.pgc - mm 모듈 서비스 MM_RATE_REQ (one service = one file). + * 기능: 수수료율 변경 요청 접수 (요율 승인 워크플로 1단계, RATE_REQ 이력 대기건 생성). + * 입력: T_MERCHANT, T_STR2=카드종류(기본 CREDIT), T_ARG1=요청 bps, T_REASON=요청 사유. + * 출력: T_AMT1=현재 적용 bps. RC: 1=가맹점 없음, 3=bps 범위 오류(1~500), 4=대기중 요청 존재. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_RATE_REQ.h" + +void MM_RATE_REQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], card[16], reason[128], afterv[32]; + long cur = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64]; + long h_alive, h_pending; + EXEC SQL END DECLARE SECTION; + long bps; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_STR2, card, sizeof(card)); + if (card[0] == 0) strcpy(card, "CREDIT"); + gets_(b, T_REASON, reason, sizeof(reason)); + if (reason[0] == 0) strcpy(reason, "요율 조정 요청"); + bps = getl(b, T_ARG1); + if (bps < 1 || bps > 500) { setl(b, T_RC, 3); OK(b); } + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT count(*) INTO :h_alive FROM mm_merchant_ext + WHERE merchant_id = :h_merch AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_alive == 0) { setl(b, T_RC, 1); OK(b); } + + /* 미처리(승인/반려 전) 요청이 있으면 중복 접수 차단 */ + EXEC SQL SELECT count(*) INTO :h_pending FROM mm_merch_hist r + WHERE r.merchant_id = :h_merch AND r.action = 'RATE_REQ' + AND NOT EXISTS (SELECT 1 FROM mm_merch_hist d + WHERE d.merchant_id = r.merchant_id + AND d.action IN ('RATE_APV', 'RATE_REJ') + AND d.hist_id > r.hist_id); + if (sqlca.sqlcode < 0) FAIL(b); + if (h_pending > 0) { setl(b, T_RC, 4); OK(b); } + + mmdb_get_fee_rate(h_merch, card, &cur); + snprintf(afterv, sizeof(afterv), "%s:%ld", card, bps); + if (mmdb_insert_merch_hist(h_merch, "RATE_REQ", "", afterv, reason) < 0) FAIL(b); + setl(b, T_AMT1, cur); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_REL_LINK.h b/app/src/mm/svc/MM_REL_LINK.h new file mode 100644 index 0000000..9394302 --- /dev/null +++ b/app/src/mm/svc/MM_REL_LINK.h @@ -0,0 +1,12 @@ +/* + * MM_REL_LINK.h - mm 서비스 MM_REL_LINK 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT(대표), T_KEY1(하위), T_RC + */ +#ifndef MM_REL_LINK_H +#define MM_REL_LINK_H + +#include "mm_svc.h" + +void MM_REL_LINK(TPSVCINFO *p); + +#endif /* MM_REL_LINK_H */ diff --git a/app/src/mm/svc/MM_REL_LINK.pgc b/app/src/mm/svc/MM_REL_LINK.pgc new file mode 100644 index 0000000..b1d5cac --- /dev/null +++ b/app/src/mm/svc/MM_REL_LINK.pgc @@ -0,0 +1,48 @@ +/* + * MM_REL_LINK.pgc - mm 모듈 서비스 MM_REL_LINK (one service = one file). + * 기능: 대표가맹점-하위가맹점 관계 연결 (MERCH_REL 코드그룹, 하위=code, 대표=code_name). + * 입력: T_MERCHANT=대표가맹점, T_KEY1=하위가맹점. + * RC: 1=대표 비활성, 2=하위 가맹점 없음, 3=자기 자신 연결 불가, 4=하위가 이미 다른 대표에 연결됨. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_REL_LINK.h" + +void MM_REL_LINK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char parent[64], child[64]; + EXEC SQL BEGIN DECLARE SECTION; + char h_parent[64], h_child[64]; + long h_pok, h_cok, h_linked; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, parent, sizeof(parent)); + gets_(b, T_KEY1, child, sizeof(child)); + if (strcmp(parent, child) == 0) { setl(b, T_RC, 3); OK(b); } + strncpy(h_parent, parent, sizeof(h_parent)-1); h_parent[sizeof(h_parent)-1] = 0; + strncpy(h_child, child, sizeof(h_child)-1); h_child[sizeof(h_child)-1] = 0; + + EXEC SQL SELECT count(*) INTO :h_pok FROM mm_merchant_ext + WHERE merchant_id = :h_parent AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_pok == 0) { setl(b, T_RC, 1); OK(b); } + EXEC SQL SELECT count(*) INTO :h_cok FROM mm_merchant_ext + WHERE merchant_id = :h_child; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cok == 0) { setl(b, T_RC, 2); OK(b); } + + EXEC SQL SELECT count(*) INTO :h_linked FROM mm_code + WHERE code_group = 'MERCH_REL' AND code = :h_child + AND status = 'ACTIVE' AND code_name <> :h_parent; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_linked > 0) { setl(b, T_RC, 4); OK(b); } + + EXEC SQL INSERT INTO mm_code (code_group, code, code_name, status) + VALUES ('MERCH_REL', :h_child, :h_parent, 'ACTIVE') + ON CONFLICT (code_group, code) + DO UPDATE SET code_name = EXCLUDED.code_name, status = 'ACTIVE'; + if (sqlca.sqlcode < 0) FAIL(b); + if (mmdb_insert_merch_hist(h_child, "REL_LINK", "", h_parent, "대표가맹점 연결") < 0) FAIL(b); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_REL_LIST.h b/app/src/mm/svc/MM_REL_LIST.h new file mode 100644 index 0000000..f14af49 --- /dev/null +++ b/app/src/mm/svc/MM_REL_LIST.h @@ -0,0 +1,12 @@ +/* + * MM_REL_LIST.h - mm 서비스 MM_REL_LIST 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT(대표), T_COUNT(하위 수), T_STR1(대표 하위 예시), T_RC + */ +#ifndef MM_REL_LIST_H +#define MM_REL_LIST_H + +#include "mm_svc.h" + +void MM_REL_LIST(TPSVCINFO *p); + +#endif /* MM_REL_LIST_H */ diff --git a/app/src/mm/svc/MM_REL_LIST.pgc b/app/src/mm/svc/MM_REL_LIST.pgc new file mode 100644 index 0000000..c6116ca --- /dev/null +++ b/app/src/mm/svc/MM_REL_LIST.pgc @@ -0,0 +1,33 @@ +/* + * MM_REL_LIST.pgc - mm 모듈 서비스 MM_REL_LIST (one service = one file). + * 기능: 대표가맹점의 하위가맹점 현황 (활성 연결 수 + 최초 하위 ID). + * 입력: T_MERCHANT=대표가맹점. 출력: T_COUNT, T_STR1=하위 중 최초 ID(사전순). + * RC: 1=하위 가맹점 없음. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_REL_LIST.h" + +void MM_REL_LIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char parent[64]; + EXEC SQL BEGIN DECLARE SECTION; + char h_parent[64], h_first[64]; + long h_cnt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, parent, sizeof(parent)); + strncpy(h_parent, parent, sizeof(h_parent)-1); h_parent[sizeof(h_parent)-1] = 0; + + EXEC SQL SELECT count(*), coalesce(min(code), '') + INTO :h_cnt, :h_first + FROM mm_code + WHERE code_group = 'MERCH_REL' AND code_name = :h_parent AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt == 0) { setl(b, T_RC, 1); OK(b); } + + setl(b, T_COUNT, h_cnt); + Bchg(b, T_STR1, 0, h_first, 0L); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_REL_UNLINK.h b/app/src/mm/svc/MM_REL_UNLINK.h new file mode 100644 index 0000000..90a4dc9 --- /dev/null +++ b/app/src/mm/svc/MM_REL_UNLINK.h @@ -0,0 +1,12 @@ +/* + * MM_REL_UNLINK.h - mm 서비스 MM_REL_UNLINK 카피북 (copybook / record header). + * 사용 UBF 필드: T_KEY1(하위 가맹점), T_RC + */ +#ifndef MM_REL_UNLINK_H +#define MM_REL_UNLINK_H + +#include "mm_svc.h" + +void MM_REL_UNLINK(TPSVCINFO *p); + +#endif /* MM_REL_UNLINK_H */ diff --git a/app/src/mm/svc/MM_REL_UNLINK.pgc b/app/src/mm/svc/MM_REL_UNLINK.pgc new file mode 100644 index 0000000..d0e90cc --- /dev/null +++ b/app/src/mm/svc/MM_REL_UNLINK.pgc @@ -0,0 +1,32 @@ +/* + * MM_REL_UNLINK.pgc - mm 모듈 서비스 MM_REL_UNLINK (one service = one file). + * 기능: 대표가맹점-하위가맹점 관계 해제 (관계 행 INACTIVE 전환 + 이력). + * 입력: T_KEY1=하위가맹점. + * RC: 1=활성 연결 없음. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_REL_UNLINK.h" + +void MM_REL_UNLINK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char child[64]; + EXEC SQL BEGIN DECLARE SECTION; + char h_child[64], h_parent[64]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, child, sizeof(child)); + strncpy(h_child, child, sizeof(h_child)-1); h_child[sizeof(h_child)-1] = 0; + + EXEC SQL SELECT code_name INTO :h_parent FROM mm_code + WHERE code_group = 'MERCH_REL' AND code = :h_child AND status = 'ACTIVE'; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + EXEC SQL UPDATE mm_code SET status = 'INACTIVE' + WHERE code_group = 'MERCH_REL' AND code = :h_child; + if (sqlca.sqlcode < 0) FAIL(b); + if (mmdb_insert_merch_hist(h_child, "REL_UNLINK", h_parent, "", "대표가맹점 연결 해제") < 0) FAIL(b); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_TERM_CNT.h b/app/src/mm/svc/MM_TERM_CNT.h new file mode 100644 index 0000000..766782e --- /dev/null +++ b/app/src/mm/svc/MM_TERM_CNT.h @@ -0,0 +1,12 @@ +/* + * MM_TERM_CNT.h - mm 서비스 MM_TERM_CNT 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_COUNT, T_ARG1, T_ARG2, T_RC + */ +#ifndef MM_TERM_CNT_H +#define MM_TERM_CNT_H + +#include "mm_svc.h" + +void MM_TERM_CNT(TPSVCINFO *p); + +#endif /* MM_TERM_CNT_H */ diff --git a/app/src/mm/svc/MM_TERM_CNT.pgc b/app/src/mm/svc/MM_TERM_CNT.pgc new file mode 100644 index 0000000..a1a039d --- /dev/null +++ b/app/src/mm/svc/MM_TERM_CNT.pgc @@ -0,0 +1,33 @@ +/* + * MM_TERM_CNT.pgc - mm 모듈 서비스 MM_TERM_CNT (one service = one file). + * 기능: 가맹점별 단말기 보유 현황 집계 (전체/가동/폐기 건수). + * 입력: T_MERCHANT. 출력: T_COUNT=전체, T_ARG1=가동, T_ARG2=폐기. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_TERM_CNT.h" + +void MM_TERM_CNT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64]; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64]; + long h_tot, h_act, h_scr; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN status = 'ACTIVE' THEN 1 ELSE 0 END), 0), + coalesce(sum(CASE WHEN status = 'SCRAPPED' THEN 1 ELSE 0 END), 0) + INTO :h_tot, :h_act, :h_scr + FROM mm_terminal WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_tot); + setl(b, T_ARG1, h_act); + setl(b, T_ARG2, h_scr); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_TERM_MOVE.h b/app/src/mm/svc/MM_TERM_MOVE.h new file mode 100644 index 0000000..d8e4980 --- /dev/null +++ b/app/src/mm/svc/MM_TERM_MOVE.h @@ -0,0 +1,12 @@ +/* + * MM_TERM_MOVE.h - mm 서비스 MM_TERM_MOVE 카피북 (copybook / record header). + * 사용 UBF 필드: T_KEY1, T_MERCHANT, T_STR1, T_RC + */ +#ifndef MM_TERM_MOVE_H +#define MM_TERM_MOVE_H + +#include "mm_svc.h" + +void MM_TERM_MOVE(TPSVCINFO *p); + +#endif /* MM_TERM_MOVE_H */ diff --git a/app/src/mm/svc/MM_TERM_MOVE.pgc b/app/src/mm/svc/MM_TERM_MOVE.pgc new file mode 100644 index 0000000..c8fea57 --- /dev/null +++ b/app/src/mm/svc/MM_TERM_MOVE.pgc @@ -0,0 +1,39 @@ +/* + * MM_TERM_MOVE.pgc - mm 모듈 서비스 MM_TERM_MOVE (one service = one file). + * 기능: 단말기 가맹점간 이동 (소속 변경 + 반입 이력). + * 입력: T_KEY1=단말ID, T_MERCHANT=이동 대상 가맹점. + * RC: 1=단말 없음, 2=대상 가맹점 비활성, 3=동일 가맹점으로 이동 불가. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_TERM_MOVE.h" + +void MM_TERM_MOVE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char term[64], dest[64]; + EXEC SQL BEGIN DECLARE SECTION; + char h_term[64], h_dest[64], h_from[64]; + long h_alive; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, term, sizeof(term)); + gets_(b, T_MERCHANT, dest, sizeof(dest)); + strncpy(h_term, term, sizeof(h_term)-1); h_term[sizeof(h_term)-1] = 0; + strncpy(h_dest, dest, sizeof(h_dest)-1); h_dest[sizeof(h_dest)-1] = 0; + + EXEC SQL SELECT merchant_id INTO :h_from FROM mm_terminal WHERE terminal_id = :h_term; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_alive FROM mm_merchant_ext + WHERE merchant_id = :h_dest AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_alive == 0) { setl(b, T_RC, 2); OK(b); } + if (strcmp(h_from, h_dest) == 0) { setl(b, T_RC, 3); OK(b); } + + EXEC SQL UPDATE mm_terminal SET merchant_id = :h_dest WHERE terminal_id = :h_term; + if (sqlca.sqlcode < 0) FAIL(b); + if (mmdb_insert_merch_hist(h_dest, "TERM_MOVE", h_from, h_dest, "단말기 이동 반입") < 0) FAIL(b); + Bchg(b, T_STR1, 0, h_from, 0L); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_TERM_SCRAP.h b/app/src/mm/svc/MM_TERM_SCRAP.h new file mode 100644 index 0000000..f825a14 --- /dev/null +++ b/app/src/mm/svc/MM_TERM_SCRAP.h @@ -0,0 +1,12 @@ +/* + * MM_TERM_SCRAP.h - mm 서비스 MM_TERM_SCRAP 카피북 (copybook / record header). + * 사용 UBF 필드: T_KEY1, T_REASON, T_RC + */ +#ifndef MM_TERM_SCRAP_H +#define MM_TERM_SCRAP_H + +#include "mm_svc.h" + +void MM_TERM_SCRAP(TPSVCINFO *p); + +#endif /* MM_TERM_SCRAP_H */ diff --git a/app/src/mm/svc/MM_TERM_SCRAP.pgc b/app/src/mm/svc/MM_TERM_SCRAP.pgc new file mode 100644 index 0000000..dd3edc5 --- /dev/null +++ b/app/src/mm/svc/MM_TERM_SCRAP.pgc @@ -0,0 +1,34 @@ +/* + * MM_TERM_SCRAP.pgc - mm 모듈 서비스 MM_TERM_SCRAP (one service = one file). + * 기능: 단말기 폐기 (SCRAPPED 전환 + 폐기 사유 이력). + * 입력: T_KEY1=단말ID, T_REASON=폐기 사유(없으면 '노후 폐기'). + * RC: 1=단말 없음, 2=이미 폐기된 단말. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_TERM_SCRAP.h" + +void MM_TERM_SCRAP(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char term[64], reason[128]; + EXEC SQL BEGIN DECLARE SECTION; + char h_term[64], h_stat[16], h_owner[64]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, term, sizeof(term)); + gets_(b, T_REASON, reason, sizeof(reason)); + if (reason[0] == 0) strcpy(reason, "노후 폐기"); + strncpy(h_term, term, sizeof(h_term)-1); h_term[sizeof(h_term)-1] = 0; + + EXEC SQL SELECT status, merchant_id INTO :h_stat, :h_owner + FROM mm_terminal WHERE terminal_id = :h_term; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + if (strcmp(h_stat, "SCRAPPED") == 0) { setl(b, T_RC, 2); OK(b); } + + EXEC SQL UPDATE mm_terminal SET status = 'SCRAPPED' WHERE terminal_id = :h_term; + if (sqlca.sqlcode < 0) FAIL(b); + if (mmdb_insert_merch_hist(h_owner, "TERM_SCRAP", h_stat, "SCRAPPED", reason) < 0) FAIL(b); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/mm/svc/MM_TERM_SUSPEND.h b/app/src/mm/svc/MM_TERM_SUSPEND.h new file mode 100644 index 0000000..f35caaa --- /dev/null +++ b/app/src/mm/svc/MM_TERM_SUSPEND.h @@ -0,0 +1,12 @@ +/* + * MM_TERM_SUSPEND.h - mm 서비스 MM_TERM_SUSPEND 카피북 (copybook / record header). + * 사용 UBF 필드: T_KEY1, T_STATUS, T_RC + */ +#ifndef MM_TERM_SUSPEND_H +#define MM_TERM_SUSPEND_H + +#include "mm_svc.h" + +void MM_TERM_SUSPEND(TPSVCINFO *p); + +#endif /* MM_TERM_SUSPEND_H */ diff --git a/app/src/mm/svc/MM_TERM_SUSPEND.pgc b/app/src/mm/svc/MM_TERM_SUSPEND.pgc new file mode 100644 index 0000000..adad238 --- /dev/null +++ b/app/src/mm/svc/MM_TERM_SUSPEND.pgc @@ -0,0 +1,36 @@ +/* + * MM_TERM_SUSPEND.pgc - mm 모듈 서비스 MM_TERM_SUSPEND (one service = one file). + * 기능: 단말기 사용정지/재개 토글 (ACTIVE<->SUSPENDED). + * 입력: T_KEY1=단말ID. 출력: T_STATUS=전환 후 상태. + * RC: 1=단말 없음, 2=폐기 단말은 전환 불가. + * caller의 XA 브랜치에서 실행(EXEC SQL CONNECT 없음); mm_svr가 tpadvertise 한다. + */ +#include "MM_TERM_SUSPEND.h" + +void MM_TERM_SUSPEND(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char term[64]; + const char *next; + EXEC SQL BEGIN DECLARE SECTION; + char h_term[64], h_stat[16], h_next[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, term, sizeof(term)); + strncpy(h_term, term, sizeof(h_term)-1); h_term[sizeof(h_term)-1] = 0; + + EXEC SQL SELECT status INTO :h_stat FROM mm_terminal WHERE terminal_id = :h_term; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + + if (strcmp(h_stat, "ACTIVE") == 0) next = "SUSPENDED"; + else if (strcmp(h_stat, "SUSPENDED") == 0) next = "ACTIVE"; + else { setl(b, T_RC, 2); OK(b); } + + strncpy(h_next, next, sizeof(h_next)-1); h_next[sizeof(h_next)-1] = 0; + EXEC SQL UPDATE mm_terminal SET status = :h_next WHERE terminal_id = :h_term; + if (sqlca.sqlcode < 0) FAIL(b); + Bchg(b, T_STATUS, 0, h_next, 0L); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/py/batch/py_acctsync_batch.pgc b/app/src/py/batch/py_acctsync_batch.pgc new file mode 100644 index 0000000..88aeeca --- /dev/null +++ b/app/src/py/batch/py_acctsync_batch.pgc @@ -0,0 +1,54 @@ +/* + * py_acctsync_batch.pgc - py 지급계좌 잔액 정합 점검 배치 (커서 + REVIEW 마킹, XA). + * + * 잔액이 음수로 뒤집힌 지급계좌를 커서로 찾아 REVIEW 상태로 마킹해 + * 지급 경로에서 격리하고, 전체 최저 잔액도 리포트한다. + * host 변수는 전용 카피북 py_acctsync_cpy.h 를 EXEC SQL INCLUDE. + * Usage: py_acctsync_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long negatives = 0; + + EXEC SQL INCLUDE py_acctsync_cpy; + + (void)argc; (void)argv; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE pas CURSOR FOR + SELECT account_no, merchant_id, balance FROM py_account + WHERE balance < 0 ORDER BY balance; + EXEC SQL OPEN pas; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH pas INTO :c_acct, :c_merch, :c_bal; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pas; tpabort(0); return 1; } + negatives++; + printf(" [음수잔액] %s (%s) balance=%ld -> REVIEW\n", c_acct, c_merch, c_bal); + } + EXEC SQL CLOSE pas; + + EXEC SQL UPDATE py_account SET status = 'REVIEW', updated_at = now() + WHERE balance < 0 AND status <> 'REVIEW'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "UPDATE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + c_flagged = sqlca.sqlerrd[2]; + + EXEC SQL SELECT coalesce(min(balance),0) INTO :c_minbal FROM py_account; + if (sqlca.sqlcode < 0) { fprintf(stderr, "SELECT FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_acctsync_batch COMMIT: 음수계좌=%ld REVIEW신규=%ld 최저잔액=%ld\n", + negatives, c_flagged, c_minbal); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_acctsync_cpy.h b/app/src/py/batch/py_acctsync_cpy.h new file mode 100644 index 0000000..e9e49e3 --- /dev/null +++ b/app/src/py/batch/py_acctsync_cpy.h @@ -0,0 +1,6 @@ +/* py_acctsync_cpy.h - py_acctsync_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char c_acct[32]; +char c_merch[64]; +long c_bal, c_flagged, c_minbal; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/py/batch/py_advrepay_batch.pgc b/app/src/py/batch/py_advrepay_batch.pgc new file mode 100644 index 0000000..ace482a --- /dev/null +++ b/app/src/py/batch/py_advrepay_batch.pgc @@ -0,0 +1,63 @@ +/* + * py_advrepay_batch.pgc - py 선지급 분할 상환 배치 (커서 + adjust 기록, XA). + * + * 잔여 net 이 남은 ADVANCE 지급건을 커서로 훑어 상환률(bps)만큼 회수: + * py_adjust 음수 기록 + net 차감, 완납 시 마감(X) 전이까지 처리한다. + * host 변수는 전용 카피북 py_advrepay_cpy.h 를 EXEC SQL INCLUDE. + * Usage: py_advrepay_batch [YYYY-MM-DD] [RATE_BPS] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long repaid = 0, repaid_amt = 0, closed = 0; + + EXEC SQL INCLUDE py_advrepay_cpy; + + strncpy(c_bizdate, bizdate, sizeof(c_bizdate)-1); c_bizdate[sizeof(c_bizdate)-1] = 0; + c_bps = (argc > 2) ? atol(argv[2]) : 1000; /* 기본 회당 10% 상환 */ + if (c_bps <= 0 || c_bps > 10000) c_bps = 1000; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE par CURSOR FOR + SELECT payout_id, net FROM py_payout + WHERE channel = 'ADVANCE' AND net > 0 AND status NOT IN ('C','V','X') + ORDER BY payout_id; + EXEC SQL OPEN par; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH par INTO :c_pid, :c_net; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE par; tpabort(0); return 1; } + c_repay = c_net * c_bps / 10000; + if (c_repay <= 0) c_repay = c_net; /* 소액 잔여는 완납 처리 */ + c_newnet = c_net - c_repay; + c_adj = -c_repay; + EXEC SQL INSERT INTO py_adjust (adj_id, payout_id, delta, reason, biz_date) + VALUES (nextval('py_adjust_seq'), :c_pid, :c_adj, '선지급상환(배치)', :c_bizdate); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE par; tpabort(0); return 1; } + EXEC SQL UPDATE py_payout + SET net = :c_newnet, + status = CASE WHEN :c_newnet <= 0 THEN 'X' ELSE status END, + updated_at = now() + WHERE payout_id = :c_pid; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE par; tpabort(0); return 1; } + repaid++; repaid_amt += c_repay; + if (c_newnet <= 0) closed++; + } + EXEC SQL CLOSE par; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_advrepay_batch COMMIT: bps=%ld 상환=%ld건/%ld원 완납마감=%ld건\n", + c_bps, repaid, repaid_amt, closed); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_advrepay_cpy.h b/app/src/py/batch/py_advrepay_cpy.h new file mode 100644 index 0000000..71d0105 --- /dev/null +++ b/app/src/py/batch/py_advrepay_cpy.h @@ -0,0 +1,6 @@ +/* py_advrepay_cpy.h - py_advrepay_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char c_bizdate[16]; +long c_bps; +long c_pid, c_net, c_repay, c_newnet, c_adj; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/py/batch/py_bankfile_batch.pgc b/app/src/py/batch/py_bankfile_batch.pgc new file mode 100644 index 0000000..e67a5c3 --- /dev/null +++ b/app/src/py/batch/py_bankfile_batch.pgc @@ -0,0 +1,66 @@ +/* + * py_bankfile_batch.pgc - py 은행별 지급파일 일괄 생성 배치 (커서 + 파일편성, XA). + * + * 당일 승인(A) 지급건의 은행코드를 DISTINCT 커서로 훑어, 은행마다 py_file + * 헤더를 만들고 해당 은행 건을 파일편성(F)+file_id 로 전이한다. ONE XA txn. + * host 변수는 전용 카피북 py_bankfile_cpy.h 를 EXEC SQL INCLUDE. + * Usage: py_bankfile_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long files = 0, recs = 0; + + EXEC SQL INCLUDE py_bankfile_cpy; + + strncpy(c_bizdate, bizdate, sizeof(c_bizdate)-1); c_bizdate[sizeof(c_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE pbf CURSOR FOR + SELECT DISTINCT a.bank_code + FROM py_payout p JOIN py_account a ON p.account_no = a.account_no + WHERE p.biz_date = :c_bizdate AND p.status = 'A' + ORDER BY a.bank_code; + EXEC SQL OPEN pbf; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH pbf INTO :c_bank; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d]\n", sqlca.sqlcode); EXEC SQL CLOSE pbf; tpabort(0); return 1; } + + EXEC SQL SELECT count(*), coalesce(sum(p.net),0) INTO :c_cnt, :c_sum + FROM py_payout p JOIN py_account a ON p.account_no = a.account_no + WHERE p.biz_date = :c_bizdate AND p.status = 'A' AND a.bank_code = :c_bank; + if (sqlca.sqlcode < 0 || c_cnt == 0) continue; + + snprintf(c_fname, sizeof(c_fname), "PAYOUT_%s_%s.dat", c_bank, c_bizdate); + EXEC SQL SELECT nextval('py_file_seq') INTO :c_fid; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pbf; tpabort(0); return 1; } + EXEC SQL INSERT INTO py_file (file_id, biz_date, file_name, record_count, total_amount, status) + VALUES (:c_fid, :c_bizdate, :c_fname, :c_cnt, :c_sum, 'G'); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pbf; tpabort(0); return 1; } + EXEC SQL UPDATE py_payout SET status = 'F', file_id = :c_fid, updated_at = now() + FROM py_account a + WHERE py_payout.account_no = a.account_no AND py_payout.biz_date = :c_bizdate + AND py_payout.status = 'A' AND a.bank_code = :c_bank; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pbf; tpabort(0); return 1; } + files++; recs += c_cnt; + printf(" 은행 %s: 파일 %ld 생성 (%ld건 / %ld원)\n", c_bank, c_fid, c_cnt, c_sum); + } + EXEC SQL CLOSE pbf; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_bankfile_batch COMMIT: bizdate=%s files=%ld recs=%ld\n", bizdate, files, recs); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_bankfile_cpy.h b/app/src/py/batch/py_bankfile_cpy.h new file mode 100644 index 0000000..5d0c476 --- /dev/null +++ b/app/src/py/batch/py_bankfile_cpy.h @@ -0,0 +1,7 @@ +/* py_bankfile_cpy.h - py_bankfile_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char c_bizdate[16]; +char c_bank[8]; +char c_fname[64]; +long c_fid, c_cnt, c_sum; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/py/batch/py_dormantstop_batch.pgc b/app/src/py/batch/py_dormantstop_batch.pgc new file mode 100644 index 0000000..71285ba --- /dev/null +++ b/app/src/py/batch/py_dormantstop_batch.pgc @@ -0,0 +1,59 @@ +/* + * py_dormantstop_batch.pgc - py 휴면 가맹점 지급정지 배치 (NOT EXISTS 커서, XA). + * + * 최근 N일 지급 이력이 없는 정상 가맹점을 NOT EXISTS 커서로 뽑아, 가맹점별로 + * 지급계좌를 DORMANT 로 내리고 잔여 요청(R)건을 '휴면정지' 보류시킨다. + * host 변수는 전용 카피북 py_dormantstop_cpy.h 를 EXEC SQL INCLUDE. + * Usage: py_dormantstop_batch [DAYS] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long stopped = 0, total_accts = 0, total_held = 0; + + EXEC SQL INCLUDE py_dormantstop_cpy; + + c_days = (argc > 1) ? atol(argv[1]) : 30; + if (c_days <= 0) c_days = 30; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE pds CURSOR FOR + SELECT m.merchant_id FROM merchant m + WHERE m.status = 'ACTIVE' + AND NOT EXISTS (SELECT 1 FROM py_payout p + WHERE p.merchant_id = m.merchant_id + AND p.created_at > now() - :c_days * interval '1 day') + ORDER BY m.merchant_id; + EXEC SQL OPEN pds; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH pds INTO :c_merch; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pds; tpabort(0); return 1; } + EXEC SQL UPDATE py_account SET status = 'DORMANT', updated_at = now() + WHERE merchant_id = :c_merch AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pds; tpabort(0); return 1; } + c_accts = sqlca.sqlerrd[2]; + EXEC SQL UPDATE py_payout SET status = 'H', reject_reason = '휴면정지', updated_at = now() + WHERE merchant_id = :c_merch AND status = 'R'; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pds; tpabort(0); return 1; } + c_held = sqlca.sqlerrd[2]; + stopped++; total_accts += c_accts; total_held += c_held; + printf(" [휴면정지] %s: 계좌 %ld개 정지, 지급 %ld건 보류\n", c_merch, c_accts, c_held); + } + EXEC SQL CLOSE pds; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_dormantstop_batch COMMIT: %ld일 기준 휴면=%ld곳 계좌=%ld 보류=%ld\n", + c_days, stopped, total_accts, total_held); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_dormantstop_cpy.h b/app/src/py/batch/py_dormantstop_cpy.h new file mode 100644 index 0000000..8da1f39 --- /dev/null +++ b/app/src/py/batch/py_dormantstop_cpy.h @@ -0,0 +1,6 @@ +/* py_dormantstop_cpy.h - py_dormantstop_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +long c_days; +char c_merch[64]; +long c_accts, c_held; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/py/batch/py_failstat_batch.pgc b/app/src/py/batch/py_failstat_batch.pgc new file mode 100644 index 0000000..7189593 --- /dev/null +++ b/app/src/py/batch/py_failstat_batch.pgc @@ -0,0 +1,55 @@ +/* + * py_failstat_batch.pgc - py 지급 실패사유 집계 리포트 배치 (GROUP BY 커서, XA). + * + * 당일 반려(J)건을 사유별 커서로 집계해 사유/건수/금액 표를 출력하고 + * 최다 사유를 강조 리포트한다 (read-only 분석 배치). + * host 변수는 전용 카피북 py_failstat_cpy.h 를 EXEC SQL INCLUDE. + * Usage: py_failstat_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long groups = 0, fails = 0, amount = 0; + char top_reason[128] = ""; + long top_cnt = 0; + + EXEC SQL INCLUDE py_failstat_cpy; + + strncpy(c_bizdate, bizdate, sizeof(c_bizdate)-1); c_bizdate[sizeof(c_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE pfs CURSOR FOR + SELECT coalesce(nullif(reject_reason,''),'(사유미기재)'), count(*), coalesce(sum(net),0) + FROM py_payout + WHERE biz_date = :c_bizdate AND status = 'J' + GROUP BY 1 ORDER BY 2 DESC, 1; + EXEC SQL OPEN pfs; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH pfs INTO :c_reason, :c_cnt, :c_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pfs; tpabort(0); return 1; } + printf(" [실패사유] %-24s %6ld건 %12ld원\n", c_reason, c_cnt, c_amt); + if (groups == 0) { strncpy(top_reason, c_reason, sizeof(top_reason)-1); top_cnt = c_cnt; } + groups++; fails += c_cnt; amount += c_amt; + } + EXEC SQL CLOSE pfs; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + if (groups > 0) + printf(">>> py_failstat_batch COMMIT: bizdate=%s 사유=%ld종 반려=%ld건/%ld원 최다='%s'(%ld건)\n", + bizdate, groups, fails, amount, top_reason, top_cnt); + else + printf(">>> py_failstat_batch COMMIT: bizdate=%s 반려 없음\n", bizdate); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_failstat_cpy.h b/app/src/py/batch/py_failstat_cpy.h new file mode 100644 index 0000000..46d7654 --- /dev/null +++ b/app/src/py/batch/py_failstat_cpy.h @@ -0,0 +1,6 @@ +/* py_failstat_cpy.h - py_failstat_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char c_bizdate[16]; +char c_reason[128]; +long c_cnt, c_amt; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/py/batch/py_feewaive_batch.pgc b/app/src/py/batch/py_feewaive_batch.pgc new file mode 100644 index 0000000..960bcb5 --- /dev/null +++ b/app/src/py/batch/py_feewaive_batch.pgc @@ -0,0 +1,50 @@ +/* + * py_feewaive_batch.pgc - py 소액 지급 수수료 일괄 면제 배치 (INSERT..SELECT + UPDATE, XA). + * + * 당일 요청(R)건 중 지급액이 임계 이하인 소액건의 수수료를 전액 면제: + * 먼저 INSERT..SELECT 로 py_adjust 증적을 뜨고, 이어서 fee=0/net=amount 로 + * 일괄 갱신한다 (증적 건수와 갱신 건수 일치 검증 출력). + * host 변수는 전용 카피북 py_feewaive_cpy.h 를 EXEC SQL INCLUDE. + * Usage: py_feewaive_batch [YYYY-MM-DD] [THRESHOLD] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL INCLUDE py_feewaive_cpy; + + strncpy(c_bizdate, bizdate, sizeof(c_bizdate)-1); c_bizdate[sizeof(c_bizdate)-1] = 0; + c_thr = (argc > 2) ? atol(argv[2]) : 30000; /* 기본 소액 기준 3만원 */ + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL INSERT INTO py_adjust (adj_id, payout_id, delta, reason, biz_date) + SELECT nextval('py_adjust_seq'), payout_id, fee, '소액수수료면제', biz_date + FROM py_payout + WHERE biz_date = :c_bizdate AND status = 'R' AND fee > 0 AND amount <= :c_thr; + if (sqlca.sqlcode < 0) { fprintf(stderr, "INSERT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + c_logged = sqlca.sqlerrd[2]; + + EXEC SQL UPDATE py_payout SET fee = 0, net = amount, updated_at = now() + WHERE biz_date = :c_bizdate AND status = 'R' AND fee > 0 AND amount <= :c_thr; + if (sqlca.sqlcode < 0) { fprintf(stderr, "UPDATE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + c_waived = sqlca.sqlerrd[2]; + + if (c_logged != c_waived) { + fprintf(stderr, "py_feewaive_batch 증적/갱신 불일치 %ld != %ld -> ROLLBACK\n", c_logged, c_waived); + tpabort(0); return 1; + } + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_feewaive_batch COMMIT: bizdate=%s 기준=%ld원 면제=%ld건(증적 %ld)\n", + bizdate, c_thr, c_waived, c_logged); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_feewaive_cpy.h b/app/src/py/batch/py_feewaive_cpy.h new file mode 100644 index 0000000..28ddf86 --- /dev/null +++ b/app/src/py/batch/py_feewaive_cpy.h @@ -0,0 +1,5 @@ +/* py_feewaive_cpy.h - py_feewaive_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char c_bizdate[16]; +long c_thr, c_logged, c_waived; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/py/batch/py_holdaging_batch.pgc b/app/src/py/batch/py_holdaging_batch.pgc new file mode 100644 index 0000000..42b73a7 --- /dev/null +++ b/app/src/py/batch/py_holdaging_batch.pgc @@ -0,0 +1,48 @@ +/* + * py_holdaging_batch.pgc - py 장기 보류 리포트 배치 (가맹점별 커서, XA). + * + * 보류(H) 상태로 N일 이상 경과한 건을 가맹점별 커서로 집계해 리포트를 + * 출력한다 (조치 없는 read-only 점검 배치). + * host 변수는 전용 카피북 py_holdaging_cpy.h 를 EXEC SQL INCLUDE. + * Usage: py_holdaging_batch [DAYS] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long merchants = 0, aged = 0, amount = 0; + + EXEC SQL INCLUDE py_holdaging_cpy; + + c_days = (argc > 1) ? atol(argv[1]) : 3; + if (c_days <= 0) c_days = 3; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE pha CURSOR FOR + SELECT merchant_id, count(*), coalesce(sum(net),0) + FROM py_payout + WHERE status = 'H' AND updated_at < now() - :c_days * interval '1 day' + GROUP BY merchant_id ORDER BY 2 DESC; + EXEC SQL OPEN pha; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH pha INTO :c_merch, :c_cnt, :c_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pha; tpabort(0); return 1; } + printf(" [장기보류] %s: %ld건 %ld원\n", c_merch, c_cnt, c_amt); + merchants++; aged += c_cnt; amount += c_amt; + } + EXEC SQL CLOSE pha; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_holdaging_batch COMMIT: %ld일+ 보류 가맹점=%ld 건수=%ld 금액=%ld\n", c_days, merchants, aged, amount); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_holdaging_cpy.h b/app/src/py/batch/py_holdaging_cpy.h new file mode 100644 index 0000000..1eced93 --- /dev/null +++ b/app/src/py/batch/py_holdaging_cpy.h @@ -0,0 +1,6 @@ +/* py_holdaging_cpy.h - py_holdaging_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +long c_days; +char c_merch[64]; +long c_cnt, c_amt; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/py/batch/py_limitguard_batch.pgc b/app/src/py/batch/py_limitguard_batch.pgc new file mode 100644 index 0000000..5c813b9 --- /dev/null +++ b/app/src/py/batch/py_limitguard_batch.pgc @@ -0,0 +1,53 @@ +/* + * py_limitguard_batch.pgc - py 지급한도 초과 자동 보류 배치 (HAVING 커서 + UPDATE, XA). + * + * 당일 확정성 지급(A/F/P) 합계가 일한도(merchant.daily_limit)를 넘긴 가맹점을 + * GROUP BY ... HAVING 커서로 뽑아, 초과액과 함께 잔여 요청(R)건을 보류시킨다. + * host 변수는 전용 카피북 py_limitguard_cpy.h 를 EXEC SQL INCLUDE. + * Usage: py_limitguard_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long over_merchants = 0, held = 0; + + EXEC SQL INCLUDE py_limitguard_cpy; + + strncpy(c_bizdate, bizdate, sizeof(c_bizdate)-1); c_bizdate[sizeof(c_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(90, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE plg CURSOR FOR + SELECT m.merchant_id, coalesce(sum(p.net),0) - m.daily_limit + FROM py_payout p JOIN merchant m ON p.merchant_id = m.merchant_id + WHERE p.biz_date = :c_bizdate AND p.status IN ('A','F','P') + GROUP BY m.merchant_id, m.daily_limit + HAVING coalesce(sum(p.net),0) > m.daily_limit; + EXEC SQL OPEN plg; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH plg INTO :c_merch, :c_over; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE plg; tpabort(0); return 1; } + over_merchants++; + EXEC SQL UPDATE py_payout SET status = 'H', reject_reason = '한도초과', updated_at = now() + WHERE merchant_id = :c_merch AND biz_date = :c_bizdate AND status = 'R'; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE plg; tpabort(0); return 1; } + held += sqlca.sqlerrd[2]; + printf(" [한도초과] %s: 초과액=%ld 보류=%ld건\n", c_merch, c_over, sqlca.sqlerrd[2]); + } + EXEC SQL CLOSE plg; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_limitguard_batch COMMIT: bizdate=%s 초과가맹점=%ld 보류=%ld건\n", bizdate, over_merchants, held); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_limitguard_cpy.h b/app/src/py/batch/py_limitguard_cpy.h new file mode 100644 index 0000000..1a73f9e --- /dev/null +++ b/app/src/py/batch/py_limitguard_cpy.h @@ -0,0 +1,6 @@ +/* py_limitguard_cpy.h - py_limitguard_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char c_bizdate[16]; +char c_merch[64]; +long c_over, c_limit; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/py/batch/py_mergesmall_batch.pgc b/app/src/py/batch/py_mergesmall_batch.pgc new file mode 100644 index 0000000..474562d --- /dev/null +++ b/app/src/py/batch/py_mergesmall_batch.pgc @@ -0,0 +1,60 @@ +/* + * py_mergesmall_batch.pgc - py 분할건 합산 정리 배치 (HAVING 커서 + 재생성, XA). + * + * 당일 요청(R) SPLIT 건이 2건 이상인 가맹점을 커서로 뽑아 가맹점마다 + * 원건들을 취소(C) 표시하고 합산 1건(BULK)으로 재생성한다 (이체 건수 절감). + * host 변수는 전용 카피북 py_mergesmall_cpy.h 를 EXEC SQL INCLUDE. + * Usage: py_mergesmall_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long merchants = 0, merged = 0; + + EXEC SQL INCLUDE py_mergesmall_cpy; + + strncpy(c_bizdate, bizdate, sizeof(c_bizdate)-1); c_bizdate[sizeof(c_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(120, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE pms CURSOR FOR + SELECT merchant_id, count(*), coalesce(sum(amount),0), coalesce(sum(fee),0), + coalesce(sum(net),0), coalesce(min(account_no),'') + FROM py_payout + WHERE biz_date = :c_bizdate AND status = 'R' AND channel = 'SPLIT' + GROUP BY merchant_id HAVING count(*) >= 2 + ORDER BY merchant_id; + EXEC SQL OPEN pms; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH pms INTO :c_merch, :c_cnt, :c_amt, :c_fee, :c_net, :c_acct; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pms; tpabort(0); return 1; } + EXEC SQL UPDATE py_payout SET status = 'C', reject_reason = '분할합산', updated_at = now() + WHERE merchant_id = :c_merch AND biz_date = :c_bizdate + AND status = 'R' AND channel = 'SPLIT'; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pms; tpabort(0); return 1; } + EXEC SQL SELECT nextval('py_payout_seq') INTO :c_newid; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pms; tpabort(0); return 1; } + EXEC SQL INSERT INTO py_payout (payout_id, merchant_id, account_no, amount, fee, net, + status, channel, biz_date) + VALUES (:c_newid, :c_merch, :c_acct, :c_amt, :c_fee, :c_net, 'R', 'BULK', :c_bizdate); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pms; tpabort(0); return 1; } + merchants++; merged += c_cnt; + printf(" [분할합산] %s: %ld건 -> BULK id=%ld (net=%ld)\n", c_merch, c_cnt, c_newid, c_net); + } + EXEC SQL CLOSE pms; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_mergesmall_batch COMMIT: bizdate=%s 가맹점=%ld 통합원건=%ld\n", bizdate, merchants, merged); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_mergesmall_cpy.h b/app/src/py/batch/py_mergesmall_cpy.h new file mode 100644 index 0000000..886b378 --- /dev/null +++ b/app/src/py/batch/py_mergesmall_cpy.h @@ -0,0 +1,7 @@ +/* py_mergesmall_cpy.h - py_mergesmall_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char c_bizdate[16]; +char c_merch[64]; +char c_acct[32]; +long c_cnt, c_amt, c_fee, c_net, c_newid; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/py/batch/py_notify_batch.pgc b/app/src/py/batch/py_notify_batch.pgc new file mode 100644 index 0000000..a93106e --- /dev/null +++ b/app/src/py/batch/py_notify_batch.pgc @@ -0,0 +1,47 @@ +/* + * py_notify_batch.pgc - py 지급완료 통지 발송 시뮬 배치 (읽기 커서, XA). + * + * 당일 지급완료(P) 건을 커서로 훑어 가맹점별 SMS 통지 발송을 stdout 으로 + * 시뮬레이션한다 (실발송 없음). 발송 건수/총액 리포트. + * host 변수는 전용 카피북 py_notify_cpy.h 를 EXEC SQL INCLUDE. + * Usage: py_notify_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long sent = 0, total = 0; + + EXEC SQL INCLUDE py_notify_cpy; + + strncpy(c_bizdate, bizdate, sizeof(c_bizdate)-1); c_bizdate[sizeof(c_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE pnt CURSOR FOR + SELECT payout_id, merchant_id, net FROM py_payout + WHERE biz_date = :c_bizdate AND status = 'P' + ORDER BY merchant_id, payout_id; + EXEC SQL OPEN pnt; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH pnt INTO :c_pid, :c_merch, :c_net; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pnt; tpabort(0); return 1; } + printf(" [SMS시뮬] %s님 지급 %ld원 완료 (payout=%ld)\n", c_merch, c_net, c_pid); + sent++; total += c_net; + } + EXEC SQL CLOSE pnt; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_notify_batch COMMIT: bizdate=%s 통지=%ld건 총액=%ld\n", bizdate, sent, total); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_notify_cpy.h b/app/src/py/batch/py_notify_cpy.h new file mode 100644 index 0000000..ef7e06b --- /dev/null +++ b/app/src/py/batch/py_notify_cpy.h @@ -0,0 +1,6 @@ +/* py_notify_cpy.h - py_notify_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char c_bizdate[16]; +char c_merch[64]; +long c_pid, c_net; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/py/batch/py_schedexpire_batch.pgc b/app/src/py/batch/py_schedexpire_batch.pgc new file mode 100644 index 0000000..a018e6a --- /dev/null +++ b/app/src/py/batch/py_schedexpire_batch.pgc @@ -0,0 +1,39 @@ +/* + * py_schedexpire_batch.pgc - py 기한경과 지급예약 만료 배치 (DELETE + 잔여 점검, XA). + * + * 기준일 이전(run_date < 기준일)인데 아직 미실행(S)인 예약을 DELETE 로 + * 만료시키고, 남은 유효 예약 건수를 함께 리포트한다. + * host 변수는 전용 카피북 py_schedexpire_cpy.h 를 EXEC SQL INCLUDE. + * Usage: py_schedexpire_batch [YYYY-MM-DD(기준일)] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *today = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL INCLUDE py_schedexpire_cpy; + + strncpy(c_today, today, sizeof(c_today)-1); c_today[sizeof(c_today)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DELETE FROM py_schedule + WHERE status = 'S' AND run_date < CAST(:c_today AS date); + if (sqlca.sqlcode < 0) { fprintf(stderr, "DELETE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + c_expired = sqlca.sqlerrd[2]; + + EXEC SQL SELECT count(*) INTO :c_remain FROM py_schedule WHERE status = 'S'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "SELECT FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_schedexpire_batch COMMIT: 기준=%s 만료삭제=%ld건 잔여예약=%ld건\n", today, c_expired, c_remain); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_schedexpire_cpy.h b/app/src/py/batch/py_schedexpire_cpy.h new file mode 100644 index 0000000..758faa6 --- /dev/null +++ b/app/src/py/batch/py_schedexpire_cpy.h @@ -0,0 +1,5 @@ +/* py_schedexpire_cpy.h - py_schedexpire_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char c_today[16]; +long c_expired, c_remain; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/py/batch/py_seizescan_batch.pgc b/app/src/py/batch/py_seizescan_batch.pgc new file mode 100644 index 0000000..6ed4595 --- /dev/null +++ b/app/src/py/batch/py_seizescan_batch.pgc @@ -0,0 +1,43 @@ +/* + * py_seizescan_batch.pgc - py 압류계좌 지급 차단 스캔 배치 (UPDATE FROM, XA). + * + * 압류(SEIZED) 계좌로 향하는 당일 미지급(R/A) 건을 UPDATE ... FROM 조인 + * 한 방으로 '압류차단' 보류(H) 전이하고, 압류 계좌 수도 함께 리포트. + * host 변수는 전용 카피북 py_seizescan_cpy.h 를 EXEC SQL INCLUDE. + * Usage: py_seizescan_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL INCLUDE py_seizescan_cpy; + + strncpy(c_bizdate, bizdate, sizeof(c_bizdate)-1); c_bizdate[sizeof(c_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*) INTO :c_accts FROM py_account WHERE status = 'SEIZED'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "SELECT FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + + EXEC SQL UPDATE py_payout SET status = 'H', reject_reason = '압류차단', updated_at = now() + FROM py_account a + WHERE py_payout.account_no = a.account_no + AND py_payout.biz_date = :c_bizdate + AND py_payout.status IN ('R','A') + AND a.status = 'SEIZED'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "UPDATE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + c_blocked = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_seizescan_batch COMMIT: bizdate=%s 압류계좌=%ld 차단보류=%ld건\n", bizdate, c_accts, c_blocked); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_seizescan_cpy.h b/app/src/py/batch/py_seizescan_cpy.h new file mode 100644 index 0000000..1cf8927 --- /dev/null +++ b/app/src/py/batch/py_seizescan_cpy.h @@ -0,0 +1,5 @@ +/* py_seizescan_cpy.h - py_seizescan_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char c_bizdate[16]; +long c_blocked, c_accts; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/py/batch/py_xferrecon_batch.pgc b/app/src/py/batch/py_xferrecon_batch.pgc new file mode 100644 index 0000000..50600c7 --- /dev/null +++ b/app/src/py/batch/py_xferrecon_batch.pgc @@ -0,0 +1,52 @@ +/* + * py_xferrecon_batch.pgc - py 이체결과 대사 보정 배치 (안티조인 커서 + INSERT, XA). + * + * 당일 지급완료(P)인데 XFER 기표가 없는 건을 NOT EXISTS 커서로 찾아 + * py_ledger 에 XFERFIX 보정 기표를 채워 원장을 맞춘다. + * host 변수는 전용 카피북 py_xferrecon_cpy.h 를 EXEC SQL INCLUDE. + * Usage: py_xferrecon_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + long fixed = 0, fixed_amt = 0; + + EXEC SQL INCLUDE py_xferrecon_cpy; + + strncpy(c_bizdate, bizdate, sizeof(c_bizdate)-1); c_bizdate[sizeof(c_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(90, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE pxr CURSOR FOR + SELECT p.payout_id, p.net FROM py_payout p + WHERE p.biz_date = :c_bizdate AND p.status = 'P' + AND NOT EXISTS (SELECT 1 FROM py_ledger l + WHERE l.payout_id = p.payout_id AND l.entry_type = 'XFER') + ORDER BY p.payout_id; + EXEC SQL OPEN pxr; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH pxr INTO :c_pid, :c_net; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pxr; tpabort(0); return 1; } + EXEC SQL INSERT INTO py_ledger (ledger_id, payout_id, entry_type, amount, biz_date) + VALUES (nextval('py_ledger_seq'), :c_pid, 'XFERFIX', :c_net, :c_bizdate); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pxr; tpabort(0); return 1; } + fixed++; fixed_amt += c_net; + printf(" [대사보정] payout=%ld XFERFIX %ld원 기표\n", c_pid, c_net); + } + EXEC SQL CLOSE pxr; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> py_xferrecon_batch COMMIT: bizdate=%s 보정기표=%ld건 %ld원\n", bizdate, fixed, fixed_amt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_xferrecon_cpy.h b/app/src/py/batch/py_xferrecon_cpy.h new file mode 100644 index 0000000..a7091bf --- /dev/null +++ b/app/src/py/batch/py_xferrecon_cpy.h @@ -0,0 +1,5 @@ +/* py_xferrecon_cpy.h - py_xferrecon_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char c_bizdate[16]; +long c_pid, c_net; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/py/py_svr.pgc b/app/src/py/py_svr.pgc index 3bbad68..7f4897a 100644 --- a/app/src/py/py_svr.pgc +++ b/app/src/py/py_svr.pgc @@ -2,7 +2,7 @@ * py_svr.pgc - py (지급) MODULE SERVER (thin dispatcher). * * The legacy "one module server, many services" pattern: this binary owns NO - * business logic. Each of py's 30 online services now lives in its own file + * business logic. Each of py's 75 online services now lives in its own file * (app/src/py/svc/.pgc) with its own copybook header * (app/src/py/svc/.h); those objects are archived into libpysvc.a and * linked into this server. Here we only: @@ -49,6 +49,51 @@ #include "PAY_REMIT.h" #include "PAY_CLOSE.h" #include "PAY_SUMMARY.h" +#include "PAY_BANKFILE.h" +#include "PAY_FILEVERIFY.h" +#include "PAY_FILEREBUILD.h" +#include "PAY_FILESTAT.h" +#include "PAY_NOTIFYSMS.h" +#include "PAY_NOTIFYEMAIL.h" +#include "PAY_NOTIFYPLAN.h" +#include "PAY_ACCTCHANGE.h" +#include "PAY_REPAYNEW.h" +#include "PAY_HOLDREASON.h" +#include "PAY_HOLDLIST.h" +#include "PAY_HOLDAGING.h" +#include "PAY_SEIZE.h" +#include "PAY_SEIZEREL.h" +#include "PAY_SEIZECHK.h" +#include "PAY_URGENT.h" +#include "PAY_ADVCALC.h" +#include "PAY_ADVREPAY.h" +#include "PAY_MERGE.h" +#include "PAY_SPLITRATIO.h" +#include "PAY_FEERATE.h" +#include "PAY_FEEWAIVE.h" +#include "PAY_FEEMIN.h" +#include "PAY_FAILSTAT.h" +#include "PAY_FAILTOP.h" +#include "PAY_FAILRATE.h" +#include "PAY_XFERMATCH.h" +#include "PAY_XFERMISS.h" +#include "PAY_REFUND.h" +#include "PAY_CANCELHIST.h" +#include "PAY_LIMITCHK.h" +#include "PAY_LIMITHOLD.h" +#include "PAY_DORMANT.h" +#include "PAY_DORMSTOP.h" +#include "PAY_APPRREQ.h" +#include "PAY_APPRBULK.h" +#include "PAY_APPRLIMIT.h" +#include "PAY_STATCHAN.h" +#include "PAY_STATBANK.h" +#include "PAY_STATDAILY.h" +#include "PAY_STATRANGE.h" +#include "PAY_SCHEDCANCEL.h" +#include "PAY_LEDGERSUM.h" +#include "PAY_ACCTLIST.h" +#include "PAY_NETRECALC.h" /* ----- advertise table: {service name, function} (fn is extern from its file) */ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { @@ -67,6 +112,29 @@ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { {"PAY_HISTORY", PAY_HISTORY}, {"PAY_REVERSE", PAY_REVERSE}, {"PAY_CONFIRM", PAY_CONFIRM}, {"PAY_REMIT", PAY_REMIT}, {"PAY_CLOSE", PAY_CLOSE}, {"PAY_SUMMARY", PAY_SUMMARY}, + {"PAY_BANKFILE", PAY_BANKFILE}, {"PAY_FILEVERIFY", PAY_FILEVERIFY}, + {"PAY_FILEREBUILD", PAY_FILEREBUILD}, {"PAY_FILESTAT", PAY_FILESTAT}, + {"PAY_NOTIFYSMS", PAY_NOTIFYSMS}, {"PAY_NOTIFYEMAIL", PAY_NOTIFYEMAIL}, + {"PAY_NOTIFYPLAN", PAY_NOTIFYPLAN}, {"PAY_ACCTCHANGE", PAY_ACCTCHANGE}, + {"PAY_REPAYNEW", PAY_REPAYNEW}, {"PAY_HOLDREASON", PAY_HOLDREASON}, + {"PAY_HOLDLIST", PAY_HOLDLIST}, {"PAY_HOLDAGING", PAY_HOLDAGING}, + {"PAY_SEIZE", PAY_SEIZE}, {"PAY_SEIZEREL", PAY_SEIZEREL}, + {"PAY_SEIZECHK", PAY_SEIZECHK}, {"PAY_URGENT", PAY_URGENT}, + {"PAY_ADVCALC", PAY_ADVCALC}, {"PAY_ADVREPAY", PAY_ADVREPAY}, + {"PAY_MERGE", PAY_MERGE}, {"PAY_SPLITRATIO", PAY_SPLITRATIO}, + {"PAY_FEERATE", PAY_FEERATE}, {"PAY_FEEWAIVE", PAY_FEEWAIVE}, + {"PAY_FEEMIN", PAY_FEEMIN}, {"PAY_FAILSTAT", PAY_FAILSTAT}, + {"PAY_FAILTOP", PAY_FAILTOP}, {"PAY_FAILRATE", PAY_FAILRATE}, + {"PAY_XFERMATCH", PAY_XFERMATCH}, {"PAY_XFERMISS", PAY_XFERMISS}, + {"PAY_REFUND", PAY_REFUND}, {"PAY_CANCELHIST", PAY_CANCELHIST}, + {"PAY_LIMITCHK", PAY_LIMITCHK}, {"PAY_LIMITHOLD", PAY_LIMITHOLD}, + {"PAY_DORMANT", PAY_DORMANT}, {"PAY_DORMSTOP", PAY_DORMSTOP}, + {"PAY_APPRREQ", PAY_APPRREQ}, {"PAY_APPRBULK", PAY_APPRBULK}, + {"PAY_APPRLIMIT", PAY_APPRLIMIT}, {"PAY_STATCHAN", PAY_STATCHAN}, + {"PAY_STATBANK", PAY_STATBANK}, {"PAY_STATDAILY", PAY_STATDAILY}, + {"PAY_STATRANGE", PAY_STATRANGE}, {"PAY_SCHEDCANCEL", PAY_SCHEDCANCEL}, + {"PAY_LEDGERSUM", PAY_LEDGERSUM}, {"PAY_ACCTLIST", PAY_ACCTLIST}, + {"PAY_NETRECALC", PAY_NETRECALC}, {NULL, NULL} }; diff --git a/app/src/py/svc/PAY_ACCTCHANGE.h b/app/src/py/svc/PAY_ACCTCHANGE.h new file mode 100644 index 0000000..25dfc1b --- /dev/null +++ b/app/src/py/svc/PAY_ACCTCHANGE.h @@ -0,0 +1,19 @@ +/* + * PAY_ACCTCHANGE.h - py 서비스 PAY_ACCTCHANGE 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id), T_KEY1(새 계좌번호) + * Response fields: T_RC(0 변경 / 1 상태불가 / 2 소유아님 / 3 비정상계좌), T_MSG + */ +#ifndef PAY_ACCTCHANGE_H +#define PAY_ACCTCHANGE_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + char new_account[32]; + char acct_status[16]; +} py_acctchange_rec_t; + +void PAY_ACCTCHANGE(TPSVCINFO *p); + +#endif /* PAY_ACCTCHANGE_H */ diff --git a/app/src/py/svc/PAY_ACCTCHANGE.pgc b/app/src/py/svc/PAY_ACCTCHANGE.pgc new file mode 100644 index 0000000..6d65c7a --- /dev/null +++ b/app/src/py/svc/PAY_ACCTCHANGE.pgc @@ -0,0 +1,42 @@ +/* + * PAY_ACCTCHANGE.pgc - py 모듈 서비스 PAY_ACCTCHANGE (one service = one file). + * 지급계좌 변경: 미지급(R/H/J) 건만 대상. 새 계좌가 동일 가맹점의 + * 정상(ACTIVE) 계좌인지 검증한 뒤 지급건의 입금계좌를 교체한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_ACCTCHANGE.h" + +void PAY_ACCTCHANGE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid; + char h_acct[32], h_st[4], h_merch[64], h_ast[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_ID1); + gets_(b, T_KEY1, h_acct, sizeof(h_acct)); + if (h_acct[0] == 0) { userlog("PAY_ACCTCHANGE: 새 계좌 누락"); FAIL(b); } + + EXEC SQL SELECT status, merchant_id INTO :h_st, :h_merch + FROM py_payout WHERE payout_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + if (h_st[0] != 'R' && h_st[0] != 'H' && h_st[0] != 'J') { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "미지급 상태에서만 계좌 변경 가능", 0L); + FAIL(b); + } + EXEC SQL SELECT status INTO :h_ast FROM py_account + WHERE account_no = :h_acct AND merchant_id = :h_merch; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { setl(b, T_RC, 2); Bchg(b, T_MSG, 0, "가맹점 소유 계좌 아님", 0L); FAIL(b); } + if (strcmp(h_ast, "ACTIVE") != 0) { setl(b, T_RC, 3); Bchg(b, T_MSG, 0, "정상 상태 계좌 아님", 0L); FAIL(b); } + + EXEC SQL UPDATE py_payout SET account_no = :h_acct, updated_at = now() + WHERE payout_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_RC, 0); + Bchg(b, T_KEY1, 0, h_acct, 0L); + userlog("PAY_ACCTCHANGE payout=%ld new_acct=%s", h_pid, h_acct); + OK(b); +} diff --git a/app/src/py/svc/PAY_ACCTLIST.h b/app/src/py/svc/PAY_ACCTLIST.h new file mode 100644 index 0000000..0e82452 --- /dev/null +++ b/app/src/py/svc/PAY_ACCTLIST.h @@ -0,0 +1,21 @@ +/* + * PAY_ACCTLIST.h - py 서비스 PAY_ACCTLIST 카피북 (copybook / record header). + * Request fields: T_MERCHANT + * Response fields: T_RC(0 있음 / 1 없음), T_COUNT(계좌수), T_ARG1(정상 계좌수), + * T_GROSS(잔액 합), occurrence: T_KEY1/T_STR1/T_STR2/T_AMT1 + */ +#ifndef PAY_ACCTLIST_H +#define PAY_ACCTLIST_H + +#include "py_svc.h" + +typedef struct { + char merchant_id[64]; + char account_no[32]; + char bank_code[8]; + long balance; +} py_acctlist_rec_t; + +void PAY_ACCTLIST(TPSVCINFO *p); + +#endif /* PAY_ACCTLIST_H */ diff --git a/app/src/py/svc/PAY_ACCTLIST.pgc b/app/src/py/svc/PAY_ACCTLIST.pgc new file mode 100644 index 0000000..eb058d4 --- /dev/null +++ b/app/src/py/svc/PAY_ACCTLIST.pgc @@ -0,0 +1,46 @@ +/* + * PAY_ACCTLIST.pgc - py 모듈 서비스 PAY_ACCTLIST (one service = one file). + * 가맹점 지급계좌 현황: 가맹점의 모든 지급계좌를 커서로 훑어 계좌/은행/ + * 상태/잔액을 occurrence 로 회신하고 정상 계좌수와 잔액 합계를 산출한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_ACCTLIST.h" + +void PAY_ACCTLIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + int i = 0; + long active = 0, bal_total = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_acct[32], h_bank[8], h_ast[16]; + long h_bal; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + EXEC SQL DECLARE cur_acctlist CURSOR FOR + SELECT account_no, bank_code, status, balance + FROM py_account WHERE merchant_id = :h_merch + ORDER BY account_no; + EXEC SQL OPEN cur_acctlist; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cur_acctlist INTO :h_acct, :h_bank, :h_ast, :h_bal; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_acctlist; FAIL(b); } + if (strcmp(h_ast, "ACTIVE") == 0) active++; + bal_total += h_bal; + if (i < 10) { + Bchg(b, T_KEY1, i, h_acct, 0L); + Bchg(b, T_STR1, i, h_bank, 0L); + Bchg(b, T_STR2, i, h_ast, 0L); + Bchg(b, T_AMT1, i, (char *)&h_bal, 0L); + i++; + } + } + EXEC SQL CLOSE cur_acctlist; + setl(b, T_COUNT, (long)i); + setl(b, T_ARG1, active); + setl(b, T_GROSS, bal_total); + setl(b, T_RC, i > 0 ? 0 : 1); + OK(b); +} diff --git a/app/src/py/svc/PAY_ADVCALC.h b/app/src/py/svc/PAY_ADVCALC.h new file mode 100644 index 0000000..a91f0da --- /dev/null +++ b/app/src/py/svc/PAY_ADVCALC.h @@ -0,0 +1,20 @@ +/* + * PAY_ADVCALC.h - py 서비스 PAY_ADVCALC 카피북 (copybook / record header). + * Request fields: T_MERCHANT, T_ARG1(지급률 bps, 기본 8000) + * Response fields: T_GROSS(미지급 잔액), T_AMT1(선지급 가능액), T_AMT2(선지급 잔액) + */ +#ifndef PAY_ADVCALC_H +#define PAY_ADVCALC_H + +#include "py_svc.h" + +typedef struct { + char merchant_id[64]; + long pending_net; + long advance_out; + long available; +} py_advcalc_rec_t; + +void PAY_ADVCALC(TPSVCINFO *p); + +#endif /* PAY_ADVCALC_H */ diff --git a/app/src/py/svc/PAY_ADVCALC.pgc b/app/src/py/svc/PAY_ADVCALC.pgc new file mode 100644 index 0000000..c0fb89d --- /dev/null +++ b/app/src/py/svc/PAY_ADVCALC.pgc @@ -0,0 +1,39 @@ +/* + * PAY_ADVCALC.pgc - py 모듈 서비스 PAY_ADVCALC (one service = one file). + * 선지급 가능액 산정: 가맹점 미지급(R/A) 잔액에 지급률(bps)을 곱한 한도에서 + * 이미 나간 선지급(ADVANCE, 미상환) 잔액을 차감해 추가 가능액을 회신한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_ADVCALC.h" + +void PAY_ADVCALC(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long bps, avail; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64]; + long h_pend, h_out; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + bps = getl(b, T_ARG1); + if (bps <= 0 || bps > 10000) bps = 8000; /* 기본 지급률 80% */ + + EXEC SQL SELECT coalesce(sum(net),0) INTO :h_pend + FROM py_payout WHERE merchant_id = :h_merch AND status IN ('R','A'); + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(net),0) INTO :h_out + FROM py_payout + WHERE merchant_id = :h_merch AND channel = 'ADVANCE' + AND status NOT IN ('C','V','X'); + if (sqlca.sqlcode < 0) FAIL(b); + + avail = h_pend * bps / 10000 - h_out; + if (avail < 0) avail = 0; + setl(b, T_RC, 0); + setl(b, T_GROSS, h_pend); + setl(b, T_AMT1, avail); + setl(b, T_AMT2, h_out); + setl(b, T_ARG1, bps); + OK(b); +} diff --git a/app/src/py/svc/PAY_ADVREPAY.h b/app/src/py/svc/PAY_ADVREPAY.h new file mode 100644 index 0000000..4b68a0c --- /dev/null +++ b/app/src/py/svc/PAY_ADVREPAY.h @@ -0,0 +1,19 @@ +/* + * PAY_ADVREPAY.h - py 서비스 PAY_ADVREPAY 카피북 (copybook / record header). + * Request fields: T_ID1(ADVANCE payout_id), T_AMOUNT(상환액), T_BIZDATE + * Response fields: T_RC(0 상환 / 1 선지급 아님), T_AMOUNT(실상환액), T_NET(잔여) + */ +#ifndef PAY_ADVREPAY_H +#define PAY_ADVREPAY_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + long repay_amount; + long remain_net; +} py_advrepay_rec_t; + +void PAY_ADVREPAY(TPSVCINFO *p); + +#endif /* PAY_ADVREPAY_H */ diff --git a/app/src/py/svc/PAY_ADVREPAY.pgc b/app/src/py/svc/PAY_ADVREPAY.pgc new file mode 100644 index 0000000..64850e2 --- /dev/null +++ b/app/src/py/svc/PAY_ADVREPAY.pgc @@ -0,0 +1,47 @@ +/* + * PAY_ADVREPAY.pgc - py 모듈 서비스 PAY_ADVREPAY (one service = one file). + * 선지급 상환: ADVANCE 채널 지급건의 잔여 net 에서 상환액을 차감하고 + * py_adjust 에 음수 조정으로 남긴다. 완납되면 마감(X) 전이. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_ADVREPAY.h" + +void PAY_ADVREPAY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long repay; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_net, h_new; + char h_chan[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_ID1); + repay = getl(b, T_AMOUNT); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + + EXEC SQL SELECT channel, net INTO :h_chan, :h_net + FROM py_payout WHERE payout_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + if (strcmp(h_chan, "ADVANCE") != 0) { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "선지급 건만 상환 가능", 0L); + FAIL(b); + } + if (repay <= 0 || repay > h_net) repay = h_net; /* 초과 상환은 잔액 전액으로 절사 */ + if (pydb_insert_adjust(h_pid, -repay, "선지급상환", bizdate) < 0) FAIL(b); + h_new = h_net - repay; + EXEC SQL UPDATE py_payout + SET net = :h_new, + status = CASE WHEN :h_new <= 0 THEN 'X' ELSE status END, + updated_at = now() + WHERE payout_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_RC, 0); + setl(b, T_AMOUNT, repay); + setl(b, T_NET, h_new); + Bchg(b, T_STATUS, 0, h_new <= 0 ? "X" : "A", 0L); + userlog("PAY_ADVREPAY id=%ld repay=%ld remain=%ld", h_pid, repay, h_new); + OK(b); +} diff --git a/app/src/py/svc/PAY_APPRBULK.h b/app/src/py/svc/PAY_APPRBULK.h new file mode 100644 index 0000000..bb1452c --- /dev/null +++ b/app/src/py/svc/PAY_APPRBULK.h @@ -0,0 +1,19 @@ +/* + * PAY_APPRBULK.h - py 서비스 PAY_APPRBULK 카피북 (copybook / record header). + * Request fields: T_BIZDATE, T_AMT1(자동승인 한도, 기본 500000) + * Response fields: T_COUNT(승인 건수), T_ARG1(잔여 대기), T_AMT1(적용 한도) + */ +#ifndef PAY_APPRBULK_H +#define PAY_APPRBULK_H + +#include "py_svc.h" + +typedef struct { + char biz_date[16]; + long approve_cap; + long approved_count; +} py_apprbulk_rec_t; + +void PAY_APPRBULK(TPSVCINFO *p); + +#endif /* PAY_APPRBULK_H */ diff --git a/app/src/py/svc/PAY_APPRBULK.pgc b/app/src/py/svc/PAY_APPRBULK.pgc new file mode 100644 index 0000000..043b2a5 --- /dev/null +++ b/app/src/py/svc/PAY_APPRBULK.pgc @@ -0,0 +1,35 @@ +/* + * PAY_APPRBULK.pgc - py 모듈 서비스 PAY_APPRBULK (one service = one file). + * 소액 일괄 승인: 당일 요청(R)건 중 net 이 승인한도 이하인 건을 한 번의 + * UPDATE 로 승인(A)하고, 남은 고액 대기 건수를 함께 회신한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_APPRBULK.h" + +void PAY_APPRBULK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cap, h_appr, h_remain; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + h_cap = getl(b, T_AMT1); + if (h_cap <= 0) h_cap = 500000; /* 기본 자동승인 한도 50만원 */ + + EXEC SQL UPDATE py_payout SET status = 'A', updated_at = now() + WHERE biz_date = :h_bizdate AND status = 'R' AND net <= :h_cap; + if (sqlca.sqlcode < 0) FAIL(b); + h_appr = sqlca.sqlerrd[2]; + EXEC SQL SELECT count(*) INTO :h_remain + FROM py_payout WHERE biz_date = :h_bizdate AND status = 'R'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_RC, 0); + setl(b, T_COUNT, h_appr); + setl(b, T_ARG1, h_remain); + setl(b, T_AMT1, h_cap); + userlog("PAY_APPRBULK date=%s cap=%ld approved=%ld remain=%ld", h_bizdate, h_cap, h_appr, h_remain); + OK(b); +} diff --git a/app/src/py/svc/PAY_APPRLIMIT.h b/app/src/py/svc/PAY_APPRLIMIT.h new file mode 100644 index 0000000..7d99edb --- /dev/null +++ b/app/src/py/svc/PAY_APPRLIMIT.h @@ -0,0 +1,19 @@ +/* + * PAY_APPRLIMIT.h - py 서비스 PAY_APPRLIMIT 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id), T_KEY2(승인자 ID), T_AMT1(고액 기준, 기본 1천만) + * Response fields: T_RC(0 승인 / 1 상태불가 / 2 승인자 필요), T_NET, T_STATUS('A') + */ +#ifndef PAY_APPRLIMIT_H +#define PAY_APPRLIMIT_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + char approver[32]; + long high_cap; +} py_apprlimit_rec_t; + +void PAY_APPRLIMIT(TPSVCINFO *p); + +#endif /* PAY_APPRLIMIT_H */ diff --git a/app/src/py/svc/PAY_APPRLIMIT.pgc b/app/src/py/svc/PAY_APPRLIMIT.pgc new file mode 100644 index 0000000..51a3426 --- /dev/null +++ b/app/src/py/svc/PAY_APPRLIMIT.pgc @@ -0,0 +1,48 @@ +/* + * PAY_APPRLIMIT.pgc - py 모듈 서비스 PAY_APPRLIMIT (one service = one file). + * 고액 승인 통제: 한도 초과 건은 승인자 지정 없이는 거절(2). 승인 시 + * py_ledger 에 APPROVE 기표를 남겨 누가/얼마를 승인했는지 증적화한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_APPRLIMIT.h" + +void PAY_APPRLIMIT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char approver[32]; + long cap; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_net; + char h_st[4], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_ID1); + gets_(b, T_KEY2, approver, sizeof(approver)); + cap = getl(b, T_AMT1); + if (cap <= 0) cap = 10000000; /* 기본 고액 기준 1천만원 */ + + EXEC SQL SELECT net, status, CAST(biz_date AS text) INTO :h_net, :h_st, :h_bizdate + FROM py_payout WHERE payout_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + if (h_st[0] != 'R') { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "요청 상태 건만 승인 가능", 0L); + FAIL(b); + } + if (h_net > cap && approver[0] == 0) { + setl(b, T_RC, 2); + Bchg(b, T_MSG, 0, "고액 지급은 승인자 지정 필수", 0L); + FAIL(b); + } + EXEC SQL UPDATE py_payout SET status = 'A', updated_at = now() WHERE payout_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO py_ledger (ledger_id, payout_id, entry_type, amount, biz_date) + VALUES (nextval('py_ledger_seq'), :h_pid, 'APPROVE', :h_net, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_RC, 0); + setl(b, T_NET, h_net); + Bchg(b, T_STATUS, 0, "A", 0L); + userlog("PAY_APPRLIMIT id=%ld net=%ld approver=%s", h_pid, h_net, approver[0] ? approver : "-"); + OK(b); +} diff --git a/app/src/py/svc/PAY_APPRREQ.h b/app/src/py/svc/PAY_APPRREQ.h new file mode 100644 index 0000000..4756a4f --- /dev/null +++ b/app/src/py/svc/PAY_APPRREQ.h @@ -0,0 +1,19 @@ +/* + * PAY_APPRREQ.h - py 서비스 PAY_APPRREQ 카피북 (copybook / record header). + * Request fields: T_BIZDATE, T_ARG1(조회 건수, 기본 5, 최대 10) + * Response fields: T_COUNT, occurrence: T_ID2[i](payout_id), T_STR1[i](가맹점), T_AMT1[i](net) + */ +#ifndef PAY_APPRREQ_H +#define PAY_APPRREQ_H + +#include "py_svc.h" + +typedef struct { + char biz_date[16]; + long queue_limit; + long queued_count; +} py_apprreq_rec_t; + +void PAY_APPRREQ(TPSVCINFO *p); + +#endif /* PAY_APPRREQ_H */ diff --git a/app/src/py/svc/PAY_APPRREQ.pgc b/app/src/py/svc/PAY_APPRREQ.pgc new file mode 100644 index 0000000..3513037 --- /dev/null +++ b/app/src/py/svc/PAY_APPRREQ.pgc @@ -0,0 +1,42 @@ +/* + * PAY_APPRREQ.pgc - py 모듈 서비스 PAY_APPRREQ (one service = one file). + * 승인 대기 큐 조회: 당일 요청(R)건을 금액 내림차순 상위 N건 커서로 훑어 + * occurrence(T_ID2[i]/T_STR1[i]/T_AMT1[i]) 로 회신한다 (승인 워크플로 1단계). + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_APPRREQ.h" + +void PAY_APPRREQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + int i = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_lim, h_pid, h_net; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + h_lim = getl(b, T_ARG1); + if (h_lim <= 0 || h_lim > 10) h_lim = 5; + + EXEC SQL DECLARE cur_apprreq CURSOR FOR + SELECT payout_id, merchant_id, net FROM py_payout + WHERE biz_date = :h_bizdate AND status = 'R' + ORDER BY net DESC, payout_id + LIMIT :h_lim; + EXEC SQL OPEN cur_apprreq; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cur_apprreq INTO :h_pid, :h_merch, :h_net; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_apprreq; FAIL(b); } + Bchg(b, T_ID2, i, (char *)&h_pid, 0L); + Bchg(b, T_STR1, i, h_merch, 0L); + Bchg(b, T_AMT1, i, (char *)&h_net, 0L); + i++; + } + EXEC SQL CLOSE cur_apprreq; + setl(b, T_COUNT, (long)i); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/py/svc/PAY_BANKFILE.h b/app/src/py/svc/PAY_BANKFILE.h new file mode 100644 index 0000000..47dc50c --- /dev/null +++ b/app/src/py/svc/PAY_BANKFILE.h @@ -0,0 +1,20 @@ +/* + * PAY_BANKFILE.h - py 서비스 PAY_BANKFILE 카피북 (copybook / record header). + * Request fields: T_KEY1(bank_code), T_BIZDATE + * Response fields: T_RC(0 ok / 1 대상없음), T_ID2(file_id), T_COUNT, T_GROSS, T_STR1(파일명) + */ +#ifndef PAY_BANKFILE_H +#define PAY_BANKFILE_H + +#include "py_svc.h" + +typedef struct { + char bank_code[8]; + long file_id; + long record_count; + long total_amount; +} py_bankfile_rec_t; + +void PAY_BANKFILE(TPSVCINFO *p); + +#endif /* PAY_BANKFILE_H */ diff --git a/app/src/py/svc/PAY_BANKFILE.pgc b/app/src/py/svc/PAY_BANKFILE.pgc new file mode 100644 index 0000000..5d237ab --- /dev/null +++ b/app/src/py/svc/PAY_BANKFILE.pgc @@ -0,0 +1,48 @@ +/* + * PAY_BANKFILE.pgc - py 모듈 서비스 PAY_BANKFILE (one service = one file). + * 은행별 지급파일 생성: 지정 은행코드의 승인(A) 지급건만 py_account 조인으로 + * 골라 집계 -> py_file 생성 -> 대상 건을 파일편성(F) + file_id 로 일괄 전이. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_BANKFILE.h" + +void PAY_BANKFILE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char fname[64]; + EXEC SQL BEGIN DECLARE SECTION; + long h_fid, h_cnt, h_sum; + char h_bank[8], h_bizdate[16], h_fname[64]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, h_bank, sizeof(h_bank)); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_bank[0] == 0) { userlog("PAY_BANKFILE: 은행코드 누락"); FAIL(b); } + + EXEC SQL SELECT count(*), coalesce(sum(p.net),0) INTO :h_cnt, :h_sum + FROM py_payout p JOIN py_account a ON p.account_no = a.account_no + WHERE p.biz_date = :h_bizdate AND p.status = 'A' AND a.bank_code = :h_bank; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt == 0) { setl(b, T_RC, 1); setl(b, T_COUNT, 0); OK(b); } + + snprintf(fname, sizeof(fname), "PAYOUT_%s_%s.dat", h_bank, h_bizdate); + strncpy(h_fname, fname, sizeof(h_fname)-1); h_fname[sizeof(h_fname)-1] = 0; + EXEC SQL SELECT nextval('py_file_seq') INTO :h_fid; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO py_file (file_id, biz_date, file_name, record_count, total_amount, status) + VALUES (:h_fid, :h_bizdate, :h_fname, :h_cnt, :h_sum, 'G'); + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL UPDATE py_payout SET status = 'F', file_id = :h_fid, updated_at = now() + FROM py_account a + WHERE py_payout.account_no = a.account_no AND py_payout.biz_date = :h_bizdate + AND py_payout.status = 'A' AND a.bank_code = :h_bank; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_RC, 0); + setl(b, T_ID2, h_fid); + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + Bchg(b, T_STR1, 0, fname, 0L); + userlog("PAY_BANKFILE bank=%s file_id=%ld recs=%ld amt=%ld", h_bank, h_fid, h_cnt, h_sum); + OK(b); +} diff --git a/app/src/py/svc/PAY_CANCELHIST.h b/app/src/py/svc/PAY_CANCELHIST.h new file mode 100644 index 0000000..c02979b --- /dev/null +++ b/app/src/py/svc/PAY_CANCELHIST.h @@ -0,0 +1,21 @@ +/* + * PAY_CANCELHIST.h - py 서비스 PAY_CANCELHIST 카피북 (copybook / record header). + * Request fields: T_MERCHANT, T_BIZDATE + * Response fields: T_ARG1/T_AMT1(취소 C 건수/금액), T_ARG2/T_AMT2(환입 V 건수/금액), T_COUNT + */ +#ifndef PAY_CANCELHIST_H +#define PAY_CANCELHIST_H + +#include "py_svc.h" + +typedef struct { + char merchant_id[64]; + long cancel_count; + long cancel_amount; + long refund_count; + long refund_amount; +} py_cancelhist_rec_t; + +void PAY_CANCELHIST(TPSVCINFO *p); + +#endif /* PAY_CANCELHIST_H */ diff --git a/app/src/py/svc/PAY_CANCELHIST.pgc b/app/src/py/svc/PAY_CANCELHIST.pgc new file mode 100644 index 0000000..b8fa8f9 --- /dev/null +++ b/app/src/py/svc/PAY_CANCELHIST.pgc @@ -0,0 +1,35 @@ +/* + * PAY_CANCELHIST.pgc - py 모듈 서비스 PAY_CANCELHIST (one service = one file). + * 취소/환입 이력 집계: 가맹점 당일 취소(C)와 역분개/환입(V)을 각각 + * 건수/금액으로 나눠 한 번의 CASE 집계로 회신한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_CANCELHIST.h" + +void PAY_CANCELHIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16]; + long h_ccnt, h_camt, h_vcnt, h_vamt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL SELECT coalesce(sum(CASE WHEN status = 'C' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN status = 'C' THEN net ELSE 0 END),0), + coalesce(sum(CASE WHEN status = 'V' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN status = 'V' THEN net ELSE 0 END),0) + INTO :h_ccnt, :h_camt, :h_vcnt, :h_vamt + FROM py_payout + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_ARG1, h_ccnt); + setl(b, T_AMT1, h_camt); + setl(b, T_ARG2, h_vcnt); + setl(b, T_AMT2, h_vamt); + setl(b, T_COUNT, h_ccnt + h_vcnt); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/py/svc/PAY_DORMANT.h b/app/src/py/svc/PAY_DORMANT.h new file mode 100644 index 0000000..10d90bb --- /dev/null +++ b/app/src/py/svc/PAY_DORMANT.h @@ -0,0 +1,19 @@ +/* + * PAY_DORMANT.h - py 서비스 PAY_DORMANT 카피북 (copybook / record header). + * Request fields: T_ARG1(무거래 일수, 기본 30) + * Response fields: T_RC(0 없음 / 1 휴면 존재), T_COUNT(휴면 수), T_MERCHANT(대표 1곳) + */ +#ifndef PAY_DORMANT_H +#define PAY_DORMANT_H + +#include "py_svc.h" + +typedef struct { + long dormant_days; + long dormant_count; + char first_merchant[64]; +} py_dormant_rec_t; + +void PAY_DORMANT(TPSVCINFO *p); + +#endif /* PAY_DORMANT_H */ diff --git a/app/src/py/svc/PAY_DORMANT.pgc b/app/src/py/svc/PAY_DORMANT.pgc new file mode 100644 index 0000000..ef3c435 --- /dev/null +++ b/app/src/py/svc/PAY_DORMANT.pgc @@ -0,0 +1,33 @@ +/* + * PAY_DORMANT.pgc - py 모듈 서비스 PAY_DORMANT (one service = one file). + * 휴면 가맹점 검출: 최근 N일간 지급 이력이 전혀 없는 정상(ACTIVE) 가맹점 + * 수를 NOT EXISTS 로 세고 대표 1곳(가장 빠른 ID)을 함께 회신한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_DORMANT.h" + +void PAY_DORMANT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_days, h_cnt; + char h_first[64]; + EXEC SQL END DECLARE SECTION; + + h_days = getl(b, T_ARG1); + if (h_days <= 0 || h_days > 730) h_days = 30; + EXEC SQL SELECT count(*), coalesce(min(m.merchant_id),'') + INTO :h_cnt, :h_first + FROM merchant m + WHERE m.status = 'ACTIVE' + AND NOT EXISTS (SELECT 1 FROM py_payout p + WHERE p.merchant_id = m.merchant_id + AND p.created_at > now() - :h_days * interval '1 day'); + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_RC, h_cnt > 0 ? 1 : 0); + setl(b, T_COUNT, h_cnt); + setl(b, T_ARG1, h_days); + Bchg(b, T_MERCHANT, 0, h_first, 0L); + OK(b); +} diff --git a/app/src/py/svc/PAY_DORMSTOP.h b/app/src/py/svc/PAY_DORMSTOP.h new file mode 100644 index 0000000..9620589 --- /dev/null +++ b/app/src/py/svc/PAY_DORMSTOP.h @@ -0,0 +1,19 @@ +/* + * PAY_DORMSTOP.h - py 서비스 PAY_DORMSTOP 카피북 (copybook / record header). + * Request fields: T_MERCHANT + * Response fields: T_RC(0 정지 / 1 대상없음), T_ARG1(정지 계좌수), T_COUNT(보류 건수) + */ +#ifndef PAY_DORMSTOP_H +#define PAY_DORMSTOP_H + +#include "py_svc.h" + +typedef struct { + char merchant_id[64]; + long stopped_accounts; + long held_payouts; +} py_dormstop_rec_t; + +void PAY_DORMSTOP(TPSVCINFO *p); + +#endif /* PAY_DORMSTOP_H */ diff --git a/app/src/py/svc/PAY_DORMSTOP.pgc b/app/src/py/svc/PAY_DORMSTOP.pgc new file mode 100644 index 0000000..5a23482 --- /dev/null +++ b/app/src/py/svc/PAY_DORMSTOP.pgc @@ -0,0 +1,39 @@ +/* + * PAY_DORMSTOP.pgc - py 모듈 서비스 PAY_DORMSTOP (one service = one file). + * 휴면 가맹점 지급정지: 대상 가맹점의 정상 계좌를 DORMANT 로 내리고 + * 잔여 요청(R) 지급건을 '휴면정지' 사유로 보류(H)한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_DORMSTOP.h" + +void PAY_DORMSTOP(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64]; + long h_accts, h_held; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + if (h_merch[0] == 0) { userlog("PAY_DORMSTOP: 가맹점 누락"); FAIL(b); } + + EXEC SQL UPDATE py_account SET status = 'DORMANT', updated_at = now() + WHERE merchant_id = :h_merch AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) FAIL(b); + h_accts = sqlca.sqlerrd[2]; + EXEC SQL UPDATE py_payout SET status = 'H', reject_reason = '휴면정지', updated_at = now() + WHERE merchant_id = :h_merch AND status = 'R'; + if (sqlca.sqlcode < 0) FAIL(b); + h_held = sqlca.sqlerrd[2]; + + if (h_accts == 0 && h_held == 0) { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "정지할 계좌/지급건 없음", 0L); + OK(b); + } + setl(b, T_RC, 0); + setl(b, T_ARG1, h_accts); + setl(b, T_COUNT, h_held); + userlog("PAY_DORMSTOP merch=%s accts=%ld held=%ld", h_merch, h_accts, h_held); + OK(b); +} diff --git a/app/src/py/svc/PAY_FAILRATE.h b/app/src/py/svc/PAY_FAILRATE.h new file mode 100644 index 0000000..211d7d4 --- /dev/null +++ b/app/src/py/svc/PAY_FAILRATE.h @@ -0,0 +1,20 @@ +/* + * PAY_FAILRATE.h - py 서비스 PAY_FAILRATE 카피북 (copybook / record header). + * Request fields: T_MERCHANT, T_BIZDATE + * Response fields: T_RC(0 산출 / 1 대상없음), T_COUNT(전체), T_ARG1(반려), T_ARG2(실패율 bps) + */ +#ifndef PAY_FAILRATE_H +#define PAY_FAILRATE_H + +#include "py_svc.h" + +typedef struct { + char merchant_id[64]; + long total_count; + long fail_count; + long fail_rate_bps; +} py_failrate_rec_t; + +void PAY_FAILRATE(TPSVCINFO *p); + +#endif /* PAY_FAILRATE_H */ diff --git a/app/src/py/svc/PAY_FAILRATE.pgc b/app/src/py/svc/PAY_FAILRATE.pgc new file mode 100644 index 0000000..720944e --- /dev/null +++ b/app/src/py/svc/PAY_FAILRATE.pgc @@ -0,0 +1,35 @@ +/* + * PAY_FAILRATE.pgc - py 모듈 서비스 PAY_FAILRATE (one service = one file). + * 지급 실패율 산출: 가맹점 당일 전체 지급건 대비 반려(J) 비중을 bps 로 + * 환산해 회신한다 (CASE 집계 1회). 대상 없으면 1. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_FAILRATE.h" + +void PAY_FAILRATE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rate; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16]; + long h_tot, h_fail; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL SELECT count(*), coalesce(sum(CASE WHEN status = 'J' THEN 1 ELSE 0 END),0) + INTO :h_tot, :h_fail + FROM py_payout WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_tot == 0) { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "산출 대상 지급건 없음", 0L); + OK(b); + } + rate = h_fail * 10000 / h_tot; + setl(b, T_RC, 0); + setl(b, T_COUNT, h_tot); + setl(b, T_ARG1, h_fail); + setl(b, T_ARG2, rate); + OK(b); +} diff --git a/app/src/py/svc/PAY_FAILSTAT.h b/app/src/py/svc/PAY_FAILSTAT.h new file mode 100644 index 0000000..abe147c --- /dev/null +++ b/app/src/py/svc/PAY_FAILSTAT.h @@ -0,0 +1,21 @@ +/* + * PAY_FAILSTAT.h - py 서비스 PAY_FAILSTAT 카피북 (copybook / record header). + * Request fields: T_BIZDATE + * Response fields: T_COUNT(총 반려), T_GROSS(총액), T_ARG2(사유 그룹수), + * occurrence: T_STR1[i](사유), T_ARG1[i](건수), T_AMT1[i](금액) + */ +#ifndef PAY_FAILSTAT_H +#define PAY_FAILSTAT_H + +#include "py_svc.h" + +typedef struct { + char biz_date[16]; + char fail_reason[128]; + long fail_count; + long fail_amount; +} py_failstat_rec_t; + +void PAY_FAILSTAT(TPSVCINFO *p); + +#endif /* PAY_FAILSTAT_H */ diff --git a/app/src/py/svc/PAY_FAILSTAT.pgc b/app/src/py/svc/PAY_FAILSTAT.pgc new file mode 100644 index 0000000..f82ab6a --- /dev/null +++ b/app/src/py/svc/PAY_FAILSTAT.pgc @@ -0,0 +1,46 @@ +/* + * PAY_FAILSTAT.pgc - py 모듈 서비스 PAY_FAILSTAT (one service = one file). + * 실패사유별 통계: 당일 반려(J)건을 사유별 커서로 집계해 상위 8개 사유를 + * occurrence 로, 총 반려건/총액을 스칼라로 회신한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_FAILSTAT.h" + +void PAY_FAILSTAT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + int i = 0; + long tot_cnt = 0, tot_amt = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_reason[128]; + long h_cnt, h_amt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL DECLARE cur_failstat CURSOR FOR + SELECT coalesce(nullif(reject_reason,''),'(사유미기재)'), count(*), coalesce(sum(net),0) + FROM py_payout + WHERE biz_date = :h_bizdate AND status = 'J' + GROUP BY 1 ORDER BY 2 DESC, 3 DESC; + EXEC SQL OPEN cur_failstat; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cur_failstat INTO :h_reason, :h_cnt, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_failstat; FAIL(b); } + tot_cnt += h_cnt; + tot_amt += h_amt; + if (i < 8) { + Bchg(b, T_STR1, i, h_reason, 0L); + Bchg(b, T_ARG1, i, (char *)&h_cnt, 0L); + Bchg(b, T_AMT1, i, (char *)&h_amt, 0L); + i++; + } + } + EXEC SQL CLOSE cur_failstat; + setl(b, T_COUNT, tot_cnt); + setl(b, T_GROSS, tot_amt); + setl(b, T_ARG2, (long)i); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/py/svc/PAY_FAILTOP.h b/app/src/py/svc/PAY_FAILTOP.h new file mode 100644 index 0000000..a902de0 --- /dev/null +++ b/app/src/py/svc/PAY_FAILTOP.h @@ -0,0 +1,19 @@ +/* + * PAY_FAILTOP.h - py 서비스 PAY_FAILTOP 카피북 (copybook / record header). + * Request fields: T_BIZDATE + * Response fields: T_RC(0 있음 / 1 없음), T_MERCHANT(최다 실패 가맹점), T_COUNT, T_GROSS + */ +#ifndef PAY_FAILTOP_H +#define PAY_FAILTOP_H + +#include "py_svc.h" + +typedef struct { + char biz_date[16]; + char top_merchant[64]; + long fail_count; +} py_failtop_rec_t; + +void PAY_FAILTOP(TPSVCINFO *p); + +#endif /* PAY_FAILTOP_H */ diff --git a/app/src/py/svc/PAY_FAILTOP.pgc b/app/src/py/svc/PAY_FAILTOP.pgc new file mode 100644 index 0000000..802ec4b --- /dev/null +++ b/app/src/py/svc/PAY_FAILTOP.pgc @@ -0,0 +1,37 @@ +/* + * PAY_FAILTOP.pgc - py 모듈 서비스 PAY_FAILTOP (one service = one file). + * 최다 실패 가맹점: 당일 반려(J)건이 가장 많은 가맹점 1곳을 ORDER BY + + * LIMIT 1 로 뽑아 가맹점/건수/금액을 회신한다. 반려 없으면 1. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_FAILTOP.h" + +void PAY_FAILTOP(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_cnt, h_amt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL SELECT merchant_id, count(*), coalesce(sum(net),0) + INTO :h_merch, :h_cnt, :h_amt + FROM py_payout + WHERE biz_date = :h_bizdate AND status = 'J' + GROUP BY merchant_id + ORDER BY count(*) DESC, merchant_id + LIMIT 1; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { + setl(b, T_RC, 1); + setl(b, T_COUNT, 0); + Bchg(b, T_MSG, 0, "당일 반려 건 없음", 0L); + OK(b); + } + setl(b, T_RC, 0); + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_amt); + Bchg(b, T_MERCHANT, 0, h_merch, 0L); + OK(b); +} diff --git a/app/src/py/svc/PAY_FEEMIN.h b/app/src/py/svc/PAY_FEEMIN.h new file mode 100644 index 0000000..ac26803 --- /dev/null +++ b/app/src/py/svc/PAY_FEEMIN.h @@ -0,0 +1,19 @@ +/* + * PAY_FEEMIN.h - py 서비스 PAY_FEEMIN 카피북 (copybook / record header). + * Request fields: T_BIZDATE, T_AMT1(최소수수료, 기본 100) + * Response fields: T_RC(0), T_COUNT(보정 건수), T_AMT1(적용 최소수수료) + */ +#ifndef PAY_FEEMIN_H +#define PAY_FEEMIN_H + +#include "py_svc.h" + +typedef struct { + char biz_date[16]; + long min_fee; + long fixed_count; +} py_feemin_rec_t; + +void PAY_FEEMIN(TPSVCINFO *p); + +#endif /* PAY_FEEMIN_H */ diff --git a/app/src/py/svc/PAY_FEEMIN.pgc b/app/src/py/svc/PAY_FEEMIN.pgc new file mode 100644 index 0000000..bc414a7 --- /dev/null +++ b/app/src/py/svc/PAY_FEEMIN.pgc @@ -0,0 +1,33 @@ +/* + * PAY_FEEMIN.pgc - py 모듈 서비스 PAY_FEEMIN (one service = one file). + * 최소수수료 보정: 당일 요청(R) 건 중 수수료가 최소수수료 미만인 건을 + * 한 번의 가드된 UPDATE 로 최소수수료로 끌어올리고 net 을 재계산한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_FEEMIN.h" + +void PAY_FEEMIN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_min, h_aff; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + h_min = getl(b, T_AMT1); + if (h_min <= 0) h_min = 100; /* 기본 최소수수료 100원 */ + + EXEC SQL UPDATE py_payout + SET fee = :h_min, net = amount - :h_min, updated_at = now() + WHERE biz_date = :h_bizdate AND status = 'R' + AND fee < :h_min AND amount > :h_min; + if (sqlca.sqlcode < 0) FAIL(b); + h_aff = sqlca.sqlerrd[2]; + + setl(b, T_RC, 0); + setl(b, T_COUNT, h_aff); + setl(b, T_AMT1, h_min); + userlog("PAY_FEEMIN date=%s min=%ld fixed=%ld", h_bizdate, h_min, h_aff); + OK(b); +} diff --git a/app/src/py/svc/PAY_FEERATE.h b/app/src/py/svc/PAY_FEERATE.h new file mode 100644 index 0000000..7c094d0 --- /dev/null +++ b/app/src/py/svc/PAY_FEERATE.h @@ -0,0 +1,20 @@ +/* + * PAY_FEERATE.h - py 서비스 PAY_FEERATE 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id) + * Response fields: T_RC(0 재적용 / 1 상태불가), T_FEE, T_NET, T_ARG1(적용 bps) + */ +#ifndef PAY_FEERATE_H +#define PAY_FEERATE_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + long mdr_bps; + long new_fee; + long new_net; +} py_feerate_rec_t; + +void PAY_FEERATE(TPSVCINFO *p); + +#endif /* PAY_FEERATE_H */ diff --git a/app/src/py/svc/PAY_FEERATE.pgc b/app/src/py/svc/PAY_FEERATE.pgc new file mode 100644 index 0000000..9f1166b --- /dev/null +++ b/app/src/py/svc/PAY_FEERATE.pgc @@ -0,0 +1,38 @@ +/* + * PAY_FEERATE.pgc - py 모듈 서비스 PAY_FEERATE (one service = one file). + * 수수료율 재적용: 가맹점 계약 요율(merchant.mdr_bps)을 조인으로 가져와 + * 지급 수수료/순액을 재계산해 미지급(R/A) 건에 반영한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_FEERATE.h" + +void PAY_FEERATE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt, h_bps, h_fee, h_net; + char h_st[4]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_ID1); + EXEC SQL SELECT p.amount, p.status, m.mdr_bps INTO :h_amt, :h_st, :h_bps + FROM py_payout p JOIN merchant m ON p.merchant_id = m.merchant_id + WHERE p.payout_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + if (h_st[0] != 'R' && h_st[0] != 'A') { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "미지급 건만 요율 재적용 가능", 0L); + FAIL(b); + } + h_fee = acq_fee(h_amt, h_bps); + h_net = h_amt - h_fee; + EXEC SQL UPDATE py_payout SET fee = :h_fee, net = :h_net, updated_at = now() + WHERE payout_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_RC, 0); + setl(b, T_FEE, h_fee); + setl(b, T_NET, h_net); + setl(b, T_ARG1, h_bps); + OK(b); +} diff --git a/app/src/py/svc/PAY_FEEWAIVE.h b/app/src/py/svc/PAY_FEEWAIVE.h new file mode 100644 index 0000000..7160705 --- /dev/null +++ b/app/src/py/svc/PAY_FEEWAIVE.h @@ -0,0 +1,19 @@ +/* + * PAY_FEEWAIVE.h - py 서비스 PAY_FEEWAIVE 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id), T_REASON, T_BIZDATE + * Response fields: T_RC(0 면제 / 1 수수료없음 / 2 상태불가), T_FEE(면제액), T_NET + */ +#ifndef PAY_FEEWAIVE_H +#define PAY_FEEWAIVE_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + long waived_fee; + char waive_reason[96]; +} py_feewaive_rec_t; + +void PAY_FEEWAIVE(TPSVCINFO *p); + +#endif /* PAY_FEEWAIVE_H */ diff --git a/app/src/py/svc/PAY_FEEWAIVE.pgc b/app/src/py/svc/PAY_FEEWAIVE.pgc new file mode 100644 index 0000000..35bd54c --- /dev/null +++ b/app/src/py/svc/PAY_FEEWAIVE.pgc @@ -0,0 +1,39 @@ +/* + * PAY_FEEWAIVE.pgc - py 모듈 서비스 PAY_FEEWAIVE (one service = one file). + * 수수료 면제: 미지급(R/A) 건의 수수료를 0 으로 하고 net 을 amount 로 복원. + * 면제분은 py_adjust 에 양수 조정으로 증적을 남긴다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_FEEWAIVE.h" + +void PAY_FEEWAIVE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char reason[96], bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_fee, h_amt; + char h_st[4]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_ID1); + gets_(b, T_REASON, reason, sizeof(reason)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (reason[0] == 0) strcpy(reason, "수수료면제"); + + EXEC SQL SELECT fee, amount, status INTO :h_fee, :h_amt, :h_st + FROM py_payout WHERE payout_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + if (h_fee <= 0) { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "면제할 수수료 없음", 0L); FAIL(b); } + if (h_st[0] != 'R' && h_st[0] != 'A') { setl(b, T_RC, 2); Bchg(b, T_MSG, 0, "미지급 건만 면제 가능", 0L); FAIL(b); } + + if (pydb_insert_adjust(h_pid, h_fee, reason, bizdate) < 0) FAIL(b); + EXEC SQL UPDATE py_payout SET fee = 0, net = amount, updated_at = now() + WHERE payout_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_RC, 0); + setl(b, T_FEE, h_fee); + setl(b, T_NET, h_amt); + userlog("PAY_FEEWAIVE id=%ld waived=%ld", h_pid, h_fee); + OK(b); +} diff --git a/app/src/py/svc/PAY_FILEREBUILD.h b/app/src/py/svc/PAY_FILEREBUILD.h new file mode 100644 index 0000000..679fb3b --- /dev/null +++ b/app/src/py/svc/PAY_FILEREBUILD.h @@ -0,0 +1,19 @@ +/* + * PAY_FILEREBUILD.h - py 서비스 PAY_FILEREBUILD 카피북 (copybook / record header). + * Request fields: T_ID2(file_id) + * Response fields: T_RC(0 ok / 1 확정파일 거절), T_COUNT, T_GROSS, T_MSG + */ +#ifndef PAY_FILEREBUILD_H +#define PAY_FILEREBUILD_H + +#include "py_svc.h" + +typedef struct { + long file_id; + char file_status[4]; + long rebuilt_count; +} py_filerebuild_rec_t; + +void PAY_FILEREBUILD(TPSVCINFO *p); + +#endif /* PAY_FILEREBUILD_H */ diff --git a/app/src/py/svc/PAY_FILEREBUILD.pgc b/app/src/py/svc/PAY_FILEREBUILD.pgc new file mode 100644 index 0000000..b7adad4 --- /dev/null +++ b/app/src/py/svc/PAY_FILEREBUILD.pgc @@ -0,0 +1,36 @@ +/* + * PAY_FILEREBUILD.pgc - py 모듈 서비스 PAY_FILEREBUILD (one service = one file). + * 지급파일 재생성: 아직 미확정(G)인 파일 헤더를 실제 편성 상세 기준으로 + * 재집계해 갱신한다. 확정(C)된 파일은 재생성 불가로 거절. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_FILEREBUILD.h" + +void PAY_FILEREBUILD(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_fid, h_cnt, h_sum; + char h_st[4]; + EXEC SQL END DECLARE SECTION; + + h_fid = getl(b, T_ID2); + EXEC SQL SELECT status INTO :h_st FROM py_file WHERE file_id = :h_fid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + if (h_st[0] == 'C') { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "확정 파일은 재생성 불가", 0L); + FAIL(b); + } + EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_cnt, :h_sum + FROM py_payout WHERE file_id = :h_fid AND status = 'F'; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL UPDATE py_file SET record_count = :h_cnt, total_amount = :h_sum + WHERE file_id = :h_fid; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_RC, 0); + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + userlog("PAY_FILEREBUILD file=%ld recs=%ld amt=%ld", h_fid, h_cnt, h_sum); + OK(b); +} diff --git a/app/src/py/svc/PAY_FILESTAT.h b/app/src/py/svc/PAY_FILESTAT.h new file mode 100644 index 0000000..588442d --- /dev/null +++ b/app/src/py/svc/PAY_FILESTAT.h @@ -0,0 +1,19 @@ +/* + * PAY_FILESTAT.h - py 서비스 PAY_FILESTAT 카피북 (copybook / record header). + * Request fields: T_BIZDATE + * Response fields: T_ARG1(생성 G), T_ARG2(확정 C), T_COUNT(총레코드), T_GROSS(총액) + */ +#ifndef PAY_FILESTAT_H +#define PAY_FILESTAT_H + +#include "py_svc.h" + +typedef struct { + char biz_date[16]; + long gen_files; + long confirmed_files; +} py_filestat_rec_t; + +void PAY_FILESTAT(TPSVCINFO *p); + +#endif /* PAY_FILESTAT_H */ diff --git a/app/src/py/svc/PAY_FILESTAT.pgc b/app/src/py/svc/PAY_FILESTAT.pgc new file mode 100644 index 0000000..b81adc9 --- /dev/null +++ b/app/src/py/svc/PAY_FILESTAT.pgc @@ -0,0 +1,32 @@ +/* + * PAY_FILESTAT.pgc - py 모듈 서비스 PAY_FILESTAT (one service = one file). + * 지급파일 현황: 당일 파일을 생성(G)/확정(C) 상태별로 세고 총 레코드수와 + * 총액을 한 번의 CASE 집계로 회신한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_FILESTAT.h" + +void PAY_FILESTAT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_gen, h_conf, h_recs, h_amt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL SELECT coalesce(sum(CASE WHEN status = 'G' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN status = 'C' THEN 1 ELSE 0 END),0), + coalesce(sum(record_count),0), + coalesce(sum(total_amount),0) + INTO :h_gen, :h_conf, :h_recs, :h_amt + FROM py_file WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_ARG1, h_gen); + setl(b, T_ARG2, h_conf); + setl(b, T_COUNT, h_recs); + setl(b, T_GROSS, h_amt); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/py/svc/PAY_FILEVERIFY.h b/app/src/py/svc/PAY_FILEVERIFY.h new file mode 100644 index 0000000..c5e6129 --- /dev/null +++ b/app/src/py/svc/PAY_FILEVERIFY.h @@ -0,0 +1,21 @@ +/* + * PAY_FILEVERIFY.h - py 서비스 PAY_FILEVERIFY 카피북 (copybook / record header). + * Request fields: T_ID2(file_id) + * Response fields: T_RC(0 일치 / 1 불일치), T_ARG1/T_AMT1(헤더), T_COUNT/T_GROSS(상세), T_MSG + */ +#ifndef PAY_FILEVERIFY_H +#define PAY_FILEVERIFY_H + +#include "py_svc.h" + +typedef struct { + long file_id; + long hdr_count; + long hdr_amount; + long dtl_count; + long dtl_amount; +} py_fileverify_rec_t; + +void PAY_FILEVERIFY(TPSVCINFO *p); + +#endif /* PAY_FILEVERIFY_H */ diff --git a/app/src/py/svc/PAY_FILEVERIFY.pgc b/app/src/py/svc/PAY_FILEVERIFY.pgc new file mode 100644 index 0000000..468ceaa --- /dev/null +++ b/app/src/py/svc/PAY_FILEVERIFY.pgc @@ -0,0 +1,38 @@ +/* + * PAY_FILEVERIFY.pgc - py 모듈 서비스 PAY_FILEVERIFY (one service = one file). + * 지급파일 검증: py_file 헤더의 건수/총액과 실제 편성된 py_payout 상세 집계를 + * 대사해 일치 여부를 회신한다 (헤더-상세 정합 점검). + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_FILEVERIFY.h" + +void PAY_FILEVERIFY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_fid, h_fcnt, h_fsum, h_acnt, h_asum; + EXEC SQL END DECLARE SECTION; + + h_fid = getl(b, T_ID2); + EXEC SQL SELECT record_count, total_amount INTO :h_fcnt, :h_fsum + FROM py_file WHERE file_id = :h_fid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_acnt, :h_asum + FROM py_payout WHERE file_id = :h_fid; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_ARG1, h_fcnt); + setl(b, T_AMT1, h_fsum); + setl(b, T_COUNT, h_acnt); + setl(b, T_GROSS, h_asum); + if (h_fcnt == h_acnt && h_fsum == h_asum) { + setl(b, T_RC, 0); + Bchg(b, T_MSG, 0, "지급파일 헤더-상세 일치", 0L); + } else { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "지급파일 헤더-상세 불일치", 0L); + userlog("PAY_FILEVERIFY 불일치 file=%ld hdr=%ld/%ld dtl=%ld/%ld", + h_fid, h_fcnt, h_fsum, h_acnt, h_asum); + } + OK(b); +} diff --git a/app/src/py/svc/PAY_HOLDAGING.h b/app/src/py/svc/PAY_HOLDAGING.h new file mode 100644 index 0000000..7d1226f --- /dev/null +++ b/app/src/py/svc/PAY_HOLDAGING.h @@ -0,0 +1,19 @@ +/* + * PAY_HOLDAGING.h - py 서비스 PAY_HOLDAGING 카피북 (copybook / record header). + * Request fields: T_ARG1(경과일수, 기본 3) + * Response fields: T_RC(0 정상 / 1 장기보류 존재), T_COUNT, T_GROSS, T_STR1(최고령 시각) + */ +#ifndef PAY_HOLDAGING_H +#define PAY_HOLDAGING_H + +#include "py_svc.h" + +typedef struct { + long aging_days; + long aged_count; + char oldest_ts[40]; +} py_holdaging_rec_t; + +void PAY_HOLDAGING(TPSVCINFO *p); + +#endif /* PAY_HOLDAGING_H */ diff --git a/app/src/py/svc/PAY_HOLDAGING.pgc b/app/src/py/svc/PAY_HOLDAGING.pgc new file mode 100644 index 0000000..73dbe21 --- /dev/null +++ b/app/src/py/svc/PAY_HOLDAGING.pgc @@ -0,0 +1,33 @@ +/* + * PAY_HOLDAGING.pgc - py 모듈 서비스 PAY_HOLDAGING (one service = one file). + * 장기 보류 점검: 보류(H) 상태로 N일 이상 경과한 건수/금액과 가장 오래된 + * 보류 시각을 회신한다. 장기 보류가 있으면 경보(1). + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_HOLDAGING.h" + +void PAY_HOLDAGING(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_days, h_cnt, h_sum; + char h_oldest[40]; + EXEC SQL END DECLARE SECTION; + + h_days = getl(b, T_ARG1); + if (h_days <= 0 || h_days > 365) h_days = 3; + EXEC SQL SELECT count(*), coalesce(sum(net),0), coalesce(min(CAST(updated_at AS text)),'') + INTO :h_cnt, :h_sum, :h_oldest + FROM py_payout + WHERE status = 'H' AND updated_at < now() - :h_days * interval '1 day'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_RC, h_cnt > 0 ? 1 : 0); + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + setl(b, T_ARG1, h_days); + Bchg(b, T_STR1, 0, h_oldest, 0L); + if (h_cnt > 0) + userlog("PAY_HOLDAGING 장기보류 경보 %ld일+ cnt=%ld amt=%ld", h_days, h_cnt, h_sum); + OK(b); +} diff --git a/app/src/py/svc/PAY_HOLDLIST.h b/app/src/py/svc/PAY_HOLDLIST.h new file mode 100644 index 0000000..b7f7241 --- /dev/null +++ b/app/src/py/svc/PAY_HOLDLIST.h @@ -0,0 +1,21 @@ +/* + * PAY_HOLDLIST.h - py 서비스 PAY_HOLDLIST 카피북 (copybook / record header). + * Request fields: T_BIZDATE + * Response fields: T_COUNT(사유 그룹수), T_GROSS(보류 총건수), + * occurrence: T_STR1[i](사유), T_ARG1[i](건수), T_AMT1[i](금액) + */ +#ifndef PAY_HOLDLIST_H +#define PAY_HOLDLIST_H + +#include "py_svc.h" + +typedef struct { + char biz_date[16]; + char reason[128]; + long reason_count; + long reason_amount; +} py_holdlist_rec_t; + +void PAY_HOLDLIST(TPSVCINFO *p); + +#endif /* PAY_HOLDLIST_H */ diff --git a/app/src/py/svc/PAY_HOLDLIST.pgc b/app/src/py/svc/PAY_HOLDLIST.pgc new file mode 100644 index 0000000..4e8f36e --- /dev/null +++ b/app/src/py/svc/PAY_HOLDLIST.pgc @@ -0,0 +1,44 @@ +/* + * PAY_HOLDLIST.pgc - py 모듈 서비스 PAY_HOLDLIST (one service = one file). + * 보류 사유별 집계: 당일 보류(H)건을 사유별로 커서 집계해 상위 10개 사유를 + * UBF occurrence(T_STR1[i]/T_ARG1[i]/T_AMT1[i]) 로 회신한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_HOLDLIST.h" + +void PAY_HOLDLIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + int i = 0; + long total = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_reason[128]; + long h_cnt, h_sum; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL DECLARE cur_holdlist CURSOR FOR + SELECT coalesce(nullif(reject_reason,''),'(사유없음)'), count(*), coalesce(sum(net),0) + FROM py_payout + WHERE biz_date = :h_bizdate AND status = 'H' + GROUP BY 1 ORDER BY 2 DESC, 1; + EXEC SQL OPEN cur_holdlist; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cur_holdlist INTO :h_reason, :h_cnt, :h_sum; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_holdlist; FAIL(b); } + total += h_cnt; + if (i < 10) { + Bchg(b, T_STR1, i, h_reason, 0L); + Bchg(b, T_ARG1, i, (char *)&h_cnt, 0L); + Bchg(b, T_AMT1, i, (char *)&h_sum, 0L); + i++; + } + } + EXEC SQL CLOSE cur_holdlist; + setl(b, T_COUNT, (long)i); + setl(b, T_GROSS, total); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/py/svc/PAY_HOLDREASON.h b/app/src/py/svc/PAY_HOLDREASON.h new file mode 100644 index 0000000..a21b6d2 --- /dev/null +++ b/app/src/py/svc/PAY_HOLDREASON.h @@ -0,0 +1,18 @@ +/* + * PAY_HOLDREASON.h - py 서비스 PAY_HOLDREASON 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id), T_REASON(보류 사유) + * Response fields: T_RC(0 보류 / 1 상태불가 / 2 사유누락), T_STATUS, T_MSG + */ +#ifndef PAY_HOLDREASON_H +#define PAY_HOLDREASON_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + char hold_reason[128]; +} py_holdreason_rec_t; + +void PAY_HOLDREASON(TPSVCINFO *p); + +#endif /* PAY_HOLDREASON_H */ diff --git a/app/src/py/svc/PAY_HOLDREASON.pgc b/app/src/py/svc/PAY_HOLDREASON.pgc new file mode 100644 index 0000000..9e0e3a7 --- /dev/null +++ b/app/src/py/svc/PAY_HOLDREASON.pgc @@ -0,0 +1,33 @@ +/* + * PAY_HOLDREASON.pgc - py 모듈 서비스 PAY_HOLDREASON (one service = one file). + * 사유부 지급보류: 요청(R)/승인(A) 건을 보류(H)로 전이하면서 보류 사유를 + * reject_reason 에 기록한다. 사유 없는 보류는 거절. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_HOLDREASON.h" + +void PAY_HOLDREASON(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char st[4]; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid; + char h_reason[128]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_ID1); + gets_(b, T_REASON, h_reason, sizeof(h_reason)); + if (h_reason[0] == 0) { setl(b, T_RC, 2); Bchg(b, T_MSG, 0, "보류 사유는 필수", 0L); FAIL(b); } + if (pydb_get_status(h_pid, st) < 0) FAIL(b); + if (st[0] != 'R' && st[0] != 'A') { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "요청/승인 상태만 보류 가능", 0L); + FAIL(b); + } + EXEC SQL UPDATE py_payout SET status = 'H', reject_reason = :h_reason, updated_at = now() + WHERE payout_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_RC, 0); + Bchg(b, T_STATUS, 0, "H", 0L); + OK(b); +} diff --git a/app/src/py/svc/PAY_LEDGERSUM.h b/app/src/py/svc/PAY_LEDGERSUM.h new file mode 100644 index 0000000..97d461c --- /dev/null +++ b/app/src/py/svc/PAY_LEDGERSUM.h @@ -0,0 +1,21 @@ +/* + * PAY_LEDGERSUM.h - py 서비스 PAY_LEDGERSUM 카피북 (copybook / record header). + * Request fields: T_BIZDATE + * Response fields: T_COUNT(유형수), T_GROSS(순합계), occurrence: T_STR1[i](유형), + * T_ARG1[i](건수), T_AMT1[i](합계), T_AMT2[i](최소), T_AMT3[i](최대) + */ +#ifndef PAY_LEDGERSUM_H +#define PAY_LEDGERSUM_H + +#include "py_svc.h" + +typedef struct { + char biz_date[16]; + char entry_type[16]; + long type_count; + long type_sum; +} py_ledgersum_rec_t; + +void PAY_LEDGERSUM(TPSVCINFO *p); + +#endif /* PAY_LEDGERSUM_H */ diff --git a/app/src/py/svc/PAY_LEDGERSUM.pgc b/app/src/py/svc/PAY_LEDGERSUM.pgc new file mode 100644 index 0000000..1ed622e --- /dev/null +++ b/app/src/py/svc/PAY_LEDGERSUM.pgc @@ -0,0 +1,46 @@ +/* + * PAY_LEDGERSUM.pgc - py 모듈 서비스 PAY_LEDGERSUM (one service = one file). + * 지급원장 유형별 합계: 당일 py_ledger 를 기표유형별 커서 집계해 + * 유형/건수/합계/최소/최대를 occurrence 로 회신한다 (원장 마감 검산용). + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_LEDGERSUM.h" + +void PAY_LEDGERSUM(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + int i = 0; + long net_total = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_type[16]; + long h_cnt, h_sum, h_min, h_max; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL DECLARE cur_ledsum CURSOR FOR + SELECT entry_type, count(*), coalesce(sum(amount),0), + coalesce(min(amount),0), coalesce(max(amount),0) + FROM py_ledger WHERE biz_date = :h_bizdate + GROUP BY entry_type ORDER BY entry_type; + EXEC SQL OPEN cur_ledsum; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cur_ledsum INTO :h_type, :h_cnt, :h_sum, :h_min, :h_max; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_ledsum; FAIL(b); } + net_total += h_sum; + if (i < 10) { + Bchg(b, T_STR1, i, h_type, 0L); + Bchg(b, T_ARG1, i, (char *)&h_cnt, 0L); + Bchg(b, T_AMT1, i, (char *)&h_sum, 0L); + Bchg(b, T_AMT2, i, (char *)&h_min, 0L); + Bchg(b, T_AMT3, i, (char *)&h_max, 0L); + i++; + } + } + EXEC SQL CLOSE cur_ledsum; + setl(b, T_COUNT, (long)i); + setl(b, T_GROSS, net_total); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/py/svc/PAY_LIMITCHK.h b/app/src/py/svc/PAY_LIMITCHK.h new file mode 100644 index 0000000..29299e1 --- /dev/null +++ b/app/src/py/svc/PAY_LIMITCHK.h @@ -0,0 +1,20 @@ +/* + * PAY_LIMITCHK.h - py 서비스 PAY_LIMITCHK 카피북 (copybook / record header). + * Request fields: T_MERCHANT, T_BIZDATE + * Response fields: T_RC(0 이내 / 1 초과), T_AMT1(한도), T_AMT2(사용), T_AMT3(잔여) + */ +#ifndef PAY_LIMITCHK_H +#define PAY_LIMITCHK_H + +#include "py_svc.h" + +typedef struct { + char merchant_id[64]; + long daily_limit; + long used_amount; + long remain_amount; +} py_limitchk_rec_t; + +void PAY_LIMITCHK(TPSVCINFO *p); + +#endif /* PAY_LIMITCHK_H */ diff --git a/app/src/py/svc/PAY_LIMITCHK.pgc b/app/src/py/svc/PAY_LIMITCHK.pgc new file mode 100644 index 0000000..7e79a23 --- /dev/null +++ b/app/src/py/svc/PAY_LIMITCHK.pgc @@ -0,0 +1,39 @@ +/* + * PAY_LIMITCHK.pgc - py 모듈 서비스 PAY_LIMITCHK (one service = one file). + * 지급한도 점검: 가맹점 일한도(merchant.daily_limit) 대비 당일 확정성 + * 지급(A/F/P) 사용액과 잔여 한도를 회신, 초과 시 경보(1). + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_LIMITCHK.h" + +void PAY_LIMITCHK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16]; + long h_lim, h_used; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL SELECT daily_limit INTO :h_lim FROM merchant WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + EXEC SQL SELECT coalesce(sum(net),0) INTO :h_used + FROM py_payout + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate + AND status IN ('A','F','P'); + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_AMT1, h_lim); + setl(b, T_AMT2, h_used); + setl(b, T_AMT3, h_lim - h_used); + if (h_used > h_lim) { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "일 지급한도 초과", 0L); + userlog("PAY_LIMITCHK 한도초과 merch=%s used=%ld limit=%ld", h_merch, h_used, h_lim); + } else { + setl(b, T_RC, 0); + Bchg(b, T_MSG, 0, "한도 이내", 0L); + } + OK(b); +} diff --git a/app/src/py/svc/PAY_LIMITHOLD.h b/app/src/py/svc/PAY_LIMITHOLD.h new file mode 100644 index 0000000..5c3049e --- /dev/null +++ b/app/src/py/svc/PAY_LIMITHOLD.h @@ -0,0 +1,19 @@ +/* + * PAY_LIMITHOLD.h - py 서비스 PAY_LIMITHOLD 카피북 (copybook / record header). + * Request fields: T_BIZDATE + * Response fields: T_RC(0 초과없음 / 1 보류 수행), T_ARG1(초과 가맹점 수), T_COUNT(보류 건수) + */ +#ifndef PAY_LIMITHOLD_H +#define PAY_LIMITHOLD_H + +#include "py_svc.h" + +typedef struct { + char biz_date[16]; + long over_merchants; + long held_count; +} py_limithold_rec_t; + +void PAY_LIMITHOLD(TPSVCINFO *p); + +#endif /* PAY_LIMITHOLD_H */ diff --git a/app/src/py/svc/PAY_LIMITHOLD.pgc b/app/src/py/svc/PAY_LIMITHOLD.pgc new file mode 100644 index 0000000..313c871 --- /dev/null +++ b/app/src/py/svc/PAY_LIMITHOLD.pgc @@ -0,0 +1,43 @@ +/* + * PAY_LIMITHOLD.pgc - py 모듈 서비스 PAY_LIMITHOLD (one service = one file). + * 한도 초과 자동 보류: 당일 사용액이 일한도를 넘은 가맹점을 HAVING 커서로 + * 뽑아 각 가맹점의 잔여 요청(R)건을 '한도초과' 사유로 보류(H)시킨다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_LIMITHOLD.h" + +void PAY_LIMITHOLD(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long merchants = 0, held = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL DECLARE cur_limhold CURSOR FOR + SELECT m.merchant_id + FROM py_payout p JOIN merchant m ON p.merchant_id = m.merchant_id + WHERE p.biz_date = :h_bizdate AND p.status IN ('A','F','P') + GROUP BY m.merchant_id, m.daily_limit + HAVING coalesce(sum(p.net),0) > m.daily_limit; + EXEC SQL OPEN cur_limhold; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cur_limhold INTO :h_merch; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_limhold; FAIL(b); } + merchants++; + EXEC SQL UPDATE py_payout SET status = 'H', reject_reason = '한도초과', updated_at = now() + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status = 'R'; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_limhold; FAIL(b); } + held += sqlca.sqlerrd[2]; + } + EXEC SQL CLOSE cur_limhold; + + setl(b, T_RC, merchants > 0 ? 1 : 0); + setl(b, T_ARG1, merchants); + setl(b, T_COUNT, held); + userlog("PAY_LIMITHOLD date=%s over=%ld held=%ld", h_bizdate, merchants, held); + OK(b); +} diff --git a/app/src/py/svc/PAY_MERGE.h b/app/src/py/svc/PAY_MERGE.h new file mode 100644 index 0000000..7f7e457 --- /dev/null +++ b/app/src/py/svc/PAY_MERGE.h @@ -0,0 +1,20 @@ +/* + * PAY_MERGE.h - py 서비스 PAY_MERGE 카피북 (copybook / record header). + * Request fields: T_MERCHANT, T_BIZDATE + * Response fields: T_RC(0 합산 / 1 대상부족), T_ID2(신규 BULK id), T_COUNT(통합 건수), T_NET + */ +#ifndef PAY_MERGE_H +#define PAY_MERGE_H + +#include "py_svc.h" + +typedef struct { + char merchant_id[64]; + long merged_count; + long merged_net; + long new_payout_id; +} py_merge_rec_t; + +void PAY_MERGE(TPSVCINFO *p); + +#endif /* PAY_MERGE_H */ diff --git a/app/src/py/svc/PAY_MERGE.pgc b/app/src/py/svc/PAY_MERGE.pgc new file mode 100644 index 0000000..6655f45 --- /dev/null +++ b/app/src/py/svc/PAY_MERGE.pgc @@ -0,0 +1,47 @@ +/* + * PAY_MERGE.pgc - py 모듈 서비스 PAY_MERGE (one service = one file). + * 지급 합산: 가맹점 당일 요청(R) STD 건이 2건 이상이면 전부 취소(C) 표시 후 + * 합산 1건(BULK)으로 재생성한다. 이체 수수료 절감용 통합. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_MERGE.h" + +void PAY_MERGE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long new_id; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16], h_acct[32]; + long h_cnt, h_amt, h_fee, h_net; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL SELECT count(*), coalesce(sum(amount),0), coalesce(sum(fee),0), + coalesce(sum(net),0), coalesce(min(account_no),'') + INTO :h_cnt, :h_amt, :h_fee, :h_net, :h_acct + FROM py_payout + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate + AND status = 'R' AND channel = 'STD'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt < 2) { + setl(b, T_RC, 1); + setl(b, T_COUNT, h_cnt); + Bchg(b, T_MSG, 0, "합산 대상 2건 미만", 0L); + OK(b); + } + EXEC SQL UPDATE py_payout SET status = 'C', reject_reason = '합산통합', updated_at = now() + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate + AND status = 'R' AND channel = 'STD'; + if (sqlca.sqlcode < 0) FAIL(b); + new_id = pydb_next_payout_id(); + if (new_id < 0) FAIL(b); + if (pydb_insert_payout(new_id, h_merch, h_acct, h_amt, h_fee, h_net, "R", "BULK", h_bizdate) < 0) FAIL(b); + + setl(b, T_RC, 0); + setl(b, T_ID2, new_id); + setl(b, T_COUNT, h_cnt); + setl(b, T_NET, h_net); + userlog("PAY_MERGE merch=%s merged=%ld -> id=%ld net=%ld", h_merch, h_cnt, new_id, h_net); + OK(b); +} diff --git a/app/src/py/svc/PAY_NETRECALC.h b/app/src/py/svc/PAY_NETRECALC.h new file mode 100644 index 0000000..226ebf3 --- /dev/null +++ b/app/src/py/svc/PAY_NETRECALC.h @@ -0,0 +1,20 @@ +/* + * PAY_NETRECALC.h - py 서비스 PAY_NETRECALC 카피북 (copybook / record header). + * Request fields: T_BIZDATE, T_ARG1(1=자동보정) + * Response fields: T_RC(0 정합 / 1 불일치), T_COUNT(불일치 건수), T_DELTA(누적 오차), T_ARG2(보정 건수) + */ +#ifndef PAY_NETRECALC_H +#define PAY_NETRECALC_H + +#include "py_svc.h" + +typedef struct { + char biz_date[16]; + long bad_count; + long drift_sum; + long fixed_count; +} py_netrecalc_rec_t; + +void PAY_NETRECALC(TPSVCINFO *p); + +#endif /* PAY_NETRECALC_H */ diff --git a/app/src/py/svc/PAY_NETRECALC.pgc b/app/src/py/svc/PAY_NETRECALC.pgc new file mode 100644 index 0000000..69f3b7d --- /dev/null +++ b/app/src/py/svc/PAY_NETRECALC.pgc @@ -0,0 +1,40 @@ +/* + * PAY_NETRECALC.pgc - py 모듈 서비스 PAY_NETRECALC (one service = one file). + * 순지급액 정합 검증: 당일 건 중 amount - fee != net 인 불일치 건수와 + * 누적 오차를 산출하고, 보정 플래그(T_ARG1=1) 시 net 을 재계산해 고친다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_NETRECALC.h" + +void PAY_NETRECALC(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long fix; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_bad, h_drift, h_fixed; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + fix = getl(b, T_ARG1); + h_fixed = 0; + + EXEC SQL SELECT count(*), coalesce(sum(amount - fee - net),0) + INTO :h_bad, :h_drift + FROM py_payout WHERE biz_date = :h_bizdate AND amount - fee <> net; + if (sqlca.sqlcode < 0) FAIL(b); + if (fix == 1 && h_bad > 0) { + EXEC SQL UPDATE py_payout SET net = amount - fee, updated_at = now() + WHERE biz_date = :h_bizdate AND amount - fee <> net; + if (sqlca.sqlcode < 0) FAIL(b); + h_fixed = sqlca.sqlerrd[2]; + } + + setl(b, T_RC, h_bad > 0 ? 1 : 0); + setl(b, T_COUNT, h_bad); + setl(b, T_DELTA, h_drift); + setl(b, T_ARG2, h_fixed); + if (h_bad > 0) + userlog("PAY_NETRECALC date=%s bad=%ld drift=%ld fixed=%ld", h_bizdate, h_bad, h_drift, h_fixed); + OK(b); +} diff --git a/app/src/py/svc/PAY_NOTIFYEMAIL.h b/app/src/py/svc/PAY_NOTIFYEMAIL.h new file mode 100644 index 0000000..71c5dea --- /dev/null +++ b/app/src/py/svc/PAY_NOTIFYEMAIL.h @@ -0,0 +1,19 @@ +/* + * PAY_NOTIFYEMAIL.h - py 서비스 PAY_NOTIFYEMAIL 카피북 (copybook / record header). + * Request fields: T_MERCHANT, T_BIZDATE + * Response fields: T_RC(0 발송 / 1 대상없음), T_COUNT, T_GROSS, T_STR1(제목), T_MSG(본문) + */ +#ifndef PAY_NOTIFYEMAIL_H +#define PAY_NOTIFYEMAIL_H + +#include "py_svc.h" + +typedef struct { + char merchant_id[64]; + char subject[128]; + char body[192]; +} py_notifyemail_rec_t; + +void PAY_NOTIFYEMAIL(TPSVCINFO *p); + +#endif /* PAY_NOTIFYEMAIL_H */ diff --git a/app/src/py/svc/PAY_NOTIFYEMAIL.pgc b/app/src/py/svc/PAY_NOTIFYEMAIL.pgc new file mode 100644 index 0000000..3a650e0 --- /dev/null +++ b/app/src/py/svc/PAY_NOTIFYEMAIL.pgc @@ -0,0 +1,38 @@ +/* + * PAY_NOTIFYEMAIL.pgc - py 모듈 서비스 PAY_NOTIFYEMAIL (one service = one file). + * 지급내역 이메일 통지(시뮬): 가맹점의 당일 지급완료(P) 건수/합계를 집계해 + * 메일 제목과 본문 요약을 조립한다. 대상이 없으면 미발송(1) 회신. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_NOTIFYEMAIL.h" + +void PAY_NOTIFYEMAIL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char subject[128], body[192]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_sum; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_cnt, :h_sum + FROM py_payout + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status = 'P'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt == 0) { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "이메일 통지 대상 없음", 0L); + OK(b); + } + snprintf(subject, sizeof(subject), "[Forge] %s 지급내역 안내 (%s)", h_merch, h_bizdate); + snprintf(body, sizeof(body), "당일 %ld건, 합계 %ld원이 지급 완료되었습니다.", h_cnt, h_sum); + userlog("PAY_NOTIFYEMAIL 발송시뮬 merch=%s cnt=%ld amt=%ld", h_merch, h_cnt, h_sum); + setl(b, T_RC, 0); + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + Bchg(b, T_STR1, 0, subject, 0L); + Bchg(b, T_MSG, 0, body, 0L); + OK(b); +} diff --git a/app/src/py/svc/PAY_NOTIFYPLAN.h b/app/src/py/svc/PAY_NOTIFYPLAN.h new file mode 100644 index 0000000..3cda82c --- /dev/null +++ b/app/src/py/svc/PAY_NOTIFYPLAN.h @@ -0,0 +1,19 @@ +/* + * PAY_NOTIFYPLAN.h - py 서비스 PAY_NOTIFYPLAN 카피북 (copybook / record header). + * Request fields: T_BIZDATE(예약 실행일) + * Response fields: T_RC(0 예정있음 / 1 없음), T_COUNT, T_GROSS, T_MSG(통보 문안) + */ +#ifndef PAY_NOTIFYPLAN_H +#define PAY_NOTIFYPLAN_H + +#include "py_svc.h" + +typedef struct { + char run_date[16]; + long plan_count; + long plan_amount; +} py_notifyplan_rec_t; + +void PAY_NOTIFYPLAN(TPSVCINFO *p); + +#endif /* PAY_NOTIFYPLAN_H */ diff --git a/app/src/py/svc/PAY_NOTIFYPLAN.pgc b/app/src/py/svc/PAY_NOTIFYPLAN.pgc new file mode 100644 index 0000000..d073262 --- /dev/null +++ b/app/src/py/svc/PAY_NOTIFYPLAN.pgc @@ -0,0 +1,29 @@ +/* + * PAY_NOTIFYPLAN.pgc - py 모듈 서비스 PAY_NOTIFYPLAN (one service = one file). + * 지급 예정 통보: 지정 실행일의 예약(S) 건을 py_payout 과 조인 집계해 + * 지급 예정 건수/총액과 통보 문안을 회신한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_NOTIFYPLAN.h" + +void PAY_NOTIFYPLAN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char msg[160]; + EXEC SQL BEGIN DECLARE SECTION; + char h_rundate[16]; + long h_cnt, h_sum; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_rundate, sizeof(h_rundate)); + EXEC SQL SELECT count(*), coalesce(sum(p.net),0) INTO :h_cnt, :h_sum + FROM py_schedule s JOIN py_payout p ON s.payout_id = p.payout_id + WHERE s.run_date = :h_rundate AND s.status = 'S'; + if (sqlca.sqlcode < 0) FAIL(b); + snprintf(msg, sizeof(msg), "%s 지급 예정 %ld건 / 총 %ld원", h_rundate, h_cnt, h_sum); + setl(b, T_RC, h_cnt > 0 ? 0 : 1); + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + Bchg(b, T_MSG, 0, msg, 0L); + OK(b); +} diff --git a/app/src/py/svc/PAY_NOTIFYSMS.h b/app/src/py/svc/PAY_NOTIFYSMS.h new file mode 100644 index 0000000..90feab7 --- /dev/null +++ b/app/src/py/svc/PAY_NOTIFYSMS.h @@ -0,0 +1,18 @@ +/* + * PAY_NOTIFYSMS.h - py 서비스 PAY_NOTIFYSMS 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id) + * Response fields: T_RC(0 발송 / 1 대상아님), T_NET, T_MSG(문자 본문) + */ +#ifndef PAY_NOTIFYSMS_H +#define PAY_NOTIFYSMS_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + char sms_body[192]; +} py_notifysms_rec_t; + +void PAY_NOTIFYSMS(TPSVCINFO *p); + +#endif /* PAY_NOTIFYSMS_H */ diff --git a/app/src/py/svc/PAY_NOTIFYSMS.pgc b/app/src/py/svc/PAY_NOTIFYSMS.pgc new file mode 100644 index 0000000..21638d1 --- /dev/null +++ b/app/src/py/svc/PAY_NOTIFYSMS.pgc @@ -0,0 +1,33 @@ +/* + * PAY_NOTIFYSMS.pgc - py 모듈 서비스 PAY_NOTIFYSMS (one service = one file). + * 지급완료 SMS 통지(시뮬): 지급완료(P)건만 대상. 가맹점/금액을 조회해 + * 문자 본문을 조립하고 발송은 userlog 로 시뮬레이션한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_NOTIFYSMS.h" + +void PAY_NOTIFYSMS(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char msg[192]; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_net; + char h_merch[64], h_st[4]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_ID1); + EXEC SQL SELECT merchant_id, net, status INTO :h_merch, :h_net, :h_st + FROM py_payout WHERE payout_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + if (h_st[0] != 'P') { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "지급완료 건만 SMS 통지 가능", 0L); + FAIL(b); + } + snprintf(msg, sizeof(msg), "[Forge지급] %s님, %ld원 지급이 완료되었습니다.", h_merch, h_net); + userlog("PAY_NOTIFYSMS 발송시뮬 payout=%ld to=%s", h_pid, h_merch); + setl(b, T_RC, 0); + setl(b, T_NET, h_net); + Bchg(b, T_MSG, 0, msg, 0L); + OK(b); +} diff --git a/app/src/py/svc/PAY_REFUND.h b/app/src/py/svc/PAY_REFUND.h new file mode 100644 index 0000000..3747858 --- /dev/null +++ b/app/src/py/svc/PAY_REFUND.h @@ -0,0 +1,19 @@ +/* + * PAY_REFUND.h - py 서비스 PAY_REFUND 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id), T_REASON(환입 사유) + * Response fields: T_RC(0 환입 / 1 상태불가), T_NET(환입액), T_STATUS('V') + */ +#ifndef PAY_REFUND_H +#define PAY_REFUND_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + long refund_net; + char refund_reason[96]; +} py_refund_rec_t; + +void PAY_REFUND(TPSVCINFO *p); + +#endif /* PAY_REFUND_H */ diff --git a/app/src/py/svc/PAY_REFUND.pgc b/app/src/py/svc/PAY_REFUND.pgc new file mode 100644 index 0000000..8c72847 --- /dev/null +++ b/app/src/py/svc/PAY_REFUND.pgc @@ -0,0 +1,46 @@ +/* + * PAY_REFUND.pgc - py 모듈 서비스 PAY_REFUND (one service = one file). + * 지급 환입: 지급완료(P) 건을 역분개(V)하며 지급계좌에 net 을 복원하고 + * py_ledger 에 음수 REFUND 기표를 남긴다 (취소/환입 원장 일관성). + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_REFUND.h" + +void PAY_REFUND(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char reason[96]; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_net, h_neg; + char h_acct[32], h_st[4], h_bizdate[16], h_rsn[96]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_ID1); + gets_(b, T_REASON, reason, sizeof(reason)); + if (reason[0] == 0) strcpy(reason, "지급환입"); + strncpy(h_rsn, reason, sizeof(h_rsn)-1); h_rsn[sizeof(h_rsn)-1] = 0; + + EXEC SQL SELECT account_no, net, status, CAST(biz_date AS text) + INTO :h_acct, :h_net, :h_st, :h_bizdate + FROM py_payout WHERE payout_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + if (h_st[0] != 'P') { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "지급완료 건만 환입 가능", 0L); + FAIL(b); + } + if (pydb_account_add(h_acct, h_net) < 0) FAIL(b); + EXEC SQL UPDATE py_payout SET status = 'V', reject_reason = :h_rsn, updated_at = now() + WHERE payout_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + h_neg = -h_net; + EXEC SQL INSERT INTO py_ledger (ledger_id, payout_id, entry_type, amount, biz_date) + VALUES (nextval('py_ledger_seq'), :h_pid, 'REFUND', :h_neg, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_RC, 0); + setl(b, T_NET, h_net); + Bchg(b, T_STATUS, 0, "V", 0L); + userlog("PAY_REFUND id=%ld net=%ld acct=%s", h_pid, h_net, h_acct); + OK(b); +} diff --git a/app/src/py/svc/PAY_REPAYNEW.h b/app/src/py/svc/PAY_REPAYNEW.h new file mode 100644 index 0000000..d278189 --- /dev/null +++ b/app/src/py/svc/PAY_REPAYNEW.h @@ -0,0 +1,19 @@ +/* + * PAY_REPAYNEW.h - py 서비스 PAY_REPAYNEW 카피북 (copybook / record header). + * Request fields: T_ID1(반려 payout_id) + * Response fields: T_RC(0 재지급 / 1 반려아님 / 2 계좌없음), T_ID2(신규 id), T_KEY1(새 계좌) + */ +#ifndef PAY_REPAYNEW_H +#define PAY_REPAYNEW_H + +#include "py_svc.h" + +typedef struct { + long old_payout_id; + long new_payout_id; + char new_account[32]; +} py_repaynew_rec_t; + +void PAY_REPAYNEW(TPSVCINFO *p); + +#endif /* PAY_REPAYNEW_H */ diff --git a/app/src/py/svc/PAY_REPAYNEW.pgc b/app/src/py/svc/PAY_REPAYNEW.pgc new file mode 100644 index 0000000..fd378ba --- /dev/null +++ b/app/src/py/svc/PAY_REPAYNEW.pgc @@ -0,0 +1,46 @@ +/* + * PAY_REPAYNEW.pgc - py 모듈 서비스 PAY_REPAYNEW (one service = one file). + * 계좌 변경 재지급: 반려(J)된 지급건을 가맹점의 최신 정상(ACTIVE) 계좌로 + * 재지급 신규건(R) 생성 후 원건은 역분개(V) 처리한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_REPAYNEW.h" + +void PAY_REPAYNEW(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long new_id; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt, h_fee, h_net; + char h_merch[64], h_st[4], h_bizdate[16], h_newacct[32]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_ID1); + EXEC SQL SELECT merchant_id, amount, fee, net, status, CAST(biz_date AS text) + INTO :h_merch, :h_amt, :h_fee, :h_net, :h_st, :h_bizdate + FROM py_payout WHERE payout_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + if (h_st[0] != 'J') { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "반려 건만 재지급 가능", 0L); + FAIL(b); + } + EXEC SQL SELECT account_no INTO :h_newacct FROM py_account + WHERE merchant_id = :h_merch AND status = 'ACTIVE' + ORDER BY updated_at DESC LIMIT 1; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { setl(b, T_RC, 2); Bchg(b, T_MSG, 0, "재지급 가능한 정상 계좌 없음", 0L); FAIL(b); } + + new_id = pydb_next_payout_id(); + if (new_id < 0) FAIL(b); + if (pydb_insert_payout(new_id, h_merch, h_newacct, h_amt, h_fee, h_net, "R", "STD", h_bizdate) < 0) FAIL(b); + EXEC SQL UPDATE py_payout SET status = 'V', reject_reason = '재지급 전환', updated_at = now() + WHERE payout_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_RC, 0); + setl(b, T_ID2, new_id); + Bchg(b, T_KEY1, 0, h_newacct, 0L); + userlog("PAY_REPAYNEW old=%ld new=%ld acct=%s", h_pid, new_id, h_newacct); + OK(b); +} diff --git a/app/src/py/svc/PAY_SCHEDCANCEL.h b/app/src/py/svc/PAY_SCHEDCANCEL.h new file mode 100644 index 0000000..5878f59 --- /dev/null +++ b/app/src/py/svc/PAY_SCHEDCANCEL.h @@ -0,0 +1,19 @@ +/* + * PAY_SCHEDCANCEL.h - py 서비스 PAY_SCHEDCANCEL 카피북 (copybook / record header). + * Request fields: T_ID1(schedule_id) + * Response fields: T_RC(0 취소 / 1 실행됨), T_ID2(연결 payout_id), T_MSG + */ +#ifndef PAY_SCHEDCANCEL_H +#define PAY_SCHEDCANCEL_H + +#include "py_svc.h" + +typedef struct { + long schedule_id; + long payout_id; + char sched_status[4]; +} py_schedcancel_rec_t; + +void PAY_SCHEDCANCEL(TPSVCINFO *p); + +#endif /* PAY_SCHEDCANCEL_H */ diff --git a/app/src/py/svc/PAY_SCHEDCANCEL.pgc b/app/src/py/svc/PAY_SCHEDCANCEL.pgc new file mode 100644 index 0000000..ff75523 --- /dev/null +++ b/app/src/py/svc/PAY_SCHEDCANCEL.pgc @@ -0,0 +1,34 @@ +/* + * PAY_SCHEDCANCEL.pgc - py 모듈 서비스 PAY_SCHEDCANCEL (one service = one file). + * 지급 예약 취소: 아직 미실행(S)인 예약만 DELETE 로 제거한다. 이미 + * 실행(D)된 예약은 취소 불가로 거절하고 연결 지급건 id 를 회신. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_SCHEDCANCEL.h" + +void PAY_SCHEDCANCEL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_sid, h_pid; + char h_st[4]; + EXEC SQL END DECLARE SECTION; + + h_sid = getl(b, T_ID1); + EXEC SQL SELECT payout_id, status INTO :h_pid, :h_st + FROM py_schedule WHERE schedule_id = :h_sid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + if (h_st[0] != 'S') { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "이미 실행된 예약은 취소 불가", 0L); + FAIL(b); + } + EXEC SQL DELETE FROM py_schedule WHERE schedule_id = :h_sid AND status = 'S'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_RC, 0); + setl(b, T_ID2, h_pid); + Bchg(b, T_MSG, 0, "지급 예약 취소 완료", 0L); + userlog("PAY_SCHEDCANCEL sched=%ld payout=%ld", h_sid, h_pid); + OK(b); +} diff --git a/app/src/py/svc/PAY_SEIZE.h b/app/src/py/svc/PAY_SEIZE.h new file mode 100644 index 0000000..56ffa22 --- /dev/null +++ b/app/src/py/svc/PAY_SEIZE.h @@ -0,0 +1,19 @@ +/* + * PAY_SEIZE.h - py 서비스 PAY_SEIZE 카피북 (copybook / record header). + * Request fields: T_KEY1(계좌번호), T_REASON(압류 사유, 기본 '법원명령') + * Response fields: T_RC(0 압류 / 1 불가), T_COUNT(보류 전환 건수), T_STR1(기록 사유) + */ +#ifndef PAY_SEIZE_H +#define PAY_SEIZE_H + +#include "py_svc.h" + +typedef struct { + char account_no[32]; + char seize_reason[96]; + long held_count; +} py_seize_rec_t; + +void PAY_SEIZE(TPSVCINFO *p); + +#endif /* PAY_SEIZE_H */ diff --git a/app/src/py/svc/PAY_SEIZE.pgc b/app/src/py/svc/PAY_SEIZE.pgc new file mode 100644 index 0000000..cbda3cc --- /dev/null +++ b/app/src/py/svc/PAY_SEIZE.pgc @@ -0,0 +1,41 @@ +/* + * PAY_SEIZE.pgc - py 모듈 서비스 PAY_SEIZE (one service = one file). + * 압류/가압류 등록: 대상 계좌를 SEIZED 로 전환하고 그 계좌의 미지급(R/A) + * 건을 '압류:<사유>' 로 일괄 보류(H)한다. 이미 압류면 거절. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_SEIZE.h" + +void PAY_SEIZE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char reason[96]; + EXEC SQL BEGIN DECLARE SECTION; + char h_acct[32], h_rsn[128]; + long h_held; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, h_acct, sizeof(h_acct)); + gets_(b, T_REASON, reason, sizeof(reason)); + if (reason[0] == 0) strcpy(reason, "법원명령"); + + EXEC SQL UPDATE py_account SET status = 'SEIZED', updated_at = now() + WHERE account_no = :h_acct AND status <> 'SEIZED'; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlerrd[2] == 0) { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "이미 압류되었거나 없는 계좌", 0L); + FAIL(b); + } + snprintf(h_rsn, sizeof(h_rsn), "압류:%s", reason); + EXEC SQL UPDATE py_payout SET status = 'H', reject_reason = :h_rsn, updated_at = now() + WHERE account_no = :h_acct AND status IN ('R','A'); + if (sqlca.sqlcode < 0) FAIL(b); + h_held = sqlca.sqlerrd[2]; + + setl(b, T_RC, 0); + setl(b, T_COUNT, h_held); + Bchg(b, T_STR1, 0, h_rsn, 0L); + userlog("PAY_SEIZE acct=%s held=%ld reason=%s", h_acct, h_held, h_rsn); + OK(b); +} diff --git a/app/src/py/svc/PAY_SEIZECHK.h b/app/src/py/svc/PAY_SEIZECHK.h new file mode 100644 index 0000000..2973033 --- /dev/null +++ b/app/src/py/svc/PAY_SEIZECHK.h @@ -0,0 +1,19 @@ +/* + * PAY_SEIZECHK.h - py 서비스 PAY_SEIZECHK 카피북 (copybook / record header). + * Request fields: T_BIZDATE + * Response fields: T_RC(0 없음 / 1 차단대상 존재), T_COUNT, T_GROSS, T_MSG + */ +#ifndef PAY_SEIZECHK_H +#define PAY_SEIZECHK_H + +#include "py_svc.h" + +typedef struct { + char biz_date[16]; + long blocked_count; + long blocked_amount; +} py_seizechk_rec_t; + +void PAY_SEIZECHK(TPSVCINFO *p); + +#endif /* PAY_SEIZECHK_H */ diff --git a/app/src/py/svc/PAY_SEIZECHK.pgc b/app/src/py/svc/PAY_SEIZECHK.pgc new file mode 100644 index 0000000..189d008 --- /dev/null +++ b/app/src/py/svc/PAY_SEIZECHK.pgc @@ -0,0 +1,34 @@ +/* + * PAY_SEIZECHK.pgc - py 모듈 서비스 PAY_SEIZECHK (one service = one file). + * 압류계좌 지급 차단 점검: 당일 미지급(R/A)건 중 압류(SEIZED) 계좌로 + * 나가려는 건수/금액을 조인 집계해 차단 필요 여부를 경보한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_SEIZECHK.h" + +void PAY_SEIZECHK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt, h_sum; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL SELECT count(*), coalesce(sum(p.net),0) INTO :h_cnt, :h_sum + FROM py_payout p JOIN py_account a ON p.account_no = a.account_no + WHERE p.biz_date = :h_bizdate AND p.status IN ('R','A') AND a.status = 'SEIZED'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + if (h_cnt > 0) { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "압류계좌 대상 지급건 존재 - 차단 필요", 0L); + userlog("PAY_SEIZECHK 차단대상 cnt=%ld amt=%ld", h_cnt, h_sum); + } else { + setl(b, T_RC, 0); + Bchg(b, T_MSG, 0, "압류계좌 지급 시도 없음", 0L); + } + OK(b); +} diff --git a/app/src/py/svc/PAY_SEIZEREL.h b/app/src/py/svc/PAY_SEIZEREL.h new file mode 100644 index 0000000..44f7add --- /dev/null +++ b/app/src/py/svc/PAY_SEIZEREL.h @@ -0,0 +1,18 @@ +/* + * PAY_SEIZEREL.h - py 서비스 PAY_SEIZEREL 카피북 (copybook / record header). + * Request fields: T_KEY1(계좌번호) + * Response fields: T_RC(0 해제 / 1 압류상태 아님), T_COUNT(복원 건수), T_STATUS + */ +#ifndef PAY_SEIZEREL_H +#define PAY_SEIZEREL_H + +#include "py_svc.h" + +typedef struct { + char account_no[32]; + long released_count; +} py_seizerel_rec_t; + +void PAY_SEIZEREL(TPSVCINFO *p); + +#endif /* PAY_SEIZEREL_H */ diff --git a/app/src/py/svc/PAY_SEIZEREL.pgc b/app/src/py/svc/PAY_SEIZEREL.pgc new file mode 100644 index 0000000..a8cf023 --- /dev/null +++ b/app/src/py/svc/PAY_SEIZEREL.pgc @@ -0,0 +1,36 @@ +/* + * PAY_SEIZEREL.pgc - py 모듈 서비스 PAY_SEIZEREL (one service = one file). + * 압류 해제: SEIZED 계좌를 ACTIVE 로 되돌리고, 압류 사유로 보류됐던 + * 지급건(H, 사유 '압류%')을 요청(R)으로 복원한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_SEIZEREL.h" + +void PAY_SEIZEREL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_acct[32]; + long h_rel; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_KEY1, h_acct, sizeof(h_acct)); + EXEC SQL UPDATE py_account SET status = 'ACTIVE', updated_at = now() + WHERE account_no = :h_acct AND status = 'SEIZED'; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlerrd[2] == 0) { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "압류 상태 계좌 아님", 0L); + FAIL(b); + } + EXEC SQL UPDATE py_payout SET status = 'R', reject_reason = '', updated_at = now() + WHERE account_no = :h_acct AND status = 'H' AND reject_reason LIKE '압류%'; + if (sqlca.sqlcode < 0) FAIL(b); + h_rel = sqlca.sqlerrd[2]; + + setl(b, T_RC, 0); + setl(b, T_COUNT, h_rel); + Bchg(b, T_STATUS, 0, "ACTIVE", 0L); + userlog("PAY_SEIZEREL acct=%s released=%ld", h_acct, h_rel); + OK(b); +} diff --git a/app/src/py/svc/PAY_SPLITRATIO.h b/app/src/py/svc/PAY_SPLITRATIO.h new file mode 100644 index 0000000..3e42728 --- /dev/null +++ b/app/src/py/svc/PAY_SPLITRATIO.h @@ -0,0 +1,20 @@ +/* + * PAY_SPLITRATIO.h - py 서비스 PAY_SPLITRATIO 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id), T_ARG1(첫 건 비율 bps, 기본 5000) + * Response fields: T_RC(0 분할 / 1 상태불가), T_ID2/T_ARG2(신규 id 2건), T_AMT1/T_AMT2(각 net) + */ +#ifndef PAY_SPLITRATIO_H +#define PAY_SPLITRATIO_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + long ratio_bps; + long split_id1; + long split_id2; +} py_splitratio_rec_t; + +void PAY_SPLITRATIO(TPSVCINFO *p); + +#endif /* PAY_SPLITRATIO_H */ diff --git a/app/src/py/svc/PAY_SPLITRATIO.pgc b/app/src/py/svc/PAY_SPLITRATIO.pgc new file mode 100644 index 0000000..60be6db --- /dev/null +++ b/app/src/py/svc/PAY_SPLITRATIO.pgc @@ -0,0 +1,53 @@ +/* + * PAY_SPLITRATIO.pgc - py 모듈 서비스 PAY_SPLITRATIO (one service = one file). + * 비율 분할: 요청(R) 건을 bps 비율로 두 건의 SPLIT 지급건으로 쪼개고 + * (수수료도 비례 배분, 끝수는 2번째 건이 흡수) 원건은 취소(C) 처리한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_SPLITRATIO.h" + +void PAY_SPLITRATIO(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long bps, id1, id2, a1, f1, n1, a2, f2, n2; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt, h_fee, h_net; + char h_merch[64], h_acct[32], h_st[4], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_ID1); + bps = getl(b, T_ARG1); + if (bps < 1 || bps > 9999) bps = 5000; + + EXEC SQL SELECT merchant_id, account_no, amount, fee, net, status, CAST(biz_date AS text) + INTO :h_merch, :h_acct, :h_amt, :h_fee, :h_net, :h_st, :h_bizdate + FROM py_payout WHERE payout_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + if (h_st[0] != 'R') { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "요청 상태 건만 분할 가능", 0L); + FAIL(b); + } + a1 = h_amt * bps / 10000; + f1 = h_fee * bps / 10000; + n1 = a1 - f1; + a2 = h_amt - a1; f2 = h_fee - f1; n2 = a2 - f2; + + id1 = pydb_next_payout_id(); + if (id1 < 0) FAIL(b); + if (pydb_insert_payout(id1, h_merch, h_acct, a1, f1, n1, "R", "SPLIT", h_bizdate) < 0) FAIL(b); + id2 = pydb_next_payout_id(); + if (id2 < 0) FAIL(b); + if (pydb_insert_payout(id2, h_merch, h_acct, a2, f2, n2, "R", "SPLIT", h_bizdate) < 0) FAIL(b); + EXEC SQL UPDATE py_payout SET status = 'C', reject_reason = '비율분할', updated_at = now() + WHERE payout_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_RC, 0); + setl(b, T_ID2, id1); + setl(b, T_ARG2, id2); + setl(b, T_AMT1, n1); + setl(b, T_AMT2, n2); + userlog("PAY_SPLITRATIO id=%ld bps=%ld -> %ld(%ld)/%ld(%ld)", h_pid, bps, id1, n1, id2, n2); + OK(b); +} diff --git a/app/src/py/svc/PAY_STATBANK.h b/app/src/py/svc/PAY_STATBANK.h new file mode 100644 index 0000000..727f2fb --- /dev/null +++ b/app/src/py/svc/PAY_STATBANK.h @@ -0,0 +1,21 @@ +/* + * PAY_STATBANK.h - py 서비스 PAY_STATBANK 카피북 (copybook / record header). + * Request fields: T_BIZDATE + * Response fields: T_COUNT(은행 수), occurrence: T_STR2[i](은행코드), + * T_ARG3[i](건수), T_AMT2[i](금액) + */ +#ifndef PAY_STATBANK_H +#define PAY_STATBANK_H + +#include "py_svc.h" + +typedef struct { + char biz_date[16]; + char bank_code[8]; + long bank_count; + long bank_amount; +} py_statbank_rec_t; + +void PAY_STATBANK(TPSVCINFO *p); + +#endif /* PAY_STATBANK_H */ diff --git a/app/src/py/svc/PAY_STATBANK.pgc b/app/src/py/svc/PAY_STATBANK.pgc new file mode 100644 index 0000000..00c23a1 --- /dev/null +++ b/app/src/py/svc/PAY_STATBANK.pgc @@ -0,0 +1,43 @@ +/* + * PAY_STATBANK.pgc - py 모듈 서비스 PAY_STATBANK (one service = one file). + * 은행별 지급 통계: 당일 지급건을 py_account 조인으로 은행코드별 커서 + * 집계해 금액 내림차순 occurrence 로 회신한다 (이체망별 물량 파악). + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_STATBANK.h" + +void PAY_STATBANK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + int i = 0; + long banks = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_bank[8]; + long h_cnt, h_amt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL DECLARE cur_statbank CURSOR FOR + SELECT a.bank_code, count(*), coalesce(sum(p.net),0) + FROM py_payout p JOIN py_account a ON p.account_no = a.account_no + WHERE p.biz_date = :h_bizdate + GROUP BY a.bank_code ORDER BY 3 DESC, 1; + EXEC SQL OPEN cur_statbank; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cur_statbank INTO :h_bank, :h_cnt, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_statbank; FAIL(b); } + banks++; + if (i < 10) { + Bchg(b, T_STR2, i, h_bank, 0L); + Bchg(b, T_ARG3, i, (char *)&h_cnt, 0L); + Bchg(b, T_AMT2, i, (char *)&h_amt, 0L); + i++; + } + } + EXEC SQL CLOSE cur_statbank; + setl(b, T_COUNT, banks); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/py/svc/PAY_STATCHAN.h b/app/src/py/svc/PAY_STATCHAN.h new file mode 100644 index 0000000..1c736cb --- /dev/null +++ b/app/src/py/svc/PAY_STATCHAN.h @@ -0,0 +1,21 @@ +/* + * PAY_STATCHAN.h - py 서비스 PAY_STATCHAN 카피북 (copybook / record header). + * Request fields: T_BIZDATE + * Response fields: T_COUNT(총건수), T_GROSS(총액), T_ARG2(채널수), + * occurrence: T_STR1[i](채널), T_ARG1[i](건수), T_AMT1[i](금액) + */ +#ifndef PAY_STATCHAN_H +#define PAY_STATCHAN_H + +#include "py_svc.h" + +typedef struct { + char biz_date[16]; + char channel[16]; + long chan_count; + long chan_amount; +} py_statchan_rec_t; + +void PAY_STATCHAN(TPSVCINFO *p); + +#endif /* PAY_STATCHAN_H */ diff --git a/app/src/py/svc/PAY_STATCHAN.pgc b/app/src/py/svc/PAY_STATCHAN.pgc new file mode 100644 index 0000000..67fbdb9 --- /dev/null +++ b/app/src/py/svc/PAY_STATCHAN.pgc @@ -0,0 +1,43 @@ +/* + * PAY_STATCHAN.pgc - py 모듈 서비스 PAY_STATCHAN (one service = one file). + * 채널별 지급 통계: 당일 지급건을 채널(STD/SPLIT/BULK/ADVANCE/URGENT)별 + * 커서 집계해 occurrence 로 회신하고 총건수/총액을 함께 준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_STATCHAN.h" + +void PAY_STATCHAN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + int i = 0; + long gcnt = 0, gamt = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_chan[16]; + long h_cnt, h_amt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL DECLARE cur_statchan CURSOR FOR + SELECT channel, count(*), coalesce(sum(net),0) + FROM py_payout WHERE biz_date = :h_bizdate + GROUP BY channel ORDER BY channel; + EXEC SQL OPEN cur_statchan; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cur_statchan INTO :h_chan, :h_cnt, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_statchan; FAIL(b); } + if (i < 8) { + Bchg(b, T_STR1, i, h_chan, 0L); + Bchg(b, T_ARG1, i, (char *)&h_cnt, 0L); + Bchg(b, T_AMT1, i, (char *)&h_amt, 0L); + } + gcnt += h_cnt; gamt += h_amt; i++; + } + EXEC SQL CLOSE cur_statchan; + setl(b, T_COUNT, gcnt); + setl(b, T_GROSS, gamt); + setl(b, T_ARG2, (long)i); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/py/svc/PAY_STATDAILY.h b/app/src/py/svc/PAY_STATDAILY.h new file mode 100644 index 0000000..15f8a8d --- /dev/null +++ b/app/src/py/svc/PAY_STATDAILY.h @@ -0,0 +1,21 @@ +/* + * PAY_STATDAILY.h - py 서비스 PAY_STATDAILY 카피북 (copybook / record header). + * Request fields: T_BIZDATE(시작일), T_ARG1(구간 일수, 기본 7) + * Response fields: T_COUNT(지급 발생 일수), T_GROSS(구간 총액), T_AMT2(일평균), + * occurrence: T_STR1[i](일자), T_ARG2[i](건수), T_AMT1[i](금액) + */ +#ifndef PAY_STATDAILY_H +#define PAY_STATDAILY_H + +#include "py_svc.h" + +typedef struct { + char from_date[16]; + long span_days; + long day_count; + long day_amount; +} py_statdaily_rec_t; + +void PAY_STATDAILY(TPSVCINFO *p); + +#endif /* PAY_STATDAILY_H */ diff --git a/app/src/py/svc/PAY_STATDAILY.pgc b/app/src/py/svc/PAY_STATDAILY.pgc new file mode 100644 index 0000000..56b6d22 --- /dev/null +++ b/app/src/py/svc/PAY_STATDAILY.pgc @@ -0,0 +1,50 @@ +/* + * PAY_STATDAILY.pgc - py 모듈 서비스 PAY_STATDAILY (one service = one file). + * 일자별 지급 추이: 시작일부터 N일 구간을 일자별 커서 집계해 occurrence 로 + * 회신하고 구간 총액과 일평균(총액/영업일수)을 산출한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_STATDAILY.h" + +void PAY_STATDAILY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + int i = 0; + long days_hit = 0, total = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_from[16], h_date[16]; + long h_span, h_cnt, h_amt; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_from, sizeof(h_from)); + h_span = getl(b, T_ARG1); + if (h_span <= 0 || h_span > 31) h_span = 7; + + EXEC SQL DECLARE cur_statdaily CURSOR FOR + SELECT CAST(biz_date AS text), count(*), coalesce(sum(net),0) + FROM py_payout + WHERE biz_date >= CAST(:h_from AS date) + AND biz_date < CAST(:h_from AS date) + CAST(:h_span AS integer) + GROUP BY biz_date ORDER BY biz_date; + EXEC SQL OPEN cur_statdaily; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH cur_statdaily INTO :h_date, :h_cnt, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cur_statdaily; FAIL(b); } + days_hit++; + total += h_amt; + if (i < 31) { + Bchg(b, T_STR1, i, h_date, 0L); + Bchg(b, T_ARG2, i, (char *)&h_cnt, 0L); + Bchg(b, T_AMT1, i, (char *)&h_amt, 0L); + i++; + } + } + EXEC SQL CLOSE cur_statdaily; + setl(b, T_COUNT, days_hit); + setl(b, T_GROSS, total); + setl(b, T_AMT2, days_hit > 0 ? total / days_hit : 0); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/py/svc/PAY_STATRANGE.h b/app/src/py/svc/PAY_STATRANGE.h new file mode 100644 index 0000000..054b7a9 --- /dev/null +++ b/app/src/py/svc/PAY_STATRANGE.h @@ -0,0 +1,20 @@ +/* + * PAY_STATRANGE.h - py 서비스 PAY_STATRANGE 카피북 (copybook / record header). + * Request fields: T_BIZDATE + * Response fields: T_ARG1(소액 <10만), T_ARG2(중액 10만~100만), T_ARG3(고액 100만+), T_COUNT + */ +#ifndef PAY_STATRANGE_H +#define PAY_STATRANGE_H + +#include "py_svc.h" + +typedef struct { + char biz_date[16]; + long small_count; + long mid_count; + long large_count; +} py_statrange_rec_t; + +void PAY_STATRANGE(TPSVCINFO *p); + +#endif /* PAY_STATRANGE_H */ diff --git a/app/src/py/svc/PAY_STATRANGE.pgc b/app/src/py/svc/PAY_STATRANGE.pgc new file mode 100644 index 0000000..f502fed --- /dev/null +++ b/app/src/py/svc/PAY_STATRANGE.pgc @@ -0,0 +1,32 @@ +/* + * PAY_STATRANGE.pgc - py 모듈 서비스 PAY_STATRANGE (one service = one file). + * 금액 구간별 분포: 당일 지급건을 소액(<10만)/중액(10만~100만)/고액(100만+) + * 3개 구간으로 CASE 집계해 건수 분포와 전체 건수를 회신한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_STATRANGE.h" + +void PAY_STATRANGE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_small, h_mid, h_large, h_tot; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL SELECT coalesce(sum(CASE WHEN net < 100000 THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN net >= 100000 AND net < 1000000 THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN net >= 1000000 THEN 1 ELSE 0 END),0), + count(*) + INTO :h_small, :h_mid, :h_large, :h_tot + FROM py_payout WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_ARG1, h_small); + setl(b, T_ARG2, h_mid); + setl(b, T_ARG3, h_large); + setl(b, T_COUNT, h_tot); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/py/svc/PAY_URGENT.h b/app/src/py/svc/PAY_URGENT.h new file mode 100644 index 0000000..5b1e10b --- /dev/null +++ b/app/src/py/svc/PAY_URGENT.h @@ -0,0 +1,21 @@ +/* + * PAY_URGENT.h - py 서비스 PAY_URGENT 카피북 (copybook / record header). + * Request fields: T_MERCHANT, T_KEY1(계좌), T_AMOUNT, T_BIZDATE + * Response fields: T_RC(0 지급 / 1 잔액부족 / 2 계좌불가 / 3 금액오류), + * T_ID1(payout_id), T_FEE, T_NET + */ +#ifndef PAY_URGENT_H +#define PAY_URGENT_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + long amount; + long urgent_fee; + long net; +} py_urgent_rec_t; + +void PAY_URGENT(TPSVCINFO *p); + +#endif /* PAY_URGENT_H */ diff --git a/app/src/py/svc/PAY_URGENT.pgc b/app/src/py/svc/PAY_URGENT.pgc new file mode 100644 index 0000000..273aecf --- /dev/null +++ b/app/src/py/svc/PAY_URGENT.pgc @@ -0,0 +1,46 @@ +/* + * PAY_URGENT.pgc - py 모듈 서비스 PAY_URGENT (one service = one file). + * 긴급지급: 긴급 수수료(0.5%) 공제 후 잔액 확인 -> 즉시 지급완료(P) 건을 + * URGENT 채널로 생성하고 계좌 차감 + py_ledger 에 URGENT 기표까지 원샷 처리. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_URGENT.h" + +void PAY_URGENT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], acct[32], st[8]; + long amount, fee, net, bal = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_net; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_KEY1, acct, sizeof(acct)); + amount = getl(b, T_AMOUNT); + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (amount <= 0) { setl(b, T_RC, 3); FAIL(b); } + + fee = acq_fee(amount, 50); /* 긴급지급 수수료 0.5% */ + net = amount - fee; + if (pydb_account_check(acct, st, &bal) < 0) FAIL(b); + if (strcmp(st, "ACTIVE") != 0) { setl(b, T_RC, 2); Bchg(b, T_MSG, 0, "지급 불가 상태 계좌", 0L); FAIL(b); } + if (bal < net) { setl(b, T_RC, 1); Bchg(b, T_MSG, 0, "재원 잔액 부족", 0L); FAIL(b); } + + h_pid = pydb_next_payout_id(); + if (h_pid < 0) FAIL(b); + if (pydb_insert_payout(h_pid, merch, acct, amount, fee, net, "P", "URGENT", h_bizdate) < 0) FAIL(b); + if (pydb_account_add(acct, -net) < 0) FAIL(b); + h_net = net; + EXEC SQL INSERT INTO py_ledger (ledger_id, payout_id, entry_type, amount, biz_date) + VALUES (nextval('py_ledger_seq'), :h_pid, 'URGENT', :h_net, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_RC, 0); + setl(b, T_ID1, h_pid); + setl(b, T_FEE, fee); + setl(b, T_NET, net); + userlog("PAY_URGENT id=%ld merch=%s net=%ld", h_pid, merch, net); + OK(b); +} diff --git a/app/src/py/svc/PAY_XFERMATCH.h b/app/src/py/svc/PAY_XFERMATCH.h new file mode 100644 index 0000000..b0a08b4 --- /dev/null +++ b/app/src/py/svc/PAY_XFERMATCH.h @@ -0,0 +1,19 @@ +/* + * PAY_XFERMATCH.h - py 서비스 PAY_XFERMATCH 카피북 (copybook / record header). + * Request fields: T_ID1(payout_id) + * Response fields: T_RC(0 일치 / 1 불일치), T_NET, T_AMT1(XFER 합계), T_DELTA(차액) + */ +#ifndef PAY_XFERMATCH_H +#define PAY_XFERMATCH_H + +#include "py_svc.h" + +typedef struct { + long payout_id; + long payout_net; + long xfer_sum; +} py_xfermatch_rec_t; + +void PAY_XFERMATCH(TPSVCINFO *p); + +#endif /* PAY_XFERMATCH_H */ diff --git a/app/src/py/svc/PAY_XFERMATCH.pgc b/app/src/py/svc/PAY_XFERMATCH.pgc new file mode 100644 index 0000000..be0b4d7 --- /dev/null +++ b/app/src/py/svc/PAY_XFERMATCH.pgc @@ -0,0 +1,35 @@ +/* + * PAY_XFERMATCH.pgc - py 모듈 서비스 PAY_XFERMATCH (one service = one file). + * 이체결과 대사(단건): 지급건 net 과 py_ledger 의 XFER 기표 합계를 비교해 + * 일치/불일치와 차액을 회신한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_XFERMATCH.h" + +void PAY_XFERMATCH(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_net, h_led; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_ID1); + EXEC SQL SELECT net INTO :h_net FROM py_payout WHERE payout_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_led + FROM py_ledger WHERE payout_id = :h_pid AND entry_type = 'XFER'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_NET, h_net); + setl(b, T_AMT1, h_led); + setl(b, T_DELTA, h_net - h_led); + if (h_net == h_led) { + setl(b, T_RC, 0); + Bchg(b, T_MSG, 0, "이체결과 대사 일치", 0L); + } else { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "이체결과 대사 불일치", 0L); + userlog("PAY_XFERMATCH 불일치 id=%ld net=%ld xfer=%ld", h_pid, h_net, h_led); + } + OK(b); +} diff --git a/app/src/py/svc/PAY_XFERMISS.h b/app/src/py/svc/PAY_XFERMISS.h new file mode 100644 index 0000000..d8e52ee --- /dev/null +++ b/app/src/py/svc/PAY_XFERMISS.h @@ -0,0 +1,19 @@ +/* + * PAY_XFERMISS.h - py 서비스 PAY_XFERMISS 카피북 (copybook / record header). + * Request fields: T_BIZDATE + * Response fields: T_RC(0 없음 / 1 누락 존재), T_COUNT(누락 건수), T_GROSS(누락 금액), T_MSG + */ +#ifndef PAY_XFERMISS_H +#define PAY_XFERMISS_H + +#include "py_svc.h" + +typedef struct { + char biz_date[16]; + long missing_count; + long missing_amount; +} py_xfermiss_rec_t; + +void PAY_XFERMISS(TPSVCINFO *p); + +#endif /* PAY_XFERMISS_H */ diff --git a/app/src/py/svc/PAY_XFERMISS.pgc b/app/src/py/svc/PAY_XFERMISS.pgc new file mode 100644 index 0000000..c6b0bee --- /dev/null +++ b/app/src/py/svc/PAY_XFERMISS.pgc @@ -0,0 +1,36 @@ +/* + * PAY_XFERMISS.pgc - py 모듈 서비스 PAY_XFERMISS (one service = one file). + * 이체 기표 누락 검출: 당일 지급완료(P)인데 py_ledger 에 XFER 기표가 없는 + * 건수/금액을 NOT EXISTS 안티조인으로 집출한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr. + */ +#include "PAY_XFERMISS.h" + +void PAY_XFERMISS(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt, h_sum; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + EXEC SQL SELECT count(*), coalesce(sum(p.net),0) INTO :h_cnt, :h_sum + FROM py_payout p + WHERE p.biz_date = :h_bizdate AND p.status = 'P' + AND NOT EXISTS (SELECT 1 FROM py_ledger l + WHERE l.payout_id = p.payout_id AND l.entry_type = 'XFER'); + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + if (h_cnt > 0) { + setl(b, T_RC, 1); + Bchg(b, T_MSG, 0, "이체 기표 누락 건 존재", 0L); + userlog("PAY_XFERMISS date=%s missing=%ld amt=%ld", h_bizdate, h_cnt, h_sum); + } else { + setl(b, T_RC, 0); + Bchg(b, T_MSG, 0, "이체 기표 누락 없음", 0L); + } + OK(b); +} diff --git a/app/src/rc/batch/rc_ageing_batch.pgc b/app/src/rc/batch/rc_ageing_batch.pgc new file mode 100644 index 0000000..c5bbb45 --- /dev/null +++ b/app/src/rc/batch/rc_ageing_batch.pgc @@ -0,0 +1,54 @@ +/* + * rc_ageing_batch.pgc - 미대사 에이징 배치 (XA). + * 미해결 예외를 당일/최근7일/7일초과 세 버킷으로 집계해 리포트를 찍고, + * 7일 초과 장기 미대사가 존재하면 WARN 에이징 경보를 남긴다. + * 사용법: rc_ageing_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" +#include "rc_ageing_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + rc_ageing_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_msg[96]; + long h_b0, h_b7, h_bx; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*) INTO :h_b0 FROM rc_exception + WHERE resolved = false AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + EXEC SQL SELECT count(*) INTO :h_b7 FROM rc_exception + WHERE resolved = false AND biz_date < CAST(:h_bizdate AS date) + AND biz_date >= CAST(:h_bizdate AS date) - 7; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + EXEC SQL SELECT count(*) INTO :h_bx FROM rc_exception + WHERE resolved = false AND biz_date < CAST(:h_bizdate AS date) - 7; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + rec.today = h_b0; rec.week = h_b7; rec.over = h_bx; + + if (rec.over > 0) { + snprintf(h_msg, sizeof(h_msg), "ageing over 7d unresolved: %ld", rec.over); + EXEC SQL INSERT INTO rc_alert (alert_id, purchase_id, lvl, message, biz_date) + VALUES (nextval('rc_alert_seq'), 0, 'WARN', :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s today=%ld week=%ld over=%ld\n", + RC_AGEING_TAG, bizdate, rec.today, rec.week, rec.over); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_cancelrec_batch.pgc b/app/src/rc/batch/rc_cancelrec_batch.pgc new file mode 100644 index 0000000..120e6b7 --- /dev/null +++ b/app/src/rc/batch/rc_cancelrec_batch.pgc @@ -0,0 +1,44 @@ +/* + * rc_cancelrec_batch.pgc - 취소분 대사 배치 (XA). + * 당일 취소 증빙(ac_cancel)이 존재하는 매입 중 아직 CANCEL 대사 로그가 없는 + * 건을 INSERT..SELECT 한 방으로 MATCHED 대사 처리한다 (중복 대사 방지). + * 사용법: rc_cancelrec_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" +#include "rc_cancelrec_batch.h" + +int main(int argc, char **argv) +{ + rc_cancelrec_rec_t rec; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + strncpy(rec.day, (argc > 1) ? argv[1] : "2026-07-19", sizeof(rec.day)-1); + rec.day[sizeof(rec.day)-1] = 0; + strcpy(h_bizdate, rec.day); + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL INSERT INTO rc_match_log (match_id, purchase_id, match_type, amount, status, biz_date) + SELECT nextval('rc_match_seq'), p.purchase_id, 'CANCEL', p.amount, 'MATCHED', p.biz_date + FROM purchase p + WHERE p.biz_date = :h_bizdate + AND EXISTS (SELECT 1 FROM ac_cancel c WHERE c.purchase_id = p.purchase_id) + AND NOT EXISTS (SELECT 1 FROM rc_match_log r + WHERE r.purchase_id = p.purchase_id AND r.match_type = 'CANCEL'); + if (sqlca.sqlcode < 0) { fprintf(stderr, "%s INSERT FAIL [%d] %s\n", RC_CANCELREC_TAG, sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.matched = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s cancel_matched=%ld\n", RC_CANCELREC_TAG, rec.day, rec.matched); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_carryover_batch.pgc b/app/src/rc/batch/rc_carryover_batch.pgc new file mode 100644 index 0000000..69c3ca0 --- /dev/null +++ b/app/src/rc/batch/rc_carryover_batch.pgc @@ -0,0 +1,50 @@ +/* + * rc_carryover_batch.pgc - 이월 대사 배치 (XA). + * 전일 EXCEPTION 대사 건을 익일 HOLD 건으로 INSERT..SELECT 일괄 이월하고, + * 이월 원본 예외는 resolved 처리하여 이중 이월을 막는다. + * 사용법: rc_carryover_batch [FROM-YYYY-MM-DD] [TO-YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" +#include "rc_carryover_batch.h" + +int main(int argc, char **argv) +{ + rc_carryover_rec_t rec; + EXEC SQL BEGIN DECLARE SECTION; + char h_from[16], h_to[16]; + long h_resolved; + EXEC SQL END DECLARE SECTION; + + strncpy(rec.from_date, (argc > 1) ? argv[1] : "2026-07-19", sizeof(rec.from_date)-1); + rec.from_date[sizeof(rec.from_date)-1] = 0; + strncpy(rec.to_date, (argc > 2) ? argv[2] : "2026-07-20", sizeof(rec.to_date)-1); + rec.to_date[sizeof(rec.to_date)-1] = 0; + strcpy(h_from, rec.from_date); + strcpy(h_to, rec.to_date); + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL INSERT INTO rc_match_log (match_id, purchase_id, match_type, amount, status, biz_date) + SELECT nextval('rc_match_seq'), purchase_id, 'CARRY', amount, 'HOLD', CAST(:h_to AS date) + FROM rc_match_log WHERE biz_date = :h_from AND status = 'EXCEPTION'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "%s INSERT FAIL [%d] %s\n", RC_CARRYOVER_TAG, sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.carried = sqlca.sqlerrd[2]; + + EXEC SQL UPDATE rc_exception SET resolved = true + WHERE biz_date = :h_from AND resolved = false; + if (sqlca.sqlcode < 0) { fprintf(stderr, "%s UPDATE FAIL [%d] %s\n", RC_CARRYOVER_TAG, sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_resolved = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: %s -> %s carried=%ld resolved=%ld\n", + RC_CARRYOVER_TAG, rec.from_date, rec.to_date, rec.carried, h_resolved); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_closeday_batch.pgc b/app/src/rc/batch/rc_closeday_batch.pgc new file mode 100644 index 0000000..4d2f60f --- /dev/null +++ b/app/src/rc/batch/rc_closeday_batch.pgc @@ -0,0 +1,71 @@ +/* + * rc_closeday_batch.pgc - 일마감 대사 확정 배치 (XA). + * 미해결 예외/보류 잔량이 있으면 마감을 거부(exit 2)하고, 없으면 요약을 + * ON CONFLICT upsert 로 확정한 뒤 당일 MATCHED 를 CONFIRMED 로 전환한다. + * 사용법: rc_closeday_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" +#include "rc_closeday_batch.h" + +int main(int argc, char **argv) +{ + rc_closeday_rec_t rec; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_open, h_hold, h_mat, h_unm, h_gross, h_conf; + EXEC SQL END DECLARE SECTION; + + strncpy(rec.day, (argc > 1) ? argv[1] : "2026-07-19", sizeof(rec.day)-1); + rec.day[sizeof(rec.day)-1] = 0; + strcpy(h_bizdate, rec.day); + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*) INTO :h_open FROM rc_exception + WHERE biz_date = :h_bizdate AND resolved = false; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + EXEC SQL SELECT count(*) INTO :h_hold FROM rc_match_log + WHERE biz_date = :h_bizdate AND status = 'HOLD'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + if (h_open > 0 || h_hold > 0) { + fprintf(stderr, "%s REFUSED: open_exceptions=%ld holds=%ld\n", + RC_CLOSEDAY_TAG, h_open, h_hold); + tpabort(0); tpclose(); tpterm(); + return 2; + } + + EXEC SQL SELECT coalesce(sum(CASE WHEN status = 'MATCHED' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN status = 'EXCEPTION' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN status = 'MATCHED' THEN amount ELSE 0 END),0) + INTO :h_mat, :h_unm, :h_gross + FROM rc_match_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + rec.matched = h_mat; rec.unmatched = h_unm; rec.gross = h_gross; + + EXEC SQL INSERT INTO rc_summary (biz_date, matched_cnt, unmatched_cnt, gross_amount, updated_at) + VALUES (:h_bizdate, :h_mat, :h_unm, :h_gross, now()) + ON CONFLICT (biz_date) DO UPDATE + SET matched_cnt = EXCLUDED.matched_cnt, + unmatched_cnt = EXCLUDED.unmatched_cnt, + gross_amount = EXCLUDED.gross_amount, + updated_at = now(); + if (sqlca.sqlcode < 0) { fprintf(stderr, "%s UPSERT FAIL [%d] %s\n", RC_CLOSEDAY_TAG, sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + EXEC SQL UPDATE rc_match_log SET status = 'CONFIRMED', updated_at = now() + WHERE biz_date = :h_bizdate AND status = 'MATCHED'; + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + h_conf = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s matched=%ld unmatched=%ld gross=%ld confirmed=%ld\n", + RC_CLOSEDAY_TAG, rec.day, rec.matched, rec.unmatched, rec.gross, h_conf); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_dupchk_batch.pgc b/app/src/rc/batch/rc_dupchk_batch.pgc new file mode 100644 index 0000000..f43c9ba --- /dev/null +++ b/app/src/rc/batch/rc_dupchk_batch.pgc @@ -0,0 +1,60 @@ +/* + * rc_dupchk_batch.pgc - 중복 대사 정리 배치 (커서 + XA). + * 당일 두 번 이상 대사된 매입을 GROUP BY/HAVING 커서로 찾아, 매입마다 최초 + * (min match_id) 대사만 남기고 나머지 중복 행을 ROLLBACK 상태로 정리한다. + * 사용법: rc_dupchk_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" +#include "rc_dupchk_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + rc_dupchk_rec_t rec; + long pids = 0, cleaned = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_pid; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE cdb CURSOR FOR + SELECT purchase_id FROM rc_match_log + WHERE biz_date = :h_bizdate + GROUP BY purchase_id HAVING count(*) > 1 + ORDER BY purchase_id; + EXEC SQL OPEN cdb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH cdb INTO :h_pid; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE cdb; tpabort(0); return 1; } + rec.pid = h_pid; + EXEC SQL UPDATE rc_match_log SET status = 'ROLLBACK', updated_at = now() + WHERE biz_date = :h_bizdate AND purchase_id = :h_pid + AND match_id <> (SELECT min(match_id) FROM rc_match_log + WHERE biz_date = :h_bizdate AND purchase_id = :h_pid); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cdb; tpabort(0); return 1; } + rec.dup_rows = sqlca.sqlerrd[2]; + cleaned += rec.dup_rows; + pids++; + } + EXEC SQL CLOSE cdb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s dup_purchases=%ld cleaned_rows=%ld\n", + RC_DUPCHK_TAG, bizdate, pids, cleaned); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_feerecon_batch.pgc b/app/src/rc/batch/rc_feerecon_batch.pgc new file mode 100644 index 0000000..4afa6dd --- /dev/null +++ b/app/src/rc/batch/rc_feerecon_batch.pgc @@ -0,0 +1,58 @@ +/* + * rc_feerecon_batch.pgc - 수수료 대사 배치 (커서 + XA). + * 당일 매입을 가맹점 MDR 과 조인해 기대 수수료와 다른 건을 커서로 순회하며 + * 건별 rc_diff 금액 차이 라인을 적재한다. 하나의 글로벌 트랜잭션으로 2PC. + * 사용법: rc_feerecon_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" +#include "rc_feerecon_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + rc_feerecon_rec_t rec; + long diffs = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_pid, h_fee, h_exp; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE cfb CURSOR FOR + SELECT p.purchase_id, p.fee, p.amount * m.mdr_bps / 10000 + FROM purchase p, merchant m + WHERE p.merchant_id = m.merchant_id AND p.biz_date = :h_bizdate + AND p.fee <> p.amount * m.mdr_bps / 10000 + ORDER BY p.purchase_id; + EXEC SQL OPEN cfb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH cfb INTO :h_pid, :h_fee, :h_exp; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE cfb; tpabort(0); return 1; } + rec.pid = h_pid; rec.fee = h_fee; rec.expected = h_exp; + EXEC SQL INSERT INTO rc_diff + (diff_id, purchase_id, expected_amt, actual_amt, diff_amt, diff_kind, biz_date) + VALUES (nextval('rc_diff_seq'), :h_pid, :h_exp, :h_fee, :h_fee - :h_exp, + 'AMT', :h_bizdate); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cfb; tpabort(0); return 1; } + diffs++; + } + EXEC SQL CLOSE cfb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s fee_diffs=%ld\n", RC_FEERECON_TAG, bizdate, diffs); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_fxrecon_batch.pgc b/app/src/rc/batch/rc_fxrecon_batch.pgc new file mode 100644 index 0000000..a2085b7 --- /dev/null +++ b/app/src/rc/batch/rc_fxrecon_batch.pgc @@ -0,0 +1,58 @@ +/* + * rc_fxrecon_batch.pgc - 외화 대사 배치 (커서 + XA). + * 당일 외화 거래(ac_fx)를 매입과 조인해 원화환산 금액이 매입 금액과 다른 + * 건을 커서로 순회, 건별로 통화코드를 담은 대사 예외를 적재한다. + * 사용법: rc_fxrecon_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" +#include "rc_fxrecon_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + rc_fxrecon_rec_t rec; + long excs = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_ccy[8], h_reason[48]; + long h_pid, h_krw, h_amt; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE cxb CURSOR FOR + SELECT f.purchase_id, f.ccy, f.krw_amount, p.amount + FROM ac_fx f, purchase p + WHERE f.purchase_id = p.purchase_id AND f.biz_date = :h_bizdate + AND f.krw_amount <> p.amount + ORDER BY f.fx_id; + EXEC SQL OPEN cxb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH cxb INTO :h_pid, :h_ccy, :h_krw, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE cxb; tpabort(0); return 1; } + rec.pid = h_pid; rec.krw = h_krw; rec.amount = h_amt; + strncpy(rec.ccy, h_ccy, sizeof(rec.ccy)-1); rec.ccy[sizeof(rec.ccy)-1] = 0; + snprintf(h_reason, sizeof(h_reason), "FX KRW MISMATCH %.4s", rec.ccy); + EXEC SQL INSERT INTO rc_exception (exc_id, purchase_id, reason, biz_date) + VALUES (nextval('rc_exc_seq'), :h_pid, :h_reason, :h_bizdate); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cxb; tpabort(0); return 1; } + excs++; + } + EXEC SQL CLOSE cxb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s fx_exceptions=%ld\n", RC_FXRECON_TAG, bizdate, excs); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_holdrsn_batch.pgc b/app/src/rc/batch/rc_holdrsn_batch.pgc new file mode 100644 index 0000000..8862153 --- /dev/null +++ b/app/src/rc/batch/rc_holdrsn_batch.pgc @@ -0,0 +1,57 @@ +/* + * rc_holdrsn_batch.pgc - 대사 보류 사유별 집계 배치 (커서 + XA). + * 당일 미해결 예외를 사유별 GROUP BY 커서로 순회하며 사유마다 건수 리포트를 + * 찍고 rc_diff 에 CNT 유형 사유별 건수 라인을 적재한다 (많은 순 정렬). + * 사용법: rc_holdrsn_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" +#include "rc_holdrsn_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + rc_holdrsn_rec_t rec; + long reasons = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_reason[64]; + long h_cnt; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE chb CURSOR FOR + SELECT reason, count(*) FROM rc_exception + WHERE biz_date = :h_bizdate AND resolved = false + GROUP BY reason ORDER BY count(*) DESC, reason; + EXEC SQL OPEN chb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH chb INTO :h_reason, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE chb; tpabort(0); return 1; } + strncpy(rec.reason, h_reason, sizeof(rec.reason)-1); rec.reason[sizeof(rec.reason)-1] = 0; + rec.cnt = h_cnt; + printf(" reason=%-32s cnt=%ld\n", rec.reason, rec.cnt); + EXEC SQL INSERT INTO rc_diff + (diff_id, purchase_id, expected_amt, actual_amt, diff_amt, diff_kind, biz_date) + VALUES (nextval('rc_diff_seq'), 0, 0, :h_cnt, :h_cnt, 'CNT', :h_bizdate); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE chb; tpabort(0); return 1; } + reasons++; + } + EXEC SQL CLOSE chb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s reasons=%ld\n", RC_HOLDRSN_TAG, bizdate, reasons); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_notify_batch.pgc b/app/src/rc/batch/rc_notify_batch.pgc new file mode 100644 index 0000000..5fb9543 --- /dev/null +++ b/app/src/rc/batch/rc_notify_batch.pgc @@ -0,0 +1,56 @@ +/* + * rc_notify_batch.pgc - 가맹점별 대사 결과 통보 배치 (커서 + XA). + * 당일 가맹점 일별 집계(merchant_summary)를 커서로 순회하며 가맹점마다 + * 건수/정산액을 담은 INFO 통보 경보를 발송(적재)한다. + * 사용법: rc_notify_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" +#include "rc_notify_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + rc_notify_rec_t rec; + long sent = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_mid[64], h_msg[120]; + long h_cnt, h_net; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE cnb CURSOR FOR + SELECT merchant_id, txn_count, net_amount FROM merchant_summary + WHERE biz_date = :h_bizdate ORDER BY merchant_id; + EXEC SQL OPEN cnb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH cnb INTO :h_mid, :h_cnt, :h_net; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE cnb; tpabort(0); return 1; } + strncpy(rec.mid, h_mid, sizeof(rec.mid)-1); rec.mid[sizeof(rec.mid)-1] = 0; + rec.txn_count = h_cnt; rec.net_amount = h_net; + snprintf(h_msg, sizeof(h_msg), "recon notice %.32s: cnt=%ld net=%ld", + rec.mid, rec.txn_count, rec.net_amount); + EXEC SQL INSERT INTO rc_alert (alert_id, purchase_id, lvl, message, biz_date) + VALUES (nextval('rc_alert_seq'), 0, 'INFO', :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cnb; tpabort(0); return 1; } + sent++; + } + EXEC SQL CLOSE cnb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s notices=%ld\n", RC_NOTIFY_TAG, bizdate, sent); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_orphan_batch.pgc b/app/src/rc/batch/rc_orphan_batch.pgc new file mode 100644 index 0000000..1f7878b --- /dev/null +++ b/app/src/rc/batch/rc_orphan_batch.pgc @@ -0,0 +1,53 @@ +/* + * rc_orphan_batch.pgc - 고아 매입 검출 배치 (XA). + * 승인(approval)이 없는 당일 매입을 두 갈래로 일괄 적재한다: (1) 미매입 + * 관리(ac_unmatch), (2) 대사 예외(rc_exception). 두 INSERT..SELECT 가 한 + * 글로벌 트랜잭션으로 묶여 2PC 커밋된다. + * 사용법: rc_orphan_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" +#include "rc_orphan_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + rc_orphan_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL INSERT INTO ac_unmatch (unmatch_id, purchase_id, reason, biz_date) + SELECT nextval('ac_unmatch_seq'), p.purchase_id, 'NO APPROVAL', p.biz_date + FROM purchase p + WHERE p.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM approval a WHERE a.purchase_id = p.purchase_id); + if (sqlca.sqlcode < 0) { fprintf(stderr, "%s INSERT unmatch FAIL [%d] %s\n", RC_ORPHAN_TAG, sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.unmatch_rows = sqlca.sqlerrd[2]; + + EXEC SQL INSERT INTO rc_exception (exc_id, purchase_id, reason, biz_date) + SELECT nextval('rc_exc_seq'), p.purchase_id, 'ORPHAN PURCHASE', p.biz_date + FROM purchase p + WHERE p.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM approval a WHERE a.purchase_id = p.purchase_id) + AND NOT EXISTS (SELECT 1 FROM rc_exception e + WHERE e.purchase_id = p.purchase_id AND e.resolved = false); + if (sqlca.sqlcode < 0) { fprintf(stderr, "%s INSERT exc FAIL [%d] %s\n", RC_ORPHAN_TAG, sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.exc_rows = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s unmatch=%ld exceptions=%ld\n", + RC_ORPHAN_TAG, bizdate, rec.unmatch_rows, rec.exc_rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_ratestat_batch.pgc b/app/src/rc/batch/rc_ratestat_batch.pgc new file mode 100644 index 0000000..f385bca --- /dev/null +++ b/app/src/rc/batch/rc_ratestat_batch.pgc @@ -0,0 +1,57 @@ +/* + * rc_ratestat_batch.pgc - 일자별 대사율 통계 배치 (커서 + XA). + * 기준일까지의 대사 로그를 영업일 단위로 GROUP BY 집계하는 커서를 돌며 + * 일자별 총건수/MATCHED 건수/대사율(bp)을 표준출력 리포트로 찍는다. + * 사용법: rc_ratestat_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" +#include "rc_ratestat_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + rc_ratestat_rec_t rec; + long days = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_day[16]; + long h_tot, h_mat; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE crb CURSOR FOR + SELECT to_char(biz_date,'YYYY-MM-DD'), count(*), + coalesce(sum(CASE WHEN status = 'MATCHED' THEN 1 ELSE 0 END),0) + FROM rc_match_log + WHERE biz_date <= :h_bizdate + GROUP BY biz_date ORDER BY biz_date; + EXEC SQL OPEN crb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH crb INTO :h_day, :h_tot, :h_mat; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE crb; tpabort(0); return 1; } + strncpy(rec.day, h_day, sizeof(rec.day)-1); rec.day[sizeof(rec.day)-1] = 0; + rec.total = h_tot; rec.matched = h_mat; + rec.rate_bps = (h_tot > 0) ? (h_mat * 10000) / h_tot : 0; + printf(" %s total=%ld matched=%ld rate=%ld.%02ld%%\n", + rec.day, rec.total, rec.matched, rec.rate_bps / 100, rec.rate_bps % 100); + days++; + } + EXEC SQL CLOSE crb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: upto=%s days=%ld\n", RC_RATESTAT_TAG, bizdate, days); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_retro_batch.pgc b/app/src/rc/batch/rc_retro_batch.pgc new file mode 100644 index 0000000..1e4396d --- /dev/null +++ b/app/src/rc/batch/rc_retro_batch.pgc @@ -0,0 +1,51 @@ +/* + * rc_retro_batch.pgc - 소급 대사 배치 (XA). + * 과거 영업일에 EXCEPTION 으로 남은 대사 건들을 새 영업일 HOLD 상태로 소급 + * 이동(UPDATE)시키고, 소급 처리 사실을 INFO 경보로 남긴다. + * 사용법: rc_retro_batch [OLD-YYYY-MM-DD] [NEW-YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" +#include "rc_retro_batch.h" + +int main(int argc, char **argv) +{ + rc_retro_rec_t rec; + EXEC SQL BEGIN DECLARE SECTION; + char h_old[16], h_new[16], h_msg[96]; + EXEC SQL END DECLARE SECTION; + + strncpy(rec.old_day, (argc > 1) ? argv[1] : "2026-07-18", sizeof(rec.old_day)-1); + rec.old_day[sizeof(rec.old_day)-1] = 0; + strncpy(rec.new_day, (argc > 2) ? argv[2] : "2026-07-19", sizeof(rec.new_day)-1); + rec.new_day[sizeof(rec.new_day)-1] = 0; + strcpy(h_old, rec.old_day); + strcpy(h_new, rec.new_day); + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL UPDATE rc_match_log + SET biz_date = CAST(:h_new AS date), status = 'HOLD', updated_at = now() + WHERE biz_date = :h_old AND status = 'EXCEPTION'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "%s UPDATE FAIL [%d] %s\n", RC_RETRO_TAG, sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.moved = sqlca.sqlerrd[2]; + + if (rec.moved > 0) { + snprintf(h_msg, sizeof(h_msg), "retro recon %.12s -> %.12s rows=%ld", + rec.old_day, rec.new_day, rec.moved); + EXEC SQL INSERT INTO rc_alert (alert_id, purchase_id, lvl, message, biz_date) + VALUES (nextval('rc_alert_seq'), 0, 'INFO', :h_msg, :h_new); + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: %s -> %s moved=%ld\n", RC_RETRO_TAG, rec.old_day, rec.new_day, rec.moved); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_threshold_batch.pgc b/app/src/rc/batch/rc_threshold_batch.pgc new file mode 100644 index 0000000..5c5023c --- /dev/null +++ b/app/src/rc/batch/rc_threshold_batch.pgc @@ -0,0 +1,52 @@ +/* + * rc_threshold_batch.pgc - 임계초과 재대사 배치 (XA). + * |차이금액| 이 임계값을 넘는 당일 차이 건들을 INSERT..SELECT 로 RERUN/HOLD + * 재대사 큐에 일괄 등록하고, 등록이 있으면 CRIT 경보를 남긴다. + * 사용법: rc_threshold_batch [YYYY-MM-DD] [LIMIT] + */ +#include +#include +#include +#include +#include "rc_dbio.h" +#include "rc_threshold_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + rc_threshold_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_msg[96]; + long h_limit; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + rec.limit = (argc > 2) ? atol(argv[2]) : RC_THRESHOLD_DEFAULT_LIMIT; + h_limit = rec.limit; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL INSERT INTO rc_match_log (match_id, purchase_id, match_type, amount, status, biz_date) + SELECT nextval('rc_match_seq'), purchase_id, 'RERUN', diff_amt, 'HOLD', biz_date + FROM rc_diff + WHERE biz_date = :h_bizdate AND abs(diff_amt) > :h_limit; + if (sqlca.sqlcode < 0) { fprintf(stderr, "%s INSERT FAIL [%d] %s\n", RC_THRESHOLD_TAG, sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.queued = sqlca.sqlerrd[2]; + + if (rec.queued > 0) { + snprintf(h_msg, sizeof(h_msg), "threshold %ld exceeded: rerun queued=%ld", + rec.limit, rec.queued); + EXEC SQL INSERT INTO rc_alert (alert_id, purchase_id, lvl, message, biz_date) + VALUES (nextval('rc_alert_seq'), 0, 'CRIT', :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) { tpabort(0); return 1; } + } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s limit=%ld queued=%ld\n", + RC_THRESHOLD_TAG, bizdate, rec.limit, rec.queued); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/dbio/rc_ageing_batch.h b/app/src/rc/dbio/rc_ageing_batch.h new file mode 100644 index 0000000..1b49e79 --- /dev/null +++ b/app/src/rc/dbio/rc_ageing_batch.h @@ -0,0 +1,6 @@ +/* rc_ageing_batch.h - 미대사 에이징 배치 copybook. */ +#ifndef RC_AGEING_BATCH_H +#define RC_AGEING_BATCH_H +#define RC_AGEING_TAG "rc_ageing_batch" +typedef struct { long today; long week; long over; } rc_ageing_rec_t; +#endif diff --git a/app/src/rc/dbio/rc_cancelrec_batch.h b/app/src/rc/dbio/rc_cancelrec_batch.h new file mode 100644 index 0000000..3829986 --- /dev/null +++ b/app/src/rc/dbio/rc_cancelrec_batch.h @@ -0,0 +1,6 @@ +/* rc_cancelrec_batch.h - 취소분 대사 배치 copybook. */ +#ifndef RC_CANCELREC_BATCH_H +#define RC_CANCELREC_BATCH_H +#define RC_CANCELREC_TAG "rc_cancelrec_batch" +typedef struct { char day[16]; long matched; } rc_cancelrec_rec_t; +#endif diff --git a/app/src/rc/dbio/rc_carryover_batch.h b/app/src/rc/dbio/rc_carryover_batch.h new file mode 100644 index 0000000..fb8abb6 --- /dev/null +++ b/app/src/rc/dbio/rc_carryover_batch.h @@ -0,0 +1,6 @@ +/* rc_carryover_batch.h - 이월 대사 배치 copybook. */ +#ifndef RC_CARRYOVER_BATCH_H +#define RC_CARRYOVER_BATCH_H +#define RC_CARRYOVER_TAG "rc_carryover_batch" +typedef struct { char from_date[16]; char to_date[16]; long carried; } rc_carryover_rec_t; +#endif diff --git a/app/src/rc/dbio/rc_closeday_batch.h b/app/src/rc/dbio/rc_closeday_batch.h new file mode 100644 index 0000000..e0ea5d3 --- /dev/null +++ b/app/src/rc/dbio/rc_closeday_batch.h @@ -0,0 +1,6 @@ +/* rc_closeday_batch.h - 일마감 대사 확정 배치 copybook. */ +#ifndef RC_CLOSEDAY_BATCH_H +#define RC_CLOSEDAY_BATCH_H +#define RC_CLOSEDAY_TAG "rc_closeday_batch" +typedef struct { char day[16]; long matched; long unmatched; long gross; } rc_closeday_rec_t; +#endif diff --git a/app/src/rc/dbio/rc_dupchk_batch.h b/app/src/rc/dbio/rc_dupchk_batch.h new file mode 100644 index 0000000..2d77b5c --- /dev/null +++ b/app/src/rc/dbio/rc_dupchk_batch.h @@ -0,0 +1,6 @@ +/* rc_dupchk_batch.h - 중복 대사 정리 배치 copybook. */ +#ifndef RC_DUPCHK_BATCH_H +#define RC_DUPCHK_BATCH_H +#define RC_DUPCHK_TAG "rc_dupchk_batch" +typedef struct { long pid; long dup_rows; } rc_dupchk_rec_t; +#endif diff --git a/app/src/rc/dbio/rc_feerecon_batch.h b/app/src/rc/dbio/rc_feerecon_batch.h new file mode 100644 index 0000000..ec30d13 --- /dev/null +++ b/app/src/rc/dbio/rc_feerecon_batch.h @@ -0,0 +1,6 @@ +/* rc_feerecon_batch.h - 수수료 대사 배치 copybook. */ +#ifndef RC_FEERECON_BATCH_H +#define RC_FEERECON_BATCH_H +#define RC_FEERECON_TAG "rc_feerecon_batch" +typedef struct { long pid; long fee; long expected; } rc_feerecon_rec_t; +#endif diff --git a/app/src/rc/dbio/rc_fxrecon_batch.h b/app/src/rc/dbio/rc_fxrecon_batch.h new file mode 100644 index 0000000..c301c19 --- /dev/null +++ b/app/src/rc/dbio/rc_fxrecon_batch.h @@ -0,0 +1,6 @@ +/* rc_fxrecon_batch.h - 외화 대사 배치 copybook. */ +#ifndef RC_FXRECON_BATCH_H +#define RC_FXRECON_BATCH_H +#define RC_FXRECON_TAG "rc_fxrecon_batch" +typedef struct { long pid; char ccy[8]; long krw; long amount; } rc_fxrecon_rec_t; +#endif diff --git a/app/src/rc/dbio/rc_holdrsn_batch.h b/app/src/rc/dbio/rc_holdrsn_batch.h new file mode 100644 index 0000000..6a44a58 --- /dev/null +++ b/app/src/rc/dbio/rc_holdrsn_batch.h @@ -0,0 +1,6 @@ +/* rc_holdrsn_batch.h - 보류 사유별 집계 배치 copybook. */ +#ifndef RC_HOLDRSN_BATCH_H +#define RC_HOLDRSN_BATCH_H +#define RC_HOLDRSN_TAG "rc_holdrsn_batch" +typedef struct { char reason[64]; long cnt; } rc_holdrsn_rec_t; +#endif diff --git a/app/src/rc/dbio/rc_notify_batch.h b/app/src/rc/dbio/rc_notify_batch.h new file mode 100644 index 0000000..15b6bcc --- /dev/null +++ b/app/src/rc/dbio/rc_notify_batch.h @@ -0,0 +1,6 @@ +/* rc_notify_batch.h - 대사 결과 통보 배치 copybook. */ +#ifndef RC_NOTIFY_BATCH_H +#define RC_NOTIFY_BATCH_H +#define RC_NOTIFY_TAG "rc_notify_batch" +typedef struct { char mid[64]; long txn_count; long net_amount; } rc_notify_rec_t; +#endif diff --git a/app/src/rc/dbio/rc_orphan_batch.h b/app/src/rc/dbio/rc_orphan_batch.h new file mode 100644 index 0000000..702ab97 --- /dev/null +++ b/app/src/rc/dbio/rc_orphan_batch.h @@ -0,0 +1,6 @@ +/* rc_orphan_batch.h - 고아 매입 검출 배치 copybook. */ +#ifndef RC_ORPHAN_BATCH_H +#define RC_ORPHAN_BATCH_H +#define RC_ORPHAN_TAG "rc_orphan_batch" +typedef struct { long unmatch_rows; long exc_rows; } rc_orphan_rec_t; +#endif diff --git a/app/src/rc/dbio/rc_ratestat_batch.h b/app/src/rc/dbio/rc_ratestat_batch.h new file mode 100644 index 0000000..60ad797 --- /dev/null +++ b/app/src/rc/dbio/rc_ratestat_batch.h @@ -0,0 +1,6 @@ +/* rc_ratestat_batch.h - 일자별 대사율 통계 배치 copybook. */ +#ifndef RC_RATESTAT_BATCH_H +#define RC_RATESTAT_BATCH_H +#define RC_RATESTAT_TAG "rc_ratestat_batch" +typedef struct { char day[16]; long total; long matched; long rate_bps; } rc_ratestat_rec_t; +#endif diff --git a/app/src/rc/dbio/rc_retro_batch.h b/app/src/rc/dbio/rc_retro_batch.h new file mode 100644 index 0000000..0b221d6 --- /dev/null +++ b/app/src/rc/dbio/rc_retro_batch.h @@ -0,0 +1,6 @@ +/* rc_retro_batch.h - 소급 대사 배치 copybook. */ +#ifndef RC_RETRO_BATCH_H +#define RC_RETRO_BATCH_H +#define RC_RETRO_TAG "rc_retro_batch" +typedef struct { char old_day[16]; char new_day[16]; long moved; } rc_retro_rec_t; +#endif diff --git a/app/src/rc/dbio/rc_threshold_batch.h b/app/src/rc/dbio/rc_threshold_batch.h new file mode 100644 index 0000000..ca73ce1 --- /dev/null +++ b/app/src/rc/dbio/rc_threshold_batch.h @@ -0,0 +1,7 @@ +/* rc_threshold_batch.h - 임계초과 재대사 배치 copybook. */ +#ifndef RC_THRESHOLD_BATCH_H +#define RC_THRESHOLD_BATCH_H +#define RC_THRESHOLD_TAG "rc_threshold_batch" +#define RC_THRESHOLD_DEFAULT_LIMIT 100000L +typedef struct { long limit; long queued; } rc_threshold_rec_t; +#endif diff --git a/app/src/rc/rc_svr.pgc b/app/src/rc/rc_svr.pgc index 15cf383..831aba7 100644 --- a/app/src/rc/rc_svr.pgc +++ b/app/src/rc/rc_svr.pgc @@ -2,14 +2,14 @@ * rc_svr.pgc - rc (대사) MODULE SERVER (thin dispatcher). * * The legacy "one module server, many services" pattern: this binary owns NO - * business logic. Each of rc's 30 online services now lives in its own file + * business logic. Each of rc's 75 online services now lives in its own file * (app/src/rc/svc/.pgc) with its own copybook header * (app/src/rc/svc/.h); those objects are archived into librcsvc.a and * linked into this server. Here we only include every service copybook, tpopen() * the ECPG XA RM, tpadvertise() all services from a {name, fn} table, and * tpclose() at shutdown. * - * rc_svr: ONE binary that tpadvertise()s ALL 30 online rc(대사) services. + * rc_svr: ONE binary that tpadvertise()s ALL 75 online rc(대사) services. * RECONCILE keeps the proven chain: distinct XA branch, own recon ledger row, * then tpcall()s SETTLE inside the SAME global tx. */ @@ -48,6 +48,51 @@ #include "RC_ROLLBACK.h" #include "RC_CONFIRM.h" #include "RC_STATUS.h" +#include "RC_4WAY.h" +#include "RC_DEPMATCH.h" +#include "RC_PAYMATCH.h" +#include "RC_BYCHANNEL.h" +#include "RC_BYCATEGORY.h" +#include "RC_BYMGROUP.h" +#include "RC_MANREG.h" +#include "RC_MANAPPRV.h" +#include "RC_MANREJECT.h" +#include "RC_HOLDRSN.h" +#include "RC_HOLDAGE.h" +#include "RC_DIFFCAUSE.h" +#include "RC_DIFFTREND.h" +#include "RC_CARRYOVER.h" +#include "RC_RETRO.h" +#include "RC_NOTIFY.h" +#include "RC_RATESTAT.h" +#include "RC_RATEBYISS.h" +#include "RC_THRESHOLD.h" +#include "RC_RERUN.h" +#include "RC_CANCELREC.h" +#include "RC_VOIDREC.h" +#include "RC_PARTACQ.h" +#include "RC_INSTRECON.h" +#include "RC_FEERECON.h" +#include "RC_FEEDIFF.h" +#include "RC_FXRECON.h" +#include "RC_FXRATECHK.h" +#include "RC_WHTRECON.h" +#include "RC_TAXRECON.h" +#include "RC_EDIRECON.h" +#include "RC_AUDITTRAIL.h" +#include "RC_AUDITLOG.h" +#include "RC_AGEING.h" +#include "RC_TOPDIFF.h" +#include "RC_ZEROAMT.h" +#include "RC_DUPCHK.h" +#include "RC_ORPHAN.h" +#include "RC_GAPSCAN.h" +#include "RC_SUMCHK.h" +#include "RC_LIMITCHK.h" +#include "RC_ESCALATE.h" +#include "RC_CLOSEDAY.h" +#include "RC_REOPEN.h" +#include "RC_SNAPSHOT.h" /* ----- advertise table: {service name, function} ---------------------- */ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { @@ -66,6 +111,29 @@ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { {"RC_LEDGERLINK", RC_LEDGERLINK}, {"RC_ALERT", RC_ALERT}, {"RC_MANUAL", RC_MANUAL}, {"RC_ROLLBACK", RC_ROLLBACK}, {"RC_CONFIRM", RC_CONFIRM}, {"RC_STATUS", RC_STATUS}, + {"RC_4WAY", RC_4WAY}, {"RC_DEPMATCH", RC_DEPMATCH}, + {"RC_PAYMATCH", RC_PAYMATCH}, {"RC_BYCHANNEL", RC_BYCHANNEL}, + {"RC_BYCATEGORY", RC_BYCATEGORY}, {"RC_BYMGROUP", RC_BYMGROUP}, + {"RC_MANREG", RC_MANREG}, {"RC_MANAPPRV", RC_MANAPPRV}, + {"RC_MANREJECT", RC_MANREJECT}, {"RC_HOLDRSN", RC_HOLDRSN}, + {"RC_HOLDAGE", RC_HOLDAGE}, {"RC_DIFFCAUSE", RC_DIFFCAUSE}, + {"RC_DIFFTREND", RC_DIFFTREND}, {"RC_CARRYOVER", RC_CARRYOVER}, + {"RC_RETRO", RC_RETRO}, {"RC_NOTIFY", RC_NOTIFY}, + {"RC_RATESTAT", RC_RATESTAT}, {"RC_RATEBYISS", RC_RATEBYISS}, + {"RC_THRESHOLD", RC_THRESHOLD}, {"RC_RERUN", RC_RERUN}, + {"RC_CANCELREC", RC_CANCELREC}, {"RC_VOIDREC", RC_VOIDREC}, + {"RC_PARTACQ", RC_PARTACQ}, {"RC_INSTRECON", RC_INSTRECON}, + {"RC_FEERECON", RC_FEERECON}, {"RC_FEEDIFF", RC_FEEDIFF}, + {"RC_FXRECON", RC_FXRECON}, {"RC_FXRATECHK", RC_FXRATECHK}, + {"RC_WHTRECON", RC_WHTRECON}, {"RC_TAXRECON", RC_TAXRECON}, + {"RC_EDIRECON", RC_EDIRECON}, {"RC_AUDITTRAIL", RC_AUDITTRAIL}, + {"RC_AUDITLOG", RC_AUDITLOG}, {"RC_AGEING", RC_AGEING}, + {"RC_TOPDIFF", RC_TOPDIFF}, {"RC_ZEROAMT", RC_ZEROAMT}, + {"RC_DUPCHK", RC_DUPCHK}, {"RC_ORPHAN", RC_ORPHAN}, + {"RC_GAPSCAN", RC_GAPSCAN}, {"RC_SUMCHK", RC_SUMCHK}, + {"RC_LIMITCHK", RC_LIMITCHK}, {"RC_ESCALATE", RC_ESCALATE}, + {"RC_CLOSEDAY", RC_CLOSEDAY}, {"RC_REOPEN", RC_REOPEN}, + {"RC_SNAPSHOT", RC_SNAPSHOT}, {NULL, NULL} }; diff --git a/app/src/rc/svc/RC_4WAY.h b/app/src/rc/svc/RC_4WAY.h new file mode 100644 index 0000000..521dd73 --- /dev/null +++ b/app/src/rc/svc/RC_4WAY.h @@ -0,0 +1,21 @@ +/* + * RC_4WAY.h - rc 서비스 RC_4WAY 카피북 (copybook / record header). + * 승인-매입-입금-지급 4-way 대사. + */ +#ifndef RC_4WAY_H +#define RC_4WAY_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + long net; + long appr_cnt; + long dep_cnt; + long pay_cnt; + char biz_date[16]; +} rc_rc_4way_rec_t; + +void RC_4WAY(TPSVCINFO *p); + +#endif /* RC_4WAY_H */ diff --git a/app/src/rc/svc/RC_4WAY.pgc b/app/src/rc/svc/RC_4WAY.pgc new file mode 100644 index 0000000..e50d60e --- /dev/null +++ b/app/src/rc/svc/RC_4WAY.pgc @@ -0,0 +1,42 @@ +/* + * RC_4WAY.pgc - rc 모듈 서비스 RC_4WAY (one service = one file). + * 승인-매입-입금-지급 4-way 대사: 승인(approval), 입금(settlement), 지급 + * (ledger SETTLE) 세 축의 증빙이 모두 존재해야 MATCHED, 하나라도 빠지면 + * EXCEPTION 으로 기록하고 예외 원장을 남긴다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_4WAY.h" + +/* 31. RC_4WAY - 승인-매입-입금-지급 4-way 대사. */ +void RC_4WAY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long mid, ok; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_net, h_appr, h_dep, h_pay; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + h_net = getl(b, T_NET); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + + EXEC SQL SELECT count(*) INTO :h_appr FROM approval + WHERE purchase_id = :h_pid AND approved = true; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_dep FROM settlement WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_pay FROM ledger + WHERE purchase_id = :h_pid AND entry_type = 'SETTLE'; + if (sqlca.sqlcode < 0) FAIL(b); + + ok = (h_appr > 0 && h_dep > 0 && h_pay > 0); + mid = rcdb_insert_match(h_pid, "4WAY", h_net, ok ? "MATCHED" : "EXCEPTION", bizdate); + if (mid < 0) FAIL(b); + if (!ok && rcdb_insert_exception(h_pid, "4WAY LEG MISSING", bizdate) < 0) FAIL(b); + setl(b, T_ID1, mid); + setl(b, T_RC, ok ? 0 : 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_AGEING.h b/app/src/rc/svc/RC_AGEING.h new file mode 100644 index 0000000..4fab21e --- /dev/null +++ b/app/src/rc/svc/RC_AGEING.h @@ -0,0 +1,19 @@ +/* + * RC_AGEING.h - rc 서비스 RC_AGEING 카피북 (copybook / record header). + * 미해결 예외 에이징 버킷(당일/7일/초과) 집계. + */ +#ifndef RC_AGEING_H +#define RC_AGEING_H + +#include "rc_svc.h" + +typedef struct { + long bucket_today; + long bucket_week; + long bucket_over; + char biz_date[16]; +} rc_rc_ageing_rec_t; + +void RC_AGEING(TPSVCINFO *p); + +#endif /* RC_AGEING_H */ diff --git a/app/src/rc/svc/RC_AGEING.pgc b/app/src/rc/svc/RC_AGEING.pgc new file mode 100644 index 0000000..efdf16a --- /dev/null +++ b/app/src/rc/svc/RC_AGEING.pgc @@ -0,0 +1,40 @@ +/* + * RC_AGEING.pgc - rc 모듈 서비스 RC_AGEING (one service = one file). + * 미대사 에이징: 미해결 예외를 당일 / 최근 7일 / 7일 초과 세 버킷으로 나눠 + * 건수를 집계하여 미대사 잔량의 노후도를 보여준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_AGEING.h" + +/* 64. RC_AGEING - 미해결 예외 에이징 (당일/7일/초과). */ +void RC_AGEING(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_b0, h_b7, h_bx; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*) INTO :h_b0 FROM rc_exception + WHERE resolved = false AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_b7 FROM rc_exception + WHERE resolved = false AND biz_date < CAST(:h_bizdate AS date) + AND biz_date >= CAST(:h_bizdate AS date) - 7; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_bx FROM rc_exception + WHERE resolved = false AND biz_date < CAST(:h_bizdate AS date) - 7; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_ARG1, h_b0); + setl(b, T_ARG2, h_b7); + setl(b, T_ARG3, h_bx); + setl(b, T_COUNT, h_b0 + h_b7 + h_bx); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_AUDITLOG.h b/app/src/rc/svc/RC_AUDITLOG.h new file mode 100644 index 0000000..71f2f4e --- /dev/null +++ b/app/src/rc/svc/RC_AUDITLOG.h @@ -0,0 +1,19 @@ +/* + * RC_AUDITLOG.h - rc 서비스 RC_AUDITLOG 카피북 (copybook / record header). + * 대사 감사 이벤트 이중 기록 (원장 RC_AUDIT + INFO 경보). + */ +#ifndef RC_AUDITLOG_H +#define RC_AUDITLOG_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char svcname[32]; + char reason[64]; + char biz_date[16]; +} rc_rc_auditlog_rec_t; + +void RC_AUDITLOG(TPSVCINFO *p); + +#endif /* RC_AUDITLOG_H */ diff --git a/app/src/rc/svc/RC_AUDITLOG.pgc b/app/src/rc/svc/RC_AUDITLOG.pgc new file mode 100644 index 0000000..3b9d99d --- /dev/null +++ b/app/src/rc/svc/RC_AUDITLOG.pgc @@ -0,0 +1,37 @@ +/* + * RC_AUDITLOG.pgc - rc 모듈 서비스 RC_AUDITLOG (one service = one file). + * 대사 감사 로그 기록: 호출 서비스명과 사유를 받아 원장에 RC_AUDIT 증적을 + * 남기고 동시에 INFO 경보로 감사 이벤트를 적재한다 (이중 기록). + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_AUDITLOG.h" + +/* 63. RC_AUDITLOG - 감사 이벤트 이중 기록 (원장 + 경보). */ +void RC_AUDITLOG(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], svcname[32], reason[64]; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid; + char h_bizdate[16], h_msg[120]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + gets_(b, T_SVCNAME, svcname, sizeof(svcname)); + gets_(b, T_REASON, reason, sizeof(reason)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date) + VALUES (nextval('ledger_seq'), :h_pid, 'RC_AUDIT', 0, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + + snprintf(h_msg, sizeof(h_msg), "audit %.28s: %.60s", svcname, reason); + EXEC SQL INSERT INTO rc_alert (alert_id, purchase_id, lvl, message, biz_date) + VALUES (nextval('rc_alert_seq'), :h_pid, 'INFO', :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_RC, 0); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_AUDITTRAIL.h b/app/src/rc/svc/RC_AUDITTRAIL.h new file mode 100644 index 0000000..4d7c869 --- /dev/null +++ b/app/src/rc/svc/RC_AUDITTRAIL.h @@ -0,0 +1,19 @@ +/* + * RC_AUDITTRAIL.h - rc 서비스 RC_AUDITTRAIL 카피북 (copybook / record header). + * 매입별 대사 감사추적 (이력/증적/최종상태). + */ +#ifndef RC_AUDITTRAIL_H +#define RC_AUDITTRAIL_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + long history_cnt; + long evidence_cnt; + char last_status[16]; +} rc_rc_audittrail_rec_t; + +void RC_AUDITTRAIL(TPSVCINFO *p); + +#endif /* RC_AUDITTRAIL_H */ diff --git a/app/src/rc/svc/RC_AUDITTRAIL.pgc b/app/src/rc/svc/RC_AUDITTRAIL.pgc new file mode 100644 index 0000000..ed8d19f --- /dev/null +++ b/app/src/rc/svc/RC_AUDITTRAIL.pgc @@ -0,0 +1,37 @@ +/* + * RC_AUDITTRAIL.pgc - rc 모듈 서비스 RC_AUDITTRAIL (one service = one file). + * 대사 감사추적 조회: 특정 매입의 대사 이력 건수, 대사 관련 원장 증적 건수, + * 그리고 최종 대사 상태를 한 번에 조회하여 감사 관점의 추적 정보를 준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_AUDITTRAIL.h" + +/* 62. RC_AUDITTRAIL - 매입별 대사 감사추적(이력/증적/최종상태). */ +void RC_AUDITTRAIL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_hist, h_evi; + char h_last[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + + EXEC SQL SELECT count(*) INTO :h_hist FROM rc_match_log WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_evi FROM ledger + WHERE purchase_id = :h_pid AND entry_type LIKE 'RC%'; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce( + (SELECT status FROM rc_match_log WHERE purchase_id = :h_pid + ORDER BY updated_at DESC, match_id DESC LIMIT 1), 'NONE') + INTO :h_last; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_ARG1, h_hist); + setl(b, T_ARG2, h_evi); + Bchg(b, T_STATUS, 0, h_last, 0L); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_BYCATEGORY.h b/app/src/rc/svc/RC_BYCATEGORY.h new file mode 100644 index 0000000..77d5e72 --- /dev/null +++ b/app/src/rc/svc/RC_BYCATEGORY.h @@ -0,0 +1,20 @@ +/* + * RC_BYCATEGORY.h - rc 서비스 RC_BYCATEGORY 카피북 (copybook / record header). + * 업종별 대사 집계 + 대사율(%). + */ +#ifndef RC_BYCATEGORY_H +#define RC_BYCATEGORY_H + +#include "rc_svc.h" + +typedef struct { + char category[16]; + long matched_cnt; + long exception_cnt; + long match_pct; + char biz_date[16]; +} rc_rc_bycategory_rec_t; + +void RC_BYCATEGORY(TPSVCINFO *p); + +#endif /* RC_BYCATEGORY_H */ diff --git a/app/src/rc/svc/RC_BYCATEGORY.pgc b/app/src/rc/svc/RC_BYCATEGORY.pgc new file mode 100644 index 0000000..a1aafae --- /dev/null +++ b/app/src/rc/svc/RC_BYCATEGORY.pgc @@ -0,0 +1,44 @@ +/* + * RC_BYCATEGORY.pgc - rc 모듈 서비스 RC_BYCATEGORY (one service = one file). + * 업종(카테고리)별 대사 세분: 요청 업종의 당일 MATCHED 건수와 EXCEPTION + * 건수를 각각 집계하고 백분율 대사율을 계산하여 반환한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_BYCATEGORY.h" + +/* 35. RC_BYCATEGORY - 업종별 대사 집계 + 대사율. */ +void RC_BYCATEGORY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], category[16]; + long total, pct; + EXEC SQL BEGIN DECLARE SECTION; + long h_mat, h_exc; + char h_bizdate[16], h_cat[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + gets_(b, T_CATEGORY, category, sizeof(category)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(h_cat, category, sizeof(h_cat)-1); h_cat[sizeof(h_cat)-1] = 0; + + EXEC SQL SELECT count(*) INTO :h_mat + FROM rc_match_log m, purchase p + WHERE m.purchase_id = p.purchase_id AND m.biz_date = :h_bizdate + AND m.status = 'MATCHED' AND p.category = :h_cat; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_exc + FROM rc_match_log m, purchase p + WHERE m.purchase_id = p.purchase_id AND m.biz_date = :h_bizdate + AND m.status = 'EXCEPTION' AND p.category = :h_cat; + if (sqlca.sqlcode < 0) FAIL(b); + + total = h_mat + h_exc; + pct = (total > 0) ? (h_mat * 100) / total : 0; + setl(b, T_ARG1, h_mat); + setl(b, T_ARG2, h_exc); + setl(b, T_RC, pct); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_BYCHANNEL.h b/app/src/rc/svc/RC_BYCHANNEL.h new file mode 100644 index 0000000..303523f --- /dev/null +++ b/app/src/rc/svc/RC_BYCHANNEL.h @@ -0,0 +1,20 @@ +/* + * RC_BYCHANNEL.h - rc 서비스 RC_BYCHANNEL 카피북 (copybook / record header). + * 채널별 대사 세분 집계. + */ +#ifndef RC_BYCHANNEL_H +#define RC_BYCHANNEL_H + +#include "rc_svc.h" + +typedef struct { + char channel[16]; + long matched_cnt; + long gross_amount; + long fee_amount; + char biz_date[16]; +} rc_rc_bychannel_rec_t; + +void RC_BYCHANNEL(TPSVCINFO *p); + +#endif /* RC_BYCHANNEL_H */ diff --git a/app/src/rc/svc/RC_BYCHANNEL.pgc b/app/src/rc/svc/RC_BYCHANNEL.pgc new file mode 100644 index 0000000..9f7a739 --- /dev/null +++ b/app/src/rc/svc/RC_BYCHANNEL.pgc @@ -0,0 +1,37 @@ +/* + * RC_BYCHANNEL.pgc - rc 모듈 서비스 RC_BYCHANNEL (one service = one file). + * 채널별 대사 세분 집계: 요청 채널(POS/EDC/EDI/FOREIGN)의 당일 MATCHED 대사 + * 건수/총액/수수료를 매입 조인으로 집계하여 반환한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_BYCHANNEL.h" + +/* 34. RC_BYCHANNEL - 채널별 대사 집계. */ +void RC_BYCHANNEL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], channel[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_amt, h_fee; + char h_bizdate[16], h_chan[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + gets_(b, T_CHANNEL, channel, sizeof(channel)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(h_chan, channel, sizeof(h_chan)-1); h_chan[sizeof(h_chan)-1] = 0; + + EXEC SQL SELECT count(*), coalesce(sum(p.amount),0), coalesce(sum(p.fee),0) + INTO :h_cnt, :h_amt, :h_fee + FROM purchase p, rc_match_log m + WHERE m.purchase_id = p.purchase_id + AND m.biz_date = :h_bizdate AND m.status = 'MATCHED' + AND p.channel = :h_chan; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_amt); + setl(b, T_FEE, h_fee); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_BYMGROUP.h b/app/src/rc/svc/RC_BYMGROUP.h new file mode 100644 index 0000000..43168c4 --- /dev/null +++ b/app/src/rc/svc/RC_BYMGROUP.h @@ -0,0 +1,19 @@ +/* + * RC_BYMGROUP.h - rc 서비스 RC_BYMGROUP 카피북 (copybook / record header). + * 가맹점그룹(ID prefix)별 대사 집계. + */ +#ifndef RC_BYMGROUP_H +#define RC_BYMGROUP_H + +#include "rc_svc.h" + +typedef struct { + char group_key[32]; + long matched_cnt; + long gross_amount; + char biz_date[16]; +} rc_rc_bymgroup_rec_t; + +void RC_BYMGROUP(TPSVCINFO *p); + +#endif /* RC_BYMGROUP_H */ diff --git a/app/src/rc/svc/RC_BYMGROUP.pgc b/app/src/rc/svc/RC_BYMGROUP.pgc new file mode 100644 index 0000000..1ac9d53 --- /dev/null +++ b/app/src/rc/svc/RC_BYMGROUP.pgc @@ -0,0 +1,35 @@ +/* + * RC_BYMGROUP.pgc - rc 모듈 서비스 RC_BYMGROUP (one service = one file). + * 가맹점그룹별 대사 세분: 가맹점 ID prefix (그룹 키) 로 묶어 당일 MATCHED + * 대사 건수와 금액 합계를 IN-서브쿼리로 집계한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_BYMGROUP.h" + +/* 36. RC_BYMGROUP - 가맹점그룹(prefix)별 대사 집계. */ +void RC_BYMGROUP(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], grp[32]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_sum; + char h_bizdate[16], h_like[40]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + gets_(b, T_KEY1, grp, sizeof(grp)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + snprintf(h_like, sizeof(h_like), "%s%%", grp); + + EXEC SQL SELECT count(*), coalesce(sum(m.amount),0) INTO :h_cnt, :h_sum + FROM rc_match_log m + WHERE m.biz_date = :h_bizdate AND m.status = 'MATCHED' + AND m.purchase_id IN + (SELECT purchase_id FROM purchase WHERE merchant_id LIKE :h_like); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_CANCELREC.h b/app/src/rc/svc/RC_CANCELREC.h new file mode 100644 index 0000000..b3777d6 --- /dev/null +++ b/app/src/rc/svc/RC_CANCELREC.h @@ -0,0 +1,20 @@ +/* + * RC_CANCELREC.h - rc 서비스 RC_CANCELREC 카피북 (copybook / record header). + * 취소분 대사: 매입 금액 대 취소 누적 금액. + */ +#ifndef RC_CANCELREC_H +#define RC_CANCELREC_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + long purchase_amt; + long cancel_sum; + long cancel_cnt; + char biz_date[16]; +} rc_rc_cancelrec_rec_t; + +void RC_CANCELREC(TPSVCINFO *p); + +#endif /* RC_CANCELREC_H */ diff --git a/app/src/rc/svc/RC_CANCELREC.pgc b/app/src/rc/svc/RC_CANCELREC.pgc new file mode 100644 index 0000000..ee5f37a --- /dev/null +++ b/app/src/rc/svc/RC_CANCELREC.pgc @@ -0,0 +1,38 @@ +/* + * RC_CANCELREC.pgc - rc 모듈 서비스 RC_CANCELREC (one service = one file). + * 취소분 대사: 매입 금액과 취소(ac_cancel) 누적 금액을 비교하여 전액 취소면 + * MATCHED, 부분 취소 잔량이 있으면 HOLD 로 대사 로그를 남긴다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_CANCELREC.h" + +/* 51. RC_CANCELREC - 취소분 대사 (전액취소=MATCHED / 잔량=HOLD). */ +void RC_CANCELREC(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long mid; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt, h_csum, h_ccnt; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + + EXEC SQL SELECT amount INTO :h_amt FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode != 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(amount),0), count(*) INTO :h_csum, :h_ccnt + FROM ac_cancel WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_ccnt == 0) FAIL(b); + + mid = rcdb_insert_match(h_pid, "CANCEL", h_csum, + (h_csum >= h_amt) ? "MATCHED" : "HOLD", bizdate); + if (mid < 0) FAIL(b); + setl(b, T_AMT1, h_amt); + setl(b, T_AMT2, h_csum); + setl(b, T_ID1, mid); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_CARRYOVER.h b/app/src/rc/svc/RC_CARRYOVER.h new file mode 100644 index 0000000..73ed6dd --- /dev/null +++ b/app/src/rc/svc/RC_CARRYOVER.h @@ -0,0 +1,18 @@ +/* + * RC_CARRYOVER.h - rc 서비스 RC_CARRYOVER 카피북 (copybook / record header). + * 이월 대사: 전일 EXCEPTION -> 익일 HOLD 일괄 이월. + */ +#ifndef RC_CARRYOVER_H +#define RC_CARRYOVER_H + +#include "rc_svc.h" + +typedef struct { + char from_date[16]; + char to_date[16]; + long carried_cnt; +} rc_rc_carryover_rec_t; + +void RC_CARRYOVER(TPSVCINFO *p); + +#endif /* RC_CARRYOVER_H */ diff --git a/app/src/rc/svc/RC_CARRYOVER.pgc b/app/src/rc/svc/RC_CARRYOVER.pgc new file mode 100644 index 0000000..772433f --- /dev/null +++ b/app/src/rc/svc/RC_CARRYOVER.pgc @@ -0,0 +1,36 @@ +/* + * RC_CARRYOVER.pgc - rc 모듈 서비스 RC_CARRYOVER (one service = one file). + * 이월 대사: 이전 영업일의 EXCEPTION 대사 건들을 다음 영업일 HOLD 건으로 + * 일괄 복제(INSERT..SELECT)하여 익일 대사 큐로 이월시킨다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_CARRYOVER.h" + +/* 44. RC_CARRYOVER - EXCEPTION 건 익일 이월 (INSERT..SELECT). */ +void RC_CARRYOVER(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char fromdate[16], todate[16]; + long rows; + EXEC SQL BEGIN DECLARE SECTION; + char h_from[16], h_to[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_STR1, fromdate, sizeof(fromdate)); + gets_(b, T_BIZDATE, todate, sizeof(todate)); + strncpy(h_from, fromdate, sizeof(h_from)-1); h_from[sizeof(h_from)-1] = 0; + strncpy(h_to, todate, sizeof(h_to)-1); h_to[sizeof(h_to)-1] = 0; + + EXEC SQL INSERT INTO rc_match_log + (match_id, purchase_id, match_type, amount, status, biz_date) + SELECT nextval('rc_match_seq'), purchase_id, 'CARRY', amount, 'HOLD', + CAST(:h_to AS date) + FROM rc_match_log + WHERE biz_date = :h_from AND status = 'EXCEPTION'; + if (sqlca.sqlcode < 0) FAIL(b); + rows = sqlca.sqlerrd[2]; + setl(b, T_COUNT, rows); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_CLOSEDAY.h b/app/src/rc/svc/RC_CLOSEDAY.h new file mode 100644 index 0000000..54bf272 --- /dev/null +++ b/app/src/rc/svc/RC_CLOSEDAY.h @@ -0,0 +1,20 @@ +/* + * RC_CLOSEDAY.h - rc 서비스 RC_CLOSEDAY 카피북 (copybook / record header). + * 일마감 대사 확정 (요약 upsert + MATCHED -> CONFIRMED). + */ +#ifndef RC_CLOSEDAY_H +#define RC_CLOSEDAY_H + +#include "rc_svc.h" + +typedef struct { + long open_exceptions; + long hold_cnt; + long confirmed_cnt; + long gross_amount; + char biz_date[16]; +} rc_rc_closeday_rec_t; + +void RC_CLOSEDAY(TPSVCINFO *p); + +#endif /* RC_CLOSEDAY_H */ diff --git a/app/src/rc/svc/RC_CLOSEDAY.pgc b/app/src/rc/svc/RC_CLOSEDAY.pgc new file mode 100644 index 0000000..170fedf --- /dev/null +++ b/app/src/rc/svc/RC_CLOSEDAY.pgc @@ -0,0 +1,54 @@ +/* + * RC_CLOSEDAY.pgc - rc 모듈 서비스 RC_CLOSEDAY (one service = one file). + * 일마감 대사 확정: 미해결 예외나 보류가 남아 있으면 마감을 거부하고, 모두 + * 정리된 경우 요약을 확정 upsert 한 뒤 당일 MATCHED 건을 CONFIRMED 로 전환. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_CLOSEDAY.h" + +/* 73. RC_CLOSEDAY - 일마감 확정 (예외/보류 잔량 시 거부). */ +void RC_CLOSEDAY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long matched, unmatched; + EXEC SQL BEGIN DECLARE SECTION; + long h_open, h_hold, h_gross; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*) INTO :h_open FROM rc_exception + WHERE biz_date = :h_bizdate AND resolved = false; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_hold FROM rc_match_log + WHERE biz_date = :h_bizdate AND status = 'HOLD'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_open > 0 || h_hold > 0) { + setl(b, T_ARG1, h_open); + setl(b, T_ARG2, h_hold); + setl(b, T_RC, 1); + FAIL(b); + } + + matched = rcdb_count_match(bizdate, "MATCHED"); + if (matched < 0) FAIL(b); + unmatched = rcdb_count_unmatched(bizdate); + if (unmatched < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_gross FROM rc_match_log + WHERE biz_date = :h_bizdate AND status = 'MATCHED'; + if (sqlca.sqlcode < 0) FAIL(b); + if (rcdb_upsert_summary(bizdate, matched, unmatched, h_gross) < 0) FAIL(b); + + EXEC SQL UPDATE rc_match_log SET status = 'CONFIRMED', updated_at = now() + WHERE biz_date = :h_bizdate AND status = 'MATCHED'; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, sqlca.sqlerrd[2]); + setl(b, T_GROSS, h_gross); + setl(b, T_RC, 0); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_DEPMATCH.h b/app/src/rc/svc/RC_DEPMATCH.h new file mode 100644 index 0000000..d92df3c --- /dev/null +++ b/app/src/rc/svc/RC_DEPMATCH.h @@ -0,0 +1,19 @@ +/* + * RC_DEPMATCH.h - rc 서비스 RC_DEPMATCH 카피북 (copybook / record header). + * 입금(정산) 대사: 매입 net 대 입금 합계 비교. + */ +#ifndef RC_DEPMATCH_H +#define RC_DEPMATCH_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + long purchase_net; + long deposit_sum; + char biz_date[16]; +} rc_rc_depmatch_rec_t; + +void RC_DEPMATCH(TPSVCINFO *p); + +#endif /* RC_DEPMATCH_H */ diff --git a/app/src/rc/svc/RC_DEPMATCH.pgc b/app/src/rc/svc/RC_DEPMATCH.pgc new file mode 100644 index 0000000..f194721 --- /dev/null +++ b/app/src/rc/svc/RC_DEPMATCH.pgc @@ -0,0 +1,38 @@ +/* + * RC_DEPMATCH.pgc - rc 모듈 서비스 RC_DEPMATCH (one service = one file). + * 입금(정산) 대사: 매입 net 금액과 settlement 입금 합계를 비교하여 일치 시 + * MATCHED, 불일치/입금없음 시 EXCEPTION 대사 로그를 남긴다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_DEPMATCH.h" + +/* 32. RC_DEPMATCH - 입금(정산) 대사. */ +void RC_DEPMATCH(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long mid; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_pnet, h_scnt, h_snet; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + + EXEC SQL SELECT net INTO :h_pnet FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode != 0) FAIL(b); + EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_scnt, :h_snet + FROM settlement WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + + mid = rcdb_insert_match(h_pid, "DEP", h_snet, + (h_scnt > 0 && h_snet == h_pnet) ? "MATCHED" : "EXCEPTION", + bizdate); + if (mid < 0) FAIL(b); + setl(b, T_AMT1, h_pnet); + setl(b, T_AMT2, h_snet); + setl(b, T_ID1, mid); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_DIFFCAUSE.h b/app/src/rc/svc/RC_DIFFCAUSE.h new file mode 100644 index 0000000..8f87e4d --- /dev/null +++ b/app/src/rc/svc/RC_DIFFCAUSE.h @@ -0,0 +1,19 @@ +/* + * RC_DIFFCAUSE.h - rc 서비스 RC_DIFFCAUSE 카피북 (copybook / record header). + * 대사 차이 원인(수수료/취소/승인부재) 분해 분석. + */ +#ifndef RC_DIFFCAUSE_H +#define RC_DIFFCAUSE_H + +#include "rc_svc.h" + +typedef struct { + long fee_cause_cnt; + long cancel_cause_cnt; + long noappr_cause_cnt; + char biz_date[16]; +} rc_rc_diffcause_rec_t; + +void RC_DIFFCAUSE(TPSVCINFO *p); + +#endif /* RC_DIFFCAUSE_H */ diff --git a/app/src/rc/svc/RC_DIFFCAUSE.pgc b/app/src/rc/svc/RC_DIFFCAUSE.pgc new file mode 100644 index 0000000..e1e2b08 --- /dev/null +++ b/app/src/rc/svc/RC_DIFFCAUSE.pgc @@ -0,0 +1,45 @@ +/* + * RC_DIFFCAUSE.pgc - rc 모듈 서비스 RC_DIFFCAUSE (one service = one file). + * 대사 차이 원인분석: 당일 차이를 (1) 수수료 계산 불일치, (2) 취소 존재, + * (3) 승인 부재 세 가지 원인으로 분해하여 각각의 건수를 반환한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_DIFFCAUSE.h" + +/* 42. RC_DIFFCAUSE - 차이 원인(수수료/취소/승인부재)별 분해. */ +void RC_DIFFCAUSE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_fee, h_cxl, h_noap; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*) INTO :h_fee + FROM purchase p, merchant m + WHERE p.merchant_id = m.merchant_id AND p.biz_date = :h_bizdate + AND p.fee <> p.amount * m.mdr_bps / 10000; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_cxl + FROM rc_match_log r + WHERE r.biz_date = :h_bizdate AND r.status = 'EXCEPTION' + AND EXISTS (SELECT 1 FROM ac_cancel c WHERE c.purchase_id = r.purchase_id); + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_noap + FROM purchase p + WHERE p.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM approval a WHERE a.purchase_id = p.purchase_id); + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_ARG1, h_fee); + setl(b, T_ARG2, h_cxl); + setl(b, T_ARG3, h_noap); + setl(b, T_COUNT, h_fee + h_cxl + h_noap); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_DIFFTREND.h b/app/src/rc/svc/RC_DIFFTREND.h new file mode 100644 index 0000000..442ad4d --- /dev/null +++ b/app/src/rc/svc/RC_DIFFTREND.h @@ -0,0 +1,19 @@ +/* + * RC_DIFFTREND.h - rc 서비스 RC_DIFFTREND 카피북 (copybook / record header). + * 당일 vs 직전 7일 평균 차이금액 추이 판정. + */ +#ifndef RC_DIFFTREND_H +#define RC_DIFFTREND_H + +#include "rc_svc.h" + +typedef struct { + long today_avg; + long prior_avg; + long worsening; + char biz_date[16]; +} rc_rc_difftrend_rec_t; + +void RC_DIFFTREND(TPSVCINFO *p); + +#endif /* RC_DIFFTREND_H */ diff --git a/app/src/rc/svc/RC_DIFFTREND.pgc b/app/src/rc/svc/RC_DIFFTREND.pgc new file mode 100644 index 0000000..f5488d0 --- /dev/null +++ b/app/src/rc/svc/RC_DIFFTREND.pgc @@ -0,0 +1,37 @@ +/* + * RC_DIFFTREND.pgc - rc 모듈 서비스 RC_DIFFTREND (one service = one file). + * 대사 차이 추이 분석: 당일 평균 |차이금액| 과 직전 7영업일 평균을 비교하여 + * 차이 규모가 커지는 추세인지(악화 여부) 판정한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_DIFFTREND.h" + +/* 43. RC_DIFFTREND - 당일 vs 직전 7일 평균 차이 추이. */ +void RC_DIFFTREND(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_today, h_prior; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT coalesce(round(avg(abs(diff_amt))),0) INTO :h_today + FROM rc_diff WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(round(avg(abs(diff_amt))),0) INTO :h_prior + FROM rc_diff + WHERE biz_date < CAST(:h_bizdate AS date) + AND biz_date >= CAST(:h_bizdate AS date) - 7; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_AMT1, h_today); + setl(b, T_AMT2, h_prior); + setl(b, T_RC, (h_today > h_prior) ? 1 : 0); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_DUPCHK.h b/app/src/rc/svc/RC_DUPCHK.h new file mode 100644 index 0000000..19cc509 --- /dev/null +++ b/app/src/rc/svc/RC_DUPCHK.h @@ -0,0 +1,17 @@ +/* + * RC_DUPCHK.h - rc 서비스 RC_DUPCHK 카피북 (copybook / record header). + * 중복 대사 매입 검출 (GROUP BY/HAVING) + WARN 경보. + */ +#ifndef RC_DUPCHK_H +#define RC_DUPCHK_H + +#include "rc_svc.h" + +typedef struct { + long dup_cnt; + char biz_date[16]; +} rc_rc_dupchk_rec_t; + +void RC_DUPCHK(TPSVCINFO *p); + +#endif /* RC_DUPCHK_H */ diff --git a/app/src/rc/svc/RC_DUPCHK.pgc b/app/src/rc/svc/RC_DUPCHK.pgc new file mode 100644 index 0000000..fb027b6 --- /dev/null +++ b/app/src/rc/svc/RC_DUPCHK.pgc @@ -0,0 +1,38 @@ +/* + * RC_DUPCHK.pgc - rc 모듈 서비스 RC_DUPCHK (one service = one file). + * 중복 대사 검출: 당일 대사 로그에서 같은 매입이 두 번 이상 대사된 매입 + * 수를 GROUP BY/HAVING 서브쿼리로 세고, 발견 시 WARN 경보를 남긴다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_DUPCHK.h" + +/* 67. RC_DUPCHK - 중복 대사 매입 검출 + WARN 경보. */ +void RC_DUPCHK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_dup; + char h_bizdate[16], h_msg[80]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*) INTO :h_dup FROM + (SELECT purchase_id FROM rc_match_log + WHERE biz_date = :h_bizdate + GROUP BY purchase_id HAVING count(*) > 1) dup; + if (sqlca.sqlcode < 0) FAIL(b); + + if (h_dup > 0) { + snprintf(h_msg, sizeof(h_msg), "duplicate recon purchases: %ld", h_dup); + EXEC SQL INSERT INTO rc_alert (alert_id, purchase_id, lvl, message, biz_date) + VALUES (nextval('rc_alert_seq'), 0, 'WARN', :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + } + setl(b, T_COUNT, h_dup); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_EDIRECON.h b/app/src/rc/svc/RC_EDIRECON.h new file mode 100644 index 0000000..0c761fb --- /dev/null +++ b/app/src/rc/svc/RC_EDIRECON.h @@ -0,0 +1,20 @@ +/* + * RC_EDIRECON.h - rc 서비스 RC_EDIRECON 카피북 (copybook / record header). + * EDI 대사: 가맹점 EDI 전문 합계 대 매입 합계. + */ +#ifndef RC_EDIRECON_H +#define RC_EDIRECON_H + +#include "rc_svc.h" + +typedef struct { + char merchant_id[64]; + long edi_sum; + long edi_cnt; + long purchase_sum; + char biz_date[16]; +} rc_rc_edirecon_rec_t; + +void RC_EDIRECON(TPSVCINFO *p); + +#endif /* RC_EDIRECON_H */ diff --git a/app/src/rc/svc/RC_EDIRECON.pgc b/app/src/rc/svc/RC_EDIRECON.pgc new file mode 100644 index 0000000..0f18f6b --- /dev/null +++ b/app/src/rc/svc/RC_EDIRECON.pgc @@ -0,0 +1,46 @@ +/* + * RC_EDIRECON.pgc - rc 모듈 서비스 RC_EDIRECON (one service = one file). + * EDI 대사: 가맹점의 당일 EDI 전문 금액 합계와 매입(취소 제외) 금액 합계를 + * 비교하고, 차이가 있으면 가맹점 단위 rc_diff 라인을 적재한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_EDIRECON.h" + +/* 61. RC_EDIRECON - 가맹점 EDI 전문 대 매입 합계 대사. */ +void RC_EDIRECON(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], merchant[64]; + EXEC SQL BEGIN DECLARE SECTION; + long h_edi, h_en, h_pur; + char h_bizdate[16], h_mid[64]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + gets_(b, T_MERCHANT, merchant, sizeof(merchant)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(h_mid, merchant, sizeof(h_mid)-1); h_mid[sizeof(h_mid)-1] = 0; + + EXEC SQL SELECT coalesce(sum(amount),0), count(*) INTO :h_edi, :h_en + FROM ac_edi WHERE merchant_id = :h_mid AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_pur + FROM purchase + WHERE merchant_id = :h_mid AND biz_date = :h_bizdate AND status <> 'C'; + if (sqlca.sqlcode < 0) FAIL(b); + + if (h_edi != h_pur) { + EXEC SQL INSERT INTO rc_diff + (diff_id, purchase_id, expected_amt, actual_amt, diff_amt, diff_kind, biz_date) + VALUES (nextval('rc_diff_seq'), 0, :h_pur, :h_edi, :h_pur - :h_edi, + 'AMT', :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + } + setl(b, T_AMT1, h_edi); + setl(b, T_AMT2, h_pur); + setl(b, T_DELTA, h_pur - h_edi); + setl(b, T_COUNT, h_en); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_ESCALATE.h b/app/src/rc/svc/RC_ESCALATE.h new file mode 100644 index 0000000..8961d8c --- /dev/null +++ b/app/src/rc/svc/RC_ESCALATE.h @@ -0,0 +1,17 @@ +/* + * RC_ESCALATE.h - rc 서비스 RC_ESCALATE 카피북 (copybook / record header). + * 미해결 예외 경보 WARN -> CRIT 에스컬레이션. + */ +#ifndef RC_ESCALATE_H +#define RC_ESCALATE_H + +#include "rc_svc.h" + +typedef struct { + long escalated_cnt; + char biz_date[16]; +} rc_rc_escalate_rec_t; + +void RC_ESCALATE(TPSVCINFO *p); + +#endif /* RC_ESCALATE_H */ diff --git a/app/src/rc/svc/RC_ESCALATE.pgc b/app/src/rc/svc/RC_ESCALATE.pgc new file mode 100644 index 0000000..c90caed --- /dev/null +++ b/app/src/rc/svc/RC_ESCALATE.pgc @@ -0,0 +1,30 @@ +/* + * RC_ESCALATE.pgc - rc 모듈 서비스 RC_ESCALATE (one service = one file). + * 예외 에스컬레이션: 미해결 예외가 걸린 매입의 당일 WARN 경보들을 IN-서브 + * 쿼리 UPDATE 로 일괄 CRIT 승격시키고 승격 건수를 반환한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_ESCALATE.h" + +/* 72. RC_ESCALATE - 미해결 예외 경보 WARN -> CRIT 일괄 승격. */ +void RC_ESCALATE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL UPDATE rc_alert SET lvl = 'CRIT' + WHERE biz_date = :h_bizdate AND lvl = 'WARN' + AND purchase_id IN (SELECT purchase_id FROM rc_exception + WHERE biz_date = :h_bizdate AND resolved = false); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, sqlca.sqlerrd[2]); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_FEEDIFF.h b/app/src/rc/svc/RC_FEEDIFF.h new file mode 100644 index 0000000..ed4070c --- /dev/null +++ b/app/src/rc/svc/RC_FEEDIFF.h @@ -0,0 +1,18 @@ +/* + * RC_FEEDIFF.h - rc 서비스 RC_FEEDIFF 카피북 (copybook / record header). + * 당일 수수료 불일치 건수/차액 합계 집계. + */ +#ifndef RC_FEEDIFF_H +#define RC_FEEDIFF_H + +#include "rc_svc.h" + +typedef struct { + long mismatch_cnt; + long delta_sum; + char biz_date[16]; +} rc_rc_feediff_rec_t; + +void RC_FEEDIFF(TPSVCINFO *p); + +#endif /* RC_FEEDIFF_H */ diff --git a/app/src/rc/svc/RC_FEEDIFF.pgc b/app/src/rc/svc/RC_FEEDIFF.pgc new file mode 100644 index 0000000..37f221e --- /dev/null +++ b/app/src/rc/svc/RC_FEEDIFF.pgc @@ -0,0 +1,34 @@ +/* + * RC_FEEDIFF.pgc - rc 모듈 서비스 RC_FEEDIFF (one service = one file). + * 수수료 차이 일괄 집계: 당일 매입 전체에서 MDR 기대치와 다른 수수료 건수와 + * 차이 합계를 한 번의 조인 집계로 산출한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_FEEDIFF.h" + +/* 56. RC_FEEDIFF - 당일 수수료 불일치 건수/차액 합계. */ +void RC_FEEDIFF(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_dsum; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*), + coalesce(sum(p.fee - p.amount * m.mdr_bps / 10000),0) + INTO :h_cnt, :h_dsum + FROM purchase p, merchant m + WHERE p.merchant_id = m.merchant_id AND p.biz_date = :h_bizdate + AND p.fee <> p.amount * m.mdr_bps / 10000; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, h_cnt); + setl(b, T_DELTA, h_dsum); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_FEERECON.h b/app/src/rc/svc/RC_FEERECON.h new file mode 100644 index 0000000..2f06caa --- /dev/null +++ b/app/src/rc/svc/RC_FEERECON.h @@ -0,0 +1,20 @@ +/* + * RC_FEERECON.h - rc 서비스 RC_FEERECON 카피북 (copybook / record header). + * 수수료 대사: MDR 기대 수수료 대 실수수료. + */ +#ifndef RC_FEERECON_H +#define RC_FEERECON_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + long fee; + long expected_fee; + long delta; + char biz_date[16]; +} rc_rc_feerecon_rec_t; + +void RC_FEERECON(TPSVCINFO *p); + +#endif /* RC_FEERECON_H */ diff --git a/app/src/rc/svc/RC_FEERECON.pgc b/app/src/rc/svc/RC_FEERECON.pgc new file mode 100644 index 0000000..022cfd7 --- /dev/null +++ b/app/src/rc/svc/RC_FEERECON.pgc @@ -0,0 +1,46 @@ +/* + * RC_FEERECON.pgc - rc 모듈 서비스 RC_FEERECON (one service = one file). + * 수수료 대사: 매입 수수료를 가맹점 MDR(bp) 기준 기대 수수료와 비교한다. + * 오차 1원 이내면 MATCHED, 초과하면 rc_diff 적재 후 EXCEPTION 처리. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_FEERECON.h" + +/* 55. RC_FEERECON - MDR 기준 수수료 대사. */ +void RC_FEERECON(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long mid, diff, ok; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_fee, h_exp; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT p.fee, p.amount * m.mdr_bps / 10000 INTO :h_fee, :h_exp + FROM purchase p, merchant m + WHERE p.merchant_id = m.merchant_id AND p.purchase_id = :h_pid; + if (sqlca.sqlcode != 0) FAIL(b); + + diff = h_fee - h_exp; + ok = (diff >= -1 && diff <= 1); + if (!ok) { + EXEC SQL INSERT INTO rc_diff + (diff_id, purchase_id, expected_amt, actual_amt, diff_amt, diff_kind, biz_date) + VALUES (nextval('rc_diff_seq'), :h_pid, :h_exp, :h_fee, + :h_fee - :h_exp, 'AMT', :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + } + mid = rcdb_insert_match(h_pid, "FEE", h_fee, ok ? "MATCHED" : "EXCEPTION", bizdate); + if (mid < 0) FAIL(b); + setl(b, T_FEE, h_fee); + setl(b, T_DELTA, diff); + setl(b, T_ID1, mid); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_FXRATECHK.h b/app/src/rc/svc/RC_FXRATECHK.h new file mode 100644 index 0000000..9b4b867 --- /dev/null +++ b/app/src/rc/svc/RC_FXRATECHK.h @@ -0,0 +1,20 @@ +/* + * RC_FXRATECHK.h - rc 서비스 RC_FXRATECHK 카피북 (copybook / record header). + * 외화 환율(bp) 재계산 검증. + */ +#ifndef RC_FXRATECHK_H +#define RC_FXRATECHK_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + char ccy[8]; + long fx_amount; + long krw_amount; + long rate_bps; +} rc_rc_fxratechk_rec_t; + +void RC_FXRATECHK(TPSVCINFO *p); + +#endif /* RC_FXRATECHK_H */ diff --git a/app/src/rc/svc/RC_FXRATECHK.pgc b/app/src/rc/svc/RC_FXRATECHK.pgc new file mode 100644 index 0000000..27f6a84 --- /dev/null +++ b/app/src/rc/svc/RC_FXRATECHK.pgc @@ -0,0 +1,36 @@ +/* + * RC_FXRATECHK.pgc - rc 모듈 서비스 RC_FXRATECHK (one service = one file). + * 외화 환율 검증: 최신 외화 거래의 원통화 금액 x 환율(bp) 재계산 값이 저장된 + * 원화 금액과 일치하는지 검증하고 통화코드와 재계산 값을 돌려준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_FXRATECHK.h" + +/* 58. RC_FXRATECHK - 환율 재계산 검증. */ +void RC_FXRATECHK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long calc; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_fx, h_krw, h_rate; + char h_ccy[8]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + + EXEC SQL SELECT ccy, fx_amount, krw_amount, rate_bps + INTO :h_ccy, :h_fx, :h_krw, :h_rate + FROM ac_fx WHERE purchase_id = :h_pid + ORDER BY fx_id DESC LIMIT 1; + if (sqlca.sqlcode != 0) FAIL(b); + + calc = h_fx * h_rate / 10000; + setl(b, T_FX_AMT, h_fx); + setl(b, T_AMT1, h_krw); + setl(b, T_AMT2, calc); + setl(b, T_RC, (calc == h_krw) ? 0 : 1); + Bchg(b, T_CCY, 0, h_ccy, 0L); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_FXRECON.h b/app/src/rc/svc/RC_FXRECON.h new file mode 100644 index 0000000..bee66ec --- /dev/null +++ b/app/src/rc/svc/RC_FXRECON.h @@ -0,0 +1,20 @@ +/* + * RC_FXRECON.h - rc 서비스 RC_FXRECON 카피북 (copybook / record header). + * 외화 대사: 원화환산 합계 대 매입 금액. + */ +#ifndef RC_FXRECON_H +#define RC_FXRECON_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + long fx_cnt; + long krw_sum; + long purchase_amt; + char biz_date[16]; +} rc_rc_fxrecon_rec_t; + +void RC_FXRECON(TPSVCINFO *p); + +#endif /* RC_FXRECON_H */ diff --git a/app/src/rc/svc/RC_FXRECON.pgc b/app/src/rc/svc/RC_FXRECON.pgc new file mode 100644 index 0000000..fc273ee --- /dev/null +++ b/app/src/rc/svc/RC_FXRECON.pgc @@ -0,0 +1,47 @@ +/* + * RC_FXRECON.pgc - rc 모듈 서비스 RC_FXRECON (one service = one file). + * 외화 대사: 외화 거래(ac_fx)의 원화 환산 금액 합계와 매입 금액을 비교하여 + * 일치 시 MATCHED, 불일치 시 rc_diff 적재 후 EXCEPTION 처리한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_FXRECON.h" + +/* 57. RC_FXRECON - 외화 원화환산 대사. */ +void RC_FXRECON(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long mid, ok; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_n, h_krw, h_amt; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*), coalesce(sum(krw_amount),0) INTO :h_n, :h_krw + FROM ac_fx WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_n == 0) FAIL(b); + EXEC SQL SELECT amount INTO :h_amt FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode != 0) FAIL(b); + + ok = (h_krw == h_amt); + if (!ok) { + EXEC SQL INSERT INTO rc_diff + (diff_id, purchase_id, expected_amt, actual_amt, diff_amt, diff_kind, biz_date) + VALUES (nextval('rc_diff_seq'), :h_pid, :h_amt, :h_krw, + :h_amt - :h_krw, 'AMT', :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + } + mid = rcdb_insert_match(h_pid, "FX", h_krw, ok ? "MATCHED" : "EXCEPTION", bizdate); + if (mid < 0) FAIL(b); + setl(b, T_AMT1, h_amt); + setl(b, T_AMT2, h_krw); + setl(b, T_ID1, mid); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_GAPSCAN.h b/app/src/rc/svc/RC_GAPSCAN.h new file mode 100644 index 0000000..8f1c5bf --- /dev/null +++ b/app/src/rc/svc/RC_GAPSCAN.h @@ -0,0 +1,18 @@ +/* + * RC_GAPSCAN.h - rc 서비스 RC_GAPSCAN 카피북 (copybook / record header). + * 대사 누락 스캔 (상태 M / 대사 로그 부재). + */ +#ifndef RC_GAPSCAN_H +#define RC_GAPSCAN_H + +#include "rc_svc.h" + +typedef struct { + long gap_cnt; + long first_pid; + char biz_date[16]; +} rc_rc_gapscan_rec_t; + +void RC_GAPSCAN(TPSVCINFO *p); + +#endif /* RC_GAPSCAN_H */ diff --git a/app/src/rc/svc/RC_GAPSCAN.pgc b/app/src/rc/svc/RC_GAPSCAN.pgc new file mode 100644 index 0000000..11ce10f --- /dev/null +++ b/app/src/rc/svc/RC_GAPSCAN.pgc @@ -0,0 +1,33 @@ +/* + * RC_GAPSCAN.pgc - rc 모듈 서비스 RC_GAPSCAN (one service = one file). + * 대사 누락 스캔: 대사완료(M) 상태인데 대사 로그가 하나도 없는 당일 매입 + * 건수와 최초 매입 ID 를 찾아 대사 파이프라인의 누락 구간을 드러낸다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_GAPSCAN.h" + +/* 69. RC_GAPSCAN - 상태 M 인데 대사 로그 없는 누락 스캔. */ +void RC_GAPSCAN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_first; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*), coalesce(min(p.purchase_id),0) INTO :h_cnt, :h_first + FROM purchase p + WHERE p.biz_date = :h_bizdate AND p.status = 'M' + AND NOT EXISTS (SELECT 1 FROM rc_match_log r + WHERE r.purchase_id = p.purchase_id); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, h_cnt); + setl(b, T_ID1, h_first); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_HOLDAGE.h b/app/src/rc/svc/RC_HOLDAGE.h new file mode 100644 index 0000000..e3a9bd1 --- /dev/null +++ b/app/src/rc/svc/RC_HOLDAGE.h @@ -0,0 +1,18 @@ +/* + * RC_HOLDAGE.h - rc 서비스 RC_HOLDAGE 카피북 (copybook / record header). + * 장기 보류 건수 + 최고령 보류 영업일. + */ +#ifndef RC_HOLDAGE_H +#define RC_HOLDAGE_H + +#include "rc_svc.h" + +typedef struct { + long aged_cnt; + char oldest_date[16]; + char biz_date[16]; +} rc_rc_holdage_rec_t; + +void RC_HOLDAGE(TPSVCINFO *p); + +#endif /* RC_HOLDAGE_H */ diff --git a/app/src/rc/svc/RC_HOLDAGE.pgc b/app/src/rc/svc/RC_HOLDAGE.pgc new file mode 100644 index 0000000..f985820 --- /dev/null +++ b/app/src/rc/svc/RC_HOLDAGE.pgc @@ -0,0 +1,33 @@ +/* + * RC_HOLDAGE.pgc - rc 모듈 서비스 RC_HOLDAGE (one service = one file). + * 보류 장기화 조회: 기준일 이전부터 남아 있는 HOLD 대사 건수와 가장 오래된 + * 보류 영업일을 조회하여 장기 미처리 보류를 드러낸다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_HOLDAGE.h" + +/* 41. RC_HOLDAGE - 장기 보류(HOLD) 건수 + 최고령 보류일. */ +void RC_HOLDAGE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt; + char h_bizdate[16], h_oldest[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*) INTO :h_cnt FROM rc_match_log + WHERE status = 'HOLD' AND biz_date < CAST(:h_bizdate AS date); + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(to_char(min(biz_date),'YYYY-MM-DD'),'') INTO :h_oldest + FROM rc_match_log WHERE status = 'HOLD'; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, h_cnt); + Bchg(b, T_STR1, 0, h_oldest, 0L); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_HOLDRSN.h b/app/src/rc/svc/RC_HOLDRSN.h new file mode 100644 index 0000000..923f9ae --- /dev/null +++ b/app/src/rc/svc/RC_HOLDRSN.h @@ -0,0 +1,19 @@ +/* + * RC_HOLDRSN.h - rc 서비스 RC_HOLDRSN 카피북 (copybook / record header). + * 대사 보류 사유별 미해결/해결 현황. + */ +#ifndef RC_HOLDRSN_H +#define RC_HOLDRSN_H + +#include "rc_svc.h" + +typedef struct { + char reason[64]; + long open_cnt; + long done_cnt; + char biz_date[16]; +} rc_rc_holdrsn_rec_t; + +void RC_HOLDRSN(TPSVCINFO *p); + +#endif /* RC_HOLDRSN_H */ diff --git a/app/src/rc/svc/RC_HOLDRSN.pgc b/app/src/rc/svc/RC_HOLDRSN.pgc new file mode 100644 index 0000000..2a6d9f1 --- /dev/null +++ b/app/src/rc/svc/RC_HOLDRSN.pgc @@ -0,0 +1,36 @@ +/* + * RC_HOLDRSN.pgc - rc 모듈 서비스 RC_HOLDRSN (one service = one file). + * 대사 보류 사유별 현황: 특정 사유의 당일 예외 건수를 미해결/해결로 나누어 + * 집계하여 사유 단위의 보류 잔량을 보여준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_HOLDRSN.h" + +/* 40. RC_HOLDRSN - 보류 사유별 미해결/해결 건수. */ +void RC_HOLDRSN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], reason[64]; + EXEC SQL BEGIN DECLARE SECTION; + long h_open, h_done; + char h_bizdate[16], h_reason[64]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + gets_(b, T_REASON, reason, sizeof(reason)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(h_reason, reason, sizeof(h_reason)-1); h_reason[sizeof(h_reason)-1] = 0; + + EXEC SQL SELECT count(*) INTO :h_open FROM rc_exception + WHERE biz_date = :h_bizdate AND reason = :h_reason AND resolved = false; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_done FROM rc_exception + WHERE biz_date = :h_bizdate AND reason = :h_reason AND resolved = true; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_ARG1, h_open); + setl(b, T_ARG2, h_done); + setl(b, T_COUNT, h_open + h_done); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_INSTRECON.h b/app/src/rc/svc/RC_INSTRECON.h new file mode 100644 index 0000000..85c40a3 --- /dev/null +++ b/app/src/rc/svc/RC_INSTRECON.h @@ -0,0 +1,20 @@ +/* + * RC_INSTRECON.h - rc 서비스 RC_INSTRECON 카피북 (copybook / record header). + * 할부 대사: 개월수 대 등록 회차수 비교. + */ +#ifndef RC_INSTRECON_H +#define RC_INSTRECON_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + long months; + long seq_cnt; + long month_sum; + char biz_date[16]; +} rc_rc_instrecon_rec_t; + +void RC_INSTRECON(TPSVCINFO *p); + +#endif /* RC_INSTRECON_H */ diff --git a/app/src/rc/svc/RC_INSTRECON.pgc b/app/src/rc/svc/RC_INSTRECON.pgc new file mode 100644 index 0000000..5304d40 --- /dev/null +++ b/app/src/rc/svc/RC_INSTRECON.pgc @@ -0,0 +1,39 @@ +/* + * RC_INSTRECON.pgc - rc 모듈 서비스 RC_INSTRECON (one service = one file). + * 할부 대사: 할부 개월수(months) 대비 등록된 회차(seq_no) 수를 비교하여 + * 회차가 모두 채워졌으면 MATCHED, 미달이면 HOLD 대사 로그를 남긴다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_INSTRECON.h" + +/* 54. RC_INSTRECON - 할부 회차 완납 대사. */ +void RC_INSTRECON(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long mid; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_months, h_seqs, h_msum; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + + EXEC SQL SELECT coalesce(max(months),0), count(DISTINCT seq_no), + coalesce(sum(month_amount),0) + INTO :h_months, :h_seqs, :h_msum + FROM ac_install WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_months == 0) FAIL(b); + + mid = rcdb_insert_match(h_pid, "INSTALL", h_msum, + (h_seqs >= h_months) ? "MATCHED" : "HOLD", bizdate); + if (mid < 0) FAIL(b); + setl(b, T_INSTALL_N, h_months); + setl(b, T_COUNT, h_seqs); + setl(b, T_GROSS, h_msum); + setl(b, T_ID1, mid); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_LIMITCHK.h b/app/src/rc/svc/RC_LIMITCHK.h new file mode 100644 index 0000000..64d71bf --- /dev/null +++ b/app/src/rc/svc/RC_LIMITCHK.h @@ -0,0 +1,19 @@ +/* + * RC_LIMITCHK.h - rc 서비스 RC_LIMITCHK 카피북 (copybook / record header). + * 가맹점 일한도 대비 사용액 검증 + CRIT 경보. + */ +#ifndef RC_LIMITCHK_H +#define RC_LIMITCHK_H + +#include "rc_svc.h" + +typedef struct { + char merchant_id[64]; + long daily_limit; + long used_amount; + char biz_date[16]; +} rc_rc_limitchk_rec_t; + +void RC_LIMITCHK(TPSVCINFO *p); + +#endif /* RC_LIMITCHK_H */ diff --git a/app/src/rc/svc/RC_LIMITCHK.pgc b/app/src/rc/svc/RC_LIMITCHK.pgc new file mode 100644 index 0000000..b569559 --- /dev/null +++ b/app/src/rc/svc/RC_LIMITCHK.pgc @@ -0,0 +1,45 @@ +/* + * RC_LIMITCHK.pgc - rc 모듈 서비스 RC_LIMITCHK (one service = one file). + * 가맹점 한도 대비 대사: 가맹점 일한도(daily_limit)와 당일 매입(취소 제외) + * 사용액을 비교하여 초과 시 CRIT 경보를 발생시키고 초과 여부를 돌려준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_LIMITCHK.h" + +/* 71. RC_LIMITCHK - 가맹점 일한도 초과 검증. */ +void RC_LIMITCHK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], merchant[64]; + long over; + EXEC SQL BEGIN DECLARE SECTION; + long h_lim, h_use; + char h_bizdate[16], h_mid[64], h_msg[96]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + gets_(b, T_MERCHANT, merchant, sizeof(merchant)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(h_mid, merchant, sizeof(h_mid)-1); h_mid[sizeof(h_mid)-1] = 0; + + EXEC SQL SELECT daily_limit INTO :h_lim FROM merchant WHERE merchant_id = :h_mid; + if (sqlca.sqlcode != 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_use FROM purchase + WHERE merchant_id = :h_mid AND biz_date = :h_bizdate AND status <> 'C'; + if (sqlca.sqlcode < 0) FAIL(b); + + over = (h_use > h_lim); + if (over) { + snprintf(h_msg, sizeof(h_msg), "daily limit exceeded %.20s use=%ld lim=%ld", + merchant, h_use, h_lim); + EXEC SQL INSERT INTO rc_alert (alert_id, purchase_id, lvl, message, biz_date) + VALUES (nextval('rc_alert_seq'), 0, 'CRIT', :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + } + setl(b, T_AMT1, h_lim); + setl(b, T_AMT2, h_use); + setl(b, T_RC, over ? 1 : 0); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_MANAPPRV.h b/app/src/rc/svc/RC_MANAPPRV.h new file mode 100644 index 0000000..738d12d --- /dev/null +++ b/app/src/rc/svc/RC_MANAPPRV.h @@ -0,0 +1,19 @@ +/* + * RC_MANAPPRV.h - rc 서비스 RC_MANAPPRV 카피북 (copybook / record header). + * 수작업 대사 승인 (HOLD -> MATCHED + 원장 증적). + */ +#ifndef RC_MANAPPRV_H +#define RC_MANAPPRV_H + +#include "rc_svc.h" + +typedef struct { + long match_id; + long purchase_id; + long amount; + char biz_date[16]; +} rc_rc_manapprv_rec_t; + +void RC_MANAPPRV(TPSVCINFO *p); + +#endif /* RC_MANAPPRV_H */ diff --git a/app/src/rc/svc/RC_MANAPPRV.pgc b/app/src/rc/svc/RC_MANAPPRV.pgc new file mode 100644 index 0000000..2a7be90 --- /dev/null +++ b/app/src/rc/svc/RC_MANAPPRV.pgc @@ -0,0 +1,41 @@ +/* + * RC_MANAPPRV.pgc - rc 모듈 서비스 RC_MANAPPRV (one service = one file). + * 수작업 대사 승인: HOLD 상태의 MANREG 수작업 대사 1건을 MATCHED 로 확정하고 + * 승인 증적을 원장(ledger) 에 남긴다. 대상이 없으면 실패를 돌려준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_MANAPPRV.h" + +/* 38. RC_MANAPPRV - 수작업 대사 승인 (HOLD -> MATCHED). */ +void RC_MANAPPRV(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long rows; + EXEC SQL BEGIN DECLARE SECTION; + long h_mid, h_pid, h_amt; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_mid = getl(b, T_ID1); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL UPDATE rc_match_log SET status = 'MATCHED', updated_at = now() + WHERE match_id = :h_mid AND match_type = 'MANREG' AND status = 'HOLD'; + if (sqlca.sqlcode < 0) FAIL(b); + rows = sqlca.sqlerrd[2]; + if (rows != 1) FAIL(b); + + EXEC SQL SELECT purchase_id, amount INTO :h_pid, :h_amt + FROM rc_match_log WHERE match_id = :h_mid; + if (sqlca.sqlcode != 0) FAIL(b); + EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date) + VALUES (nextval('ledger_seq'), :h_pid, 'RC_MANOK', :h_amt, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, rows); + setl(b, T_PURCHASE_ID, h_pid); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_MANREG.h b/app/src/rc/svc/RC_MANREG.h new file mode 100644 index 0000000..fc35d38 --- /dev/null +++ b/app/src/rc/svc/RC_MANREG.h @@ -0,0 +1,19 @@ +/* + * RC_MANREG.h - rc 서비스 RC_MANREG 카피북 (copybook / record header). + * 수작업 대사 등록 (HOLD 상태 승인 대기). + */ +#ifndef RC_MANREG_H +#define RC_MANREG_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + long net; + char reason[64]; + char biz_date[16]; +} rc_rc_manreg_rec_t; + +void RC_MANREG(TPSVCINFO *p); + +#endif /* RC_MANREG_H */ diff --git a/app/src/rc/svc/RC_MANREG.pgc b/app/src/rc/svc/RC_MANREG.pgc new file mode 100644 index 0000000..a6a5986 --- /dev/null +++ b/app/src/rc/svc/RC_MANREG.pgc @@ -0,0 +1,36 @@ +/* + * RC_MANREG.pgc - rc 모듈 서비스 RC_MANREG (one service = one file). + * 수작업 대사 등록: 담당자가 입력한 수작업 대사를 HOLD 상태(승인 대기)로 + * 등록하고, 등록 사실을 INFO 경보로 남긴다. 승인 전까지 확정되지 않는다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_MANREG.h" + +/* 37. RC_MANREG - 수작업 대사 등록 (승인 대기 HOLD). */ +void RC_MANREG(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], reason[64]; + long mid; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid; + char h_bizdate[16], h_msg[96]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + gets_(b, T_REASON, reason, sizeof(reason)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + mid = rcdb_insert_match(h_pid, "MANREG", getl(b, T_NET), "HOLD", bizdate); + if (mid < 0) FAIL(b); + + snprintf(h_msg, sizeof(h_msg), "manual recon registered: %.60s", reason); + EXEC SQL INSERT INTO rc_alert (alert_id, purchase_id, lvl, message, biz_date) + VALUES (nextval('rc_alert_seq'), :h_pid, 'INFO', :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_ID1, mid); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_MANREJECT.h b/app/src/rc/svc/RC_MANREJECT.h new file mode 100644 index 0000000..af61cab --- /dev/null +++ b/app/src/rc/svc/RC_MANREJECT.h @@ -0,0 +1,19 @@ +/* + * RC_MANREJECT.h - rc 서비스 RC_MANREJECT 카피북 (copybook / record header). + * 수작업 대사 반려 (ROLLBACK + 예외 적재). + */ +#ifndef RC_MANREJECT_H +#define RC_MANREJECT_H + +#include "rc_svc.h" + +typedef struct { + long match_id; + long purchase_id; + char reason[64]; + char biz_date[16]; +} rc_rc_manreject_rec_t; + +void RC_MANREJECT(TPSVCINFO *p); + +#endif /* RC_MANREJECT_H */ diff --git a/app/src/rc/svc/RC_MANREJECT.pgc b/app/src/rc/svc/RC_MANREJECT.pgc new file mode 100644 index 0000000..12764d3 --- /dev/null +++ b/app/src/rc/svc/RC_MANREJECT.pgc @@ -0,0 +1,37 @@ +/* + * RC_MANREJECT.pgc - rc 모듈 서비스 RC_MANREJECT (one service = one file). + * 수작업 대사 반려: 승인 대기(HOLD) 중인 수작업 대사를 ROLLBACK 처리하고, + * 반려 사유를 대사 예외(rc_exception) 로 적재하여 재처리 대상으로 만든다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_MANREJECT.h" + +/* 39. RC_MANREJECT - 수작업 대사 반려 (HOLD -> ROLLBACK + 예외). */ +void RC_MANREJECT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], reason[64]; + long eid; + EXEC SQL BEGIN DECLARE SECTION; + long h_mid, h_pid; + EXEC SQL END DECLARE SECTION; + + h_mid = getl(b, T_ID1); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + gets_(b, T_REASON, reason, sizeof(reason)); + + EXEC SQL UPDATE rc_match_log SET status = 'ROLLBACK', updated_at = now() + WHERE match_id = :h_mid AND status = 'HOLD'; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlerrd[2] == 0) FAIL(b); + + EXEC SQL SELECT purchase_id INTO :h_pid FROM rc_match_log WHERE match_id = :h_mid; + if (sqlca.sqlcode != 0) FAIL(b); + eid = rcdb_insert_exception(h_pid, reason[0] ? reason : "MANUAL REJECT", bizdate); + if (eid < 0) FAIL(b); + setl(b, T_ID2, eid); + setl(b, T_PURCHASE_ID, h_pid); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_NOTIFY.h b/app/src/rc/svc/RC_NOTIFY.h new file mode 100644 index 0000000..181009b --- /dev/null +++ b/app/src/rc/svc/RC_NOTIFY.h @@ -0,0 +1,20 @@ +/* + * RC_NOTIFY.h - rc 서비스 RC_NOTIFY 카피북 (copybook / record header). + * 대사 결과 요약 통보 (INFO/WARN 경보 발송). + */ +#ifndef RC_NOTIFY_H +#define RC_NOTIFY_H + +#include "rc_svc.h" + +typedef struct { + long matched_cnt; + long unmatched_cnt; + long gross_amount; + char lvl[8]; + char biz_date[16]; +} rc_rc_notify_rec_t; + +void RC_NOTIFY(TPSVCINFO *p); + +#endif /* RC_NOTIFY_H */ diff --git a/app/src/rc/svc/RC_NOTIFY.pgc b/app/src/rc/svc/RC_NOTIFY.pgc new file mode 100644 index 0000000..33066da --- /dev/null +++ b/app/src/rc/svc/RC_NOTIFY.pgc @@ -0,0 +1,40 @@ +/* + * RC_NOTIFY.pgc - rc 모듈 서비스 RC_NOTIFY (one service = one file). + * 대사 결과 통보: 당일 대사 요약(rc_summary)을 읽어 통보 메시지를 구성하고, + * 미대사 잔량 유무에 따라 INFO/WARN 레벨 경보로 발송(적재)한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_NOTIFY.h" + +/* 46. RC_NOTIFY - 대사 결과 요약 통보 (경보 발송). */ +void RC_NOTIFY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_mat, h_unm, h_gross; + char h_bizdate[16], h_lvl[8], h_msg[96]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + h_mat = h_unm = h_gross = 0; + EXEC SQL SELECT matched_cnt, unmatched_cnt, gross_amount + INTO :h_mat, :h_unm, :h_gross + FROM rc_summary WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + strcpy(h_lvl, (h_unm > 0) ? "WARN" : "INFO"); + snprintf(h_msg, sizeof(h_msg), + "recon result: matched=%ld unmatched=%ld gross=%ld", h_mat, h_unm, h_gross); + EXEC SQL INSERT INTO rc_alert (alert_id, purchase_id, lvl, message, biz_date) + VALUES (nextval('rc_alert_seq'), 0, :h_lvl, :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_ARG1, h_mat); + setl(b, T_ARG2, h_unm); + setl(b, T_GROSS, h_gross); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_ORPHAN.h b/app/src/rc/svc/RC_ORPHAN.h new file mode 100644 index 0000000..7337207 --- /dev/null +++ b/app/src/rc/svc/RC_ORPHAN.h @@ -0,0 +1,17 @@ +/* + * RC_ORPHAN.h - rc 서비스 RC_ORPHAN 카피북 (copybook / record header). + * 승인 없는 고아 매입 검출/적재. + */ +#ifndef RC_ORPHAN_H +#define RC_ORPHAN_H + +#include "rc_svc.h" + +typedef struct { + long orphan_cnt; + char biz_date[16]; +} rc_rc_orphan_rec_t; + +void RC_ORPHAN(TPSVCINFO *p); + +#endif /* RC_ORPHAN_H */ diff --git a/app/src/rc/svc/RC_ORPHAN.pgc b/app/src/rc/svc/RC_ORPHAN.pgc new file mode 100644 index 0000000..fdd7673 --- /dev/null +++ b/app/src/rc/svc/RC_ORPHAN.pgc @@ -0,0 +1,31 @@ +/* + * RC_ORPHAN.pgc - rc 모듈 서비스 RC_ORPHAN (one service = one file). + * 고아 매입 검출: 승인(approval) 이 전혀 없는 당일 매입을 NOT EXISTS 로 + * 찾아 미매입 관리 테이블(ac_unmatch)에 일괄 적재하고 건수를 돌려준다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_ORPHAN.h" + +/* 68. RC_ORPHAN - 승인 없는 고아 매입 적재. */ +void RC_ORPHAN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL INSERT INTO ac_unmatch (unmatch_id, purchase_id, reason, biz_date) + SELECT nextval('ac_unmatch_seq'), p.purchase_id, 'NO APPROVAL', p.biz_date + FROM purchase p + WHERE p.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM approval a WHERE a.purchase_id = p.purchase_id); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, sqlca.sqlerrd[2]); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_PARTACQ.h b/app/src/rc/svc/RC_PARTACQ.h new file mode 100644 index 0000000..856211b --- /dev/null +++ b/app/src/rc/svc/RC_PARTACQ.h @@ -0,0 +1,20 @@ +/* + * RC_PARTACQ.h - rc 서비스 RC_PARTACQ 카피북 (copybook / record header). + * 부분매입 대사: 분할 회차 합계 대 원매입 금액. + */ +#ifndef RC_PARTACQ_H +#define RC_PARTACQ_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + long purchase_amt; + long part_cnt; + long part_sum; + char biz_date[16]; +} rc_rc_partacq_rec_t; + +void RC_PARTACQ(TPSVCINFO *p); + +#endif /* RC_PARTACQ_H */ diff --git a/app/src/rc/svc/RC_PARTACQ.pgc b/app/src/rc/svc/RC_PARTACQ.pgc new file mode 100644 index 0000000..6d8463c --- /dev/null +++ b/app/src/rc/svc/RC_PARTACQ.pgc @@ -0,0 +1,50 @@ +/* + * RC_PARTACQ.pgc - rc 모듈 서비스 RC_PARTACQ (one service = one file). + * 부분매입 대사: 분할 매입(ac_install 회차) 합계가 원매입 금액과 일치하는지 + * 검증한다. 회차가 없으면 EXCEPTION, 합계 불일치면 HOLD + rc_diff 적재. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_PARTACQ.h" + +/* 53. RC_PARTACQ - 부분매입(회차 합계) 대사. */ +void RC_PARTACQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long mid; + const char *st; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt, h_n, h_psum; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT amount INTO :h_amt FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode != 0) FAIL(b); + EXEC SQL SELECT count(*), coalesce(sum(month_amount),0) INTO :h_n, :h_psum + FROM ac_install WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + + if (h_n == 0) st = "EXCEPTION"; + else if (h_psum == h_amt) st = "MATCHED"; + else st = "HOLD"; + + if (h_n > 0 && h_psum != h_amt) { + EXEC SQL INSERT INTO rc_diff + (diff_id, purchase_id, expected_amt, actual_amt, diff_amt, diff_kind, biz_date) + VALUES (nextval('rc_diff_seq'), :h_pid, :h_amt, :h_psum, + :h_amt - :h_psum, 'AMT', :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + } + mid = rcdb_insert_match(h_pid, "PARTACQ", h_psum, st, bizdate); + if (mid < 0) FAIL(b); + setl(b, T_COUNT, h_n); + setl(b, T_AMT2, h_psum); + setl(b, T_ID1, mid); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_PAYMATCH.h b/app/src/rc/svc/RC_PAYMATCH.h new file mode 100644 index 0000000..f3708a2 --- /dev/null +++ b/app/src/rc/svc/RC_PAYMATCH.h @@ -0,0 +1,20 @@ +/* + * RC_PAYMATCH.h - rc 서비스 RC_PAYMATCH 카피북 (copybook / record header). + * 지급(원장 SETTLE/XFER) 대사 + rc_diff 적재. + */ +#ifndef RC_PAYMATCH_H +#define RC_PAYMATCH_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + long purchase_net; + long pay_sum; + long delta; + char biz_date[16]; +} rc_rc_paymatch_rec_t; + +void RC_PAYMATCH(TPSVCINFO *p); + +#endif /* RC_PAYMATCH_H */ diff --git a/app/src/rc/svc/RC_PAYMATCH.pgc b/app/src/rc/svc/RC_PAYMATCH.pgc new file mode 100644 index 0000000..f4f151a --- /dev/null +++ b/app/src/rc/svc/RC_PAYMATCH.pgc @@ -0,0 +1,45 @@ +/* + * RC_PAYMATCH.pgc - rc 모듈 서비스 RC_PAYMATCH (one service = one file). + * 지급 대사: 매입 net 대비 원장(ledger) SETTLE/XFER 지급 합계를 비교하고, + * 차이가 있으면 rc_diff 에 금액 차이 라인을 적재한 뒤 대사 로그를 남긴다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_PAYMATCH.h" + +/* 33. RC_PAYMATCH - 지급(원장) 대사 + 차이 적재. */ +void RC_PAYMATCH(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long mid; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_pnet, h_paysum; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT net INTO :h_pnet FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode != 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_paysum FROM ledger + WHERE purchase_id = :h_pid AND entry_type IN ('SETTLE','XFER'); + if (sqlca.sqlcode < 0) FAIL(b); + + if (h_paysum != h_pnet) { + EXEC SQL INSERT INTO rc_diff + (diff_id, purchase_id, expected_amt, actual_amt, diff_amt, diff_kind, biz_date) + VALUES (nextval('rc_diff_seq'), :h_pid, :h_pnet, :h_paysum, + :h_pnet - :h_paysum, 'AMT', :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + } + mid = rcdb_insert_match(h_pid, "PAY", h_paysum, + (h_paysum == h_pnet) ? "MATCHED" : "EXCEPTION", bizdate); + if (mid < 0) FAIL(b); + setl(b, T_DELTA, h_pnet - h_paysum); + setl(b, T_ID1, mid); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_RATEBYISS.h b/app/src/rc/svc/RC_RATEBYISS.h new file mode 100644 index 0000000..3381b77 --- /dev/null +++ b/app/src/rc/svc/RC_RATEBYISS.h @@ -0,0 +1,20 @@ +/* + * RC_RATEBYISS.h - rc 서비스 RC_RATEBYISS 카피북 (copybook / record header). + * 발급사별 대사율(bp) 집계. + */ +#ifndef RC_RATEBYISS_H +#define RC_RATEBYISS_H + +#include "rc_svc.h" + +typedef struct { + char issuer[32]; + long total_cnt; + long matched_cnt; + long rate_bps; + char biz_date[16]; +} rc_rc_ratebyiss_rec_t; + +void RC_RATEBYISS(TPSVCINFO *p); + +#endif /* RC_RATEBYISS_H */ diff --git a/app/src/rc/svc/RC_RATEBYISS.pgc b/app/src/rc/svc/RC_RATEBYISS.pgc new file mode 100644 index 0000000..970d029 --- /dev/null +++ b/app/src/rc/svc/RC_RATEBYISS.pgc @@ -0,0 +1,41 @@ +/* + * RC_RATEBYISS.pgc - rc 모듈 서비스 RC_RATEBYISS (one service = one file). + * 발급사별 대사율: 요청 발급사의 당일 대사 로그를 매입과 조인하여 총건수와 + * MATCHED 건수를 한 번에 집계하고 발급사 대사율(bp)을 계산한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_RATEBYISS.h" + +/* 48. RC_RATEBYISS - 발급사별 대사율(bp). */ +void RC_RATEBYISS(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], issuer[32]; + long rate; + EXEC SQL BEGIN DECLARE SECTION; + long h_tot, h_mat; + char h_bizdate[16], h_issuer[32]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + gets_(b, T_ISSUER, issuer, sizeof(issuer)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(h_issuer, issuer, sizeof(h_issuer)-1); h_issuer[sizeof(h_issuer)-1] = 0; + + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN m.status = 'MATCHED' THEN 1 ELSE 0 END),0) + INTO :h_tot, :h_mat + FROM rc_match_log m, purchase p + WHERE m.purchase_id = p.purchase_id + AND m.biz_date = :h_bizdate AND p.issuer = :h_issuer; + if (sqlca.sqlcode < 0) FAIL(b); + + rate = (h_tot > 0) ? (h_mat * 10000) / h_tot : 0; + setl(b, T_COUNT, h_tot); + setl(b, T_ARG1, h_mat); + setl(b, T_RC, rate); + Bchg(b, T_ISSUER, 0, h_issuer, 0L); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_RATESTAT.h b/app/src/rc/svc/RC_RATESTAT.h new file mode 100644 index 0000000..1c3ca2f --- /dev/null +++ b/app/src/rc/svc/RC_RATESTAT.h @@ -0,0 +1,19 @@ +/* + * RC_RATESTAT.h - rc 서비스 RC_RATESTAT 카피북 (copybook / record header). + * 당일 대사율(bp) 통계. + */ +#ifndef RC_RATESTAT_H +#define RC_RATESTAT_H + +#include "rc_svc.h" + +typedef struct { + long total_cnt; + long matched_cnt; + long rate_bps; + char biz_date[16]; +} rc_rc_ratestat_rec_t; + +void RC_RATESTAT(TPSVCINFO *p); + +#endif /* RC_RATESTAT_H */ diff --git a/app/src/rc/svc/RC_RATESTAT.pgc b/app/src/rc/svc/RC_RATESTAT.pgc new file mode 100644 index 0000000..0c3d4bf --- /dev/null +++ b/app/src/rc/svc/RC_RATESTAT.pgc @@ -0,0 +1,36 @@ +/* + * RC_RATESTAT.pgc - rc 모듈 서비스 RC_RATESTAT (one service = one file). + * 대사율 통계: 당일 전체 대사 로그 대비 MATCHED 비율을 bp(1/10000) 단위로 + * 계산하여 총건수/성공건수/대사율을 반환한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_RATESTAT.h" + +/* 47. RC_RATESTAT - 당일 대사율(bp) 통계. */ +void RC_RATESTAT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long rate; + EXEC SQL BEGIN DECLARE SECTION; + long h_tot, h_mat; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN status = 'MATCHED' THEN 1 ELSE 0 END),0) + INTO :h_tot, :h_mat + FROM rc_match_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + rate = (h_tot > 0) ? (h_mat * 10000) / h_tot : 0; + setl(b, T_COUNT, h_tot); + setl(b, T_ARG1, h_mat); + setl(b, T_RC, rate); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_REOPEN.h b/app/src/rc/svc/RC_REOPEN.h new file mode 100644 index 0000000..5e39eb9 --- /dev/null +++ b/app/src/rc/svc/RC_REOPEN.h @@ -0,0 +1,17 @@ +/* + * RC_REOPEN.h - rc 서비스 RC_REOPEN 카피북 (copybook / record header). + * 일마감 해제 (CONFIRMED -> MATCHED + WARN 경보). + */ +#ifndef RC_REOPEN_H +#define RC_REOPEN_H + +#include "rc_svc.h" + +typedef struct { + long reopened_cnt; + char biz_date[16]; +} rc_rc_reopen_rec_t; + +void RC_REOPEN(TPSVCINFO *p); + +#endif /* RC_REOPEN_H */ diff --git a/app/src/rc/svc/RC_REOPEN.pgc b/app/src/rc/svc/RC_REOPEN.pgc new file mode 100644 index 0000000..6242c5d --- /dev/null +++ b/app/src/rc/svc/RC_REOPEN.pgc @@ -0,0 +1,36 @@ +/* + * RC_REOPEN.pgc - rc 모듈 서비스 RC_REOPEN (one service = one file). + * 마감 재개: 확정(CONFIRMED)된 당일 대사 건을 다시 MATCHED 로 되돌려 마감을 + * 해제하고, 재개 사실을 WARN 경보로 남긴다. 되돌린 건수를 반환한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_REOPEN.h" + +/* 74. RC_REOPEN - 일마감 해제 (CONFIRMED -> MATCHED). */ +void RC_REOPEN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long rows; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_msg[64]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL UPDATE rc_match_log SET status = 'MATCHED', updated_at = now() + WHERE biz_date = :h_bizdate AND status = 'CONFIRMED'; + if (sqlca.sqlcode < 0) FAIL(b); + rows = sqlca.sqlerrd[2]; + if (rows == 0) FAIL(b); + + snprintf(h_msg, sizeof(h_msg), "recon day reopened: rows=%ld", rows); + EXEC SQL INSERT INTO rc_alert (alert_id, purchase_id, lvl, message, biz_date) + VALUES (nextval('rc_alert_seq'), 0, 'WARN', :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, rows); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_RERUN.h b/app/src/rc/svc/RC_RERUN.h new file mode 100644 index 0000000..3e246bc --- /dev/null +++ b/app/src/rc/svc/RC_RERUN.h @@ -0,0 +1,20 @@ +/* + * RC_RERUN.h - rc 서비스 RC_RERUN 카피북 (copybook / record header). + * 임계초과 차이 건 재대사(RERUN/HOLD) 등록. + */ +#ifndef RC_RERUN_H +#define RC_RERUN_H + +#include "rc_svc.h" + +typedef struct { + long threshold_amt; + long purchase_id; + long diff_amt; + long rerun_cnt; + char biz_date[16]; +} rc_rc_rerun_rec_t; + +void RC_RERUN(TPSVCINFO *p); + +#endif /* RC_RERUN_H */ diff --git a/app/src/rc/svc/RC_RERUN.pgc b/app/src/rc/svc/RC_RERUN.pgc new file mode 100644 index 0000000..f4690f4 --- /dev/null +++ b/app/src/rc/svc/RC_RERUN.pgc @@ -0,0 +1,44 @@ +/* + * RC_RERUN.pgc - rc 모듈 서비스 RC_RERUN (one service = one file). + * 임계초과 재대사 실행: 임계값을 넘는 당일 차이 건들을 커서로 순회하며 + * 각 매입에 대해 RERUN 유형의 HOLD 대사 로그를 새로 등록한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_RERUN.h" + +/* 50. RC_RERUN - 임계초과 건 재대사 등록 (커서 순회). */ +void RC_RERUN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long done = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_limit, h_pid, h_diff; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_limit = getl(b, T_AMT1); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL DECLARE rrn CURSOR FOR + SELECT purchase_id, diff_amt FROM rc_diff + WHERE biz_date = :h_bizdate AND abs(diff_amt) > :h_limit + ORDER BY diff_id; + EXEC SQL OPEN rrn; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH rrn INTO :h_pid, :h_diff; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE rrn; FAIL(b); } + if (rcdb_insert_match(h_pid, "RERUN", h_diff, "HOLD", bizdate) < 0) { + EXEC SQL CLOSE rrn; FAIL(b); + } + done++; + } + EXEC SQL CLOSE rrn; + setl(b, T_COUNT, done); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_RETRO.h b/app/src/rc/svc/RC_RETRO.h new file mode 100644 index 0000000..0da4406 --- /dev/null +++ b/app/src/rc/svc/RC_RETRO.h @@ -0,0 +1,19 @@ +/* + * RC_RETRO.h - rc 서비스 RC_RETRO 카피북 (copybook / record header). + * 소급 대사: 과거 보류/예외 건을 새 영업일로 소급 확정. + */ +#ifndef RC_RETRO_H +#define RC_RETRO_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + long net; + char old_date[16]; + char new_date[16]; +} rc_rc_retro_rec_t; + +void RC_RETRO(TPSVCINFO *p); + +#endif /* RC_RETRO_H */ diff --git a/app/src/rc/svc/RC_RETRO.pgc b/app/src/rc/svc/RC_RETRO.pgc new file mode 100644 index 0000000..3a7ac6e --- /dev/null +++ b/app/src/rc/svc/RC_RETRO.pgc @@ -0,0 +1,42 @@ +/* + * RC_RETRO.pgc - rc 모듈 서비스 RC_RETRO (one service = one file). + * 소급 대사: 과거 영업일에 보류/예외로 남은 특정 매입의 대사를 새 영업일로 + * 소급 확정(MATCHED)하고 소급 증적을 원장에 기록한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_RETRO.h" + +/* 45. RC_RETRO - 과거 보류/예외 건 소급 확정. */ +void RC_RETRO(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char olddate[16], newdate[16]; + long rows; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_net; + char h_old[16], h_new[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + h_net = getl(b, T_NET); + gets_(b, T_STR1, olddate, sizeof(olddate)); + gets_(b, T_BIZDATE, newdate, sizeof(newdate)); + strncpy(h_old, olddate, sizeof(h_old)-1); h_old[sizeof(h_old)-1] = 0; + strncpy(h_new, newdate, sizeof(h_new)-1); h_new[sizeof(h_new)-1] = 0; + + EXEC SQL UPDATE rc_match_log + SET biz_date = CAST(:h_new AS date), status = 'MATCHED', updated_at = now() + WHERE purchase_id = :h_pid AND biz_date = :h_old + AND status IN ('HOLD','EXCEPTION'); + if (sqlca.sqlcode < 0) FAIL(b); + rows = sqlca.sqlerrd[2]; + if (rows > 0) { + EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date) + VALUES (nextval('ledger_seq'), :h_pid, 'RC_RETRO', :h_net, :h_new); + if (sqlca.sqlcode < 0) FAIL(b); + } + setl(b, T_COUNT, rows); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_SNAPSHOT.h b/app/src/rc/svc/RC_SNAPSHOT.h new file mode 100644 index 0000000..bfdfc26 --- /dev/null +++ b/app/src/rc/svc/RC_SNAPSHOT.h @@ -0,0 +1,20 @@ +/* + * RC_SNAPSHOT.h - rc 서비스 RC_SNAPSHOT 카피북 (copybook / record header). + * 대사 현황 스냅샷 (CASE 집계 + 요약 upsert). + */ +#ifndef RC_SNAPSHOT_H +#define RC_SNAPSHOT_H + +#include "rc_svc.h" + +typedef struct { + long total_cnt; + long matched_cnt; + long unmatched_cnt; + long gross_amount; + char biz_date[16]; +} rc_rc_snapshot_rec_t; + +void RC_SNAPSHOT(TPSVCINFO *p); + +#endif /* RC_SNAPSHOT_H */ diff --git a/app/src/rc/svc/RC_SNAPSHOT.pgc b/app/src/rc/svc/RC_SNAPSHOT.pgc new file mode 100644 index 0000000..bbd55a1 --- /dev/null +++ b/app/src/rc/svc/RC_SNAPSHOT.pgc @@ -0,0 +1,41 @@ +/* + * RC_SNAPSHOT.pgc - rc 모듈 서비스 RC_SNAPSHOT (one service = one file). + * 대사 스냅샷: 당일 대사 로그의 총건수/MATCHED 건수/MATCHED 금액을 CASE + * 집계 한 방으로 산출하고 미대사 건수와 함께 요약에 스냅샷 upsert 한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_SNAPSHOT.h" + +/* 75. RC_SNAPSHOT - 현재 시점 대사 현황 스냅샷 upsert. */ +void RC_SNAPSHOT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long unmatched; + EXEC SQL BEGIN DECLARE SECTION; + long h_tot, h_mat, h_gross; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN status = 'MATCHED' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN status = 'MATCHED' THEN amount ELSE 0 END),0) + INTO :h_tot, :h_mat, :h_gross + FROM rc_match_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + + unmatched = rcdb_count_unmatched(bizdate); + if (unmatched < 0) FAIL(b); + if (rcdb_upsert_summary(bizdate, h_mat, unmatched, h_gross) < 0) FAIL(b); + + setl(b, T_COUNT, h_tot); + setl(b, T_ARG1, h_mat); + setl(b, T_ARG2, unmatched); + setl(b, T_GROSS, h_gross); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_SUMCHK.h b/app/src/rc/svc/RC_SUMCHK.h new file mode 100644 index 0000000..0043761 --- /dev/null +++ b/app/src/rc/svc/RC_SUMCHK.h @@ -0,0 +1,19 @@ +/* + * RC_SUMCHK.h - rc 서비스 RC_SUMCHK 카피북 (copybook / record header). + * 요약(rc_summary) 대 실측(rc_match_log) 정합성 검증. + */ +#ifndef RC_SUMCHK_H +#define RC_SUMCHK_H + +#include "rc_svc.h" + +typedef struct { + long summary_cnt; + long live_cnt; + long mismatch; + char biz_date[16]; +} rc_rc_sumchk_rec_t; + +void RC_SUMCHK(TPSVCINFO *p); + +#endif /* RC_SUMCHK_H */ diff --git a/app/src/rc/svc/RC_SUMCHK.pgc b/app/src/rc/svc/RC_SUMCHK.pgc new file mode 100644 index 0000000..2c3f291 --- /dev/null +++ b/app/src/rc/svc/RC_SUMCHK.pgc @@ -0,0 +1,35 @@ +/* + * RC_SUMCHK.pgc - rc 모듈 서비스 RC_SUMCHK (one service = one file). + * 요약-상세 정합성 검증: rc_summary 의 확정 matched 건수와 대사 로그 실측 + * 건수를 대조하여 요약 테이블 훼손/누락 여부를 판정한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_SUMCHK.h" + +/* 70. RC_SUMCHK - 요약 대 실측 정합성 검증. */ +void RC_SUMCHK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_sum, h_live; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + h_sum = -1; + EXEC SQL SELECT matched_cnt INTO :h_sum FROM rc_summary WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_live FROM rc_match_log + WHERE biz_date = :h_bizdate AND status = 'MATCHED'; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_ARG1, h_sum); + setl(b, T_ARG2, h_live); + setl(b, T_RC, (h_sum == h_live) ? 0 : 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_TAXRECON.h b/app/src/rc/svc/RC_TAXRECON.h new file mode 100644 index 0000000..31b945f --- /dev/null +++ b/app/src/rc/svc/RC_TAXRECON.h @@ -0,0 +1,20 @@ +/* + * RC_TAXRECON.h - rc 서비스 RC_TAXRECON 카피북 (copybook / record header). + * 세금계산서 대사: 공급가+VAT 합계 대 매입 금액. + */ +#ifndef RC_TAXRECON_H +#define RC_TAXRECON_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + long invoice_sum; + long invoice_cnt; + long purchase_amt; + char biz_date[16]; +} rc_rc_taxrecon_rec_t; + +void RC_TAXRECON(TPSVCINFO *p); + +#endif /* RC_TAXRECON_H */ diff --git a/app/src/rc/svc/RC_TAXRECON.pgc b/app/src/rc/svc/RC_TAXRECON.pgc new file mode 100644 index 0000000..c7d80a9 --- /dev/null +++ b/app/src/rc/svc/RC_TAXRECON.pgc @@ -0,0 +1,47 @@ +/* + * RC_TAXRECON.pgc - rc 모듈 서비스 RC_TAXRECON (one service = one file). + * 세금계산서 대사: 발행된 세금계산서(공급가+부가세) 합계와 매입 금액을 + * 비교하여 차이가 있으면 rc_diff 적재, 상태를 MATCHED/EXCEPTION 으로 남긴다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_TAXRECON.h" + +/* 60. RC_TAXRECON - 세금계산서(공급가+VAT) 대사. */ +void RC_TAXRECON(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long mid, diff; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_inv, h_invn, h_amt; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT coalesce(sum(supply + vat),0), count(*) INTO :h_inv, :h_invn + FROM ac_tax_invoice WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_invn == 0) FAIL(b); + EXEC SQL SELECT amount INTO :h_amt FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode != 0) FAIL(b); + + diff = h_amt - h_inv; + if (diff != 0) { + EXEC SQL INSERT INTO rc_diff + (diff_id, purchase_id, expected_amt, actual_amt, diff_amt, diff_kind, biz_date) + VALUES (nextval('rc_diff_seq'), :h_pid, :h_amt, :h_inv, :h_amt - :h_inv, + 'AMT', :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + } + mid = rcdb_insert_match(h_pid, "TAXINV", h_inv, + (diff == 0) ? "MATCHED" : "EXCEPTION", bizdate); + if (mid < 0) FAIL(b); + setl(b, T_DELTA, diff); + setl(b, T_ID1, mid); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_THRESHOLD.h b/app/src/rc/svc/RC_THRESHOLD.h new file mode 100644 index 0000000..aa10db8 --- /dev/null +++ b/app/src/rc/svc/RC_THRESHOLD.h @@ -0,0 +1,18 @@ +/* + * RC_THRESHOLD.h - rc 서비스 RC_THRESHOLD 카피북 (copybook / record header). + * 임계초과 차이 검출 + CRIT 경보 발생. + */ +#ifndef RC_THRESHOLD_H +#define RC_THRESHOLD_H + +#include "rc_svc.h" + +typedef struct { + long threshold_amt; + long over_cnt; + char biz_date[16]; +} rc_rc_threshold_rec_t; + +void RC_THRESHOLD(TPSVCINFO *p); + +#endif /* RC_THRESHOLD_H */ diff --git a/app/src/rc/svc/RC_THRESHOLD.pgc b/app/src/rc/svc/RC_THRESHOLD.pgc new file mode 100644 index 0000000..2ee1cb1 --- /dev/null +++ b/app/src/rc/svc/RC_THRESHOLD.pgc @@ -0,0 +1,37 @@ +/* + * RC_THRESHOLD.pgc - rc 모듈 서비스 RC_THRESHOLD (one service = one file). + * 임계초과 검출: 당일 차이 중 |차이금액| 이 임계값을 초과하는 건수를 세고, + * 존재하면 CRIT 경보를 발생시켜 재대사 트리거를 만든다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_THRESHOLD.h" + +/* 49. RC_THRESHOLD - 임계초과 차이 검출 + CRIT 경보. */ +void RC_THRESHOLD(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_limit, h_cnt; + char h_bizdate[16], h_msg[80]; + EXEC SQL END DECLARE SECTION; + + h_limit = getl(b, T_AMT1); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT count(*) INTO :h_cnt FROM rc_diff + WHERE biz_date = :h_bizdate AND abs(diff_amt) > :h_limit; + if (sqlca.sqlcode < 0) FAIL(b); + + if (h_cnt > 0) { + snprintf(h_msg, sizeof(h_msg), "diff over threshold %ld: cnt=%ld", h_limit, h_cnt); + EXEC SQL INSERT INTO rc_alert (alert_id, purchase_id, lvl, message, biz_date) + VALUES (nextval('rc_alert_seq'), 0, 'CRIT', :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + } + setl(b, T_COUNT, h_cnt); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_TOPDIFF.h b/app/src/rc/svc/RC_TOPDIFF.h new file mode 100644 index 0000000..f5ce364 --- /dev/null +++ b/app/src/rc/svc/RC_TOPDIFF.h @@ -0,0 +1,19 @@ +/* + * RC_TOPDIFF.h - rc 서비스 RC_TOPDIFF 카피북 (copybook / record header). + * 차이 상위 요약: 최대/평균/평균초과 건수. + */ +#ifndef RC_TOPDIFF_H +#define RC_TOPDIFF_H + +#include "rc_svc.h" + +typedef struct { + long max_diff; + long avg_diff; + long over_avg_cnt; + char biz_date[16]; +} rc_rc_topdiff_rec_t; + +void RC_TOPDIFF(TPSVCINFO *p); + +#endif /* RC_TOPDIFF_H */ diff --git a/app/src/rc/svc/RC_TOPDIFF.pgc b/app/src/rc/svc/RC_TOPDIFF.pgc new file mode 100644 index 0000000..ab55123 --- /dev/null +++ b/app/src/rc/svc/RC_TOPDIFF.pgc @@ -0,0 +1,36 @@ +/* + * RC_TOPDIFF.pgc - rc 모듈 서비스 RC_TOPDIFF (one service = one file). + * 차이 상위 조회: 당일 |차이금액| 의 최대값과 평균값을 구한 뒤 평균을 넘는 + * 이상치 차이 건수를 세어 상위 차이 분포를 요약한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_TOPDIFF.h" + +/* 65. RC_TOPDIFF - 최대/평균 차이 + 평균 초과 건수. */ +void RC_TOPDIFF(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_max, h_avg, h_over; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL SELECT coalesce(max(abs(diff_amt)),0), coalesce(round(avg(abs(diff_amt))),0) + INTO :h_max, :h_avg + FROM rc_diff WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_over FROM rc_diff + WHERE biz_date = :h_bizdate AND abs(diff_amt) > :h_avg; + if (sqlca.sqlcode < 0) FAIL(b); + + setl(b, T_AMT1, h_max); + setl(b, T_AMT2, h_avg); + setl(b, T_COUNT, h_over); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_VOIDREC.h b/app/src/rc/svc/RC_VOIDREC.h new file mode 100644 index 0000000..0e3740c --- /dev/null +++ b/app/src/rc/svc/RC_VOIDREC.h @@ -0,0 +1,19 @@ +/* + * RC_VOIDREC.h - rc 서비스 RC_VOIDREC 카피북 (copybook / record header). + * VOID 매입 대사 (상태 V + VOID 취소 증빙 검증). + */ +#ifndef RC_VOIDREC_H +#define RC_VOIDREC_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + long void_flag; + long evidence_cnt; + char biz_date[16]; +} rc_rc_voidrec_rec_t; + +void RC_VOIDREC(TPSVCINFO *p); + +#endif /* RC_VOIDREC_H */ diff --git a/app/src/rc/svc/RC_VOIDREC.pgc b/app/src/rc/svc/RC_VOIDREC.pgc new file mode 100644 index 0000000..d61bb1e --- /dev/null +++ b/app/src/rc/svc/RC_VOIDREC.pgc @@ -0,0 +1,39 @@ +/* + * RC_VOIDREC.pgc - rc 모듈 서비스 RC_VOIDREC (one service = one file). + * VOID 매입 대사: 매입 상태가 V(void) 인지와 VOID 유형 취소 증빙 존재를 + * 함께 검증하여 양쪽 모두 있으면 MATCHED, 아니면 EXCEPTION + 예외 적재. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_VOIDREC.h" + +/* 52. RC_VOIDREC - VOID 매입 대사 (상태 V + VOID 취소 증빙). */ +void RC_VOIDREC(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long mid, ok; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_void, h_evi; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + + EXEC SQL SELECT count(*) INTO :h_void FROM purchase + WHERE purchase_id = :h_pid AND status = 'V'; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_evi FROM ac_cancel + WHERE purchase_id = :h_pid AND cancel_type = 'VOID'; + if (sqlca.sqlcode < 0) FAIL(b); + + ok = (h_void > 0 && h_evi > 0); + mid = rcdb_insert_match(h_pid, "VOID", 0, ok ? "MATCHED" : "EXCEPTION", bizdate); + if (mid < 0) FAIL(b); + if (!ok && rcdb_insert_exception(h_pid, "VOID EVIDENCE MISSING", bizdate) < 0) + FAIL(b); + setl(b, T_ID1, mid); + setl(b, T_RC, ok ? 0 : 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_WHTRECON.h b/app/src/rc/svc/RC_WHTRECON.h new file mode 100644 index 0000000..68b3c3e --- /dev/null +++ b/app/src/rc/svc/RC_WHTRECON.h @@ -0,0 +1,19 @@ +/* + * RC_WHTRECON.h - rc 서비스 RC_WHTRECON 카피북 (copybook / record header). + * 원천세 대사: 세액 합계 대 정산 net 한도 검증. + */ +#ifndef RC_WHTRECON_H +#define RC_WHTRECON_H + +#include "rc_svc.h" + +typedef struct { + long purchase_id; + long tax_sum; + long net; + char biz_date[16]; +} rc_rc_whtrecon_rec_t; + +void RC_WHTRECON(TPSVCINFO *p); + +#endif /* RC_WHTRECON_H */ diff --git a/app/src/rc/svc/RC_WHTRECON.pgc b/app/src/rc/svc/RC_WHTRECON.pgc new file mode 100644 index 0000000..e9fa28b --- /dev/null +++ b/app/src/rc/svc/RC_WHTRECON.pgc @@ -0,0 +1,38 @@ +/* + * RC_WHTRECON.pgc - rc 모듈 서비스 RC_WHTRECON (one service = one file). + * 원천세 대사: 매입의 원천징수(ac_wht) 세액 합계가 정산 net 금액을 넘지 + * 않는지 검증하고 초과 시 EXCEPTION, 정상 범위면 MATCHED 로 기록한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_WHTRECON.h" + +/* 59. RC_WHTRECON - 원천세 한도 대사. */ +void RC_WHTRECON(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long mid, over; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_tax, h_net; + EXEC SQL END DECLARE SECTION; + + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + + EXEC SQL SELECT coalesce(sum(tax_amount),0) INTO :h_tax + FROM ac_wht WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT net INTO :h_net FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode != 0) FAIL(b); + + over = (h_tax > h_net); + mid = rcdb_insert_match(h_pid, "WHT", h_tax, over ? "EXCEPTION" : "MATCHED", bizdate); + if (mid < 0) FAIL(b); + setl(b, T_TAX, h_tax); + setl(b, T_NET, h_net); + setl(b, T_RC, over ? 1 : 0); + setl(b, T_ID1, mid); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/rc/svc/RC_ZEROAMT.h b/app/src/rc/svc/RC_ZEROAMT.h new file mode 100644 index 0000000..21ab224 --- /dev/null +++ b/app/src/rc/svc/RC_ZEROAMT.h @@ -0,0 +1,17 @@ +/* + * RC_ZEROAMT.h - rc 서비스 RC_ZEROAMT 카피북 (copybook / record header). + * 0원/음수 net 이상 거래 예외 일괄 적재. + */ +#ifndef RC_ZEROAMT_H +#define RC_ZEROAMT_H + +#include "rc_svc.h" + +typedef struct { + long detected_cnt; + char biz_date[16]; +} rc_rc_zeroamt_rec_t; + +void RC_ZEROAMT(TPSVCINFO *p); + +#endif /* RC_ZEROAMT_H */ diff --git a/app/src/rc/svc/RC_ZEROAMT.pgc b/app/src/rc/svc/RC_ZEROAMT.pgc new file mode 100644 index 0000000..7285dce --- /dev/null +++ b/app/src/rc/svc/RC_ZEROAMT.pgc @@ -0,0 +1,30 @@ +/* + * RC_ZEROAMT.pgc - rc 모듈 서비스 RC_ZEROAMT (one service = one file). + * 이상금액 검출: 당일 매입 중 금액 0원 또는 음수 net 인 이상 거래를 찾아 + * 일괄 INSERT..SELECT 로 대사 예외에 적재하고 검출 건수를 반환한다. + * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by rc_svr. + */ +#include "RC_ZEROAMT.h" + +/* 66. RC_ZEROAMT - 0원/음수 net 이상 거래 예외 적재. */ +void RC_ZEROAMT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + EXEC SQL INSERT INTO rc_exception (exc_id, purchase_id, reason, biz_date) + SELECT nextval('rc_exc_seq'), purchase_id, 'ZERO OR NEGATIVE AMT', biz_date + FROM purchase + WHERE biz_date = :h_bizdate AND (amount = 0 OR net < 0); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, sqlca.sqlerrd[2]); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/batch/st_approve_batch.pgc b/app/src/st/batch/st_approve_batch.pgc new file mode 100644 index 0000000..2265e08 --- /dev/null +++ b/app/src/st/batch/st_approve_batch.pgc @@ -0,0 +1,48 @@ +/* + * st_approve_batch.pgc - 정산 승인 워크플로 배치 (2단 검증 + 집합 승격 + XA). + * 당일 CONFIRMED 정산을 확인한 뒤 net>0 건만 APPROVED 로 일괄 승격하고, + * 승인 대상에서 빠진 건수(net<=0)를 별도 집계한다. 승인 원장 마커 기록. + * 사용법: st_approve_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" +#include "st_approve_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + st_approve_rec_t rec; + memset(&rec, 0, sizeof(rec)); + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_confirmed; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*) INTO :h_confirmed FROM settlement + WHERE biz_date = :h_bizdate AND status = 'CONFIRMED'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "CHECK FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + EXEC SQL UPDATE settlement SET status = 'APPROVED' + WHERE biz_date = :h_bizdate AND status = 'CONFIRMED' AND net > 0; + if (sqlca.sqlcode < 0) { fprintf(stderr, "APPROVE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.approved = (long) sqlca.sqlerrd[2]; + rec.skipped = h_confirmed - rec.approved; + + EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date) + VALUES (nextval('ledger_seq'), 0, 'STAPPROVE', :h_confirmed, :h_bizdate); + if (sqlca.sqlcode < 0) { fprintf(stderr, "MARKER FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s approved=%ld skipped=%ld\n", ST_APPROVE_TAG, bizdate, rec.approved, rec.skipped); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_deposit_batch.pgc b/app/src/st/batch/st_deposit_batch.pgc new file mode 100644 index 0000000..0de17e7 --- /dev/null +++ b/app/src/st/batch/st_deposit_batch.pgc @@ -0,0 +1,47 @@ +/* + * st_deposit_batch.pgc - 보증금 차감 배치 (조건부 집합 UPDATE + 총액 조정 + XA). + * 차감 후에도 지급액이 남는(net_amount > cut) SCHEDULED 지급 전체에서 보증금 + * 단위를 일괄 차감하고, 총 차감액을 음수 조정 1건으로 기록한다. + * 사용법: st_deposit_batch [YYYY-MM-DD] [cut] + */ +#include +#include +#include +#include +#include "st_dbio.h" +#include "st_deposit_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + st_deposit_rec_t rec; + rec.cut_each = (argc > 2) ? atol(argv[2]) : ST_DEPOSIT_DEF_CUT; + rec.cut_rows = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cut; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + h_cut = rec.cut_each; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL UPDATE st_payment SET net_amount = net_amount - :h_cut + WHERE biz_date = :h_bizdate AND status = 'SCHEDULED' AND net_amount > :h_cut; + if (sqlca.sqlcode < 0) { fprintf(stderr, "CUT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.cut_rows = (long) sqlca.sqlerrd[2]; + + if (rec.cut_rows > 0) { + if (stdb_insert_adjust(0, 0, -(rec.cut_each * rec.cut_rows), "DEPOSIT", bizdate) < 0) { tpabort(0); return 1; } + } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s cut_each=%ld cut_rows=%ld total=%ld\n", + ST_DEPOSIT_TAG, bizdate, rec.cut_each, rec.cut_rows, rec.cut_each * rec.cut_rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_grproll_batch.pgc b/app/src/st/batch/st_grproll_batch.pgc new file mode 100644 index 0000000..67276e8 --- /dev/null +++ b/app/src/st/batch/st_grproll_batch.pgc @@ -0,0 +1,54 @@ +/* + * st_grproll_batch.pgc - 가맹점그룹 통합정산 롤업 배치 (prefix 그룹 커서 + upsert + XA). + * 가맹점 ID 앞 2자리를 그룹키로 삼아 당일 정산을 그룹 단위로 롤업 집계하고, + * 그룹키 행을 st_merch_settle 에 upsert 한다 (그룹 통합정산 기초). + * 사용법: st_grproll_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" +#include "st_grproll_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + st_grproll_rec_t rec; + memset(&rec, 0, sizeof(rec)); + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_grp[8]; + long h_cnt, h_net; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE grproll_bc CURSOR FOR + SELECT substr(merchant_id, 1, 2), count(*), coalesce(sum(net),0) + FROM settlement WHERE biz_date = :h_bizdate + GROUP BY substr(merchant_id, 1, 2) ORDER BY 1; + EXEC SQL OPEN grproll_bc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH grproll_bc INTO :h_grp, :h_cnt, :h_net; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE grproll_bc; tpabort(0); return 1; } + { + char grpkey[16]; + snprintf(grpkey, sizeof(grpkey), "GRP:%s", h_grp); + if (stdb_upsert_merch_settle(grpkey, bizdate, h_net, 0, h_net, h_cnt) < 0) { EXEC SQL CLOSE grproll_bc; tpabort(0); return 1; } + rec.groups++; + rec.rolled_net += h_net; + } + } + EXEC SQL CLOSE grproll_bc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s groups=%ld rolled_net=%ld\n", ST_GRPROLL_TAG, bizdate, rec.groups, rec.rolled_net); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_incent_batch.pgc b/app/src/st/batch/st_incent_batch.pgc new file mode 100644 index 0000000..2ee8a4e --- /dev/null +++ b/app/src/st/batch/st_incent_batch.pgc @@ -0,0 +1,58 @@ +/* + * st_incent_batch.pgc - 구간별 인센티브 정산 배치 (커서 + 3단 요율 사다리 + XA). + * 가맹점 일 집계(gross) 를 내림차순 커서로 순회, 1억/5천만/1천만 구간별 + * 인센티브(30/20/10bps)를 산정해 양수 조정으로 지급한다. 사용법: st_incent_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" +#include "st_incent_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + st_incent_rec_t rec; + memset(&rec, 0, sizeof(rec)); + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_gross; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE incent_bc CURSOR FOR + SELECT merchant_id, gross_amount FROM st_merch_settle + WHERE biz_date = :h_bizdate AND gross_amount >= 10000000 + ORDER BY gross_amount DESC; + EXEC SQL OPEN incent_bc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH incent_bc INTO :h_merch, :h_gross; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE incent_bc; tpabort(0); return 1; } + { + long bps; + if (h_gross >= 100000000) { bps = 30; rec.tier1++; } + else if (h_gross >= 50000000) { bps = 20; rec.tier2++; } + else { bps = 10; rec.tier3++; } + { + long inc = h_gross * bps / 10000; + if (stdb_insert_adjust(0, 0, inc, "INCENTIVE", bizdate) < 0) { EXEC SQL CLOSE incent_bc; tpabort(0); return 1; } + rec.paid_sum += inc; + } + } + } + EXEC SQL CLOSE incent_bc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s t1=%ld t2=%ld t3=%ld incentive_sum=%ld\n", + ST_INCENT_TAG, bizdate, rec.tier1, rec.tier2, rec.tier3, rec.paid_sum); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_partial_batch.pgc b/app/src/st/batch/st_partial_batch.pgc new file mode 100644 index 0000000..37d5154 --- /dev/null +++ b/app/src/st/batch/st_partial_batch.pgc @@ -0,0 +1,54 @@ +/* + * st_partial_batch.pgc - 부분정산 잔여 스윕 배치 (HAVING 커서 + XA). + * 부분정산으로 인해 정산 합계가 매입 net 에 못 미치는 매입을 HAVING 커서로 + * 찾아, 잔여분을 PARTIAL 정산 레코드로 마저 생성한다. 사용법: st_partial_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" +#include "st_partial_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + st_partial_rec_t rec; + memset(&rec, 0, sizeof(rec)); + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_pid, h_remain; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE partial_bc CURSOR FOR + SELECT p.purchase_id, p.merchant_id, p.net - sum(s.net) + FROM purchase p JOIN settlement s ON s.purchase_id = p.purchase_id + WHERE p.biz_date = :h_bizdate AND s.status <> 'CANCELLED' + GROUP BY p.purchase_id, p.merchant_id, p.net + HAVING sum(s.net) < p.net; + EXEC SQL OPEN partial_bc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH partial_bc INTO :h_pid, :h_merch, :h_remain; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE partial_bc; tpabort(0); return 1; } + if (h_remain <= 0) continue; + EXEC SQL INSERT INTO settlement (settlement_id, purchase_id, merchant_id, net, biz_date, status) + VALUES (nextval('settlement_seq'), :h_pid, :h_merch, :h_remain, :h_bizdate, 'PARTIAL'); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE partial_bc; tpabort(0); return 1; } + rec.swept++; + rec.swept_amt += h_remain; + } + EXEC SQL CLOSE partial_bc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s partial_swept=%ld amt=%ld\n", ST_PARTIAL_TAG, bizdate, rec.swept, rec.swept_amt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_promo_batch.pgc b/app/src/st/batch/st_promo_batch.pgc new file mode 100644 index 0000000..6557797 --- /dev/null +++ b/app/src/st/batch/st_promo_batch.pgc @@ -0,0 +1,43 @@ +/* + * st_promo_batch.pgc - 프로모션/이벤트 정산 배치 (집합 INSERT..SELECT + XA). + * 당일 거래대금이 기준액 이상인 가맹점 집계에 대해 수수료의 10% 를 이벤트 + * 환급 조정(PROMO)으로 일괄 생성한다. 사용법: st_promo_batch [YYYY-MM-DD] [threshold] + */ +#include +#include +#include +#include +#include "st_dbio.h" +#include "st_promo_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + st_promo_rec_t rec; + rec.threshold = (argc > 2) ? atol(argv[2]) : ST_PROMO_DEF_THRESH; + rec.granted = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_thr; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + h_thr = rec.threshold; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL INSERT INTO st_adjust (adj_id, settlement_id, purchase_id, delta, reason, biz_date) + SELECT nextval('st_adj_seq'), 0, 0, fee_amount / 10, 'PROMO', biz_date + FROM st_merch_settle + WHERE biz_date = :h_bizdate AND gross_amount >= :h_thr AND fee_amount > 0; + if (sqlca.sqlcode < 0) { fprintf(stderr, "%s FAIL [%d] %s\n", ST_PROMO_TAG, sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.granted = (long) sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s threshold=%ld promo_granted=%ld\n", ST_PROMO_TAG, bizdate, rec.threshold, rec.granted); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_ratesim_batch.pgc b/app/src/st/batch/st_ratesim_batch.pgc new file mode 100644 index 0000000..d177453 --- /dev/null +++ b/app/src/st/batch/st_ratesim_batch.pgc @@ -0,0 +1,58 @@ +/* + * st_ratesim_batch.pgc - 요율 시뮬레이션 배치 (XA 배치). + * 당일 매입 총액을 기준으로 요율표(st_fee_rate)의 모든 구간 요율을 커서로 + * 순회하며 구간별 예상 수수료를 시뮬레이션 출력한다 (조회 전용). + * ATMI 클라이언트: tpinit/tpopen 으로 ECPG XA RM 를 열고 tpbegin 으로 ONE global tx + * 를 시작, EXEC SQL 수행 후 tpcommit 이 XA 2PC 를 구동. 사용법: st_ratesim_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" +#include "st_ratesim_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + st_ratesim_rec_t rec; + memset(&rec, 0, sizeof(rec)); + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_type[16]; + long h_gross, h_min, h_bps; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_gross FROM purchase WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "GROSS FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.day_gross = h_gross; + + EXEC SQL DECLARE ratesim_bc CURSOR FOR + SELECT fee_type, band_min, rate_bps FROM st_fee_rate ORDER BY fee_type, band_min; + EXEC SQL OPEN ratesim_bc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH ratesim_bc INTO :h_type, :h_min, :h_bps; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ratesim_bc; tpabort(0); return 1; } + { + long sim = h_gross * h_bps / 10000; /* 이 구간 요율이 전액 적용됐을 때 */ + rec.bands++; + rec.projected += sim; + printf(" SIM %-8s band_min=%ld bps=%ld -> fee=%ld\n", h_type, h_min, h_bps, sim); + } + } + EXEC SQL CLOSE ratesim_bc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s gross=%ld bands=%ld projected_total=%ld\n", + ST_RATESIM_TAG, bizdate, rec.day_gross, rec.bands, rec.projected); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_recvsweep_batch.pgc b/app/src/st/batch/st_recvsweep_batch.pgc new file mode 100644 index 0000000..907e306 --- /dev/null +++ b/app/src/st/batch/st_recvsweep_batch.pgc @@ -0,0 +1,62 @@ +/* + * st_recvsweep_batch.pgc - 미수금 상계 스윕 배치 (미수 가맹점 커서 + 상계 + XA). + * 매입 조인으로 미수금(음수 조정 누계)이 있는 가맹점을 커서로 찾고, 각 + * 가맹점의 당일 예정 지급에서 미수금과 가용액의 min 만큼 상계 차감한다. + * 사용법: st_recvsweep_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" +#include "st_recvsweep_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + st_recvsweep_rec_t rec; + memset(&rec, 0, sizeof(rec)); + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_recv, h_avail, h_off; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE recvsweep_bc CURSOR FOR + SELECT p.merchant_id, -sum(a.delta) + FROM st_adjust a JOIN purchase p ON p.purchase_id = a.purchase_id + WHERE a.delta < 0 + GROUP BY p.merchant_id + HAVING sum(a.delta) < 0 + ORDER BY p.merchant_id; + EXEC SQL OPEN recvsweep_bc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH recvsweep_bc INTO :h_merch, :h_recv; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE recvsweep_bc; tpabort(0); return 1; } + EXEC SQL SELECT coalesce(sum(net_amount),0) INTO :h_avail FROM st_payment + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status = 'SCHEDULED'; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE recvsweep_bc; tpabort(0); return 1; } + h_off = (h_recv < h_avail) ? h_recv : h_avail; + if (h_off <= 0) continue; + EXEC SQL UPDATE st_payment SET net_amount = net_amount - :h_off + WHERE pay_id = (SELECT min(pay_id) FROM st_payment + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status = 'SCHEDULED'); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE recvsweep_bc; tpabort(0); return 1; } + rec.merchants++; + rec.offset_sum += h_off; + } + EXEC SQL CLOSE recvsweep_bc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s offset_merchants=%ld offset_sum=%ld\n", + ST_RECVSWEEP_TAG, bizdate, rec.merchants, rec.offset_sum); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_rerun_batch.pgc b/app/src/st/batch/st_rerun_batch.pgc new file mode 100644 index 0000000..bf20da0 --- /dev/null +++ b/app/src/st/batch/st_rerun_batch.pgc @@ -0,0 +1,43 @@ +/* + * st_rerun_batch.pgc - 정산 재실행 배치 (DELETE + INSERT..SELECT + XA). + * 당일 MDR 정산 상세를 전량 삭제한 뒤 정산-매입 조인으로 매입 fee 기준의 + * MDR 상세를 집합 재생성한다 (정산 재실행). 사용법: st_rerun_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" +#include "st_rerun_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + st_rerun_rec_t rec; + memset(&rec, 0, sizeof(rec)); + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DELETE FROM st_settle_dtl WHERE biz_date = :h_bizdate AND fee_type = 'MDR'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "PURGE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.purged = (long) sqlca.sqlerrd[2]; + + EXEC SQL INSERT INTO st_settle_dtl (dtl_id, settlement_id, purchase_id, merchant_id, fee_type, amount, biz_date) + SELECT nextval('st_dtl_seq'), s.settlement_id, s.purchase_id, s.merchant_id, 'MDR', p.fee, s.biz_date + FROM settlement s JOIN purchase p ON p.purchase_id = s.purchase_id + WHERE s.biz_date = :h_bizdate AND s.status <> 'CANCELLED'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "REBUILD FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.rebuilt = (long) sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s purged=%ld rebuilt=%ld\n", ST_RERUN_TAG, bizdate, rec.purged, rec.rebuilt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_retrorate_batch.pgc b/app/src/st/batch/st_retrorate_batch.pgc new file mode 100644 index 0000000..67bd094 --- /dev/null +++ b/app/src/st/batch/st_retrorate_batch.pgc @@ -0,0 +1,57 @@ +/* + * st_retrorate_batch.pgc - 요율 소급적용 배치 (조인 커서 + 행별 조정 + XA). + * 당일 MDR 정산 상세를 매입 금액과 조인 순회하며 신규 bps 로 재계산, 기계상 + * 수수료와의 차액을 건별 소급 조정으로 기록한다. 사용법: st_retrorate_batch [YYYY-MM-DD] [bps] + */ +#include +#include +#include +#include +#include "st_dbio.h" +#include "st_retrorate_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + st_retrorate_rec_t rec; + memset(&rec, 0, sizeof(rec)); + rec.new_bps = (argc > 2) ? atol(argv[2]) : ST_RETRORATE_DEF_BPS; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_sid, h_pid, h_old, h_amount; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE retro_bc CURSOR FOR + SELECT d.settlement_id, d.purchase_id, d.amount, p.amount + FROM st_settle_dtl d JOIN purchase p ON p.purchase_id = d.purchase_id + WHERE d.biz_date = :h_bizdate AND d.fee_type = 'MDR'; + EXEC SQL OPEN retro_bc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH retro_bc INTO :h_sid, :h_pid, :h_old, :h_amount; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE retro_bc; tpabort(0); return 1; } + { + long newfee = h_amount * rec.new_bps / 10000; + long diff = newfee - h_old; + if (diff == 0) continue; + if (stdb_insert_adjust(h_sid, h_pid, diff, "RETRO_RATE", bizdate) < 0) { EXEC SQL CLOSE retro_bc; tpabort(0); return 1; } + rec.adjusted++; + rec.delta_sum += diff; + } + } + EXEC SQL CLOSE retro_bc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s new_bps=%ld adjusted=%ld delta_sum=%ld\n", + ST_RETRORATE_TAG, bizdate, rec.new_bps, rec.adjusted, rec.delta_sum); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_vatreport_batch.pgc b/app/src/st/batch/st_vatreport_batch.pgc new file mode 100644 index 0000000..60d9ddd --- /dev/null +++ b/app/src/st/batch/st_vatreport_batch.pgc @@ -0,0 +1,52 @@ +/* + * st_vatreport_batch.pgc - 부가세 신고자료 생성 배치 (월 집계 + 원장 마커 + XA). + * 대상 월의 정산 상세 VAT 합계와 세금계산서 공급가/세액/건수를 집계·대사해 + * 신고 기초자료를 출력하고, VATRPT 원장 마커를 남긴다. + * 사용법: st_vatreport_batch [YYYY-MM-DD (해당 월 기준일)] + */ +#include +#include +#include +#include +#include "st_dbio.h" +#include "st_vatreport_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + st_vatreport_rec_t rec; + memset(&rec, 0, sizeof(rec)); + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_dtlvat, h_cnt, h_supply, h_invvat; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_dtlvat FROM st_settle_dtl + WHERE fee_type = 'VAT' + AND to_char(biz_date,'YYYY-MM') = to_char(:h_bizdate::date,'YYYY-MM'); + if (sqlca.sqlcode < 0) { fprintf(stderr, "DTLVAT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.dtl_vat = h_dtlvat; + + EXEC SQL SELECT count(*), coalesce(sum(supply),0), coalesce(sum(vat),0) + INTO :h_cnt, :h_supply, :h_invvat FROM ac_tax_invoice + WHERE to_char(biz_date,'YYYY-MM') = to_char(:h_bizdate::date,'YYYY-MM'); + if (sqlca.sqlcode < 0) { fprintf(stderr, "INV FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.inv_cnt = h_cnt; rec.inv_supply = h_supply; rec.inv_vat = h_invvat; + + EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date) + VALUES (nextval('ledger_seq'), 0, 'VATRPT', :h_dtlvat, :h_bizdate); + if (sqlca.sqlcode < 0) { fprintf(stderr, "MARKER FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: base=%s dtl_vat=%ld inv_cnt=%ld supply=%ld inv_vat=%ld diff=%ld\n", + ST_VATREPORT_TAG, bizdate, rec.dtl_vat, rec.inv_cnt, rec.inv_supply, rec.inv_vat, + rec.inv_vat - rec.dtl_vat); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_weekly_batch.pgc b/app/src/st/batch/st_weekly_batch.pgc new file mode 100644 index 0000000..58aad29 --- /dev/null +++ b/app/src/st/batch/st_weekly_batch.pgc @@ -0,0 +1,42 @@ +/* + * st_weekly_batch.pgc - 주간 정산주기 배치 (7일 윈도 집합 INSERT..SELECT + XA). + * 주 종료일 기준 직전 7일 정산 net 을 가맹점별 GROUP BY/HAVING 으로 합산해 + * 주 1회 지급(SCHEDULED)을 일괄 생성한다. 사용법: st_weekly_batch [주종료일 YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" +#include "st_weekly_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + st_weekly_rec_t rec; + rec.merchants = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL INSERT INTO st_payment (pay_id, merchant_id, net_amount, pay_date, status, biz_date) + SELECT nextval('st_pay_seq'), merchant_id, sum(net), :h_bizdate::date + 1, 'SCHEDULED', :h_bizdate + FROM settlement + WHERE status <> 'CANCELLED' + AND biz_date BETWEEN :h_bizdate::date - 6 AND :h_bizdate::date + GROUP BY merchant_id + HAVING sum(net) > 0; + if (sqlca.sqlcode < 0) { fprintf(stderr, "%s FAIL [%d] %s\n", ST_WEEKLY_TAG, sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.merchants = (long) sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: week_end=%s weekly_payments=%ld\n", ST_WEEKLY_TAG, bizdate, rec.merchants); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_whtreport_batch.pgc b/app/src/st/batch/st_whtreport_batch.pgc new file mode 100644 index 0000000..73f0108 --- /dev/null +++ b/app/src/st/batch/st_whtreport_batch.pgc @@ -0,0 +1,51 @@ +/* + * st_whtreport_batch.pgc - 원천세 명세 배치 (가맹점별 커서 명세 출력 + XA). + * 대상 월의 정산 상세(WHT)를 가맹점별 GROUP BY 커서로 순회하며 원천세 명세 + * 라인을 한 줄씩 출력하고 총액을 집계한다 (신고 명세서 생성, 조회 전용). + * 사용법: st_whtreport_batch [YYYY-MM-DD (해당 월 기준일)] + */ +#include +#include +#include +#include +#include "st_dbio.h" +#include "st_whtreport_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + st_whtreport_rec_t rec; + memset(&rec, 0, sizeof(rec)); + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_cnt, h_wht; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE whtrpt_bc CURSOR FOR + SELECT merchant_id, count(*), coalesce(sum(amount),0) FROM st_settle_dtl + WHERE fee_type = 'WHT' + AND to_char(biz_date,'YYYY-MM') = to_char(:h_bizdate::date,'YYYY-MM') + GROUP BY merchant_id ORDER BY merchant_id; + EXEC SQL OPEN whtrpt_bc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH whtrpt_bc INTO :h_merch, :h_cnt, :h_wht; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE whtrpt_bc; tpabort(0); return 1; } + printf(" WHT %-16s lines=%ld tax=%ld\n", h_merch, h_cnt, h_wht); + rec.lines++; + rec.wht_total += h_wht; + } + EXEC SQL CLOSE whtrpt_bc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: base=%s merchants=%ld wht_total=%ld\n", ST_WHTREPORT_TAG, bizdate, rec.lines, rec.wht_total); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/dbio/st_approve_batch.h b/app/src/st/dbio/st_approve_batch.h new file mode 100644 index 0000000..0256163 --- /dev/null +++ b/app/src/st/dbio/st_approve_batch.h @@ -0,0 +1,6 @@ +/* st_approve_batch.h - st 정산 승인 워크플로 배치 copybook. */ +#ifndef ST_APPROVE_BATCH_H +#define ST_APPROVE_BATCH_H +#define ST_APPROVE_TAG "st_approve_batch" +typedef struct { long approved; long skipped; } st_approve_rec_t; +#endif diff --git a/app/src/st/dbio/st_deposit_batch.h b/app/src/st/dbio/st_deposit_batch.h new file mode 100644 index 0000000..c10ff81 --- /dev/null +++ b/app/src/st/dbio/st_deposit_batch.h @@ -0,0 +1,7 @@ +/* st_deposit_batch.h - st 보증금 차감 배치 copybook. */ +#ifndef ST_DEPOSIT_BATCH_H +#define ST_DEPOSIT_BATCH_H +#define ST_DEPOSIT_TAG "st_deposit_batch" +#define ST_DEPOSIT_DEF_CUT 50000L +typedef struct { long cut_each; long cut_rows; } st_deposit_rec_t; +#endif diff --git a/app/src/st/dbio/st_grproll_batch.h b/app/src/st/dbio/st_grproll_batch.h new file mode 100644 index 0000000..a017879 --- /dev/null +++ b/app/src/st/dbio/st_grproll_batch.h @@ -0,0 +1,7 @@ +/* st_grproll_batch.h - st 가맹점그룹 통합정산 롤업 배치 copybook. */ +#ifndef ST_GRPROLL_BATCH_H +#define ST_GRPROLL_BATCH_H +#define ST_GRPROLL_TAG "st_grproll_batch" +#define ST_GRPROLL_PFX_LEN 2 +typedef struct { long groups; long rolled_net; } st_grproll_rec_t; +#endif diff --git a/app/src/st/dbio/st_incent_batch.h b/app/src/st/dbio/st_incent_batch.h new file mode 100644 index 0000000..68e8b5a --- /dev/null +++ b/app/src/st/dbio/st_incent_batch.h @@ -0,0 +1,6 @@ +/* st_incent_batch.h - st 구간별 인센티브 배치 copybook. */ +#ifndef ST_INCENT_BATCH_H +#define ST_INCENT_BATCH_H +#define ST_INCENT_TAG "st_incent_batch" +typedef struct { long tier1; long tier2; long tier3; long paid_sum; } st_incent_rec_t; +#endif diff --git a/app/src/st/dbio/st_partial_batch.h b/app/src/st/dbio/st_partial_batch.h new file mode 100644 index 0000000..bcc3c06 --- /dev/null +++ b/app/src/st/dbio/st_partial_batch.h @@ -0,0 +1,6 @@ +/* st_partial_batch.h - st 부분정산 잔여 스윕 배치 copybook. */ +#ifndef ST_PARTIAL_BATCH_H +#define ST_PARTIAL_BATCH_H +#define ST_PARTIAL_TAG "st_partial_batch" +typedef struct { long swept; long swept_amt; } st_partial_rec_t; +#endif diff --git a/app/src/st/dbio/st_promo_batch.h b/app/src/st/dbio/st_promo_batch.h new file mode 100644 index 0000000..3ec7a2e --- /dev/null +++ b/app/src/st/dbio/st_promo_batch.h @@ -0,0 +1,7 @@ +/* st_promo_batch.h - st 프로모션/이벤트 정산 배치 copybook. */ +#ifndef ST_PROMO_BATCH_H +#define ST_PROMO_BATCH_H +#define ST_PROMO_TAG "st_promo_batch" +#define ST_PROMO_DEF_THRESH 10000000L +typedef struct { long threshold; long granted; } st_promo_rec_t; +#endif diff --git a/app/src/st/dbio/st_ratesim_batch.h b/app/src/st/dbio/st_ratesim_batch.h new file mode 100644 index 0000000..f8a1caa --- /dev/null +++ b/app/src/st/dbio/st_ratesim_batch.h @@ -0,0 +1,6 @@ +/* st_ratesim_batch.h - st 요율 시뮬레이션 배치 copybook. */ +#ifndef ST_RATESIM_BATCH_H +#define ST_RATESIM_BATCH_H +#define ST_RATESIM_TAG "st_ratesim_batch" +typedef struct { long day_gross; long bands; long projected; } st_ratesim_rec_t; +#endif diff --git a/app/src/st/dbio/st_recvsweep_batch.h b/app/src/st/dbio/st_recvsweep_batch.h new file mode 100644 index 0000000..4dbca9c --- /dev/null +++ b/app/src/st/dbio/st_recvsweep_batch.h @@ -0,0 +1,6 @@ +/* st_recvsweep_batch.h - st 미수금 상계 스윕 배치 copybook. */ +#ifndef ST_RECVSWEEP_BATCH_H +#define ST_RECVSWEEP_BATCH_H +#define ST_RECVSWEEP_TAG "st_recvsweep_batch" +typedef struct { long merchants; long offset_sum; } st_recvsweep_rec_t; +#endif diff --git a/app/src/st/dbio/st_rerun_batch.h b/app/src/st/dbio/st_rerun_batch.h new file mode 100644 index 0000000..e6009f2 --- /dev/null +++ b/app/src/st/dbio/st_rerun_batch.h @@ -0,0 +1,6 @@ +/* st_rerun_batch.h - st 정산 재실행 배치 copybook. */ +#ifndef ST_RERUN_BATCH_H +#define ST_RERUN_BATCH_H +#define ST_RERUN_TAG "st_rerun_batch" +typedef struct { long purged; long rebuilt; } st_rerun_rec_t; +#endif diff --git a/app/src/st/dbio/st_retrorate_batch.h b/app/src/st/dbio/st_retrorate_batch.h new file mode 100644 index 0000000..c65708a --- /dev/null +++ b/app/src/st/dbio/st_retrorate_batch.h @@ -0,0 +1,7 @@ +/* st_retrorate_batch.h - st 요율 소급적용 배치 copybook. */ +#ifndef ST_RETRORATE_BATCH_H +#define ST_RETRORATE_BATCH_H +#define ST_RETRORATE_TAG "st_retrorate_batch" +#define ST_RETRORATE_DEF_BPS 22L +typedef struct { long new_bps; long adjusted; long delta_sum; } st_retrorate_rec_t; +#endif diff --git a/app/src/st/dbio/st_vatreport_batch.h b/app/src/st/dbio/st_vatreport_batch.h new file mode 100644 index 0000000..8540ddc --- /dev/null +++ b/app/src/st/dbio/st_vatreport_batch.h @@ -0,0 +1,6 @@ +/* st_vatreport_batch.h - st 부가세 신고자료 생성 배치 copybook. */ +#ifndef ST_VATREPORT_BATCH_H +#define ST_VATREPORT_BATCH_H +#define ST_VATREPORT_TAG "st_vatreport_batch" +typedef struct { long dtl_vat; long inv_cnt; long inv_supply; long inv_vat; } st_vatreport_rec_t; +#endif diff --git a/app/src/st/dbio/st_weekly_batch.h b/app/src/st/dbio/st_weekly_batch.h new file mode 100644 index 0000000..222aca1 --- /dev/null +++ b/app/src/st/dbio/st_weekly_batch.h @@ -0,0 +1,6 @@ +/* st_weekly_batch.h - st 주간 정산주기 배치 copybook. */ +#ifndef ST_WEEKLY_BATCH_H +#define ST_WEEKLY_BATCH_H +#define ST_WEEKLY_TAG "st_weekly_batch" +typedef struct { long merchants; } st_weekly_rec_t; +#endif diff --git a/app/src/st/dbio/st_whtreport_batch.h b/app/src/st/dbio/st_whtreport_batch.h new file mode 100644 index 0000000..af8ddab --- /dev/null +++ b/app/src/st/dbio/st_whtreport_batch.h @@ -0,0 +1,6 @@ +/* st_whtreport_batch.h - st 원천세 명세 배치 copybook. */ +#ifndef ST_WHTREPORT_BATCH_H +#define ST_WHTREPORT_BATCH_H +#define ST_WHTREPORT_TAG "st_whtreport_batch" +typedef struct { long lines; long wht_total; } st_whtreport_rec_t; +#endif diff --git a/app/src/st/st_svr.pgc b/app/src/st/st_svr.pgc index ca5c742..cd3c969 100644 --- a/app/src/st/st_svr.pgc +++ b/app/src/st/st_svr.pgc @@ -2,14 +2,14 @@ * st_svr.pgc - st (정산/수수료) MODULE SERVER (thin dispatcher). * * The legacy "one module server, many services" pattern: this binary owns NO - * business logic. Each of st's 30 online services now lives in its own file + * business logic. Each of st's 75 online services now lives in its own file * (app/src/st/svc/.pgc) with its own copybook header * (app/src/st/svc/.h); those objects are archived into libstsvc.a and * linked into this server. Here we only include every service copybook, tpopen() * the ECPG XA RM, tpadvertise() all services from a {name, fn} table, and * tpclose() at shutdown. * - * st_svr: ONE binary that tpadvertise()s ALL 30 online st(정산/수수료) services. + * st_svr: ONE binary that tpadvertise()s ALL 75 online st(정산/수수료) services. * SETTLE keeps the proven behavior: its own XA branch writes the settlement + * settle ledger rows. Terminal service of the 매입 chain. */ @@ -48,6 +48,51 @@ #include "ST_FXCONV.h" #include "ST_CONFIRM.h" #include "ST_STATUS.h" +#include "ST_RATESIM.h" +#include "ST_PROMOCALC.h" +#include "ST_PARTIAL.h" +#include "ST_RERUN.h" +#include "ST_CXLRECALC.h" +#include "ST_RETRORATE.h" +#include "ST_TIERINCENT.h" +#include "ST_CYCLED1.h" +#include "ST_CYCLED2.h" +#include "ST_CYCLEWEEK.h" +#include "ST_CYCLEMON.h" +#include "ST_DEPOSITCUT.h" +#include "ST_RECVOFFSET.h" +#include "ST_DEBTCOLLECT.h" +#include "ST_VATREPORT.h" +#include "ST_WHTREPORT.h" +#include "ST_APPROVE.h" +#include "ST_GRPSETTLE.h" +#include "ST_DISPUTE.h" +#include "ST_STATDETAIL.h" +#include "ST_FEEPREVIEW.h" +#include "ST_HOLDAMT.h" +#include "ST_PAYSTATUS.h" +#include "ST_ADJHIST.h" +#include "ST_DTLSUM.h" +#include "ST_MERCHRATE.h" +#include "ST_CAPCHECK.h" +#include "ST_ROUNDING.h" +#include "ST_MINFEE.h" +#include "ST_DAILYCLOSE.h" +#include "ST_REOPEN.h" +#include "ST_CARRYOVER.h" +#include "ST_NEGSWEEP.h" +#include "ST_TOPMERCH.h" +#include "ST_FEERATIO.h" +#include "ST_SPLITEVEN.h" +#include "ST_CHNLSTAT.h" +#include "ST_ISSUERSTAT.h" +#include "ST_TAXINVCHK.h" +#include "ST_FXSETTLE.h" +#include "ST_EDIRECON.h" +#include "ST_UNPAIDSUM.h" +#include "ST_PAYRETRY.h" +#include "ST_LIMITCHK.h" +#include "ST_LEDGERSNAP.h" /* ----- advertise table: {service name, function} ---------------------- */ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { @@ -66,6 +111,29 @@ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { {"ST_LEDGERLINK", ST_LEDGERLINK}, {"ST_BANDRATE", ST_BANDRATE}, {"ST_CAP", ST_CAP}, {"ST_FXCONV", ST_FXCONV}, {"ST_CONFIRM", ST_CONFIRM}, {"ST_STATUS", ST_STATUS}, + {"ST_RATESIM", ST_RATESIM}, {"ST_PROMOCALC", ST_PROMOCALC}, + {"ST_PARTIAL", ST_PARTIAL}, {"ST_RERUN", ST_RERUN}, + {"ST_CXLRECALC", ST_CXLRECALC}, {"ST_RETRORATE", ST_RETRORATE}, + {"ST_TIERINCENT", ST_TIERINCENT}, {"ST_CYCLED1", ST_CYCLED1}, + {"ST_CYCLED2", ST_CYCLED2}, {"ST_CYCLEWEEK", ST_CYCLEWEEK}, + {"ST_CYCLEMON", ST_CYCLEMON}, {"ST_DEPOSITCUT", ST_DEPOSITCUT}, + {"ST_RECVOFFSET", ST_RECVOFFSET}, {"ST_DEBTCOLLECT", ST_DEBTCOLLECT}, + {"ST_VATREPORT", ST_VATREPORT}, {"ST_WHTREPORT", ST_WHTREPORT}, + {"ST_APPROVE", ST_APPROVE}, {"ST_GRPSETTLE", ST_GRPSETTLE}, + {"ST_DISPUTE", ST_DISPUTE}, {"ST_STATDETAIL", ST_STATDETAIL}, + {"ST_FEEPREVIEW", ST_FEEPREVIEW}, {"ST_HOLDAMT", ST_HOLDAMT}, + {"ST_PAYSTATUS", ST_PAYSTATUS}, {"ST_ADJHIST", ST_ADJHIST}, + {"ST_DTLSUM", ST_DTLSUM}, {"ST_MERCHRATE", ST_MERCHRATE}, + {"ST_CAPCHECK", ST_CAPCHECK}, {"ST_ROUNDING", ST_ROUNDING}, + {"ST_MINFEE", ST_MINFEE}, {"ST_DAILYCLOSE", ST_DAILYCLOSE}, + {"ST_REOPEN", ST_REOPEN}, {"ST_CARRYOVER", ST_CARRYOVER}, + {"ST_NEGSWEEP", ST_NEGSWEEP}, {"ST_TOPMERCH", ST_TOPMERCH}, + {"ST_FEERATIO", ST_FEERATIO}, {"ST_SPLITEVEN", ST_SPLITEVEN}, + {"ST_CHNLSTAT", ST_CHNLSTAT}, {"ST_ISSUERSTAT", ST_ISSUERSTAT}, + {"ST_TAXINVCHK", ST_TAXINVCHK}, {"ST_FXSETTLE", ST_FXSETTLE}, + {"ST_EDIRECON", ST_EDIRECON}, {"ST_UNPAIDSUM", ST_UNPAIDSUM}, + {"ST_PAYRETRY", ST_PAYRETRY}, {"ST_LIMITCHK", ST_LIMITCHK}, + {"ST_LEDGERSNAP", ST_LEDGERSNAP}, {NULL, NULL} }; diff --git a/app/src/st/svc/ST_ADJHIST.h b/app/src/st/svc/ST_ADJHIST.h new file mode 100644 index 0000000..03f647e --- /dev/null +++ b/app/src/st/svc/ST_ADJHIST.h @@ -0,0 +1,19 @@ +/* + * ST_ADJHIST.h - st 서비스 ST_ADJHIST 카피북 (copybook / record header). + * 조정 이력 조회: 매입 단위 조정 건수/증감 합계/최근 사유. + */ +#ifndef ST_ADJHIST_H +#define ST_ADJHIST_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + long adj_count; + long delta_sum; + char last_reason[128]; +} st_st_adjhist_rec_t; + +void ST_ADJHIST(TPSVCINFO *p); + +#endif /* ST_ADJHIST_H */ diff --git a/app/src/st/svc/ST_ADJHIST.pgc b/app/src/st/svc/ST_ADJHIST.pgc new file mode 100644 index 0000000..125eb19 --- /dev/null +++ b/app/src/st/svc/ST_ADJHIST.pgc @@ -0,0 +1,39 @@ +/* + * ST_ADJHIST.pgc - st 모듈 서비스 ST_ADJHIST (one service = one file). + * 조정 이력 조회: 매입에 걸린 정산 조정을 최신순 커서로 순회해 건수·증감 + * 누계와 가장 최근 조정 사유를 회신한다. 이력 없으면 RC=1. 조회 전용. + */ +#include "ST_ADJHIST.h" + +/* 54. ST_ADJHIST - 매입별 조정 이력 요약. */ +void ST_ADJHIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char last[128] = ""; + long cnt = 0, sum = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_delta; + char h_reason[128]; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + EXEC SQL DECLARE adjhist_c CURSOR FOR + SELECT delta, reason FROM st_adjust WHERE purchase_id = :h_pid ORDER BY adj_id DESC; + EXEC SQL OPEN adjhist_c; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH adjhist_c INTO :h_delta, :h_reason; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE adjhist_c; FAIL(b); } + if (cnt == 0) { strncpy(last, h_reason, sizeof(last)-1); last[sizeof(last)-1] = 0; } + cnt++; + sum += h_delta; + } + EXEC SQL CLOSE adjhist_c; + if (cnt == 0) { setl(b, T_RC, 1); OK(b); } + setl(b, T_COUNT, cnt); + setl(b, T_DELTA, sum); + Bchg(b, T_REASON, 0, last, 0L); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_APPROVE.h b/app/src/st/svc/ST_APPROVE.h new file mode 100644 index 0000000..806aa03 --- /dev/null +++ b/app/src/st/svc/ST_APPROVE.h @@ -0,0 +1,18 @@ +/* + * ST_APPROVE.h - st 서비스 ST_APPROVE 카피북 (copybook / record header). + * 정산 확정/승인 워크플로: CONFIRMED -> APPROVED 승격 + 승인 원장. + */ +#ifndef ST_APPROVE_H +#define ST_APPROVE_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char approver[32]; + char cur_status[16]; +} st_st_approve_rec_t; + +void ST_APPROVE(TPSVCINFO *p); + +#endif /* ST_APPROVE_H */ diff --git a/app/src/st/svc/ST_APPROVE.pgc b/app/src/st/svc/ST_APPROVE.pgc new file mode 100644 index 0000000..43e3755 --- /dev/null +++ b/app/src/st/svc/ST_APPROVE.pgc @@ -0,0 +1,37 @@ +/* + * ST_APPROVE.pgc - st 모듈 서비스 ST_APPROVE (one service = one file). + * 정산 승인 워크플로: 최신 정산 상태를 검증해 CONFIRMED 인 건만 APPROVED 로 + * 승격하고 승인 원장(STAPPROVE)을 남긴다. 상태 위반 시 RC=2 로 현재 상태 회신. + */ +#include "ST_APPROVE.h" + +/* 47. ST_APPROVE - 정산 승인 (CONFIRMED -> APPROVED). */ +void ST_APPROVE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid; + char h_status[16], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + EXEC SQL SELECT status, to_char(biz_date,'YYYY-MM-DD') INTO :h_status, :h_bizdate + FROM settlement WHERE purchase_id = :h_pid ORDER BY settlement_id DESC LIMIT 1; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); Bchg(b, T_STATUS, 0, "NONE", 0L); OK(b); } + if (strcmp(h_status, "CONFIRMED") != 0) { + setl(b, T_RC, 2); /* 워크플로 순서 위반: 확정 전 승인 불가 */ + Bchg(b, T_STATUS, 0, h_status, 0L); + OK(b); + } + EXEC SQL UPDATE settlement SET status = 'APPROVED' + WHERE purchase_id = :h_pid AND status = 'CONFIRMED'; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date) + VALUES (nextval('ledger_seq'), :h_pid, 'STAPPROVE', 0, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_RC, 0); + Bchg(b, T_STATUS, 0, "A", 0L); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_CAPCHECK.h b/app/src/st/svc/ST_CAPCHECK.h new file mode 100644 index 0000000..83303af --- /dev/null +++ b/app/src/st/svc/ST_CAPCHECK.h @@ -0,0 +1,18 @@ +/* + * ST_CAPCHECK.h - st 서비스 ST_CAPCHECK 카피북 (copybook / record header). + * 수수료 상한 도달 점검: 상한 초과 여부/초과분 조회 (판정 전용). + */ +#ifndef ST_CAPCHECK_H +#define ST_CAPCHECK_H + +#include "st_svc.h" + +typedef struct { + long fee; + long cap; + long over; +} st_st_capcheck_rec_t; + +void ST_CAPCHECK(TPSVCINFO *p); + +#endif /* ST_CAPCHECK_H */ diff --git a/app/src/st/svc/ST_CAPCHECK.pgc b/app/src/st/svc/ST_CAPCHECK.pgc new file mode 100644 index 0000000..20c0a3c --- /dev/null +++ b/app/src/st/svc/ST_CAPCHECK.pgc @@ -0,0 +1,26 @@ +/* + * ST_CAPCHECK.pgc - st 모듈 서비스 ST_CAPCHECK (one service = one file). + * 수수료 상한 도달 점검: 수수료가 상한을 초과하는지 판정만 하고 초과분을 + * 회신한다. ST_CAP 과 달리 조정을 기록하지 않는 순수 판정 서비스 (SQL 없음). + */ +#include "ST_CAPCHECK.h" + +/* 57. ST_CAPCHECK - 상한 초과 여부 판정 (조정 없음). */ +void ST_CAPCHECK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long fee = getl(b, T_FEE), cap = getl(b, T_ARG1), over; + if (cap <= 0) cap = 100000; + over = fee - cap; + if (over > 0) { + setl(b, T_RC, 1); /* 상한 초과: 감액 대상 */ + setl(b, T_DELTA, over); + } else { + setl(b, T_RC, 0); + setl(b, T_DELTA, 0); + } + setl(b, T_AMT1, cap); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_CARRYOVER.h b/app/src/st/svc/ST_CARRYOVER.h new file mode 100644 index 0000000..f4fbfa8 --- /dev/null +++ b/app/src/st/svc/ST_CARRYOVER.h @@ -0,0 +1,20 @@ +/* + * ST_CARRYOVER.h - st 서비스 ST_CARRYOVER 카피북 (copybook / record header). + * 이월 정산: 미정산 매입 합계를 익영업일 지급으로 이월 예약. + */ +#ifndef ST_CARRYOVER_H +#define ST_CARRYOVER_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long unsettled_cnt; + long unsettled_sum; + char carry_date[16]; +} st_st_carryover_rec_t; + +void ST_CARRYOVER(TPSVCINFO *p); + +#endif /* ST_CARRYOVER_H */ diff --git a/app/src/st/svc/ST_CARRYOVER.pgc b/app/src/st/svc/ST_CARRYOVER.pgc new file mode 100644 index 0000000..d3b1b17 --- /dev/null +++ b/app/src/st/svc/ST_CARRYOVER.pgc @@ -0,0 +1,37 @@ +/* + * ST_CARRYOVER.pgc - st 모듈 서비스 ST_CARRYOVER (one service = one file). + * 이월 정산: 대사완료(M) 상태이지만 정산 레코드가 없는 매입(NOT EXISTS)을 + * 집계해 익영업일 지급으로 이월 예약한다. 대상 없으면 RC=1 (dbio). + */ +#include "ST_CARRYOVER.h" + +/* 62. ST_CARRYOVER - 미정산분 익영업일 이월 예약. */ +void ST_CARRYOVER(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16], carry[16]; + long pay; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_sum; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_cnt, :h_sum FROM purchase p + WHERE p.merchant_id = :h_merch AND p.biz_date = :h_bizdate AND p.status = 'M' + AND NOT EXISTS (SELECT 1 FROM settlement s WHERE s.purchase_id = p.purchase_id); + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt == 0) { setl(b, T_RC, 1); setl(b, T_COUNT, 0); OK(b); } + if (acq_next_bizday(bizdate, carry) < 0) FAIL(b); + pay = stdb_insert_payment(merch, h_sum, carry, "SCHEDULED", bizdate); + if (pay < 0) FAIL(b); + setl(b, T_ID1, pay); + setl(b, T_COUNT, h_cnt); + setl(b, T_NET, h_sum); + Bchg(b, T_KEY1, 0, carry, 0L); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_CHNLSTAT.h b/app/src/st/svc/ST_CHNLSTAT.h new file mode 100644 index 0000000..7171e94 --- /dev/null +++ b/app/src/st/svc/ST_CHNLSTAT.h @@ -0,0 +1,20 @@ +/* + * ST_CHNLSTAT.h - st 서비스 ST_CHNLSTAT 카피북 (copybook / record header). + * 채널별 정산 통계: 매입 채널 단위 정산 분해 + 최대 채널. + */ +#ifndef ST_CHNLSTAT_H +#define ST_CHNLSTAT_H + +#include "st_svc.h" + +typedef struct { + char biz_date[16]; + long channel_cnt; + long grand_net; + char top_channel[16]; + long top_net; +} st_st_chnlstat_rec_t; + +void ST_CHNLSTAT(TPSVCINFO *p); + +#endif /* ST_CHNLSTAT_H */ diff --git a/app/src/st/svc/ST_CHNLSTAT.pgc b/app/src/st/svc/ST_CHNLSTAT.pgc new file mode 100644 index 0000000..92d0409 --- /dev/null +++ b/app/src/st/svc/ST_CHNLSTAT.pgc @@ -0,0 +1,46 @@ +/* + * ST_CHNLSTAT.pgc - st 모듈 서비스 ST_CHNLSTAT (one service = one file). + * 채널별 정산 통계 세분: 정산-매입 조인 커서로 채널(POS/EDC/EDI/FOREIGN)별 + * 건수·net 을 분해 집계하고 최대 채널을 회신한다. 조회 전용. + */ +#include "ST_CHNLSTAT.h" + +/* 67. ST_CHNLSTAT - 채널별 정산 net 분해 통계. */ +void ST_CHNLSTAT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], topch[16] = ""; + long channels = 0, grand = 0, topsum = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_net; + char h_ch[16], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE chnlstat_c CURSOR FOR + SELECT p.channel, count(*), coalesce(sum(s.net),0) + FROM settlement s JOIN purchase p ON p.purchase_id = s.purchase_id + WHERE s.biz_date = :h_bizdate GROUP BY p.channel ORDER BY 3 DESC; + EXEC SQL OPEN chnlstat_c; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH chnlstat_c INTO :h_ch, :h_cnt, :h_net; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE chnlstat_c; FAIL(b); } + if (channels == 0) { + strncpy(topch, h_ch, sizeof(topch)-1); topch[sizeof(topch)-1] = 0; + topsum = h_net; + } + channels++; + grand += h_net; + } + EXEC SQL CLOSE chnlstat_c; + if (channels == 0) { setl(b, T_RC, 1); OK(b); } + setl(b, T_COUNT, channels); + setl(b, T_GROSS, grand); + setl(b, T_AMT1, topsum); + Bchg(b, T_CHANNEL, 0, topch, 0L); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_CXLRECALC.h b/app/src/st/svc/ST_CXLRECALC.h new file mode 100644 index 0000000..196193b --- /dev/null +++ b/app/src/st/svc/ST_CXLRECALC.h @@ -0,0 +1,18 @@ +/* + * ST_CXLRECALC.h - st 서비스 ST_CXLRECALC 카피북 (copybook / record header). + * 정산 취소분 재계산: 취소 누계를 반영한 정산 net 재산정. + */ +#ifndef ST_CXLRECALC_H +#define ST_CXLRECALC_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + long cancel_sum; + long new_net; +} st_st_cxlrecalc_rec_t; + +void ST_CXLRECALC(TPSVCINFO *p); + +#endif /* ST_CXLRECALC_H */ diff --git a/app/src/st/svc/ST_CXLRECALC.pgc b/app/src/st/svc/ST_CXLRECALC.pgc new file mode 100644 index 0000000..f13919a --- /dev/null +++ b/app/src/st/svc/ST_CXLRECALC.pgc @@ -0,0 +1,29 @@ +/* + * ST_CXLRECALC.pgc - st 모듈 서비스 ST_CXLRECALC (one service = one file). + * 정산 취소분 재계산: ac_cancel 누계를 매입 net 에서 차감해 정산 net 을 + * 재산정하고 (음수 방지 클램프) SETTLED 정산 레코드에 반영한다. + */ +#include "ST_CXLRECALC.h" + +/* 35. ST_CXLRECALC - 취소 누계 반영 정산 net 재계산. */ +void ST_CXLRECALC(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_cxl, h_pnet, h_new; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_cxl FROM ac_cancel WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT net INTO :h_pnet FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + h_new = h_pnet - h_cxl; + if (h_new < 0) h_new = 0; /* 과취소 방지 클램프 */ + EXEC SQL UPDATE settlement SET net = :h_new WHERE purchase_id = :h_pid AND status = 'SETTLED'; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_NET, h_new); + setl(b, T_DELTA, -h_cxl); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_CYCLED1.h b/app/src/st/svc/ST_CYCLED1.h new file mode 100644 index 0000000..fa5320e --- /dev/null +++ b/app/src/st/svc/ST_CYCLED1.h @@ -0,0 +1,19 @@ +/* + * ST_CYCLED1.h - st 서비스 ST_CYCLED1 카피북 (copybook / record header). + * D+1 정산주기: 당일 SETTLED 합계를 익영업일 지급으로 예약. + */ +#ifndef ST_CYCLED1_H +#define ST_CYCLED1_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + char pay_date[16]; + long net_sum; +} st_st_cycled1_rec_t; + +void ST_CYCLED1(TPSVCINFO *p); + +#endif /* ST_CYCLED1_H */ diff --git a/app/src/st/svc/ST_CYCLED1.pgc b/app/src/st/svc/ST_CYCLED1.pgc new file mode 100644 index 0000000..5f42c18 --- /dev/null +++ b/app/src/st/svc/ST_CYCLED1.pgc @@ -0,0 +1,35 @@ +/* + * ST_CYCLED1.pgc - st 모듈 서비스 ST_CYCLED1 (one service = one file). + * D+1 정산주기: 당일 SETTLED 정산 net 합계를 산출해 익영업일(pay_date=D+1) + * 지급(SCHEDULED)으로 예약한다 (dbio). + */ +#include "ST_CYCLED1.h" + +/* 38. ST_CYCLED1 - D+1 주기 지급 예약. */ +void ST_CYCLED1(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16], payd[16]; + long pay; + EXEC SQL BEGIN DECLARE SECTION; + long h_sum; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(net),0) INTO :h_sum FROM settlement + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status = 'SETTLED'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_sum <= 0) { setl(b, T_RC, 1); OK(b); } + if (acq_next_bizday(bizdate, payd) < 0) FAIL(b); + pay = stdb_insert_payment(merch, h_sum, payd, "SCHEDULED", bizdate); + if (pay < 0) FAIL(b); + setl(b, T_ID1, pay); + setl(b, T_NET, h_sum); + Bchg(b, T_KEY1, 0, payd, 0L); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_CYCLED2.h b/app/src/st/svc/ST_CYCLED2.h new file mode 100644 index 0000000..a10ef5b --- /dev/null +++ b/app/src/st/svc/ST_CYCLED2.h @@ -0,0 +1,20 @@ +/* + * ST_CYCLED2.h - st 서비스 ST_CYCLED2 카피북 (copybook / record header). + * D+2 정산주기: 확정(CONFIRMED)분을 2영업일 뒤 지급으로 예약. + */ +#ifndef ST_CYCLED2_H +#define ST_CYCLED2_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + char pay_date[16]; + long confirmed_cnt; + long net_sum; +} st_st_cycled2_rec_t; + +void ST_CYCLED2(TPSVCINFO *p); + +#endif /* ST_CYCLED2_H */ diff --git a/app/src/st/svc/ST_CYCLED2.pgc b/app/src/st/svc/ST_CYCLED2.pgc new file mode 100644 index 0000000..41dfd2b --- /dev/null +++ b/app/src/st/svc/ST_CYCLED2.pgc @@ -0,0 +1,37 @@ +/* + * ST_CYCLED2.pgc - st 모듈 서비스 ST_CYCLED2 (one service = one file). + * D+2 정산주기: 당일 CONFIRMED 확정 정산만 집계해 2영업일 뒤(D+2, 주말 스킵) + * 지급으로 예약한다. 확정 건이 없으면 RC=1 (dbio). + */ +#include "ST_CYCLED2.h" + +/* 39. ST_CYCLED2 - D+2 주기 지급 예약 (확정분만). */ +void ST_CYCLED2(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16], d1[16], d2[16]; + long pay; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_sum; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_cnt, :h_sum FROM settlement + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status = 'CONFIRMED'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt == 0) { setl(b, T_RC, 1); setl(b, T_COUNT, 0); OK(b); } + if (acq_next_bizday(bizdate, d1) < 0) FAIL(b); + if (acq_next_bizday(d1, d2) < 0) FAIL(b); /* D+1 -> D+2, 주말 스킵 2회 */ + pay = stdb_insert_payment(merch, h_sum, d2, "SCHEDULED", bizdate); + if (pay < 0) FAIL(b); + setl(b, T_ID1, pay); + setl(b, T_COUNT, h_cnt); + setl(b, T_NET, h_sum); + Bchg(b, T_KEY1, 0, d2, 0L); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_CYCLEMON.h b/app/src/st/svc/ST_CYCLEMON.h new file mode 100644 index 0000000..bd94184 --- /dev/null +++ b/app/src/st/svc/ST_CYCLEMON.h @@ -0,0 +1,20 @@ +/* + * ST_CYCLEMON.h - st 서비스 ST_CYCLEMON 카피북 (copybook / record header). + * 월간 정산주기: 해당 월 정산 합계를 월 1회 지급으로 예약. + */ +#ifndef ST_CYCLEMON_H +#define ST_CYCLEMON_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + char yyyymm[16]; + long month_cnt; + long month_sum; + long pay_id; +} st_st_cyclemon_rec_t; + +void ST_CYCLEMON(TPSVCINFO *p); + +#endif /* ST_CYCLEMON_H */ diff --git a/app/src/st/svc/ST_CYCLEMON.pgc b/app/src/st/svc/ST_CYCLEMON.pgc new file mode 100644 index 0000000..aad0bda --- /dev/null +++ b/app/src/st/svc/ST_CYCLEMON.pgc @@ -0,0 +1,39 @@ +/* + * ST_CYCLEMON.pgc - st 모듈 서비스 ST_CYCLEMON (one service = one file). + * 월간 정산주기: 대상 월(YYYY-MM)의 가맹점 정산 net 을 합산해 지급일 기준으로 + * 월 1회 지급 레코드를 직접 INSERT 한다. 합계 0 이하이면 RC=1. + */ +#include "ST_CYCLEMON.h" + +/* 41. ST_CYCLEMON - 월간 통합 지급 예약 (직접 INSERT). */ +void ST_CYCLEMON(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16], ym[16], like[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_sum, h_pay; + char h_merch[64], h_bizdate[16], h_like[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + gets_(b, T_KEY1, ym, sizeof(ym)); if (ym[0] == 0) strcpy(ym, "2026-07"); + snprintf(like, sizeof(like), "%s%%", ym); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(h_like, like, sizeof(h_like)-1); h_like[sizeof(h_like)-1] = 0; + EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_cnt, :h_sum FROM settlement + WHERE merchant_id = :h_merch AND to_char(biz_date,'YYYY-MM-DD') LIKE :h_like; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_sum <= 0) { setl(b, T_RC, 1); setl(b, T_COUNT, h_cnt); OK(b); } + EXEC SQL SELECT nextval('st_pay_seq') INTO :h_pay; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO st_payment (pay_id, merchant_id, net_amount, pay_date, status, biz_date) + VALUES (:h_pay, :h_merch, :h_sum, :h_bizdate, 'SCHEDULED', :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_ID1, h_pay); + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_CYCLEWEEK.h b/app/src/st/svc/ST_CYCLEWEEK.h new file mode 100644 index 0000000..72dfa43 --- /dev/null +++ b/app/src/st/svc/ST_CYCLEWEEK.h @@ -0,0 +1,19 @@ +/* + * ST_CYCLEWEEK.h - st 서비스 ST_CYCLEWEEK 카피북 (copybook / record header). + * 주간 정산주기: 최근 7일 정산 합계를 주 1회 지급으로 예약. + */ +#ifndef ST_CYCLEWEEK_H +#define ST_CYCLEWEEK_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + char week_end[16]; + long week_cnt; + long week_sum; +} st_st_cycleweek_rec_t; + +void ST_CYCLEWEEK(TPSVCINFO *p); + +#endif /* ST_CYCLEWEEK_H */ diff --git a/app/src/st/svc/ST_CYCLEWEEK.pgc b/app/src/st/svc/ST_CYCLEWEEK.pgc new file mode 100644 index 0000000..39d76f7 --- /dev/null +++ b/app/src/st/svc/ST_CYCLEWEEK.pgc @@ -0,0 +1,36 @@ +/* + * ST_CYCLEWEEK.pgc - st 모듈 서비스 ST_CYCLEWEEK (one service = one file). + * 주간 정산주기: 주 종료일 기준 직전 7일(취소 제외) 정산 net 을 합산해 + * 주 1회 지급으로 예약한다. 대상 없으면 RC=1 (dbio). + */ +#include "ST_CYCLEWEEK.h" + +/* 40. ST_CYCLEWEEK - 주간(7일 윈도) 통합 지급 예약. */ +void ST_CYCLEWEEK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16], payd[16]; + long pay; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_sum; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_cnt, :h_sum FROM settlement + WHERE merchant_id = :h_merch AND status <> 'CANCELLED' + AND biz_date BETWEEN :h_bizdate::date - 6 AND :h_bizdate::date; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt == 0 || h_sum <= 0) { setl(b, T_RC, 1); OK(b); } + if (acq_next_bizday(bizdate, payd) < 0) FAIL(b); + pay = stdb_insert_payment(merch, h_sum, payd, "SCHEDULED", bizdate); + if (pay < 0) FAIL(b); + setl(b, T_ID1, pay); + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_DAILYCLOSE.h b/app/src/st/svc/ST_DAILYCLOSE.h new file mode 100644 index 0000000..4626322 --- /dev/null +++ b/app/src/st/svc/ST_DAILYCLOSE.h @@ -0,0 +1,20 @@ +/* + * ST_DAILYCLOSE.h - st 서비스 ST_DAILYCLOSE 카피북 (copybook / record header). + * 일마감: 정산/지급/조정 건수 집계 + 마감 원장(STCLOSE) 기록. + */ +#ifndef ST_DAILYCLOSE_H +#define ST_DAILYCLOSE_H + +#include "st_svc.h" + +typedef struct { + char biz_date[16]; + long settle_cnt; + long pay_cnt; + long adj_cnt; + long net_sum; +} st_st_dailyclose_rec_t; + +void ST_DAILYCLOSE(TPSVCINFO *p); + +#endif /* ST_DAILYCLOSE_H */ diff --git a/app/src/st/svc/ST_DAILYCLOSE.pgc b/app/src/st/svc/ST_DAILYCLOSE.pgc new file mode 100644 index 0000000..894809b --- /dev/null +++ b/app/src/st/svc/ST_DAILYCLOSE.pgc @@ -0,0 +1,36 @@ +/* + * ST_DAILYCLOSE.pgc - st 모듈 서비스 ST_DAILYCLOSE (one service = one file). + * 정산 일마감: 당일 정산·지급·조정 건수를 각각 검증 집계하고 정산 net 총액으로 + * 마감 원장(STCLOSE)을 기록해 마감을 선언한다. + */ +#include "ST_DAILYCLOSE.h" + +/* 60. ST_DAILYCLOSE - 정산 일마감 선언 (3원 집계 + 마감 원장). */ +void ST_DAILYCLOSE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_scnt, h_sum, h_pcnt, h_acnt; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_scnt, :h_sum + FROM settlement WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_pcnt FROM st_payment WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_acnt FROM st_adjust WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date) + VALUES (nextval('ledger_seq'), 0, 'STCLOSE', :h_sum, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, h_scnt); + setl(b, T_ARG1, h_pcnt); + setl(b, T_ARG2, h_acnt); + setl(b, T_GROSS, h_sum); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_DEBTCOLLECT.h b/app/src/st/svc/ST_DEBTCOLLECT.h new file mode 100644 index 0000000..f6055b0 --- /dev/null +++ b/app/src/st/svc/ST_DEBTCOLLECT.h @@ -0,0 +1,19 @@ +/* + * ST_DEBTCOLLECT.h - st 서비스 ST_DEBTCOLLECT 카피북 (copybook / record header). + * 채권 회수 정산: 회수 입금액을 채권 잔액에 반영하고 원장 기록. + */ +#ifndef ST_DEBTCOLLECT_H +#define ST_DEBTCOLLECT_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long collected; + long outstanding; +} st_st_debtcollect_rec_t; + +void ST_DEBTCOLLECT(TPSVCINFO *p); + +#endif /* ST_DEBTCOLLECT_H */ diff --git a/app/src/st/svc/ST_DEBTCOLLECT.pgc b/app/src/st/svc/ST_DEBTCOLLECT.pgc new file mode 100644 index 0000000..fe4d5b5 --- /dev/null +++ b/app/src/st/svc/ST_DEBTCOLLECT.pgc @@ -0,0 +1,40 @@ +/* + * ST_DEBTCOLLECT.pgc - st 모듈 서비스 ST_DEBTCOLLECT (one service = one file). + * 채권 회수 정산: 회수 입금액을 양수 조정(COLLECT)으로 기록하고 원장에 COLLECT + * 엔트리를 남긴 뒤, 회수 후 잔여 채권액을 회신한다. + */ +#include "ST_DEBTCOLLECT.h" + +/* 44. ST_DEBTCOLLECT - 채권 회수 입금 정산. */ +void ST_DEBTCOLLECT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + long adj, remain; + EXEC SQL BEGIN DECLARE SECTION; + long h_col, h_out; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + h_col = getl(b, T_AMOUNT); + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (h_col <= 0) FAIL(b); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(a.delta) FILTER (WHERE a.delta < 0),0) INTO :h_out + FROM st_adjust a JOIN purchase p ON p.purchase_id = a.purchase_id + WHERE p.merchant_id = :h_merch; + if (sqlca.sqlcode < 0) FAIL(b); + adj = stdb_insert_adjust(0, 0, h_col, "COLLECT", bizdate); + if (adj < 0) FAIL(b); + EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date) + VALUES (nextval('ledger_seq'), 0, 'COLLECT', :h_col, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + remain = (-h_out) - h_col; + if (remain < 0) remain = 0; + setl(b, T_ADJ, adj); + setl(b, T_DELTA, remain); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_DEPOSITCUT.h b/app/src/st/svc/ST_DEPOSITCUT.h new file mode 100644 index 0000000..ad05d44 --- /dev/null +++ b/app/src/st/svc/ST_DEPOSITCUT.h @@ -0,0 +1,19 @@ +/* + * ST_DEPOSITCUT.h - st 서비스 ST_DEPOSITCUT 카피북 (copybook / record header). + * 보증금 차감: 예정 지급액에서 보증금 납부 예정액을 원천 차감. + */ +#ifndef ST_DEPOSITCUT_H +#define ST_DEPOSITCUT_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long deposit_due; + long cut; +} st_st_depositcut_rec_t; + +void ST_DEPOSITCUT(TPSVCINFO *p); + +#endif /* ST_DEPOSITCUT_H */ diff --git a/app/src/st/svc/ST_DEPOSITCUT.pgc b/app/src/st/svc/ST_DEPOSITCUT.pgc new file mode 100644 index 0000000..918eb0d --- /dev/null +++ b/app/src/st/svc/ST_DEPOSITCUT.pgc @@ -0,0 +1,39 @@ +/* + * ST_DEPOSITCUT.pgc - st 모듈 서비스 ST_DEPOSITCUT (one service = one file). + * 보증금 차감: 보증금 납부 예정액과 SCHEDULED 지급 가용액 중 작은 금액을 + * 최초 지급 레코드에서 차감하고 음수 조정(DEPOSIT)으로 기록한다. + */ +#include "ST_DEPOSITCUT.h" + +/* 42. ST_DEPOSITCUT - 지급액 보증금 원천 차감. */ +void ST_DEPOSITCUT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + long due = getl(b, T_AMT1), adj; + EXEC SQL BEGIN DECLARE SECTION; + long h_avail, h_cut; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (due <= 0) due = 50000; /* 기본 보증금 납부 단위 */ + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(net_amount),0) INTO :h_avail FROM st_payment + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status = 'SCHEDULED'; + if (sqlca.sqlcode < 0) FAIL(b); + h_cut = (due < h_avail) ? due : h_avail; + if (h_cut <= 0) { setl(b, T_RC, 1); setl(b, T_DELTA, 0); OK(b); } + EXEC SQL UPDATE st_payment SET net_amount = net_amount - :h_cut + WHERE pay_id = (SELECT min(pay_id) FROM st_payment + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status = 'SCHEDULED'); + if (sqlca.sqlcode < 0) FAIL(b); + adj = stdb_insert_adjust(0, 0, -h_cut, "DEPOSIT", bizdate); + if (adj < 0) FAIL(b); + setl(b, T_ADJ, adj); + setl(b, T_DELTA, -h_cut); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_DISPUTE.h b/app/src/st/svc/ST_DISPUTE.h new file mode 100644 index 0000000..2f7108e --- /dev/null +++ b/app/src/st/svc/ST_DISPUTE.h @@ -0,0 +1,19 @@ +/* + * ST_DISPUTE.h - st 서비스 ST_DISPUTE 카피북 (copybook / record header). + * 정산 이의신청 처리: 지급 보류 + 이의 금액 조정 기록. + */ +#ifndef ST_DISPUTE_H +#define ST_DISPUTE_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + long claimed; + char reason[64]; + char merchant_id[64]; +} st_st_dispute_rec_t; + +void ST_DISPUTE(TPSVCINFO *p); + +#endif /* ST_DISPUTE_H */ diff --git a/app/src/st/svc/ST_DISPUTE.pgc b/app/src/st/svc/ST_DISPUTE.pgc new file mode 100644 index 0000000..e97c9d9 --- /dev/null +++ b/app/src/st/svc/ST_DISPUTE.pgc @@ -0,0 +1,35 @@ +/* + * ST_DISPUTE.pgc - st 모듈 서비스 ST_DISPUTE (one service = one file). + * 정산 이의신청 처리: 대상 매입의 가맹점을 역추적해 예정 지급을 보류(HELD)로 + * 전환하고, 이의 금액을 DISPUTE 사유 조정으로 기록한다 (dbio). + */ +#include "ST_DISPUTE.h" + +/* 49. ST_DISPUTE - 정산 이의신청 접수 (지급 보류 + 조정). */ +void ST_DISPUTE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], reason[64], rbuf[96], merch[64]; + long claimed = getl(b, T_DELTA), adj; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid; + char h_merch[64]; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + gets_(b, T_REASON, reason, sizeof(reason)); if (reason[0] == 0) strcpy(reason, "UNSPEC"); + EXEC SQL SELECT merchant_id INTO :h_merch FROM settlement + WHERE purchase_id = :h_pid ORDER BY settlement_id LIMIT 1; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + strncpy(merch, h_merch, sizeof(merch)-1); merch[sizeof(merch)-1] = 0; + if (stdb_update_payment_status(merch, "SCHEDULED", "HELD", bizdate) < 0) FAIL(b); + snprintf(rbuf, sizeof(rbuf), "DISPUTE:%s", reason); + adj = stdb_insert_adjust(0, h_pid, claimed, rbuf, bizdate); + if (adj < 0) FAIL(b); + setl(b, T_ADJ, adj); + Bchg(b, T_MERCHANT, 0, merch, 0L); + Bchg(b, T_STATUS, 0, "D", 0L); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_DTLSUM.h b/app/src/st/svc/ST_DTLSUM.h new file mode 100644 index 0000000..4b1d123 --- /dev/null +++ b/app/src/st/svc/ST_DTLSUM.h @@ -0,0 +1,20 @@ +/* + * ST_DTLSUM.h - st 서비스 ST_DTLSUM 카피북 (copybook / record header). + * 정산 상세 합계: 수수료 종류별 건수/합계/최소/최대/평균. + */ +#ifndef ST_DTLSUM_H +#define ST_DTLSUM_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + char fee_type[16]; + char biz_date[16]; + long cnt; + long total; +} st_st_dtlsum_rec_t; + +void ST_DTLSUM(TPSVCINFO *p); + +#endif /* ST_DTLSUM_H */ diff --git a/app/src/st/svc/ST_DTLSUM.pgc b/app/src/st/svc/ST_DTLSUM.pgc new file mode 100644 index 0000000..f78c73f --- /dev/null +++ b/app/src/st/svc/ST_DTLSUM.pgc @@ -0,0 +1,37 @@ +/* + * ST_DTLSUM.pgc - st 모듈 서비스 ST_DTLSUM (one service = one file). + * 정산 상세 합계: 가맹점+수수료종류+영업일 단위로 상세 라인의 건수·합계· + * 최소·최대를 집계하고 평균을 계산해 회신한다. 조회 전용. + */ +#include "ST_DTLSUM.h" + +/* 55. ST_DTLSUM - 정산 상세 다중 집계 (건수/합/min/max/평균). */ +void ST_DTLSUM(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], ftype[16], bizdate[16]; + long avg; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_sum, h_min, h_max; + char h_merch[64], h_ftype[16], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_STR1, ftype, sizeof(ftype)); if (ftype[0] == 0) strcpy(ftype, "MDR"); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_ftype, ftype, sizeof(h_ftype)-1); h_ftype[sizeof(h_ftype)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*), coalesce(sum(amount),0), coalesce(min(amount),0), coalesce(max(amount),0) + INTO :h_cnt, :h_sum, :h_min, :h_max FROM st_settle_dtl + WHERE merchant_id = :h_merch AND fee_type = :h_ftype AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + avg = (h_cnt > 0) ? h_sum / h_cnt : 0; + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + setl(b, T_AMT1, h_min); + setl(b, T_AMT2, h_max); + setl(b, T_AMT3, avg); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_EDIRECON.h b/app/src/st/svc/ST_EDIRECON.h new file mode 100644 index 0000000..88cae1e --- /dev/null +++ b/app/src/st/svc/ST_EDIRECON.h @@ -0,0 +1,20 @@ +/* + * ST_EDIRECON.h - st 서비스 ST_EDIRECON 카피북 (copybook / record header). + * EDI 정산 대사: EDI 청구액 vs 가맹점 정산 집계 net 대사. + */ +#ifndef ST_EDIRECON_H +#define ST_EDIRECON_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long edi_cnt; + long edi_sum; + long settle_net; +} st_st_edirecon_rec_t; + +void ST_EDIRECON(TPSVCINFO *p); + +#endif /* ST_EDIRECON_H */ diff --git a/app/src/st/svc/ST_EDIRECON.pgc b/app/src/st/svc/ST_EDIRECON.pgc new file mode 100644 index 0000000..d671097 --- /dev/null +++ b/app/src/st/svc/ST_EDIRECON.pgc @@ -0,0 +1,36 @@ +/* + * ST_EDIRECON.pgc - st 모듈 서비스 ST_EDIRECON (one service = one file). + * EDI 정산 대사: 가맹점 당일 EDI 문서(ac_edi) 청구 합계와 정산 집계 + * (st_merch_settle) net 을 대사한다. 집계 미존재 시 0 으로 간주. 조회 전용. + */ +#include "ST_EDIRECON.h" + +/* 71. ST_EDIRECON - EDI 청구 vs 정산 집계 대사. */ +void ST_EDIRECON(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_edi, h_net; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_edi + FROM ac_edi WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT net_amount INTO :h_net FROM st_merch_settle + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) h_net = 0; /* 집계 미존재: 0 대사 */ + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_edi); + setl(b, T_NET, h_net); + setl(b, T_DELTA, h_edi - h_net); + setl(b, T_RC, (h_edi == h_net) ? 0 : 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_FEEPREVIEW.h b/app/src/st/svc/ST_FEEPREVIEW.h new file mode 100644 index 0000000..44bb3f3 --- /dev/null +++ b/app/src/st/svc/ST_FEEPREVIEW.h @@ -0,0 +1,22 @@ +/* + * ST_FEEPREVIEW.h - st 서비스 ST_FEEPREVIEW 카피북 (copybook / record header). + * 수수료 사전 미리보기: 가맹점 요율 기준 수수료/부가세/원천세/실수령 예상. + */ +#ifndef ST_FEEPREVIEW_H +#define ST_FEEPREVIEW_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + long amount; + long mdr_bps; + long fee; + long vat; + long wht; + long take_home; +} st_st_feepreview_rec_t; + +void ST_FEEPREVIEW(TPSVCINFO *p); + +#endif /* ST_FEEPREVIEW_H */ diff --git a/app/src/st/svc/ST_FEEPREVIEW.pgc b/app/src/st/svc/ST_FEEPREVIEW.pgc new file mode 100644 index 0000000..03928a4 --- /dev/null +++ b/app/src/st/svc/ST_FEEPREVIEW.pgc @@ -0,0 +1,34 @@ +/* + * ST_FEEPREVIEW.pgc - st 모듈 서비스 ST_FEEPREVIEW (one service = one file). + * 수수료 사전 미리보기: 가맹점 등록 요율(mdr_bps)로 수수료·부가세·원천세와 + * 실수령 예상액을 계산해 회신한다. DB 쓰기 없는 견적 서비스. + */ +#include "ST_FEEPREVIEW.h" + +/* 51. ST_FEEPREVIEW - 정산 실수령 예상 견적. */ +void ST_FEEPREVIEW(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64]; + long amount = getl(b, T_AMOUNT), fee, vat, wht, net; + EXEC SQL BEGIN DECLARE SECTION; + long h_bps; + char h_merch[64]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + EXEC SQL SELECT mdr_bps INTO :h_bps FROM merchant WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + fee = acq_fee(amount, h_bps); + vat = acq_vat(fee); + wht = acq_wht_amt(fee, 2200); + net = amount - fee - vat; + setl(b, T_FEE, fee); + setl(b, T_TAX, vat); + setl(b, T_AMT1, wht); + setl(b, T_NET, net); + setl(b, T_RC, h_bps); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_FEERATIO.h b/app/src/st/svc/ST_FEERATIO.h new file mode 100644 index 0000000..b4a1c1c --- /dev/null +++ b/app/src/st/svc/ST_FEERATIO.h @@ -0,0 +1,19 @@ +/* + * ST_FEERATIO.h - st 서비스 ST_FEERATIO 카피북 (copybook / record header). + * 수수료율 실효 검증: 실효 bps vs 계약 bps 편차 판정. + */ +#ifndef ST_FEERATIO_H +#define ST_FEERATIO_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + long eff_bps; + long contract_bps; + long diff_bps; +} st_st_feeratio_rec_t; + +void ST_FEERATIO(TPSVCINFO *p); + +#endif /* ST_FEERATIO_H */ diff --git a/app/src/st/svc/ST_FEERATIO.pgc b/app/src/st/svc/ST_FEERATIO.pgc new file mode 100644 index 0000000..34c46b0 --- /dev/null +++ b/app/src/st/svc/ST_FEERATIO.pgc @@ -0,0 +1,32 @@ +/* + * ST_FEERATIO.pgc - st 모듈 서비스 ST_FEERATIO (one service = one file). + * 수수료율 실효 검증: 매입-가맹점 조인 한 번으로 실제 부과 수수료의 실효 + * bps 를 역산하고 계약 요율과의 편차(±5bps 허용)를 판정한다. 조회 전용. + */ +#include "ST_FEERATIO.h" + +/* 65. ST_FEERATIO - 실효 수수료율 vs 계약 요율 편차 검증. */ +void ST_FEERATIO(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long eff, diff, adiff; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amount, h_fee, h_bps; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + EXEC SQL SELECT p.amount, p.fee, m.mdr_bps INTO :h_amount, :h_fee, :h_bps + FROM purchase p JOIN merchant m ON m.merchant_id = p.merchant_id + WHERE p.purchase_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + if (h_amount <= 0) FAIL(b); + eff = h_fee * 10000 / h_amount; /* 실효 bps 역산 */ + diff = eff - h_bps; + adiff = (diff < 0) ? -diff : diff; + setl(b, T_ARG1, eff); + setl(b, T_ARG2, h_bps); + setl(b, T_DELTA, diff); + setl(b, T_RC, (adiff > 5) ? 1 : 0); /* 허용 편차 5bps 초과 시 이상 */ + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_FXSETTLE.h b/app/src/st/svc/ST_FXSETTLE.h new file mode 100644 index 0000000..953d1ea --- /dev/null +++ b/app/src/st/svc/ST_FXSETTLE.h @@ -0,0 +1,20 @@ +/* + * ST_FXSETTLE.h - st 서비스 ST_FXSETTLE 카피북 (copybook / record header). + * 해외 매입 정산: ac_fx 원화 환산액 합산 + FX 정산 상세 기록. + */ +#ifndef ST_FXSETTLE_H +#define ST_FXSETTLE_H + +#include "st_svc.h" + +typedef struct { + long settlement_id; + long purchase_id; + char merchant_id[64]; + long fx_cnt; + long krw_total; +} st_st_fxsettle_rec_t; + +void ST_FXSETTLE(TPSVCINFO *p); + +#endif /* ST_FXSETTLE_H */ diff --git a/app/src/st/svc/ST_FXSETTLE.pgc b/app/src/st/svc/ST_FXSETTLE.pgc new file mode 100644 index 0000000..85640ab --- /dev/null +++ b/app/src/st/svc/ST_FXSETTLE.pgc @@ -0,0 +1,32 @@ +/* + * ST_FXSETTLE.pgc - st 모듈 서비스 ST_FXSETTLE (one service = one file). + * 해외 매입 정산: 매입에 걸린 외화 내역(ac_fx)의 원화 환산액을 합산해 FX + * 정산 상세로 기록한다. 외화 내역 없으면 RC=1 (dbio). + */ +#include "ST_FXSETTLE.h" + +/* 70. ST_FXSETTLE - 외화 환산액 정산 상세 기록. */ +void ST_FXSETTLE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + long sid = getl(b, T_SETTLE_ID), dtl; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_cnt, h_krw; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + EXEC SQL SELECT count(*), coalesce(sum(krw_amount),0) INTO :h_cnt, :h_krw + FROM ac_fx WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt == 0) { setl(b, T_RC, 1); setl(b, T_AMOUNT, 0); OK(b); } + dtl = stdb_insert_fee_dtl(sid, h_pid, merch, "FX", h_krw, bizdate); + if (dtl < 0) FAIL(b); + setl(b, T_AMOUNT, h_krw); + setl(b, T_COUNT, h_cnt); + setl(b, T_RC, 0); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_GRPSETTLE.h b/app/src/st/svc/ST_GRPSETTLE.h new file mode 100644 index 0000000..52937f9 --- /dev/null +++ b/app/src/st/svc/ST_GRPSETTLE.h @@ -0,0 +1,20 @@ +/* + * ST_GRPSETTLE.h - st 서비스 ST_GRPSETTLE 카피북 (copybook / record header). + * 가맹점그룹 통합정산: 그룹 prefix 소속 가맹점 정산을 통합 지급. + */ +#ifndef ST_GRPSETTLE_H +#define ST_GRPSETTLE_H + +#include "st_svc.h" + +typedef struct { + char group_prefix[16]; + char biz_date[16]; + long members; + long group_total; + long pay_id; +} st_st_grpsettle_rec_t; + +void ST_GRPSETTLE(TPSVCINFO *p); + +#endif /* ST_GRPSETTLE_H */ diff --git a/app/src/st/svc/ST_GRPSETTLE.pgc b/app/src/st/svc/ST_GRPSETTLE.pgc new file mode 100644 index 0000000..4ebe2cf --- /dev/null +++ b/app/src/st/svc/ST_GRPSETTLE.pgc @@ -0,0 +1,46 @@ +/* + * ST_GRPSETTLE.pgc - st 모듈 서비스 ST_GRPSETTLE (one service = one file). + * 가맹점그룹 통합정산: 그룹 prefix(LIKE) 소속 가맹점별 정산 net 을 커서로 + * 합산해 그룹 대표 앞 통합 지급 1건으로 예약한다. 소속원 없으면 RC=1 (dbio). + */ +#include "ST_GRPSETTLE.h" + +/* 48. ST_GRPSETTLE - 그룹 prefix 통합정산 지급. */ +void ST_GRPSETTLE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char prefix[16], bizdate[16], like[24]; + long members = 0, total = 0, pay; + EXEC SQL BEGIN DECLARE SECTION; + long h_msum; + char h_mid[64], h_like[24], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_KEY1, prefix, sizeof(prefix)); if (prefix[0] == 0) strcpy(prefix, "M0"); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + snprintf(like, sizeof(like), "%s%%", prefix); + strncpy(h_like, like, sizeof(h_like)-1); h_like[sizeof(h_like)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE grpset_c CURSOR FOR + SELECT merchant_id, coalesce(sum(net),0) FROM settlement + WHERE biz_date = :h_bizdate AND merchant_id LIKE :h_like + GROUP BY merchant_id ORDER BY merchant_id; + EXEC SQL OPEN grpset_c; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH grpset_c INTO :h_mid, :h_msum; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE grpset_c; FAIL(b); } + members++; + total += h_msum; + } + EXEC SQL CLOSE grpset_c; + if (members == 0) { setl(b, T_RC, 1); OK(b); } + pay = stdb_insert_payment(prefix, total, bizdate, "SCHEDULED", bizdate); /* 그룹 대표 지급 */ + if (pay < 0) FAIL(b); + setl(b, T_ID1, pay); + setl(b, T_COUNT, members); + setl(b, T_GROSS, total); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_HOLDAMT.h b/app/src/st/svc/ST_HOLDAMT.h new file mode 100644 index 0000000..e43e0f7 --- /dev/null +++ b/app/src/st/svc/ST_HOLDAMT.h @@ -0,0 +1,18 @@ +/* + * ST_HOLDAMT.h - st 서비스 ST_HOLDAMT 카피북 (copybook / record header). + * 금액 조건부 보류: 임계액 이상 예정 지급의 일괄 보류. + */ +#ifndef ST_HOLDAMT_H +#define ST_HOLDAMT_H + +#include "st_svc.h" + +typedef struct { + char biz_date[16]; + long threshold; + long held_rows; +} st_st_holdamt_rec_t; + +void ST_HOLDAMT(TPSVCINFO *p); + +#endif /* ST_HOLDAMT_H */ diff --git a/app/src/st/svc/ST_HOLDAMT.pgc b/app/src/st/svc/ST_HOLDAMT.pgc new file mode 100644 index 0000000..a4882b7 --- /dev/null +++ b/app/src/st/svc/ST_HOLDAMT.pgc @@ -0,0 +1,31 @@ +/* + * ST_HOLDAMT.pgc - st 모듈 서비스 ST_HOLDAMT (one service = one file). + * 금액 조건부 보류: 임계액(threshold) 이상의 SCHEDULED 지급을 집합 UPDATE 로 + * 일괄 HELD 전환하고 보류 건수를 회신한다 (고액 지급 심사용). + */ +#include "ST_HOLDAMT.h" + +/* 52. ST_HOLDAMT - 임계액 이상 지급 일괄 보류. */ +void ST_HOLDAMT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long rows; + EXEC SQL BEGIN DECLARE SECTION; + long h_thr; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + h_thr = getl(b, T_AMT1); + if (h_thr <= 0) h_thr = 1000000; /* 기본 고액 기준 100만원 */ + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL UPDATE st_payment SET status = 'HELD' + WHERE status = 'SCHEDULED' AND biz_date = :h_bizdate AND net_amount >= :h_thr; + if (sqlca.sqlcode < 0) FAIL(b); + rows = (long) sqlca.sqlerrd[2]; + setl(b, T_COUNT, rows); + setl(b, T_RC, rows > 0 ? 0 : 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_ISSUERSTAT.h b/app/src/st/svc/ST_ISSUERSTAT.h new file mode 100644 index 0000000..670f8a9 --- /dev/null +++ b/app/src/st/svc/ST_ISSUERSTAT.h @@ -0,0 +1,18 @@ +/* + * ST_ISSUERSTAT.h - st 서비스 ST_ISSUERSTAT 카피북 (copybook / record header). + * 발급사별 수수료 통계: 발급사 단위 매입 수수료 분해 집계. + */ +#ifndef ST_ISSUERSTAT_H +#define ST_ISSUERSTAT_H + +#include "st_svc.h" + +typedef struct { + char biz_date[16]; + long issuer_cnt; + long fee_total; +} st_st_issuerstat_rec_t; + +void ST_ISSUERSTAT(TPSVCINFO *p); + +#endif /* ST_ISSUERSTAT_H */ diff --git a/app/src/st/svc/ST_ISSUERSTAT.pgc b/app/src/st/svc/ST_ISSUERSTAT.pgc new file mode 100644 index 0000000..540b51e --- /dev/null +++ b/app/src/st/svc/ST_ISSUERSTAT.pgc @@ -0,0 +1,41 @@ +/* + * ST_ISSUERSTAT.pgc - st 모듈 서비스 ST_ISSUERSTAT (one service = one file). + * 발급사별 수수료 통계 세분: 당일 정산완료(S) 매입을 발급사(issuer) 단위로 + * 커서 순회하며 발급사 수와 수수료 총액을 집계한다. 조회 전용. + */ +#include "ST_ISSUERSTAT.h" + +/* 68. ST_ISSUERSTAT - 발급사별 수수료 분해 통계. */ +void ST_ISSUERSTAT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long issuers = 0, feesum = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_fee; + char h_issuer[32], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE issuerstat_c CURSOR FOR + SELECT issuer, coalesce(sum(fee),0) FROM purchase + WHERE biz_date = :h_bizdate AND status = 'S' + GROUP BY issuer ORDER BY issuer; + EXEC SQL OPEN issuerstat_c; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH issuerstat_c INTO :h_issuer, :h_fee; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE issuerstat_c; FAIL(b); } + issuers++; + feesum += h_fee; + } + EXEC SQL CLOSE issuerstat_c; + if (issuers == 0) { setl(b, T_RC, 1); OK(b); } + setl(b, T_COUNT, issuers); + setl(b, T_FEE, feesum); + setl(b, T_RC, 0); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_LEDGERSNAP.h b/app/src/st/svc/ST_LEDGERSNAP.h new file mode 100644 index 0000000..532dfbd --- /dev/null +++ b/app/src/st/svc/ST_LEDGERSNAP.h @@ -0,0 +1,19 @@ +/* + * ST_LEDGERSNAP.h - st 서비스 ST_LEDGERSNAP 카피북 (copybook / record header). + * 정산 스냅샷: 당일 정산 총액의 원장 스냅샷(STSNAP) 기록. + */ +#ifndef ST_LEDGERSNAP_H +#define ST_LEDGERSNAP_H + +#include "st_svc.h" + +typedef struct { + char biz_date[16]; + long snap_ledger_id; + long settle_cnt; + long net_sum; +} st_st_ledgersnap_rec_t; + +void ST_LEDGERSNAP(TPSVCINFO *p); + +#endif /* ST_LEDGERSNAP_H */ diff --git a/app/src/st/svc/ST_LEDGERSNAP.pgc b/app/src/st/svc/ST_LEDGERSNAP.pgc new file mode 100644 index 0000000..556484b --- /dev/null +++ b/app/src/st/svc/ST_LEDGERSNAP.pgc @@ -0,0 +1,33 @@ +/* + * ST_LEDGERSNAP.pgc - st 모듈 서비스 ST_LEDGERSNAP (one service = one file). + * 정산 스냅샷: 당일 정산 건수·net 총액을 집계해 원장에 STSNAP 스냅샷 엔트리로 + * 기록하고 스냅샷 원장 ID 를 회신한다 (감사 추적용). + */ +#include "ST_LEDGERSNAP.h" + +/* 75. ST_LEDGERSNAP - 당일 정산 총액 원장 스냅샷. */ +void ST_LEDGERSNAP(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_sum, h_lid; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_cnt, :h_sum + FROM settlement WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT nextval('ledger_seq') INTO :h_lid; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO ledger (ledger_id, purchase_id, entry_type, amount, biz_date) + VALUES (:h_lid, 0, 'STSNAP', :h_sum, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_ID1, h_lid); + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_LIMITCHK.h b/app/src/st/svc/ST_LIMITCHK.h new file mode 100644 index 0000000..e3dbb31 --- /dev/null +++ b/app/src/st/svc/ST_LIMITCHK.h @@ -0,0 +1,20 @@ +/* + * ST_LIMITCHK.h - st 서비스 ST_LIMITCHK 카피북 (copybook / record header). + * 가맹점 한도 점검: 일 매입 누계 vs daily_limit 잔여 확인. + */ +#ifndef ST_LIMITCHK_H +#define ST_LIMITCHK_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long daily_limit; + long used; + long remain; +} st_st_limitchk_rec_t; + +void ST_LIMITCHK(TPSVCINFO *p); + +#endif /* ST_LIMITCHK_H */ diff --git a/app/src/st/svc/ST_LIMITCHK.pgc b/app/src/st/svc/ST_LIMITCHK.pgc new file mode 100644 index 0000000..17d415f --- /dev/null +++ b/app/src/st/svc/ST_LIMITCHK.pgc @@ -0,0 +1,35 @@ +/* + * ST_LIMITCHK.pgc - st 모듈 서비스 ST_LIMITCHK (one service = one file). + * 가맹점 한도 점검: 가맹점 일 한도(daily_limit)와 당일 매입 누계(취소 제외)를 + * 비교해 잔여 한도를 회신한다. 한도 초과 시 RC=1. 조회 전용. + */ +#include "ST_LIMITCHK.h" + +/* 74. ST_LIMITCHK - 일 매입 한도 잔여 점검. */ +void ST_LIMITCHK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + long remain; + EXEC SQL BEGIN DECLARE SECTION; + long h_limit, h_used; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT daily_limit INTO :h_limit FROM merchant WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_used FROM purchase + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status <> 'C'; + if (sqlca.sqlcode < 0) FAIL(b); + remain = h_limit - h_used; + setl(b, T_AMT1, h_limit); + setl(b, T_GROSS, h_used); + setl(b, T_DELTA, remain); + setl(b, T_RC, (remain < 0) ? 1 : 0); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_MERCHRATE.h b/app/src/st/svc/ST_MERCHRATE.h new file mode 100644 index 0000000..a2f8c9e --- /dev/null +++ b/app/src/st/svc/ST_MERCHRATE.h @@ -0,0 +1,21 @@ +/* + * ST_MERCHRATE.h - st 서비스 ST_MERCHRATE 카피북 (copybook / record header). + * 가맹점 개별 요율 적용: merchant.mdr_bps 로 수수료 산출 + 상세 기록. + */ +#ifndef ST_MERCHRATE_H +#define ST_MERCHRATE_H + +#include "st_svc.h" + +typedef struct { + long settlement_id; + long purchase_id; + char merchant_id[64]; + long amount; + long merch_bps; + long fee; +} st_st_merchrate_rec_t; + +void ST_MERCHRATE(TPSVCINFO *p); + +#endif /* ST_MERCHRATE_H */ diff --git a/app/src/st/svc/ST_MERCHRATE.pgc b/app/src/st/svc/ST_MERCHRATE.pgc new file mode 100644 index 0000000..295b4e7 --- /dev/null +++ b/app/src/st/svc/ST_MERCHRATE.pgc @@ -0,0 +1,32 @@ +/* + * ST_MERCHRATE.pgc - st 모듈 서비스 ST_MERCHRATE (one service = one file). + * 가맹점 개별 요율 적용: 구간요율표 대신 가맹점 마스터의 계약 요율(mdr_bps)로 + * 수수료를 산출하고 MDR 정산 상세로 기록한다 (dbio). + */ +#include "ST_MERCHRATE.h" + +/* 56. ST_MERCHRATE - 가맹점 계약 요율 수수료 산출/기록. */ +void ST_MERCHRATE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID); + long amount = getl(b, T_AMOUNT), fee, dtl; + EXEC SQL BEGIN DECLARE SECTION; + long h_bps; + char h_merch[64]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + EXEC SQL SELECT mdr_bps INTO :h_bps FROM merchant WHERE merchant_id = :h_merch; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + fee = acq_fee(amount, h_bps); + dtl = stdb_insert_fee_dtl(sid, pid, merch, "MDR", fee, bizdate); + if (dtl < 0) FAIL(b); + setl(b, T_FEE, fee); + setl(b, T_RC, h_bps); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_MINFEE.h b/app/src/st/svc/ST_MINFEE.h new file mode 100644 index 0000000..6a6f93b --- /dev/null +++ b/app/src/st/svc/ST_MINFEE.h @@ -0,0 +1,20 @@ +/* + * ST_MINFEE.h - st 서비스 ST_MINFEE 카피북 (copybook / record header). + * 최저 수수료 보정: 최저액 미달 수수료의 부족분 보정 상세 기록. + */ +#ifndef ST_MINFEE_H +#define ST_MINFEE_H + +#include "st_svc.h" + +typedef struct { + long settlement_id; + long purchase_id; + char merchant_id[64]; + long fee; + long min_fee; +} st_st_minfee_rec_t; + +void ST_MINFEE(TPSVCINFO *p); + +#endif /* ST_MINFEE_H */ diff --git a/app/src/st/svc/ST_MINFEE.pgc b/app/src/st/svc/ST_MINFEE.pgc new file mode 100644 index 0000000..bc57674 --- /dev/null +++ b/app/src/st/svc/ST_MINFEE.pgc @@ -0,0 +1,31 @@ +/* + * ST_MINFEE.pgc - st 모듈 서비스 ST_MINFEE (one service = one file). + * 최저 수수료 보정: 산출 수수료가 최저 수수료에 미달하면 부족분을 ADJ 정산 + * 상세로 추가 기록해 최저액을 보장한다 (dbio). 충족 시 변경 없음. + */ +#include "ST_MINFEE.h" + +/* 59. ST_MINFEE - 최저 수수료 미달분 보정. */ +void ST_MINFEE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID); + long fee = getl(b, T_FEE), minfee = getl(b, T_ARG1), diff, dtl; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (minfee <= 0) minfee = 100; /* 기본 최저 수수료 100원 */ + if (fee >= minfee) { + setl(b, T_FEE, fee); + setl(b, T_DELTA, 0); + OK(b); + } + diff = minfee - fee; + dtl = stdb_insert_fee_dtl(sid, pid, merch, "ADJ", diff, bizdate); + if (dtl < 0) FAIL(b); + setl(b, T_FEE, minfee); + setl(b, T_DELTA, diff); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_NEGSWEEP.h b/app/src/st/svc/ST_NEGSWEEP.h new file mode 100644 index 0000000..64884e9 --- /dev/null +++ b/app/src/st/svc/ST_NEGSWEEP.h @@ -0,0 +1,18 @@ +/* + * ST_NEGSWEEP.h - st 서비스 ST_NEGSWEEP 카피북 (copybook / record header). + * 음수 정산 정리: 당일 음수 net 정산의 일괄 취소 전환. + */ +#ifndef ST_NEGSWEEP_H +#define ST_NEGSWEEP_H + +#include "st_svc.h" + +typedef struct { + char biz_date[16]; + long neg_sum; + long swept; +} st_st_negsweep_rec_t; + +void ST_NEGSWEEP(TPSVCINFO *p); + +#endif /* ST_NEGSWEEP_H */ diff --git a/app/src/st/svc/ST_NEGSWEEP.pgc b/app/src/st/svc/ST_NEGSWEEP.pgc new file mode 100644 index 0000000..27811e2 --- /dev/null +++ b/app/src/st/svc/ST_NEGSWEEP.pgc @@ -0,0 +1,33 @@ +/* + * ST_NEGSWEEP.pgc - st 모듈 서비스 ST_NEGSWEEP (one service = one file). + * 음수 정산 정리: 당일 net 이 음수인 SETTLED 정산의 합계를 먼저 파악한 뒤 + * 집합 UPDATE 로 일괄 CANCELLED 전환한다. 처리 건수/음수 합계를 회신. + */ +#include "ST_NEGSWEEP.h" + +/* 63. ST_NEGSWEEP - 음수 net 정산 일괄 취소. */ +void ST_NEGSWEEP(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long rows; + EXEC SQL BEGIN DECLARE SECTION; + long h_neg; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(net),0) INTO :h_neg FROM settlement + WHERE biz_date = :h_bizdate AND net < 0 AND status = 'SETTLED'; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL UPDATE settlement SET status = 'CANCELLED' + WHERE biz_date = :h_bizdate AND net < 0 AND status = 'SETTLED'; + if (sqlca.sqlcode < 0) FAIL(b); + rows = (long) sqlca.sqlerrd[2]; + setl(b, T_COUNT, rows); + setl(b, T_GROSS, h_neg); + setl(b, T_RC, rows > 0 ? 0 : 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_PARTIAL.h b/app/src/st/svc/ST_PARTIAL.h new file mode 100644 index 0000000..298c0f8 --- /dev/null +++ b/app/src/st/svc/ST_PARTIAL.h @@ -0,0 +1,20 @@ +/* + * ST_PARTIAL.h - st 서비스 ST_PARTIAL 카피북 (copybook / record header). + * 부분정산: 매입 net 잔여 한도 내에서 일부 금액만 정산 확정. + */ +#ifndef ST_PARTIAL_H +#define ST_PARTIAL_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char merchant_id[64]; + long part_amount; + long remain; + char biz_date[16]; +} st_st_partial_rec_t; + +void ST_PARTIAL(TPSVCINFO *p); + +#endif /* ST_PARTIAL_H */ diff --git a/app/src/st/svc/ST_PARTIAL.pgc b/app/src/st/svc/ST_PARTIAL.pgc new file mode 100644 index 0000000..2973ccd --- /dev/null +++ b/app/src/st/svc/ST_PARTIAL.pgc @@ -0,0 +1,44 @@ +/* + * ST_PARTIAL.pgc - st 모듈 서비스 ST_PARTIAL (one service = one file). + * 부분정산: 매입 net 에서 기정산 합계를 뺀 잔여 한도 내에서 요청 금액만큼 + * PARTIAL 상태의 정산 레코드를 추가 생성한다. 한도 초과/0 이하는 RC=1. + */ +#include "ST_PARTIAL.h" + +/* 33. ST_PARTIAL - 부분정산 (잔여 한도 검증 후 PARTIAL 정산 생성). */ +void ST_PARTIAL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_sid, h_pnet, h_done, h_part; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + h_part = getl(b, T_AMOUNT); + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT net INTO :h_pnet FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + EXEC SQL SELECT coalesce(sum(net),0) INTO :h_done FROM settlement + WHERE purchase_id = :h_pid AND status <> 'CANCELLED'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_part <= 0 || h_part > h_pnet - h_done) { + setl(b, T_RC, 1); + setl(b, T_NET, h_pnet - h_done); /* 잔여 한도 통지 */ + OK(b); + } + EXEC SQL SELECT nextval('settlement_seq') INTO :h_sid; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL INSERT INTO settlement (settlement_id, purchase_id, merchant_id, net, biz_date, status) + VALUES (:h_sid, :h_pid, :h_merch, :h_part, :h_bizdate, 'PARTIAL'); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_SETTLE_ID, h_sid); + setl(b, T_NET, h_pnet - h_done - h_part); + setl(b, T_RC, 0); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_PAYRETRY.h b/app/src/st/svc/ST_PAYRETRY.h new file mode 100644 index 0000000..918492c --- /dev/null +++ b/app/src/st/svc/ST_PAYRETRY.h @@ -0,0 +1,19 @@ +/* + * ST_PAYRETRY.h - st 서비스 ST_PAYRETRY 카피북 (copybook / record header). + * 지급 재시도: 취소된 지급을 익영업일 재예약으로 복구. + */ +#ifndef ST_PAYRETRY_H +#define ST_PAYRETRY_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + char retry_date[16]; + long retried; +} st_st_payretry_rec_t; + +void ST_PAYRETRY(TPSVCINFO *p); + +#endif /* ST_PAYRETRY_H */ diff --git a/app/src/st/svc/ST_PAYRETRY.pgc b/app/src/st/svc/ST_PAYRETRY.pgc new file mode 100644 index 0000000..3c9d4c9 --- /dev/null +++ b/app/src/st/svc/ST_PAYRETRY.pgc @@ -0,0 +1,34 @@ +/* + * ST_PAYRETRY.pgc - st 모듈 서비스 ST_PAYRETRY (one service = one file). + * 지급 재시도: 이체 실패 등으로 CANCELLED 된 지급을 익영업일 지급일로 갱신해 + * SCHEDULED 재예약한다. 대상 없으면 RC=1. + */ +#include "ST_PAYRETRY.h" + +/* 73. ST_PAYRETRY - 취소 지급 익영업일 재예약. */ +void ST_PAYRETRY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16], retry[16]; + long rows; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16], h_retry[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (acq_next_bizday(bizdate, retry) < 0) FAIL(b); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(h_retry, retry, sizeof(h_retry)-1); h_retry[sizeof(h_retry)-1] = 0; + EXEC SQL UPDATE st_payment SET status = 'SCHEDULED', pay_date = :h_retry + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status = 'CANCELLED'; + if (sqlca.sqlcode < 0) FAIL(b); + rows = (long) sqlca.sqlerrd[2]; + if (rows == 0) { setl(b, T_RC, 1); setl(b, T_COUNT, 0); OK(b); } + setl(b, T_COUNT, rows); + Bchg(b, T_KEY1, 0, retry, 0L); + setl(b, T_RC, 0); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_PAYSTATUS.h b/app/src/st/svc/ST_PAYSTATUS.h new file mode 100644 index 0000000..4a6bd3d --- /dev/null +++ b/app/src/st/svc/ST_PAYSTATUS.h @@ -0,0 +1,19 @@ +/* + * ST_PAYSTATUS.h - st 서비스 ST_PAYSTATUS 카피북 (copybook / record header). + * 지급 상태 조회: 가맹점 최신 지급 레코드의 상태/금액/지급일. + */ +#ifndef ST_PAYSTATUS_H +#define ST_PAYSTATUS_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + char status[16]; + long net_amount; + char pay_date[16]; +} st_st_paystatus_rec_t; + +void ST_PAYSTATUS(TPSVCINFO *p); + +#endif /* ST_PAYSTATUS_H */ diff --git a/app/src/st/svc/ST_PAYSTATUS.pgc b/app/src/st/svc/ST_PAYSTATUS.pgc new file mode 100644 index 0000000..0413142 --- /dev/null +++ b/app/src/st/svc/ST_PAYSTATUS.pgc @@ -0,0 +1,35 @@ +/* + * ST_PAYSTATUS.pgc - st 모듈 서비스 ST_PAYSTATUS (one service = one file). + * 지급 상태 조회: 가맹점의 가장 최근 지급 레코드에서 상태·금액·지급 예정일을 + * 조회해 회신한다. 지급 이력이 없으면 NONE/RC=1. + */ +#include "ST_PAYSTATUS.h" + +/* 53. ST_PAYSTATUS - 가맹점 최신 지급 상태 조회. */ +void ST_PAYSTATUS(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64]; + EXEC SQL BEGIN DECLARE SECTION; + long h_net; + char h_merch[64], h_status[16], h_payd[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + EXEC SQL SELECT status, net_amount, to_char(pay_date,'YYYY-MM-DD') + INTO :h_status, :h_net, :h_payd FROM st_payment + WHERE merchant_id = :h_merch ORDER BY pay_id DESC LIMIT 1; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { + Bchg(b, T_STATUS, 0, "NONE", 0L); + setl(b, T_RC, 1); + OK(b); + } + Bchg(b, T_STATUS, 0, h_status, 0L); + setl(b, T_NET, h_net); + Bchg(b, T_KEY1, 0, h_payd, 0L); + setl(b, T_RC, 0); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_PROMOCALC.h b/app/src/st/svc/ST_PROMOCALC.h new file mode 100644 index 0000000..eebe59f --- /dev/null +++ b/app/src/st/svc/ST_PROMOCALC.h @@ -0,0 +1,21 @@ +/* + * ST_PROMOCALC.h - st 서비스 ST_PROMOCALC 카피북 (copybook / record header). + * 프로모션/이벤트 정산: 이벤트 코드별 수수료 할인율 적용 + 할인 조정 기록. + */ +#ifndef ST_PROMOCALC_H +#define ST_PROMOCALC_H + +#include "st_svc.h" + +typedef struct { + long settlement_id; + long purchase_id; + char event_code[32]; + long fee; + long promo_pct; + long discount; +} st_st_promocalc_rec_t; + +void ST_PROMOCALC(TPSVCINFO *p); + +#endif /* ST_PROMOCALC_H */ diff --git a/app/src/st/svc/ST_PROMOCALC.pgc b/app/src/st/svc/ST_PROMOCALC.pgc new file mode 100644 index 0000000..12083d9 --- /dev/null +++ b/app/src/st/svc/ST_PROMOCALC.pgc @@ -0,0 +1,29 @@ +/* + * ST_PROMOCALC.pgc - st 모듈 서비스 ST_PROMOCALC (one service = one file). + * 프로모션/이벤트 정산: 이벤트 코드와 할인율(%)을 받아 수수료 할인액을 계산하고 + * 양수 조정(가맹점 환급)으로 st_adjust 에 기록한다 (dbio). + */ +#include "ST_PROMOCALC.h" + +/* 32. ST_PROMOCALC - 프로모션/이벤트 수수료 할인 정산. */ +void ST_PROMOCALC(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], event[32], reason[64]; + long sid = getl(b, T_SETTLE_ID), pid = getl(b, T_PURCHASE_ID); + long fee = getl(b, T_FEE), pct = getl(b, T_ARG1), disc, adj; + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + gets_(b, T_STR1, event, sizeof(event)); if (event[0] == 0) strcpy(event, "EVENT"); + if (pct <= 0 || pct > 100) pct = 30; /* 기본 30% 할인 */ + disc = fee * pct / 100; + if (disc <= 0) { setl(b, T_RC, 1); setl(b, T_DELTA, 0); OK(b); } + snprintf(reason, sizeof(reason), "PROMO:%s", event); + adj = stdb_insert_adjust(sid, pid, disc, reason, bizdate); + if (adj < 0) FAIL(b); + setl(b, T_ADJ, adj); + setl(b, T_DELTA, disc); + setl(b, T_FEE, fee - disc); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_RATESIM.h b/app/src/st/svc/ST_RATESIM.h new file mode 100644 index 0000000..6c4407c --- /dev/null +++ b/app/src/st/svc/ST_RATESIM.h @@ -0,0 +1,20 @@ +/* + * ST_RATESIM.h - st 서비스 ST_RATESIM 카피북 (copybook / record header). + * 요율 시뮬레이션: 요율표 전 구간 순회 + 적용 구간 판정 + 예상 수수료 계산. + */ +#ifndef ST_RATESIM_H +#define ST_RATESIM_H + +#include "st_svc.h" + +typedef struct { + char fee_type[16]; + long amount; + long band_count; + long applied_bps; + long sim_fee; +} st_st_ratesim_rec_t; + +void ST_RATESIM(TPSVCINFO *p); + +#endif /* ST_RATESIM_H */ diff --git a/app/src/st/svc/ST_RATESIM.pgc b/app/src/st/svc/ST_RATESIM.pgc new file mode 100644 index 0000000..fed9734 --- /dev/null +++ b/app/src/st/svc/ST_RATESIM.pgc @@ -0,0 +1,40 @@ +/* + * ST_RATESIM.pgc - st 모듈 서비스 ST_RATESIM (one service = one file). + * 요율 시뮬레이션: 요율표(st_fee_rate)의 전 구간을 커서로 순회하며 입력 금액에 + * 적용될 구간(bps)을 판정하고 예상 수수료를 계산한다. 조회 전용(쓰기 없음). + */ +#include "ST_RATESIM.h" + +/* 31. ST_RATESIM - 요율 시뮬레이션 (구간 판정 + 예상 수수료). */ +void ST_RATESIM(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char ftype[16]; + long amount = getl(b, T_AMOUNT), bands = 0, applied = 0, fee; + EXEC SQL BEGIN DECLARE SECTION; + long h_min, h_bps; + char h_ftype[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_STR1, ftype, sizeof(ftype)); if (ftype[0] == 0) strcpy(ftype, "MDR"); + strncpy(h_ftype, ftype, sizeof(h_ftype)-1); h_ftype[sizeof(h_ftype)-1] = 0; + EXEC SQL DECLARE ratesim_c CURSOR FOR + SELECT band_min, rate_bps FROM st_fee_rate WHERE fee_type = :h_ftype ORDER BY band_min; + EXEC SQL OPEN ratesim_c; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH ratesim_c INTO :h_min, :h_bps; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ratesim_c; FAIL(b); } + bands++; + if (h_min <= amount) applied = h_bps; /* band_min 오름차순: 마지막 매칭 구간이 적용 요율 */ + } + EXEC SQL CLOSE ratesim_c; + if (bands == 0) { setl(b, T_RC, -1); OK(b); } + fee = acq_fee(amount, applied); + setl(b, T_COUNT, bands); + setl(b, T_RC, applied); + setl(b, T_FEE, fee); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_RECVOFFSET.h b/app/src/st/svc/ST_RECVOFFSET.h new file mode 100644 index 0000000..9914410 --- /dev/null +++ b/app/src/st/svc/ST_RECVOFFSET.h @@ -0,0 +1,19 @@ +/* + * ST_RECVOFFSET.h - st 서비스 ST_RECVOFFSET 카피북 (copybook / record header). + * 미수금 상계: 누적 미수금(음수 조정)을 예정 지급액과 상계. + */ +#ifndef ST_RECVOFFSET_H +#define ST_RECVOFFSET_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long receivable; + long offset_amt; +} st_st_recvoffset_rec_t; + +void ST_RECVOFFSET(TPSVCINFO *p); + +#endif /* ST_RECVOFFSET_H */ diff --git a/app/src/st/svc/ST_RECVOFFSET.pgc b/app/src/st/svc/ST_RECVOFFSET.pgc new file mode 100644 index 0000000..7714f62 --- /dev/null +++ b/app/src/st/svc/ST_RECVOFFSET.pgc @@ -0,0 +1,41 @@ +/* + * ST_RECVOFFSET.pgc - st 모듈 서비스 ST_RECVOFFSET (one service = one file). + * 미수금 상계: 매입 조인으로 가맹점의 누적 미수금(음수 조정 절대값)을 산출하고 + * 예정 지급 가용액과의 min 만큼 최근 지급 레코드에서 상계 차감한다. + */ +#include "ST_RECVOFFSET.h" + +/* 43. ST_RECVOFFSET - 미수금-지급 상계 처리. */ +void ST_RECVOFFSET(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_recv, h_avail, h_off; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(-sum(a.delta),0) INTO :h_recv + FROM st_adjust a JOIN purchase p ON p.purchase_id = a.purchase_id + WHERE p.merchant_id = :h_merch AND a.delta < 0 AND a.biz_date <= :h_bizdate::date; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_recv <= 0) { setl(b, T_RC, 1); setl(b, T_AMOUNT, 0); OK(b); } + EXEC SQL SELECT coalesce(sum(net_amount),0) INTO :h_avail FROM st_payment + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status = 'SCHEDULED'; + if (sqlca.sqlcode < 0) FAIL(b); + h_off = (h_recv < h_avail) ? h_recv : h_avail; + if (h_off <= 0) { setl(b, T_RC, 2); setl(b, T_AMOUNT, 0); OK(b); } /* 상계 재원 없음 */ + EXEC SQL UPDATE st_payment SET net_amount = net_amount - :h_off + WHERE pay_id = (SELECT max(pay_id) FROM st_payment + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status = 'SCHEDULED'); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_AMOUNT, h_off); + setl(b, T_AMT1, h_recv - h_off); /* 잔여 미수금 */ + setl(b, T_RC, 0); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_REOPEN.h b/app/src/st/svc/ST_REOPEN.h new file mode 100644 index 0000000..accbd2d --- /dev/null +++ b/app/src/st/svc/ST_REOPEN.h @@ -0,0 +1,17 @@ +/* + * ST_REOPEN.h - st 서비스 ST_REOPEN 카피북 (copybook / record header). + * 마감 해제: 당일 마감 원장(STCLOSE) 철회. + */ +#ifndef ST_REOPEN_H +#define ST_REOPEN_H + +#include "st_svc.h" + +typedef struct { + char biz_date[16]; + long removed; +} st_st_reopen_rec_t; + +void ST_REOPEN(TPSVCINFO *p); + +#endif /* ST_REOPEN_H */ diff --git a/app/src/st/svc/ST_REOPEN.pgc b/app/src/st/svc/ST_REOPEN.pgc new file mode 100644 index 0000000..8e9900a --- /dev/null +++ b/app/src/st/svc/ST_REOPEN.pgc @@ -0,0 +1,27 @@ +/* + * ST_REOPEN.pgc - st 모듈 서비스 ST_REOPEN (one service = one file). + * 정산 마감 해제: 당일 마감 원장(STCLOSE)을 삭제해 마감을 철회한다. + * 마감 원장이 없으면(미마감) RC=1 로 회신한다. + */ +#include "ST_REOPEN.h" + +/* 61. ST_REOPEN - 정산 마감 철회 (STCLOSE 삭제). */ +void ST_REOPEN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long rows; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DELETE FROM ledger WHERE entry_type = 'STCLOSE' AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + rows = (long) sqlca.sqlerrd[2]; + setl(b, T_COUNT, rows); + setl(b, T_RC, rows > 0 ? 0 : 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_RERUN.h b/app/src/st/svc/ST_RERUN.h new file mode 100644 index 0000000..b74e90e --- /dev/null +++ b/app/src/st/svc/ST_RERUN.h @@ -0,0 +1,19 @@ +/* + * ST_RERUN.h - st 서비스 ST_RERUN 카피북 (copybook / record header). + * 정산 재실행: 기존 정산 상세 삭제 후 매입 원본 기준으로 재생성. + */ +#ifndef ST_RERUN_H +#define ST_RERUN_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + long removed; + long fee; + char merchant_id[64]; +} st_st_rerun_rec_t; + +void ST_RERUN(TPSVCINFO *p); + +#endif /* ST_RERUN_H */ diff --git a/app/src/st/svc/ST_RERUN.pgc b/app/src/st/svc/ST_RERUN.pgc new file mode 100644 index 0000000..ebe5ddf --- /dev/null +++ b/app/src/st/svc/ST_RERUN.pgc @@ -0,0 +1,32 @@ +/* + * ST_RERUN.pgc - st 모듈 서비스 ST_RERUN (one service = one file). + * 정산 재실행: 해당 매입의 정산 상세를 전량 삭제하고 매입 원본(fee)을 기준으로 + * MDR 상세 라인을 다시 생성한다. 삭제 건수를 회신한다. + */ +#include "ST_RERUN.h" + +/* 34. ST_RERUN - 정산 재실행 (상세 삭제 + 원본 기준 재생성). */ +void ST_RERUN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long removed; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amount, h_fee; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + EXEC SQL DELETE FROM st_settle_dtl WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + removed = (long) sqlca.sqlerrd[2]; + EXEC SQL SELECT amount, fee, merchant_id, to_char(biz_date,'YYYY-MM-DD') + INTO :h_amount, :h_fee, :h_merch, :h_bizdate FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + EXEC SQL INSERT INTO st_settle_dtl (dtl_id, settlement_id, purchase_id, merchant_id, fee_type, amount, biz_date) + VALUES (nextval('st_dtl_seq'), 0, :h_pid, :h_merch, 'MDR', :h_fee, :h_bizdate); + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_COUNT, removed); + setl(b, T_FEE, h_fee); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_RETRORATE.h b/app/src/st/svc/ST_RETRORATE.h new file mode 100644 index 0000000..1485e30 --- /dev/null +++ b/app/src/st/svc/ST_RETRORATE.h @@ -0,0 +1,20 @@ +/* + * ST_RETRORATE.h - st 서비스 ST_RETRORATE 카피북 (copybook / record header). + * 요율 소급적용: 신규 bps 로 재계산한 수수료와 기계상분의 차액 조정. + */ +#ifndef ST_RETRORATE_H +#define ST_RETRORATE_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + char fee_type[16]; + long new_bps; + long old_fee; + long new_fee; +} st_st_retrorate_rec_t; + +void ST_RETRORATE(TPSVCINFO *p); + +#endif /* ST_RETRORATE_H */ diff --git a/app/src/st/svc/ST_RETRORATE.pgc b/app/src/st/svc/ST_RETRORATE.pgc new file mode 100644 index 0000000..d86f974 --- /dev/null +++ b/app/src/st/svc/ST_RETRORATE.pgc @@ -0,0 +1,40 @@ +/* + * ST_RETRORATE.pgc - st 모듈 서비스 ST_RETRORATE (one service = one file). + * 요율 소급적용: 신규 요율(bps)로 매입 금액의 수수료를 재계산하고, 기계상된 + * 정산 상세 합계와의 차액만큼 소급 조정(st_adjust)을 기록한다 (dbio). + */ +#include "ST_RETRORATE.h" + +/* 36. ST_RETRORATE - 요율 소급적용 (차액 조정 기록). */ +void ST_RETRORATE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], ftype[16]; + long sid = getl(b, T_SETTLE_ID), newbps = getl(b, T_ARG1); + long newfee, diff, adj; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amount, h_old; + char h_ftype[16]; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + gets_(b, T_STR1, ftype, sizeof(ftype)); if (ftype[0] == 0) strcpy(ftype, "MDR"); + if (newbps <= 0) newbps = 25; + strncpy(h_ftype, ftype, sizeof(h_ftype)-1); h_ftype[sizeof(h_ftype)-1] = 0; + EXEC SQL SELECT amount INTO :h_amount FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_old FROM st_settle_dtl + WHERE purchase_id = :h_pid AND fee_type = :h_ftype; + if (sqlca.sqlcode < 0) FAIL(b); + newfee = acq_fee(h_amount, newbps); + diff = newfee - h_old; + if (diff == 0) { setl(b, T_RC, 0); setl(b, T_DELTA, 0); OK(b); } + adj = stdb_insert_adjust(sid, h_pid, diff, "RETRO_RATE", bizdate); + if (adj < 0) FAIL(b); + setl(b, T_ADJ, adj); + setl(b, T_DELTA, diff); + setl(b, T_FEE, newfee); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_ROUNDING.h b/app/src/st/svc/ST_ROUNDING.h new file mode 100644 index 0000000..6fcd0e3 --- /dev/null +++ b/app/src/st/svc/ST_ROUNDING.h @@ -0,0 +1,19 @@ +/* + * ST_ROUNDING.h - st 서비스 ST_ROUNDING 카피북 (copybook / record header). + * 원단위 절사: 정산 net 10원 미만 절사 + 절사분 조정. + */ +#ifndef ST_ROUNDING_H +#define ST_ROUNDING_H + +#include "st_svc.h" + +typedef struct { + long purchase_id; + long settlement_id; + long net_before; + long truncated; +} st_st_rounding_rec_t; + +void ST_ROUNDING(TPSVCINFO *p); + +#endif /* ST_ROUNDING_H */ diff --git a/app/src/st/svc/ST_ROUNDING.pgc b/app/src/st/svc/ST_ROUNDING.pgc new file mode 100644 index 0000000..ff58b47 --- /dev/null +++ b/app/src/st/svc/ST_ROUNDING.pgc @@ -0,0 +1,34 @@ +/* + * ST_ROUNDING.pgc - st 모듈 서비스 ST_ROUNDING (one service = one file). + * 원단위 절사: 최신 정산 net 의 10원 미만 끝자리를 절사해 갱신하고 절사분을 + * 음수 조정(ROUND)으로 기록한다. 끝자리 0 이면 변경 없음. + */ +#include "ST_ROUNDING.h" + +/* 58. ST_ROUNDING - 정산 net 원단위 절사. */ +void ST_ROUNDING(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + long adj; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_sid, h_net, h_cut; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + EXEC SQL SELECT settlement_id, net INTO :h_sid, :h_net FROM settlement + WHERE purchase_id = :h_pid ORDER BY settlement_id DESC LIMIT 1; + if (sqlca.sqlcode < 0 || sqlca.sqlcode == 100) FAIL(b); + h_cut = h_net % 10; + if (h_cut == 0) { setl(b, T_RC, 0); setl(b, T_DELTA, 0); OK(b); } + EXEC SQL UPDATE settlement SET net = net - :h_cut WHERE settlement_id = :h_sid; + if (sqlca.sqlcode < 0) FAIL(b); + adj = stdb_insert_adjust(h_sid, h_pid, -h_cut, "ROUND", bizdate); + if (adj < 0) FAIL(b); + setl(b, T_ADJ, adj); + setl(b, T_DELTA, -h_cut); + setl(b, T_NET, h_net - h_cut); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_SPLITEVEN.h b/app/src/st/svc/ST_SPLITEVEN.h new file mode 100644 index 0000000..ac93cef --- /dev/null +++ b/app/src/st/svc/ST_SPLITEVEN.h @@ -0,0 +1,20 @@ +/* + * ST_SPLITEVEN.h - st 서비스 ST_SPLITEVEN 카피북 (copybook / record header). + * 분할 지급 잔여 보정: n 균등 분할 + 마지막 회차 끝수 몰아주기. + */ +#ifndef ST_SPLITEVEN_H +#define ST_SPLITEVEN_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + long net; + long n_parts; + long part; + long last_part; +} st_st_spliteven_rec_t; + +void ST_SPLITEVEN(TPSVCINFO *p); + +#endif /* ST_SPLITEVEN_H */ diff --git a/app/src/st/svc/ST_SPLITEVEN.pgc b/app/src/st/svc/ST_SPLITEVEN.pgc new file mode 100644 index 0000000..e516865 --- /dev/null +++ b/app/src/st/svc/ST_SPLITEVEN.pgc @@ -0,0 +1,31 @@ +/* + * ST_SPLITEVEN.pgc - st 모듈 서비스 ST_SPLITEVEN (one service = one file). + * 분할 지급 잔여 보정: net 을 n 회로 균등 분할하되 나눗셈 끝수를 마지막 + * 회차에 몰아 합계가 정확히 net 이 되도록 지급을 생성한다 (dbio 루프). + */ +#include "ST_SPLITEVEN.h" + +/* 66. ST_SPLITEVEN - 끝수 보정 균등 분할 지급. */ +void ST_SPLITEVEN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + long net = getl(b, T_NET), n = getl(b, T_INSTALL_N); + long part, last, amt, i, pay; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (n < 2) n = 3; + part = net / n; + last = net - part * (n - 1); /* 끝수는 마지막 회차로 */ + for (i = 0; i < n; i++) { + amt = (i == n - 1) ? last : part; + pay = stdb_insert_payment(merch, amt, bizdate, "SCHEDULED", bizdate); + if (pay < 0) FAIL(b); + } + setl(b, T_COUNT, n); + setl(b, T_AMT1, part); + setl(b, T_AMT2, last); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_STATDETAIL.h b/app/src/st/svc/ST_STATDETAIL.h new file mode 100644 index 0000000..e50275f --- /dev/null +++ b/app/src/st/svc/ST_STATDETAIL.h @@ -0,0 +1,20 @@ +/* + * ST_STATDETAIL.h - st 서비스 ST_STATDETAIL 카피북 (copybook / record header). + * 정산 통계 세분: 수수료 종류별 건수/합계 분해 + 최대 항목. + */ +#ifndef ST_STATDETAIL_H +#define ST_STATDETAIL_H + +#include "st_svc.h" + +typedef struct { + char biz_date[16]; + long type_count; + long grand_total; + char top_type[16]; + long top_amount; +} st_st_statdetail_rec_t; + +void ST_STATDETAIL(TPSVCINFO *p); + +#endif /* ST_STATDETAIL_H */ diff --git a/app/src/st/svc/ST_STATDETAIL.pgc b/app/src/st/svc/ST_STATDETAIL.pgc new file mode 100644 index 0000000..330c72a --- /dev/null +++ b/app/src/st/svc/ST_STATDETAIL.pgc @@ -0,0 +1,45 @@ +/* + * ST_STATDETAIL.pgc - st 모듈 서비스 ST_STATDETAIL (one service = one file). + * 정산 통계 세분: 당일 정산 상세를 수수료 종류(fee_type)별로 분해 집계하는 + * 커서를 순회해 종류 수/총액과 최대 비중 항목을 회신한다. 조회 전용. + */ +#include "ST_STATDETAIL.h" + +/* 50. ST_STATDETAIL - 수수료 종류별 세분 통계. */ +void ST_STATDETAIL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], top[16] = ""; + long types = 0, grand = 0, topamt = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_sum; + char h_type[16], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE statdtl_c CURSOR FOR + SELECT fee_type, count(*), coalesce(sum(amount),0) FROM st_settle_dtl + WHERE biz_date = :h_bizdate GROUP BY fee_type ORDER BY 3 DESC; + EXEC SQL OPEN statdtl_c; + if (sqlca.sqlcode < 0) FAIL(b); + for (;;) { + EXEC SQL FETCH statdtl_c INTO :h_type, :h_cnt, :h_sum; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE statdtl_c; FAIL(b); } + if (types == 0) { /* 합계 내림차순: 첫 행이 최대 항목 */ + strncpy(top, h_type, sizeof(top)-1); top[sizeof(top)-1] = 0; + topamt = h_sum; + } + types++; + grand += h_sum; + } + EXEC SQL CLOSE statdtl_c; + if (types == 0) { setl(b, T_RC, 1); OK(b); } + setl(b, T_COUNT, types); + setl(b, T_GROSS, grand); + setl(b, T_AMT1, topamt); + Bchg(b, T_STR1, 0, top, 0L); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_TAXINVCHK.h b/app/src/st/svc/ST_TAXINVCHK.h new file mode 100644 index 0000000..0236c02 --- /dev/null +++ b/app/src/st/svc/ST_TAXINVCHK.h @@ -0,0 +1,20 @@ +/* + * ST_TAXINVCHK.h - st 서비스 ST_TAXINVCHK 카피북 (copybook / record header). + * 세금계산서 대사: 계산서 세액 vs 정산 상세 VAT 대사. + */ +#ifndef ST_TAXINVCHK_H +#define ST_TAXINVCHK_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long inv_supply; + long inv_vat; + long dtl_vat; +} st_st_taxinvchk_rec_t; + +void ST_TAXINVCHK(TPSVCINFO *p); + +#endif /* ST_TAXINVCHK_H */ diff --git a/app/src/st/svc/ST_TAXINVCHK.pgc b/app/src/st/svc/ST_TAXINVCHK.pgc new file mode 100644 index 0000000..c0f147d --- /dev/null +++ b/app/src/st/svc/ST_TAXINVCHK.pgc @@ -0,0 +1,35 @@ +/* + * ST_TAXINVCHK.pgc - st 모듈 서비스 ST_TAXINVCHK (one service = one file). + * 세금계산서 대사: 가맹점 당일 세금계산서(ac_tax_invoice) 공급가·세액 합계와 + * 정산 상세 VAT 합계를 대사해 차이를 판정한다. 불일치 RC=1. 조회 전용. + */ +#include "ST_TAXINVCHK.h" + +/* 69. ST_TAXINVCHK - 세금계산서-정산 VAT 대사. */ +void ST_TAXINVCHK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_supply, h_invvat, h_dtlvat; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(supply),0), coalesce(sum(vat),0) INTO :h_supply, :h_invvat + FROM ac_tax_invoice WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_dtlvat FROM st_settle_dtl + WHERE merchant_id = :h_merch AND fee_type = 'VAT' AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_GROSS, h_supply); + setl(b, T_AMT1, h_invvat); + setl(b, T_TAX, h_dtlvat); + setl(b, T_DELTA, h_invvat - h_dtlvat); + setl(b, T_RC, (h_invvat == h_dtlvat) ? 0 : 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_TIERINCENT.h b/app/src/st/svc/ST_TIERINCENT.h new file mode 100644 index 0000000..7d9dad6 --- /dev/null +++ b/app/src/st/svc/ST_TIERINCENT.h @@ -0,0 +1,20 @@ +/* + * ST_TIERINCENT.h - st 서비스 ST_TIERINCENT 카피북 (copybook / record header). + * 구간별 인센티브: 가맹점 일 거래대금 구간에 따른 인센티브 지급 조정. + */ +#ifndef ST_TIERINCENT_H +#define ST_TIERINCENT_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + char biz_date[16]; + long gross; + long tier_bps; + long incentive; +} st_st_tierincent_rec_t; + +void ST_TIERINCENT(TPSVCINFO *p); + +#endif /* ST_TIERINCENT_H */ diff --git a/app/src/st/svc/ST_TIERINCENT.pgc b/app/src/st/svc/ST_TIERINCENT.pgc new file mode 100644 index 0000000..252b8bd --- /dev/null +++ b/app/src/st/svc/ST_TIERINCENT.pgc @@ -0,0 +1,40 @@ +/* + * ST_TIERINCENT.pgc - st 모듈 서비스 ST_TIERINCENT (one service = one file). + * 구간별 인센티브: 가맹점 일 거래대금(gross) 구간에 따라 인센티브 요율을 + * 판정(1억↑ 30bps / 5천만↑ 20bps / 1천만↑ 10bps)하고 양수 조정으로 기록. + */ +#include "ST_TIERINCENT.h" + +/* 37. ST_TIERINCENT - 거래대금 구간별 인센티브 정산. */ +void ST_TIERINCENT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + long bps, inc, adj; + EXEC SQL BEGIN DECLARE SECTION; + long h_gross; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT gross_amount INTO :h_gross FROM st_merch_settle + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + if (h_gross >= 100000000) bps = 30; + else if (h_gross >= 50000000) bps = 20; + else if (h_gross >= 10000000) bps = 10; + else bps = 0; + if (bps == 0) { setl(b, T_RC, 1); setl(b, T_AMOUNT, 0); OK(b); } + inc = acq_fee(h_gross, bps); + adj = stdb_insert_adjust(0, 0, inc, "INCENTIVE", bizdate); + if (adj < 0) FAIL(b); + setl(b, T_ADJ, adj); + setl(b, T_AMOUNT, inc); + setl(b, T_RC, bps); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_TOPMERCH.h b/app/src/st/svc/ST_TOPMERCH.h new file mode 100644 index 0000000..c54fdcb --- /dev/null +++ b/app/src/st/svc/ST_TOPMERCH.h @@ -0,0 +1,18 @@ +/* + * ST_TOPMERCH.h - st 서비스 ST_TOPMERCH 카피북 (copybook / record header). + * 상위 가맹점 조회: 당일 정산 net 1위 가맹점. + */ +#ifndef ST_TOPMERCH_H +#define ST_TOPMERCH_H + +#include "st_svc.h" + +typedef struct { + char biz_date[16]; + char top_merchant[64]; + long top_net; +} st_st_topmerch_rec_t; + +void ST_TOPMERCH(TPSVCINFO *p); + +#endif /* ST_TOPMERCH_H */ diff --git a/app/src/st/svc/ST_TOPMERCH.pgc b/app/src/st/svc/ST_TOPMERCH.pgc new file mode 100644 index 0000000..d7070fc --- /dev/null +++ b/app/src/st/svc/ST_TOPMERCH.pgc @@ -0,0 +1,30 @@ +/* + * ST_TOPMERCH.pgc - st 모듈 서비스 ST_TOPMERCH (one service = one file). + * 상위 가맹점 조회: 당일 정산 net 합계 1위 가맹점을 GROUP BY + ORDER BY + * DESC LIMIT 1 로 조회해 회신한다. 정산 없으면 RC=1. 조회 전용. + */ +#include "ST_TOPMERCH.h" + +/* 64. ST_TOPMERCH - 당일 정산 1위 가맹점 조회. */ +void ST_TOPMERCH(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_sum; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT merchant_id, coalesce(sum(net),0) INTO :h_merch, :h_sum + FROM settlement WHERE biz_date = :h_bizdate + GROUP BY merchant_id ORDER BY 2 DESC LIMIT 1; + if (sqlca.sqlcode < 0) FAIL(b); + if (sqlca.sqlcode == 100) { setl(b, T_RC, 1); OK(b); } + Bchg(b, T_MERCHANT, 0, h_merch, 0L); + setl(b, T_GROSS, h_sum); + setl(b, T_RC, 0); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_UNPAIDSUM.h b/app/src/st/svc/ST_UNPAIDSUM.h new file mode 100644 index 0000000..026b352 --- /dev/null +++ b/app/src/st/svc/ST_UNPAIDSUM.h @@ -0,0 +1,19 @@ +/* + * ST_UNPAIDSUM.h - st 서비스 ST_UNPAIDSUM 카피북 (copybook / record header). + * 미지급 집계: 지급 예정일 경과 미완료 지급 건수/합계. + */ +#ifndef ST_UNPAIDSUM_H +#define ST_UNPAIDSUM_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + char base_date[16]; + long unpaid_cnt; + long unpaid_sum; +} st_st_unpaidsum_rec_t; + +void ST_UNPAIDSUM(TPSVCINFO *p); + +#endif /* ST_UNPAIDSUM_H */ diff --git a/app/src/st/svc/ST_UNPAIDSUM.pgc b/app/src/st/svc/ST_UNPAIDSUM.pgc new file mode 100644 index 0000000..b037491 --- /dev/null +++ b/app/src/st/svc/ST_UNPAIDSUM.pgc @@ -0,0 +1,32 @@ +/* + * ST_UNPAIDSUM.pgc - st 모듈 서비스 ST_UNPAIDSUM (one service = one file). + * 미지급 집계: 기준일 이전이 지급 예정일인데 아직 완료(PAID)되지 않은 + * 지급(SCHEDULED/HELD)의 건수·합계를 집계한다. 없으면 RC=1. 조회 전용. + */ +#include "ST_UNPAIDSUM.h" + +/* 72. ST_UNPAIDSUM - 예정일 경과 미지급 집계. */ +void ST_UNPAIDSUM(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_sum; + char h_merch[64], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*), coalesce(sum(net_amount),0) INTO :h_cnt, :h_sum + FROM st_payment WHERE merchant_id = :h_merch + AND status IN ('SCHEDULED','HELD') AND pay_date < :h_bizdate::date; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt == 0) { setl(b, T_RC, 1); setl(b, T_COUNT, 0); OK(b); } + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_sum); + setl(b, T_RC, 0); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_VATREPORT.h b/app/src/st/svc/ST_VATREPORT.h new file mode 100644 index 0000000..e5c7edb --- /dev/null +++ b/app/src/st/svc/ST_VATREPORT.h @@ -0,0 +1,20 @@ +/* + * ST_VATREPORT.h - st 서비스 ST_VATREPORT 카피북 (copybook / record header). + * 부가세 신고자료: 월 단위 VAT 상세/세금계산서 대사 집계. + */ +#ifndef ST_VATREPORT_H +#define ST_VATREPORT_H + +#include "st_svc.h" + +typedef struct { + char yyyymm[16]; + long dtl_vat; + long inv_cnt; + long inv_supply; + long inv_vat; +} st_st_vatreport_rec_t; + +void ST_VATREPORT(TPSVCINFO *p); + +#endif /* ST_VATREPORT_H */ diff --git a/app/src/st/svc/ST_VATREPORT.pgc b/app/src/st/svc/ST_VATREPORT.pgc new file mode 100644 index 0000000..ff2ed2f --- /dev/null +++ b/app/src/st/svc/ST_VATREPORT.pgc @@ -0,0 +1,35 @@ +/* + * ST_VATREPORT.pgc - st 모듈 서비스 ST_VATREPORT (one service = one file). + * 부가세 신고자료: 대상 월의 정산 상세 VAT 합계와 세금계산서(ac_tax_invoice) + * 공급가/세액 합계·건수를 함께 집계해 신고 기초자료를 회신한다. 조회 전용. + */ +#include "ST_VATREPORT.h" + +/* 45. ST_VATREPORT - 월별 부가세 신고자료 집계. */ +void ST_VATREPORT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char ym[16], like[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_vat, h_cnt, h_supply, h_invvat; + char h_like[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_KEY1, ym, sizeof(ym)); if (ym[0] == 0) strcpy(ym, "2026-07"); + snprintf(like, sizeof(like), "%s%%", ym); + strncpy(h_like, like, sizeof(h_like)-1); h_like[sizeof(h_like)-1] = 0; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_vat FROM st_settle_dtl + WHERE fee_type = 'VAT' AND to_char(biz_date,'YYYY-MM-DD') LIKE :h_like; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*), coalesce(sum(supply),0), coalesce(sum(vat),0) + INTO :h_cnt, :h_supply, :h_invvat FROM ac_tax_invoice + WHERE to_char(biz_date,'YYYY-MM-DD') LIKE :h_like; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_TAX, h_vat); + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_supply); + setl(b, T_AMT1, h_invvat); + setl(b, T_DELTA, h_invvat - h_vat); /* 신고자료-상세 대사 차이 */ + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/st/svc/ST_WHTREPORT.h b/app/src/st/svc/ST_WHTREPORT.h new file mode 100644 index 0000000..3ee6b1a --- /dev/null +++ b/app/src/st/svc/ST_WHTREPORT.h @@ -0,0 +1,20 @@ +/* + * ST_WHTREPORT.h - st 서비스 ST_WHTREPORT 카피북 (copybook / record header). + * 원천세 명세: 가맹점 월별 원천세 상세 vs 원천 대장 대사. + */ +#ifndef ST_WHTREPORT_H +#define ST_WHTREPORT_H + +#include "st_svc.h" + +typedef struct { + char merchant_id[64]; + char yyyymm[16]; + long dtl_wht; + long reg_wht; + long diff; +} st_st_whtreport_rec_t; + +void ST_WHTREPORT(TPSVCINFO *p); + +#endif /* ST_WHTREPORT_H */ diff --git a/app/src/st/svc/ST_WHTREPORT.pgc b/app/src/st/svc/ST_WHTREPORT.pgc new file mode 100644 index 0000000..3a0f25d --- /dev/null +++ b/app/src/st/svc/ST_WHTREPORT.pgc @@ -0,0 +1,37 @@ +/* + * ST_WHTREPORT.pgc - st 모듈 서비스 ST_WHTREPORT (one service = one file). + * 원천세 명세: 가맹점의 대상 월 정산 상세(WHT) 합계와 원천 대장(ac_wht) 합계를 + * 각각 집계·대사하고 차이를 회신한다. 불일치 시 RC=1. 조회 전용. + */ +#include "ST_WHTREPORT.h" + +/* 46. ST_WHTREPORT - 가맹점 월별 원천세 명세 대사. */ +void ST_WHTREPORT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char merch[64], ym[16], like[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_dtl, h_reg; + char h_merch[64], h_like[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, merch, sizeof(merch)); + gets_(b, T_KEY1, ym, sizeof(ym)); if (ym[0] == 0) strcpy(ym, "2026-07"); + snprintf(like, sizeof(like), "%s%%", ym); + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + strncpy(h_like, like, sizeof(h_like)-1); h_like[sizeof(h_like)-1] = 0; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_dtl FROM st_settle_dtl + WHERE merchant_id = :h_merch AND fee_type = 'WHT' + AND to_char(biz_date,'YYYY-MM-DD') LIKE :h_like; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(w.tax_amount),0) INTO :h_reg + FROM ac_wht w JOIN purchase p ON p.purchase_id = w.purchase_id + WHERE p.merchant_id = :h_merch AND to_char(w.biz_date,'YYYY-MM-DD') LIKE :h_like; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_TAX, h_dtl); + setl(b, T_AMT1, h_reg); + setl(b, T_DELTA, h_reg - h_dtl); + setl(b, T_RC, (h_reg == h_dtl) ? 0 : 1); + OK(b); +} + +/* vim: set ts=4 sw=4 et smartindent: */ diff --git a/app/src/vl/batch/vl_agedopen_batch.pgc b/app/src/vl/batch/vl_agedopen_batch.pgc new file mode 100644 index 0000000..d683e46 --- /dev/null +++ b/app/src/vl/batch/vl_agedopen_batch.pgc @@ -0,0 +1,43 @@ +/* + * vl_agedopen_batch.pgc - 장기미결 의심거래 에스컬레이션 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; N일 초과 OPEN 의심거래의 severity를 CRIT로 승격한 뒤 + * tpcommit drives XA 2PC. + * Usage: vl_agedopen_batch [YYYY-MM-DD] [경과일수] + */ +#include +#include +#include +#include +#include "vl_agedopen_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + long h_days; + EXEC SQL END DECLARE SECTION; + vl_agedopen_rec_t rec; + (void)bizdate; + + h_days = (argc > 2) ? atol(argv[2]) : VL_AGEDOPEN_DEF_DAYS; + if (h_days <= 0) h_days = VL_AGEDOPEN_DEF_DAYS; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL UPDATE vl_suspect SET severity = 'CRIT' + WHERE status = 'OPEN' AND severity <> 'CRIT' + AND created_at < now() - (:h_days || ' days')::interval; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_agedopen_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.escalated = (long)sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + rec.days = h_days; + printf(">>> vl_agedopen_batch COMMIT: 기준=%ld일 CRIT승격=%ld\n", rec.days, rec.escalated); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_apprsum_batch.pgc b/app/src/vl/batch/vl_apprsum_batch.pgc new file mode 100644 index 0000000..e7179a6 --- /dev/null +++ b/app/src/vl/batch/vl_apprsum_batch.pgc @@ -0,0 +1,46 @@ +/* + * vl_apprsum_batch.pgc - 일자별 매입-승인 금액 대사 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 취소 제외 매입 총액과 가결 승인이 붙은 매입 총액을 대조한 뒤 + * tpcommit drives XA 2PC. + * Usage: vl_apprsum_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_apprsum_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : VL_APPRSUM_DEF_DATE; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_all, h_appr; + EXEC SQL END DECLARE SECTION; + vl_apprsum_rec_t rec; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_all FROM purchase + WHERE biz_date = :h_bizdate AND status <> 'C'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_apprsum_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + EXEC SQL SELECT coalesce(sum(p.amount),0) INTO :h_appr FROM purchase p + WHERE p.biz_date = :h_bizdate AND p.status <> 'C' + AND EXISTS (SELECT 1 FROM approval a + WHERE a.purchase_id = p.purchase_id AND a.approved); + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_apprsum_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + rec.total_amt = h_all; rec.appr_amt = h_appr; rec.gap_amt = h_all - h_appr; + printf(">>> vl_apprsum_batch COMMIT: bizdate=%s 매입총액=%ld 승인보유=%ld 미승인=%ld\n", + bizdate, rec.total_amt, rec.appr_amt, rec.gap_amt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_daterev_batch.pgc b/app/src/vl/batch/vl_daterev_batch.pgc new file mode 100644 index 0000000..2186c8f --- /dev/null +++ b/app/src/vl/batch/vl_daterev_batch.pgc @@ -0,0 +1,46 @@ +/* + * vl_daterev_batch.pgc - 일자 역전 검출 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 매입일보다 과거로 기표된 정산/원장 건수를 각각 집계한 뒤 + * tpcommit drives XA 2PC. + * Usage: vl_daterev_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_daterev_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : VL_DATEREV_DEF_DATE; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_srev, h_lrev; + EXEC SQL END DECLARE SECTION; + vl_daterev_rec_t rec; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*) INTO :h_srev + FROM settlement s JOIN purchase p ON p.purchase_id = s.purchase_id + WHERE s.biz_date = :h_bizdate AND s.biz_date < p.biz_date; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_daterev_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + EXEC SQL SELECT count(*) INTO :h_lrev + FROM ledger l JOIN purchase p ON p.purchase_id = l.purchase_id + WHERE l.biz_date = :h_bizdate AND l.biz_date < p.biz_date; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_daterev_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + rec.settle_rev = h_srev; rec.ledger_rev = h_lrev; + printf(">>> vl_daterev_batch COMMIT: bizdate=%s 정산역전=%ld 원장역전=%ld\n", + bizdate, rec.settle_rev, rec.ledger_rev); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_duppay_batch.pgc b/app/src/vl/batch/vl_duppay_batch.pgc new file mode 100644 index 0000000..a466bf8 --- /dev/null +++ b/app/src/vl/batch/vl_duppay_batch.pgc @@ -0,0 +1,56 @@ +/* + * vl_duppay_batch.pgc - 중복지급 스캔 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 커서로 지급(XFER) 원장이 2건 이상인 매입을 훑어 의심거래로 + * 적재한 뒤 tpcommit drives XA 2PC. + * Usage: vl_duppay_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_dbio.h" +#include "vl_duppay_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_pid, h_cnt; + EXEC SQL END DECLARE SECTION; + vl_duppay_rec_t rec; rec.dup_groups = 0; rec.suspects = 0; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE vdpc CURSOR FOR + SELECT purchase_id, count(*) FROM ledger + WHERE biz_date = :h_bizdate AND entry_type = 'XFER' + GROUP BY purchase_id HAVING count(*) > 1; + EXEC SQL OPEN vdpc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_duppay_batch OPEN FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH vdpc INTO :h_pid, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_duppay_batch FETCH FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + rec.dup_groups++; + if (vldb_insert_suspect(h_pid, VL_DUPPAY_RULE, VL_DUPPAY_SEV, + "지급 원장 중복 기표", h_bizdate) < 0) { + fprintf(stderr, "vl_duppay_batch 의심거래 적재 FAIL\n"); tpabort(0); return 1; + } + rec.suspects++; + } + EXEC SQL CLOSE vdpc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_duppay_batch COMMIT: bizdate=%s 중복지급그룹=%ld 의심거래적재=%ld\n", + bizdate, rec.dup_groups, rec.suspects); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_feerecalc_batch.pgc b/app/src/vl/batch/vl_feerecalc_batch.pgc new file mode 100644 index 0000000..be62804 --- /dev/null +++ b/app/src/vl/batch/vl_feerecalc_batch.pgc @@ -0,0 +1,47 @@ +/* + * vl_feerecalc_batch.pgc - 수수료 재계산 편차 검증 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 가맹점 요율로 수수료를 재계산해 허용오차를 넘는 매입 건수와 + * 편차 총액을 집계한 뒤 tpcommit drives XA 2PC. + * Usage: vl_feerecalc_batch [YYYY-MM-DD] [허용오차원] + */ +#include +#include +#include +#include +#include "vl_feerecalc_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_tol, h_viol, h_devsum; + EXEC SQL END DECLARE SECTION; + vl_feerecalc_rec_t rec; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + h_tol = (argc > 2) ? atol(argv[2]) : VL_FEERECALC_DEF_TOL; + if (h_tol < 0) h_tol = VL_FEERECALC_DEF_TOL; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*), + coalesce(sum(abs(p.fee - p.amount * m.mdr_bps / 10000)),0) + INTO :h_viol, :h_devsum + FROM purchase p JOIN merchant m ON m.merchant_id = p.merchant_id + WHERE p.biz_date = :h_bizdate + AND abs(p.fee - p.amount * m.mdr_bps / 10000) > :h_tol; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_feerecalc_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + rec.tol_won = h_tol; rec.viol = h_viol; rec.dev_sum = h_devsum; + printf(">>> vl_feerecalc_batch COMMIT: bizdate=%s 허용=%ld원 편차초과=%ld 편차합=%ld\n", + bizdate, rec.tol_won, rec.viol, rec.dev_sum); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_negbal_batch.pgc b/app/src/vl/batch/vl_negbal_batch.pgc new file mode 100644 index 0000000..49da960 --- /dev/null +++ b/app/src/vl/batch/vl_negbal_batch.pgc @@ -0,0 +1,57 @@ +/* + * vl_negbal_batch.pgc - 마이너스 잔액 가맹점 검출 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 커서로 일별 집계 순액이 음수인 가맹점을 훑어 의심거래로 적재하고 + * 최악치를 보고한 뒤 tpcommit drives XA 2PC. + * Usage: vl_negbal_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_dbio.h" +#include "vl_negbal_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_net; + EXEC SQL END DECLARE SECTION; + char detail[96]; + vl_negbal_rec_t rec; rec.merchants = 0; rec.worst_amt = 0; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE vnbc CURSOR FOR + SELECT merchant_id, net_amount FROM merchant_summary + WHERE biz_date = :h_bizdate AND net_amount < 0 + ORDER BY net_amount; + EXEC SQL OPEN vnbc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_negbal_batch OPEN FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH vnbc INTO :h_merch, :h_net; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_negbal_batch FETCH FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + if (rec.merchants == 0) rec.worst_amt = h_net; /* 정렬상 첫 행이 최악치 */ + snprintf(detail, sizeof(detail), "가맹점 %s 순액 %ld", h_merch, h_net); + if (vldb_insert_suspect(0, VL_NEGBAL_RULE, "CRIT", detail, h_bizdate) < 0) { + fprintf(stderr, "vl_negbal_batch 의심거래 적재 FAIL\n"); tpabort(0); return 1; + } + rec.merchants++; + } + EXEC SQL CLOSE vnbc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_negbal_batch COMMIT: bizdate=%s 음수잔액가맹점=%ld 최악=%ld\n", + bizdate, rec.merchants, rec.worst_amt); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_ratedev_batch.pgc b/app/src/vl/batch/vl_ratedev_batch.pgc new file mode 100644 index 0000000..33392b8 --- /dev/null +++ b/app/src/vl/batch/vl_ratedev_batch.pgc @@ -0,0 +1,47 @@ +/* + * vl_ratedev_batch.pgc - 실적용 요율 편차 스캔 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 매입별 실적용 요율(fee*10000/amount)과 가맹점 요율의 편차가 + * 허용치를 넘는 건수/최대편차를 집계한 뒤 tpcommit drives XA 2PC. + * Usage: vl_ratedev_batch [YYYY-MM-DD] [허용편차bps] + */ +#include +#include +#include +#include +#include "vl_ratedev_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_tol, h_viol, h_maxdev; + EXEC SQL END DECLARE SECTION; + vl_ratedev_rec_t rec; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + h_tol = (argc > 2) ? atol(argv[2]) : VL_RATEDEV_DEF_TOL; + if (h_tol <= 0) h_tol = VL_RATEDEV_DEF_TOL; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*), + coalesce(max(abs(p.fee * 10000 / p.amount - m.mdr_bps)),0) + INTO :h_viol, :h_maxdev + FROM purchase p JOIN merchant m ON m.merchant_id = p.merchant_id + WHERE p.biz_date = :h_bizdate AND p.amount > 0 + AND abs(p.fee * 10000 / p.amount - m.mdr_bps) > :h_tol; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_ratedev_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + rec.tol_bps = h_tol; rec.viol = h_viol; rec.max_dev = h_maxdev; + printf(">>> vl_ratedev_batch COMMIT: bizdate=%s 허용=%ldbps 위반=%ld 최대편차=%ldbps\n", + bizdate, rec.tol_bps, rec.viol, rec.max_dev); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_ruleaudit_batch.pgc b/app/src/vl/batch/vl_ruleaudit_batch.pgc new file mode 100644 index 0000000..fb12e85 --- /dev/null +++ b/app/src/vl/batch/vl_ruleaudit_batch.pgc @@ -0,0 +1,55 @@ +/* + * vl_ruleaudit_batch.pgc - 검증 규칙 감사 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 커서로 임계값이 설정되지 않은 ACTIVE 고위험(ERROR/CRIT) 규칙을 + * 훑어 규칙별로 검증로그에 남긴 뒤 tpcommit drives XA 2PC. + * Usage: vl_ruleaudit_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_dbio.h" +#include "vl_ruleaudit_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + char h_rule[64]; + EXEC SQL END DECLARE SECTION; + char detail[96]; + vl_ruleaudit_rec_t rec; rec.bad_rules = 0; rec.logged = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DECLARE vrac CURSOR FOR + SELECT rule_code FROM vl_rule + WHERE status = 'ACTIVE' AND severity IN ('ERROR','CRIT') + AND threshold <= 0 + ORDER BY rule_code; + EXEC SQL OPEN vrac; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_ruleaudit_batch OPEN FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH vrac INTO :h_rule; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_ruleaudit_batch FETCH FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + rec.bad_rules++; + snprintf(detail, sizeof(detail), "%s 임계값 미설정 고위험 규칙", h_rule); + if (vldb_insert_check_log(VL_RULEAUDIT_CHECK, 0, 1, detail, bizdate) < 0) { + fprintf(stderr, "vl_ruleaudit_batch 검증로그 적재 FAIL\n"); tpabort(0); return 1; + } + rec.logged++; + } + EXEC SQL CLOSE vrac; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_ruleaudit_batch COMMIT: bizdate=%s 감사적발규칙=%ld 로그=%ld\n", + bizdate, rec.bad_rules, rec.logged); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_sumrecon_batch.pgc b/app/src/vl/batch/vl_sumrecon_batch.pgc new file mode 100644 index 0000000..92f5ea5 --- /dev/null +++ b/app/src/vl/batch/vl_sumrecon_batch.pgc @@ -0,0 +1,51 @@ +/* + * vl_sumrecon_batch.pgc - 가맹점 일별 통계-원천 대사 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; merchant_summary의 건수/총액을 purchase 원천 재집계와 대조해 + * 불일치 행 수를 산출한 뒤 tpcommit drives XA 2PC. + * Usage: vl_sumrecon_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_sumrecon_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : VL_SUMRECON_DEF_DATE; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_rows, h_mis; + EXEC SQL END DECLARE SECTION; + vl_sumrecon_rec_t rec; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*) INTO :h_rows FROM merchant_summary + WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_sumrecon_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + EXEC SQL SELECT count(*) INTO :h_mis FROM merchant_summary ms + WHERE ms.biz_date = :h_bizdate + AND (ms.txn_count <> (SELECT count(*) FROM purchase p + WHERE p.merchant_id = ms.merchant_id + AND p.biz_date = ms.biz_date AND p.status <> 'C') + OR ms.gross_amount <> coalesce((SELECT sum(p.amount) FROM purchase p + WHERE p.merchant_id = ms.merchant_id + AND p.biz_date = ms.biz_date + AND p.status <> 'C'), 0)); + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_sumrecon_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + rec.summary_rows = h_rows; rec.mismatched = h_mis; + printf(">>> vl_sumrecon_batch COMMIT: bizdate=%s 통계행=%ld 대사불일치=%ld\n", + bizdate, rec.summary_rows, rec.mismatched); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_thrtrim_batch.pgc b/app/src/vl/batch/vl_thrtrim_batch.pgc new file mode 100644 index 0000000..b35da07 --- /dev/null +++ b/app/src/vl/batch/vl_thrtrim_batch.pgc @@ -0,0 +1,42 @@ +/* + * vl_thrtrim_batch.pgc - 검증 규칙 임계값 정리 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 잘못 저장된 음수 임계값을 0으로 클램프하고 전체 규칙 수를 + * 함께 보고한 뒤 tpcommit drives XA 2PC. + * Usage: vl_thrtrim_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_thrtrim_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + long h_total; + EXEC SQL END DECLARE SECTION; + vl_thrtrim_rec_t rec; + (void)bizdate; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL UPDATE vl_rule SET threshold = 0, updated_at = now() + WHERE threshold < 0; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_thrtrim_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.clamped = (long)sqlca.sqlerrd[2]; + EXEC SQL SELECT count(*) INTO :h_total FROM vl_rule; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_thrtrim_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + rec.total_rules = h_total; + printf(">>> vl_thrtrim_batch COMMIT: 음수임계값정리=%ld 전체규칙=%ld\n", + rec.clamped, rec.total_rules); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_tristage_batch.pgc b/app/src/vl/batch/vl_tristage_batch.pgc new file mode 100644 index 0000000..5eb3388 --- /dev/null +++ b/app/src/vl/batch/vl_tristage_batch.pgc @@ -0,0 +1,55 @@ +/* + * vl_tristage_batch.pgc - 매입-정산-지급 3단 정합 대사 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 정산완료 매입 순액/정산 순액/지급(XFER) 원장 합계와 건별 3단 + * 불일치 건수를 집계한 뒤 tpcommit drives XA 2PC. + * Usage: vl_tristage_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_tristage_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : VL_TRISTAGE_DEF_DATE; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_pnet, h_snet, h_xamt, h_bad; + EXEC SQL END DECLARE SECTION; + vl_tristage_rec_t rec; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT coalesce(sum(net),0) INTO :h_pnet FROM purchase + WHERE biz_date = :h_bizdate AND status = 'S'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_tristage_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + EXEC SQL SELECT coalesce(sum(net),0) INTO :h_snet FROM settlement + WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_tristage_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_xamt FROM ledger + WHERE biz_date = :h_bizdate AND entry_type = 'XFER'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_tristage_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + EXEC SQL SELECT count(*) INTO :h_bad FROM purchase p + WHERE p.biz_date = :h_bizdate AND p.status = 'S' + AND (p.net <> coalesce((SELECT sum(s.net) FROM settlement s + WHERE s.purchase_id = p.purchase_id), 0) + OR p.net <> coalesce((SELECT sum(l.amount) FROM ledger l + WHERE l.purchase_id = p.purchase_id + AND l.entry_type = 'XFER'), 0)); + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_tristage_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + rec.p_net = h_pnet; rec.s_net = h_snet; rec.x_amt = h_xamt; rec.broken = h_bad; + printf(">>> vl_tristage_batch COMMIT: bizdate=%s 매입=%ld 정산=%ld 지급=%ld 3단불일치=%ld\n", + bizdate, rec.p_net, rec.s_net, rec.x_amt, rec.broken); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_unacq_batch.pgc b/app/src/vl/batch/vl_unacq_batch.pgc new file mode 100644 index 0000000..f489cb2 --- /dev/null +++ b/app/src/vl/batch/vl_unacq_batch.pgc @@ -0,0 +1,45 @@ +/* + * vl_unacq_batch.pgc - 미매입 승인 검출 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 매입 원장이 없는 고아 승인을 집계하고 결과를 검증로그에 남긴 뒤 + * tpcommit drives XA 2PC. + * Usage: vl_unacq_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_dbio.h" +#include "vl_unacq_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_first; + EXEC SQL END DECLARE SECTION; + vl_unacq_rec_t rec; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT count(*), coalesce(min(a.approval_id),0) INTO :h_cnt, :h_first + FROM approval a + WHERE NOT EXISTS (SELECT 1 FROM purchase p + WHERE p.purchase_id = a.purchase_id); + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_unacq_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + if (vldb_insert_check_log(VL_UNACQ_CHECK, h_first, (h_cnt > 0) ? 1 : 0, + "미매입 승인 정기 스캔", bizdate) < 0) { + fprintf(stderr, "vl_unacq_batch 검증로그 적재 FAIL\n"); tpabort(0); return 1; + } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + rec.orphans = h_cnt; rec.first_id = h_first; + printf(">>> vl_unacq_batch COMMIT: bizdate=%s 고아승인=%ld 최초ID=%ld\n", + bizdate, rec.orphans, rec.first_id); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_wfstat_batch.pgc b/app/src/vl/batch/vl_wfstat_batch.pgc new file mode 100644 index 0000000..d78a6f3 --- /dev/null +++ b/app/src/vl/batch/vl_wfstat_batch.pgc @@ -0,0 +1,45 @@ +/* + * vl_wfstat_batch.pgc - 의심거래 워크플로 상태 통계 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction; 일자별 의심거래를 확인/조치/종결 워크플로 상태별로 집계한 뒤 + * tpcommit drives XA 2PC. + * Usage: vl_wfstat_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_wfstat_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : VL_WFSTAT_DEF_DATE; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_open, h_conf, h_closed; + EXEC SQL END DECLARE SECTION; + vl_wfstat_rec_t rec; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + + if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpopen() < 0) { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; } + if (tpbegin(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL SELECT + coalesce(sum(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN status = 'CONFIRMED' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END),0) + INTO :h_open, :h_conf, :h_closed + FROM vl_suspect WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_wfstat_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + rec.open_c = h_open; rec.conf_c = h_conf; rec.closed_c = h_closed; + printf(">>> vl_wfstat_batch COMMIT: bizdate=%s 미결=%ld 확인=%ld 종결=%ld\n", + bizdate, rec.open_c, rec.conf_c, rec.closed_c); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/dbio/vl_agedopen_batch.h b/app/src/vl/dbio/vl_agedopen_batch.h new file mode 100644 index 0000000..250a27f --- /dev/null +++ b/app/src/vl/dbio/vl_agedopen_batch.h @@ -0,0 +1,12 @@ +/* + * vl_agedopen_batch.h - vl vl_agedopen_batch 배치 전용 카피북 (copybook). 장기미결 의심거래 에스컬레이션 배치 + * 기본 경과일 상수 + 승격 결과 레코드. Included by vl_agedopen_batch.pgc. + */ +#ifndef VL_AGEDOPEN_BATCH_H +#define VL_AGEDOPEN_BATCH_H + +#define VL_AGEDOPEN_DEF_DAYS 7L + +typedef struct { long days; long escalated; } vl_agedopen_rec_t; + +#endif /* VL_AGEDOPEN_BATCH_H */ diff --git a/app/src/vl/dbio/vl_apprsum_batch.h b/app/src/vl/dbio/vl_apprsum_batch.h new file mode 100644 index 0000000..f1567e0 --- /dev/null +++ b/app/src/vl/dbio/vl_apprsum_batch.h @@ -0,0 +1,12 @@ +/* + * vl_apprsum_batch.h - vl vl_apprsum_batch 배치 전용 카피북 (copybook). 일자별 매입-승인 금액 대사 배치 + * 대사 결과 레코드 + 기본 영업일 상수. Included by vl_apprsum_batch.pgc. + */ +#ifndef VL_APPRSUM_BATCH_H +#define VL_APPRSUM_BATCH_H + +#define VL_APPRSUM_DEF_DATE "2026-07-19" + +typedef struct { long total_amt; long appr_amt; long gap_amt; } vl_apprsum_rec_t; + +#endif /* VL_APPRSUM_BATCH_H */ diff --git a/app/src/vl/dbio/vl_daterev_batch.h b/app/src/vl/dbio/vl_daterev_batch.h new file mode 100644 index 0000000..4411077 --- /dev/null +++ b/app/src/vl/dbio/vl_daterev_batch.h @@ -0,0 +1,12 @@ +/* + * vl_daterev_batch.h - vl vl_daterev_batch 배치 전용 카피북 (copybook). 일자 역전 검출 배치 + * 역전 유형별 집계 레코드 + 기본 영업일 상수. Included by vl_daterev_batch.pgc. + */ +#ifndef VL_DATEREV_BATCH_H +#define VL_DATEREV_BATCH_H + +#define VL_DATEREV_DEF_DATE "2026-07-19" + +typedef struct { long settle_rev; long ledger_rev; } vl_daterev_rec_t; + +#endif /* VL_DATEREV_BATCH_H */ diff --git a/app/src/vl/dbio/vl_duppay_batch.h b/app/src/vl/dbio/vl_duppay_batch.h new file mode 100644 index 0000000..0799727 --- /dev/null +++ b/app/src/vl/dbio/vl_duppay_batch.h @@ -0,0 +1,13 @@ +/* + * vl_duppay_batch.h - vl vl_duppay_batch 배치 전용 카피북 (copybook). 중복지급 스캔/의심거래 적재 배치 + * 적재 규칙 코드/심각도 상수 + 스캔 결과 레코드. Included by vl_duppay_batch.pgc. + */ +#ifndef VL_DUPPAY_BATCH_H +#define VL_DUPPAY_BATCH_H + +#define VL_DUPPAY_RULE "DUP_PAY" +#define VL_DUPPAY_SEV "ERROR" + +typedef struct { long dup_groups; long suspects; } vl_duppay_rec_t; + +#endif /* VL_DUPPAY_BATCH_H */ diff --git a/app/src/vl/dbio/vl_feerecalc_batch.h b/app/src/vl/dbio/vl_feerecalc_batch.h new file mode 100644 index 0000000..65399cb --- /dev/null +++ b/app/src/vl/dbio/vl_feerecalc_batch.h @@ -0,0 +1,12 @@ +/* + * vl_feerecalc_batch.h - vl vl_feerecalc_batch 배치 전용 카피북 (copybook). 수수료 재계산 편차 배치 + * 기본 허용오차(원) 상수 + 재계산 결과 레코드. Included by vl_feerecalc_batch.pgc. + */ +#ifndef VL_FEERECALC_BATCH_H +#define VL_FEERECALC_BATCH_H + +#define VL_FEERECALC_DEF_TOL 10L + +typedef struct { long tol_won; long viol; long dev_sum; } vl_feerecalc_rec_t; + +#endif /* VL_FEERECALC_BATCH_H */ diff --git a/app/src/vl/dbio/vl_negbal_batch.h b/app/src/vl/dbio/vl_negbal_batch.h new file mode 100644 index 0000000..59bb26b --- /dev/null +++ b/app/src/vl/dbio/vl_negbal_batch.h @@ -0,0 +1,12 @@ +/* + * vl_negbal_batch.h - vl vl_negbal_batch 배치 전용 카피북 (copybook). 마이너스 잔액 가맹점 검출 배치 + * 적재 규칙 코드 상수 + 검출 결과 레코드. Included by vl_negbal_batch.pgc. + */ +#ifndef VL_NEGBAL_BATCH_H +#define VL_NEGBAL_BATCH_H + +#define VL_NEGBAL_RULE "NEG_BAL" + +typedef struct { long merchants; long worst_amt; } vl_negbal_rec_t; + +#endif /* VL_NEGBAL_BATCH_H */ diff --git a/app/src/vl/dbio/vl_ratedev_batch.h b/app/src/vl/dbio/vl_ratedev_batch.h new file mode 100644 index 0000000..7050e25 --- /dev/null +++ b/app/src/vl/dbio/vl_ratedev_batch.h @@ -0,0 +1,12 @@ +/* + * vl_ratedev_batch.h - vl vl_ratedev_batch 배치 전용 카피북 (copybook). 요율 편차 스캔 배치 + * 기본 허용편차(bps) 상수 + 스캔 결과 레코드. Included by vl_ratedev_batch.pgc. + */ +#ifndef VL_RATEDEV_BATCH_H +#define VL_RATEDEV_BATCH_H + +#define VL_RATEDEV_DEF_TOL 5L + +typedef struct { long tol_bps; long viol; long max_dev; } vl_ratedev_rec_t; + +#endif /* VL_RATEDEV_BATCH_H */ diff --git a/app/src/vl/dbio/vl_ruleaudit_batch.h b/app/src/vl/dbio/vl_ruleaudit_batch.h new file mode 100644 index 0000000..db864f1 --- /dev/null +++ b/app/src/vl/dbio/vl_ruleaudit_batch.h @@ -0,0 +1,12 @@ +/* + * vl_ruleaudit_batch.h - vl vl_ruleaudit_batch 배치 전용 카피북 (copybook). 검증 규칙 감사 배치 + * 검증로그 check_type 상수 + 감사 결과 레코드. Included by vl_ruleaudit_batch.pgc. + */ +#ifndef VL_RULEAUDIT_BATCH_H +#define VL_RULEAUDIT_BATCH_H + +#define VL_RULEAUDIT_CHECK "VL_RULEAUDIT" + +typedef struct { long bad_rules; long logged; } vl_ruleaudit_rec_t; + +#endif /* VL_RULEAUDIT_BATCH_H */ diff --git a/app/src/vl/dbio/vl_sumrecon_batch.h b/app/src/vl/dbio/vl_sumrecon_batch.h new file mode 100644 index 0000000..b8228fb --- /dev/null +++ b/app/src/vl/dbio/vl_sumrecon_batch.h @@ -0,0 +1,12 @@ +/* + * vl_sumrecon_batch.h - vl vl_sumrecon_batch 배치 전용 카피북 (copybook). 통계-원천 대사 배치 + * 대사 결과 레코드 + 기본 영업일 상수. Included by vl_sumrecon_batch.pgc. + */ +#ifndef VL_SUMRECON_BATCH_H +#define VL_SUMRECON_BATCH_H + +#define VL_SUMRECON_DEF_DATE "2026-07-19" + +typedef struct { long summary_rows; long mismatched; } vl_sumrecon_rec_t; + +#endif /* VL_SUMRECON_BATCH_H */ diff --git a/app/src/vl/dbio/vl_thrtrim_batch.h b/app/src/vl/dbio/vl_thrtrim_batch.h new file mode 100644 index 0000000..9e0957b --- /dev/null +++ b/app/src/vl/dbio/vl_thrtrim_batch.h @@ -0,0 +1,10 @@ +/* + * vl_thrtrim_batch.h - vl vl_thrtrim_batch 배치 전용 카피북 (copybook). 임계값 정리(음수 클램프) 배치 + * 정리 결과 레코드. Included by vl_thrtrim_batch.pgc. + */ +#ifndef VL_THRTRIM_BATCH_H +#define VL_THRTRIM_BATCH_H + +typedef struct { long clamped; long total_rules; } vl_thrtrim_rec_t; + +#endif /* VL_THRTRIM_BATCH_H */ diff --git a/app/src/vl/dbio/vl_tristage_batch.h b/app/src/vl/dbio/vl_tristage_batch.h new file mode 100644 index 0000000..34927cb --- /dev/null +++ b/app/src/vl/dbio/vl_tristage_batch.h @@ -0,0 +1,12 @@ +/* + * vl_tristage_batch.h - vl vl_tristage_batch 배치 전용 카피북 (copybook). 매입-정산-지급 3단 대사 배치 + * 3단 합계/불일치 레코드 + 기본 영업일 상수. Included by vl_tristage_batch.pgc. + */ +#ifndef VL_TRISTAGE_BATCH_H +#define VL_TRISTAGE_BATCH_H + +#define VL_TRISTAGE_DEF_DATE "2026-07-19" + +typedef struct { long p_net; long s_net; long x_amt; long broken; } vl_tristage_rec_t; + +#endif /* VL_TRISTAGE_BATCH_H */ diff --git a/app/src/vl/dbio/vl_unacq_batch.h b/app/src/vl/dbio/vl_unacq_batch.h new file mode 100644 index 0000000..cf7ca8e --- /dev/null +++ b/app/src/vl/dbio/vl_unacq_batch.h @@ -0,0 +1,12 @@ +/* + * vl_unacq_batch.h - vl vl_unacq_batch 배치 전용 카피북 (copybook). 미매입 승인 검출 배치 + * 검증로그 check_type 상수 + 검출 결과 레코드. Included by vl_unacq_batch.pgc. + */ +#ifndef VL_UNACQ_BATCH_H +#define VL_UNACQ_BATCH_H + +#define VL_UNACQ_CHECK "VL_UNACQ_BATCH" + +typedef struct { long orphans; long first_id; } vl_unacq_rec_t; + +#endif /* VL_UNACQ_BATCH_H */ diff --git a/app/src/vl/dbio/vl_wfstat_batch.h b/app/src/vl/dbio/vl_wfstat_batch.h new file mode 100644 index 0000000..6786a40 --- /dev/null +++ b/app/src/vl/dbio/vl_wfstat_batch.h @@ -0,0 +1,12 @@ +/* + * vl_wfstat_batch.h - vl vl_wfstat_batch 배치 전용 카피북 (copybook). 의심거래 워크플로 상태 통계 배치 + * 상태별 건수 레코드 + 기본 영업일 상수. Included by vl_wfstat_batch.pgc. + */ +#ifndef VL_WFSTAT_BATCH_H +#define VL_WFSTAT_BATCH_H + +#define VL_WFSTAT_DEF_DATE "2026-07-19" + +typedef struct { long open_c; long conf_c; long closed_c; } vl_wfstat_rec_t; + +#endif /* VL_WFSTAT_BATCH_H */ diff --git a/app/src/vl/svc/VL_AGEDCONF.h b/app/src/vl/svc/VL_AGEDCONF.h new file mode 100644 index 0000000..79ed07f --- /dev/null +++ b/app/src/vl/svc/VL_AGEDCONF.h @@ -0,0 +1,12 @@ +/* + * VL_AGEDCONF.h - vl 서비스 VL_AGEDCONF 카피북 (copybook / record header). + * 사용 UBF 필드: T_ARG1, T_ARG2, T_COUNT, T_ARG3, T_RC + */ +#ifndef VL_AGEDCONF_H +#define VL_AGEDCONF_H + +#include "vl_svc.h" + +void VL_AGEDCONF(TPSVCINFO *p); + +#endif /* VL_AGEDCONF_H */ diff --git a/app/src/vl/svc/VL_AGEDCONF.pgc b/app/src/vl/svc/VL_AGEDCONF.pgc new file mode 100644 index 0000000..9be125c --- /dev/null +++ b/app/src/vl/svc/VL_AGEDCONF.pgc @@ -0,0 +1,35 @@ +/* + * VL_AGEDCONF.pgc - vl 모듈 서비스 VL_AGEDCONF (one service = one file). + * 기능: 장기 미종결 확인건 검출 (CONFIRMED N일 초과, RC 1=존재; T_ARG2=1이면 severity CRIT 승격). + * 경과일 기준은 T_ARG1(기본 14일). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_AGEDCONF.h" + +void VL_AGEDCONF(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0, esc, bumped = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_days, h_cnt; + EXEC SQL END DECLARE SECTION; + h_days = getl(b, T_ARG1); + if (h_days <= 0) h_days = 14; + esc = getl(b, T_ARG2); + EXEC SQL SELECT count(*) INTO :h_cnt FROM vl_suspect + WHERE status = 'CONFIRMED' + AND created_at < now() - (:h_days || ' days')::interval; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt > 0) rc = 1; /* 장기 미종결 확인건 존재 */ + if (rc == 1 && esc == 1) { + EXEC SQL UPDATE vl_suspect SET severity = 'CRIT' + WHERE status = 'CONFIRMED' AND severity <> 'CRIT' + AND created_at < now() - (:h_days || ' days')::interval; + if (sqlca.sqlcode < 0) FAIL(b); + bumped = (long)sqlca.sqlerrd[2]; /* CRIT 승격 건수 */ + } + setl(b, T_COUNT, h_cnt); + setl(b, T_ARG3, bumped); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_AGEDOPEN.h b/app/src/vl/svc/VL_AGEDOPEN.h new file mode 100644 index 0000000..93e5c5c --- /dev/null +++ b/app/src/vl/svc/VL_AGEDOPEN.h @@ -0,0 +1,12 @@ +/* + * VL_AGEDOPEN.h - vl 서비스 VL_AGEDOPEN 카피북 (copybook / record header). + * 사용 UBF 필드: T_ARG1, T_COUNT, T_ID1, T_RC + */ +#ifndef VL_AGEDOPEN_H +#define VL_AGEDOPEN_H + +#include "vl_svc.h" + +void VL_AGEDOPEN(TPSVCINFO *p); + +#endif /* VL_AGEDOPEN_H */ diff --git a/app/src/vl/svc/VL_AGEDOPEN.pgc b/app/src/vl/svc/VL_AGEDOPEN.pgc new file mode 100644 index 0000000..8e7e3ef --- /dev/null +++ b/app/src/vl/svc/VL_AGEDOPEN.pgc @@ -0,0 +1,28 @@ +/* + * VL_AGEDOPEN.pgc - vl 모듈 서비스 VL_AGEDOPEN (one service = one file). + * 기능: 장기미결 의심거래 검출 (OPEN 상태로 N일 초과 경과, RC 1=존재). + * 경과일 기준은 T_ARG1(기본 7일). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_AGEDOPEN.h" + +void VL_AGEDOPEN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_days, h_cnt, h_oldest; + EXEC SQL END DECLARE SECTION; + h_days = getl(b, T_ARG1); + if (h_days <= 0) h_days = 7; + EXEC SQL SELECT count(*), coalesce(min(suspect_id),0) INTO :h_cnt, :h_oldest + FROM vl_suspect + WHERE status = 'OPEN' + AND created_at < now() - (:h_days || ' days')::interval; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt > 0) rc = 1; /* 장기미결 의심거래 존재 */ + setl(b, T_COUNT, h_cnt); + setl(b, T_ID1, h_oldest); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_APPRAGG.h b/app/src/vl/svc/VL_APPRAGG.h new file mode 100644 index 0000000..80f27b5 --- /dev/null +++ b/app/src/vl/svc/VL_APPRAGG.h @@ -0,0 +1,12 @@ +/* + * VL_APPRAGG.h - vl 서비스 VL_APPRAGG 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_GROSS, T_AMT1, T_DELTA, T_RC + */ +#ifndef VL_APPRAGG_H +#define VL_APPRAGG_H + +#include "vl_svc.h" + +void VL_APPRAGG(TPSVCINFO *p); + +#endif /* VL_APPRAGG_H */ diff --git a/app/src/vl/svc/VL_APPRAGG.pgc b/app/src/vl/svc/VL_APPRAGG.pgc new file mode 100644 index 0000000..7ad773f --- /dev/null +++ b/app/src/vl/svc/VL_APPRAGG.pgc @@ -0,0 +1,33 @@ +/* + * VL_APPRAGG.pgc - vl 모듈 서비스 VL_APPRAGG (one service = one file). + * 기능: 일자별 매입-승인 금액 대사 (승인보유 매입금액 vs 전체 매입금액, RC 1=미승인 금액 존재). + * 취소(C) 제외 매입 총액과 가결 승인이 붙은 매입 총액의 차이를 계산한다. + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_APPRAGG.h" + +void VL_APPRAGG(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_all, h_appr; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_bizdate[0] == 0) strcpy(h_bizdate, "2026-07-19"); + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_all FROM purchase + WHERE biz_date = :h_bizdate AND status <> 'C'; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(p.amount),0) INTO :h_appr FROM purchase p + WHERE p.biz_date = :h_bizdate AND p.status <> 'C' + AND EXISTS (SELECT 1 FROM approval a + WHERE a.purchase_id = p.purchase_id AND a.approved); + if (sqlca.sqlcode < 0) FAIL(b); + if (h_all != h_appr) rc = 1; /* 미승인 매입금액 존재 */ + setl(b, T_GROSS, h_all); + setl(b, T_AMT1, h_appr); + setl(b, T_DELTA, h_all - h_appr); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_APPRAMT.h b/app/src/vl/svc/VL_APPRAMT.h new file mode 100644 index 0000000..e687835 --- /dev/null +++ b/app/src/vl/svc/VL_APPRAMT.h @@ -0,0 +1,12 @@ +/* + * VL_APPRAMT.h - vl 서비스 VL_APPRAMT 카피북 (copybook / record header). + * 사용 UBF 필드: T_PURCHASE_ID, T_AMOUNT, T_COUNT, T_ARG1, T_RC + */ +#ifndef VL_APPRAMT_H +#define VL_APPRAMT_H + +#include "vl_svc.h" + +void VL_APPRAMT(TPSVCINFO *p); + +#endif /* VL_APPRAMT_H */ diff --git a/app/src/vl/svc/VL_APPRAMT.pgc b/app/src/vl/svc/VL_APPRAMT.pgc new file mode 100644 index 0000000..b897c8d --- /dev/null +++ b/app/src/vl/svc/VL_APPRAMT.pgc @@ -0,0 +1,30 @@ +/* + * VL_APPRAMT.pgc - vl 모듈 서비스 VL_APPRAMT (one service = one file). + * 기능: 건별 매입-승인 금액/존재 정합 검증 (RC 1=승인없음, 2=거절승인만 존재, 3=매입없음). + * 매입 1건에 대해 승인 레코드 존재와 승인가결 건수를 대조한다. + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_APPRAMT.h" + +void VL_APPRAMT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt, h_tot, h_ok; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + EXEC SQL SELECT amount INTO :h_amt FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); } /* 3=매입 없음 */ + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*), coalesce(sum(CASE WHEN approved THEN 1 ELSE 0 END),0) + INTO :h_tot, :h_ok FROM approval WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_tot == 0) rc = 1; /* 승인 레코드 자체가 없음 */ + else if (h_ok == 0) rc = 2; /* 거절 승인만 존재 */ + setl(b, T_AMOUNT, h_amt); + setl(b, T_COUNT, h_tot); + setl(b, T_ARG1, h_ok); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_CANCELMIS.h b/app/src/vl/svc/VL_CANCELMIS.h new file mode 100644 index 0000000..1a495da --- /dev/null +++ b/app/src/vl/svc/VL_CANCELMIS.h @@ -0,0 +1,12 @@ +/* + * VL_CANCELMIS.h - vl 서비스 VL_CANCELMIS 카피북 (copybook / record header). + * 사용 UBF 필드: T_PURCHASE_ID, T_AMOUNT, T_AMT1, T_COUNT, T_STATUS, T_RC + */ +#ifndef VL_CANCELMIS_H +#define VL_CANCELMIS_H + +#include "vl_svc.h" + +void VL_CANCELMIS(TPSVCINFO *p); + +#endif /* VL_CANCELMIS_H */ diff --git a/app/src/vl/svc/VL_CANCELMIS.pgc b/app/src/vl/svc/VL_CANCELMIS.pgc new file mode 100644 index 0000000..6202a22 --- /dev/null +++ b/app/src/vl/svc/VL_CANCELMIS.pgc @@ -0,0 +1,33 @@ +/* + * VL_CANCELMIS.pgc - vl 모듈 서비스 VL_CANCELMIS (one service = one file). + * 기능: 취소 정합 검증 (RC 비트합 1=취소합>매입금액, 2=취소 존재하나 매입상태 미반영, 3=매입없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_CANCELMIS.h" + +void VL_CANCELMIS(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt, h_csum, h_ccnt; + char h_st[8]; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + EXEC SQL SELECT amount, status INTO :h_amt, :h_st FROM purchase + WHERE purchase_id = :h_pid; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); } /* 3=매입 없음 */ + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(amount),0), count(*) INTO :h_csum, :h_ccnt + FROM ac_cancel WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_csum > h_amt) rc |= 1; /* 과다 취소 */ + if (h_ccnt > 0 && strcmp(h_st, "C") != 0 && strcmp(h_st, "V") != 0) + rc |= 2; /* 취소 반영 안 된 매입상태 */ + setl(b, T_AMOUNT, h_amt); + setl(b, T_AMT1, h_csum); + setl(b, T_COUNT, h_ccnt); + Bchg(b, T_STATUS, 0, h_st, 0L); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_DATEREV.h b/app/src/vl/svc/VL_DATEREV.h new file mode 100644 index 0000000..4c29f43 --- /dev/null +++ b/app/src/vl/svc/VL_DATEREV.h @@ -0,0 +1,12 @@ +/* + * VL_DATEREV.h - vl 서비스 VL_DATEREV 카피북 (copybook / record header). + * 사용 UBF 필드: T_PURCHASE_ID, T_COUNT, T_RC + */ +#ifndef VL_DATEREV_H +#define VL_DATEREV_H + +#include "vl_svc.h" + +void VL_DATEREV(TPSVCINFO *p); + +#endif /* VL_DATEREV_H */ diff --git a/app/src/vl/svc/VL_DATEREV.pgc b/app/src/vl/svc/VL_DATEREV.pgc new file mode 100644 index 0000000..3ba2a21 --- /dev/null +++ b/app/src/vl/svc/VL_DATEREV.pgc @@ -0,0 +1,27 @@ +/* + * VL_DATEREV.pgc - vl 모듈 서비스 VL_DATEREV (one service = one file). + * 기능: 건별 정산 일자 역전 검출 (settlement.biz_date < purchase.biz_date → RC 1, 3=매입없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_DATEREV.h" + +void VL_DATEREV(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_exists, h_rev; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + EXEC SQL SELECT count(*) INTO :h_exists FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_exists == 0) { setl(b, T_RC, 3); OK(b); } /* 3=매입 없음 */ + EXEC SQL SELECT count(*) INTO :h_rev + FROM settlement s JOIN purchase p ON p.purchase_id = s.purchase_id + WHERE p.purchase_id = :h_pid AND s.biz_date < p.biz_date; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_rev > 0) rc = 1; /* 정산일이 매입일보다 과거 */ + setl(b, T_COUNT, h_rev); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_DAYLIMIT.h b/app/src/vl/svc/VL_DAYLIMIT.h new file mode 100644 index 0000000..085b8a8 --- /dev/null +++ b/app/src/vl/svc/VL_DAYLIMIT.h @@ -0,0 +1,12 @@ +/* + * VL_DAYLIMIT.h - vl 서비스 VL_DAYLIMIT 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_BIZDATE, T_AMT1, T_GROSS, T_DELTA, T_RC + */ +#ifndef VL_DAYLIMIT_H +#define VL_DAYLIMIT_H + +#include "vl_svc.h" + +void VL_DAYLIMIT(TPSVCINFO *p); + +#endif /* VL_DAYLIMIT_H */ diff --git a/app/src/vl/svc/VL_DAYLIMIT.pgc b/app/src/vl/svc/VL_DAYLIMIT.pgc new file mode 100644 index 0000000..ff39588 --- /dev/null +++ b/app/src/vl/svc/VL_DAYLIMIT.pgc @@ -0,0 +1,33 @@ +/* + * VL_DAYLIMIT.pgc - vl 모듈 서비스 VL_DAYLIMIT (one service = one file). + * 기능: 가맹점 일한도 위반 검증 (일자 매입합 > daily_limit → RC 1, 3=가맹점 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_DAYLIMIT.h" + +void VL_DAYLIMIT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16]; + long h_lim, h_used; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + if (h_merch[0] == 0) { userlog("VL_DAYLIMIT: T_MERCHANT 누락"); FAIL(b); } + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_bizdate[0] == 0) strcpy(h_bizdate, "2026-07-19"); + EXEC SQL SELECT daily_limit INTO :h_lim FROM merchant + WHERE merchant_id = :h_merch; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); } /* 3=가맹점 없음 */ + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_used FROM purchase + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status <> 'C'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_used > h_lim) rc = 1; /* 일한도 초과 */ + setl(b, T_AMT1, h_lim); + setl(b, T_GROSS, h_used); + setl(b, T_DELTA, h_lim - h_used); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_DUPPAY.h b/app/src/vl/svc/VL_DUPPAY.h new file mode 100644 index 0000000..35d22c9 --- /dev/null +++ b/app/src/vl/svc/VL_DUPPAY.h @@ -0,0 +1,12 @@ +/* + * VL_DUPPAY.h - vl 서비스 VL_DUPPAY 카피북 (copybook / record header). + * 사용 UBF 필드: T_PURCHASE_ID, T_BIZDATE, T_COUNT, T_GROSS, T_RC + */ +#ifndef VL_DUPPAY_H +#define VL_DUPPAY_H + +#include "vl_svc.h" + +void VL_DUPPAY(TPSVCINFO *p); + +#endif /* VL_DUPPAY_H */ diff --git a/app/src/vl/svc/VL_DUPPAY.pgc b/app/src/vl/svc/VL_DUPPAY.pgc new file mode 100644 index 0000000..0393397 --- /dev/null +++ b/app/src/vl/svc/VL_DUPPAY.pgc @@ -0,0 +1,31 @@ +/* + * VL_DUPPAY.pgc - vl 모듈 서비스 VL_DUPPAY (one service = one file). + * 기능: 건별 중복지급 검출 (ledger XFER 엔트리 2건 이상 → RC 1, 검증로그 기록). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_DUPPAY.h" + +void VL_DUPPAY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_cnt, h_amt; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) strcpy(bizdate, "2026-07-19"); + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_amt + FROM ledger WHERE purchase_id = :h_pid AND entry_type = 'XFER'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt > 1) { + rc = 1; /* 동일 매입 다중 지급 */ + if (vldb_insert_check_log("VL_DUPPAY", h_pid, rc, "중복지급 검출", bizdate) < 0) + FAIL(b); + } + setl(b, T_COUNT, h_cnt); + setl(b, T_GROSS, h_amt); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_DUPPAYDAY.h b/app/src/vl/svc/VL_DUPPAYDAY.h new file mode 100644 index 0000000..39097eb --- /dev/null +++ b/app/src/vl/svc/VL_DUPPAYDAY.h @@ -0,0 +1,12 @@ +/* + * VL_DUPPAYDAY.h - vl 서비스 VL_DUPPAYDAY 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_COUNT, T_ARG2, T_RC + */ +#ifndef VL_DUPPAYDAY_H +#define VL_DUPPAYDAY_H + +#include "vl_svc.h" + +void VL_DUPPAYDAY(TPSVCINFO *p); + +#endif /* VL_DUPPAYDAY_H */ diff --git a/app/src/vl/svc/VL_DUPPAYDAY.pgc b/app/src/vl/svc/VL_DUPPAYDAY.pgc new file mode 100644 index 0000000..d9b5636 --- /dev/null +++ b/app/src/vl/svc/VL_DUPPAYDAY.pgc @@ -0,0 +1,28 @@ +/* + * VL_DUPPAYDAY.pgc - vl 모듈 서비스 VL_DUPPAYDAY (one service = one file). + * 기능: 일자별 중복지급 그룹 집계 (XFER 2건 이상 매입 그룹수/초과건수, RC 1=중복지급 존재). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_DUPPAYDAY.h" + +void VL_DUPPAYDAY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_grp, h_extra; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_bizdate[0] == 0) strcpy(h_bizdate, "2026-07-19"); + EXEC SQL SELECT count(*), coalesce(sum(c - 1),0) INTO :h_grp, :h_extra + FROM (SELECT count(*) AS c FROM ledger + WHERE biz_date = :h_bizdate AND entry_type = 'XFER' + GROUP BY purchase_id HAVING count(*) > 1) t; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_grp > 0) rc = 1; /* 중복지급 매입 존재 */ + setl(b, T_COUNT, h_grp); + setl(b, T_ARG2, h_extra); /* 초과 지급 건수 */ + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_DUPSETTLE.h b/app/src/vl/svc/VL_DUPSETTLE.h new file mode 100644 index 0000000..e818c6e --- /dev/null +++ b/app/src/vl/svc/VL_DUPSETTLE.h @@ -0,0 +1,12 @@ +/* + * VL_DUPSETTLE.h - vl 서비스 VL_DUPSETTLE 카피북 (copybook / record header). + * 사용 UBF 필드: T_PURCHASE_ID, T_COUNT, T_NET, T_ID1, T_ID2, T_RC + */ +#ifndef VL_DUPSETTLE_H +#define VL_DUPSETTLE_H + +#include "vl_svc.h" + +void VL_DUPSETTLE(TPSVCINFO *p); + +#endif /* VL_DUPSETTLE_H */ diff --git a/app/src/vl/svc/VL_DUPSETTLE.pgc b/app/src/vl/svc/VL_DUPSETTLE.pgc new file mode 100644 index 0000000..688d0c3 --- /dev/null +++ b/app/src/vl/svc/VL_DUPSETTLE.pgc @@ -0,0 +1,28 @@ +/* + * VL_DUPSETTLE.pgc - vl 모듈 서비스 VL_DUPSETTLE (one service = one file). + * 기능: 건별 중복 정산 검출 (동일 매입에 정산 2건 이상 → RC 1, 최초/최종 정산 ID 반환). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_DUPSETTLE.h" + +void VL_DUPSETTLE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_cnt, h_net, h_min, h_max; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + EXEC SQL SELECT count(*), coalesce(sum(net),0), + coalesce(min(settlement_id),0), coalesce(max(settlement_id),0) + INTO :h_cnt, :h_net, :h_min, :h_max + FROM settlement WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt > 1) rc = 1; /* 중복 정산 */ + setl(b, T_COUNT, h_cnt); + setl(b, T_NET, h_net); + setl(b, T_ID1, h_min); + setl(b, T_ID2, h_max); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_FEEADJNET.h b/app/src/vl/svc/VL_FEEADJNET.h new file mode 100644 index 0000000..b5cf2d2 --- /dev/null +++ b/app/src/vl/svc/VL_FEEADJNET.h @@ -0,0 +1,12 @@ +/* + * VL_FEEADJNET.h - vl 서비스 VL_FEEADJNET 카피북 (copybook / record header). + * 사용 UBF 필드: T_PURCHASE_ID, T_FEE, T_DELTA, T_COUNT, T_RC + */ +#ifndef VL_FEEADJNET_H +#define VL_FEEADJNET_H + +#include "vl_svc.h" + +void VL_FEEADJNET(TPSVCINFO *p); + +#endif /* VL_FEEADJNET_H */ diff --git a/app/src/vl/svc/VL_FEEADJNET.pgc b/app/src/vl/svc/VL_FEEADJNET.pgc new file mode 100644 index 0000000..d5f466e --- /dev/null +++ b/app/src/vl/svc/VL_FEEADJNET.pgc @@ -0,0 +1,31 @@ +/* + * VL_FEEADJNET.pgc - vl 모듈 서비스 VL_FEEADJNET (one service = one file). + * 기능: 수수료 조정 반영 검증 (fee+sum(ac_fee_adj.delta) 유효범위 점검, RC 1=음수 수수료 2=금액 초과 3=매입없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_FEEADJNET.h" + +void VL_FEEADJNET(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0, eff; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt, h_fee, h_adj, h_acnt; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + EXEC SQL SELECT amount, fee INTO :h_amt, :h_fee FROM purchase + WHERE purchase_id = :h_pid; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); } /* 3=매입 없음 */ + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(delta),0), count(*) INTO :h_adj, :h_acnt + FROM ac_fee_adj WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + eff = h_fee + h_adj; + if (eff < 0) rc = 1; /* 조정 후 수수료 음수 */ + else if (eff > h_amt) rc = 2; /* 조정 후 수수료가 매입금액 초과 */ + setl(b, T_FEE, eff); + setl(b, T_DELTA, h_adj); + setl(b, T_COUNT, h_acnt); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_FEERECALC.h b/app/src/vl/svc/VL_FEERECALC.h new file mode 100644 index 0000000..d40c926 --- /dev/null +++ b/app/src/vl/svc/VL_FEERECALC.h @@ -0,0 +1,12 @@ +/* + * VL_FEERECALC.h - vl 서비스 VL_FEERECALC 카피북 (copybook / record header). + * 사용 UBF 필드: T_PURCHASE_ID, T_ARG1, T_FEE, T_AMT1, T_DELTA, T_RC + */ +#ifndef VL_FEERECALC_H +#define VL_FEERECALC_H + +#include "vl_svc.h" + +void VL_FEERECALC(TPSVCINFO *p); + +#endif /* VL_FEERECALC_H */ diff --git a/app/src/vl/svc/VL_FEERECALC.pgc b/app/src/vl/svc/VL_FEERECALC.pgc new file mode 100644 index 0000000..b484817 --- /dev/null +++ b/app/src/vl/svc/VL_FEERECALC.pgc @@ -0,0 +1,33 @@ +/* + * VL_FEERECALC.pgc - vl 모듈 서비스 VL_FEERECALC (one service = one file). + * 기능: 수수료 재계산 검증 (가맹점 요율 mdr_bps로 기대수수료 재산출, |fee-기대|>허용오차 → RC 1, 3=매입/가맹점 없음). + * 허용오차는 T_ARG1(기본 10원). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_FEERECALC.h" + +void VL_FEERECALC(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0, tol, expfee, d; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt, h_fee, h_bps; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + tol = getl(b, T_ARG1); + if (tol <= 0) tol = 10; + EXEC SQL SELECT p.amount, p.fee, m.mdr_bps INTO :h_amt, :h_fee, :h_bps + FROM purchase p JOIN merchant m ON m.merchant_id = p.merchant_id + WHERE p.purchase_id = :h_pid; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); } /* 3=매입/가맹점 없음 */ + if (sqlca.sqlcode < 0) FAIL(b); + expfee = h_amt * h_bps / 10000; + d = h_fee - expfee; + if (d < 0) d = -d; + if (d > tol) rc = 1; /* 재계산 편차 초과 */ + setl(b, T_FEE, h_fee); + setl(b, T_AMT1, expfee); + setl(b, T_DELTA, h_fee - expfee); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_FXCHK.h b/app/src/vl/svc/VL_FXCHK.h new file mode 100644 index 0000000..d136d20 --- /dev/null +++ b/app/src/vl/svc/VL_FXCHK.h @@ -0,0 +1,12 @@ +/* + * VL_FXCHK.h - vl 서비스 VL_FXCHK 카피북 (copybook / record header). + * 사용 UBF 필드: T_PURCHASE_ID, T_ARG1, T_FX_AMT, T_AMT1, T_DELTA, T_RC + */ +#ifndef VL_FXCHK_H +#define VL_FXCHK_H + +#include "vl_svc.h" + +void VL_FXCHK(TPSVCINFO *p); + +#endif /* VL_FXCHK_H */ diff --git a/app/src/vl/svc/VL_FXCHK.pgc b/app/src/vl/svc/VL_FXCHK.pgc new file mode 100644 index 0000000..bf2df75 --- /dev/null +++ b/app/src/vl/svc/VL_FXCHK.pgc @@ -0,0 +1,33 @@ +/* + * VL_FXCHK.pgc - vl 모듈 서비스 VL_FXCHK (one service = one file). + * 기능: 해외 매입 환산 정합 검증 (krw vs fx*rate_bps/10000, 오차>T_ARG1 → RC 1, 2=해외 내역 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_FXCHK.h" + +void VL_FXCHK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0, tol, expkrw, d; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_cnt, h_fx, h_krw, h_rate; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + tol = getl(b, T_ARG1); + if (tol <= 0) tol = 100; /* 환산 반올림 허용오차 */ + EXEC SQL SELECT count(*), coalesce(sum(fx_amount),0), + coalesce(sum(krw_amount),0), coalesce(max(rate_bps),0) + INTO :h_cnt, :h_fx, :h_krw, :h_rate + FROM ac_fx WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt == 0) { setl(b, T_RC, 2); OK(b); } /* 해외 매입 아님 */ + expkrw = h_fx * h_rate / 10000; + d = h_krw - expkrw; + if (d < 0) d = -d; + if (d > tol) rc = 1; /* 환산 금액 불일치 */ + setl(b, T_FX_AMT, h_fx); + setl(b, T_AMT1, expkrw); + setl(b, T_DELTA, h_krw - expkrw); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_INSTCHK.h b/app/src/vl/svc/VL_INSTCHK.h new file mode 100644 index 0000000..15fdb24 --- /dev/null +++ b/app/src/vl/svc/VL_INSTCHK.h @@ -0,0 +1,12 @@ +/* + * VL_INSTCHK.h - vl 서비스 VL_INSTCHK 카피북 (copybook / record header). + * 사용 UBF 필드: T_PURCHASE_ID, T_ARG1, T_AMT1, T_INSTALL_N, T_COUNT, T_DELTA, T_RC + */ +#ifndef VL_INSTCHK_H +#define VL_INSTCHK_H + +#include "vl_svc.h" + +void VL_INSTCHK(TPSVCINFO *p); + +#endif /* VL_INSTCHK_H */ diff --git a/app/src/vl/svc/VL_INSTCHK.pgc b/app/src/vl/svc/VL_INSTCHK.pgc new file mode 100644 index 0000000..6c47b36 --- /dev/null +++ b/app/src/vl/svc/VL_INSTCHK.pgc @@ -0,0 +1,37 @@ +/* + * VL_INSTCHK.pgc - vl 모듈 서비스 VL_INSTCHK (one service = one file). + * 기능: 할부 정합 검증 (sum(월부금) vs 매입금액, 오차>T_ARG1 → RC 1, 2=할부 없음 3=매입없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_INSTCHK.h" + +void VL_INSTCHK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0, tol, d; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt, h_isum, h_icnt, h_months; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + tol = getl(b, T_ARG1); + if (tol < 0) tol = 0; /* 기본: 원단위 완전일치 */ + EXEC SQL SELECT amount INTO :h_amt FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); } /* 3=매입 없음 */ + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(month_amount),0), count(*), coalesce(max(months),0) + INTO :h_isum, :h_icnt, :h_months + FROM ac_install WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_icnt == 0) rc = 2; /* 할부 내역 없음 */ + else { + d = h_isum - h_amt; + if (d < 0) d = -d; + if (d > tol) rc = 1; /* 할부합-매입금액 오차 초과 */ + } + setl(b, T_AMT1, h_isum); + setl(b, T_INSTALL_N, h_months); + setl(b, T_COUNT, h_icnt); + setl(b, T_DELTA, h_isum - h_amt); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_LEDGBAL.h b/app/src/vl/svc/VL_LEDGBAL.h new file mode 100644 index 0000000..c5f6b11 --- /dev/null +++ b/app/src/vl/svc/VL_LEDGBAL.h @@ -0,0 +1,12 @@ +/* + * VL_LEDGBAL.h - vl 서비스 VL_LEDGBAL 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_AMT1, T_AMT2, T_COUNT, T_DELTA, T_RC + */ +#ifndef VL_LEDGBAL_H +#define VL_LEDGBAL_H + +#include "vl_svc.h" + +void VL_LEDGBAL(TPSVCINFO *p); + +#endif /* VL_LEDGBAL_H */ diff --git a/app/src/vl/svc/VL_LEDGBAL.pgc b/app/src/vl/svc/VL_LEDGBAL.pgc new file mode 100644 index 0000000..d28bcef --- /dev/null +++ b/app/src/vl/svc/VL_LEDGBAL.pgc @@ -0,0 +1,33 @@ +/* + * VL_LEDGBAL.pgc - vl 모듈 서비스 VL_LEDGBAL (one service = one file). + * 기능: 일자별 원장 대차 정합 검증 (차변 RECON 합 vs 대변 SETTLE+XFER 합, RC 1=대차 불일치). + * 한 SELECT의 CASE 집계로 차/대변을 동시에 산출한다. + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_LEDGBAL.h" + +void VL_LEDGBAL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_dr, h_cr, h_cnt; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_bizdate[0] == 0) strcpy(h_bizdate, "2026-07-19"); + EXEC SQL SELECT + coalesce(sum(CASE WHEN entry_type = 'RECON' THEN amount ELSE 0 END),0), + coalesce(sum(CASE WHEN entry_type IN ('SETTLE','XFER') THEN amount ELSE 0 END),0), + count(*) + INTO :h_dr, :h_cr, :h_cnt + FROM ledger WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_dr != h_cr) rc = 1; /* 대차 불일치 */ + setl(b, T_AMT1, h_dr); + setl(b, T_AMT2, h_cr); + setl(b, T_COUNT, h_cnt); + setl(b, T_DELTA, h_dr - h_cr); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_LEDGDATE.h b/app/src/vl/svc/VL_LEDGDATE.h new file mode 100644 index 0000000..06aab25 --- /dev/null +++ b/app/src/vl/svc/VL_LEDGDATE.h @@ -0,0 +1,12 @@ +/* + * VL_LEDGDATE.h - vl 서비스 VL_LEDGDATE 카피북 (copybook / record header). + * 사용 UBF 필드: T_PURCHASE_ID, T_COUNT, T_ID1, T_RC + */ +#ifndef VL_LEDGDATE_H +#define VL_LEDGDATE_H + +#include "vl_svc.h" + +void VL_LEDGDATE(TPSVCINFO *p); + +#endif /* VL_LEDGDATE_H */ diff --git a/app/src/vl/svc/VL_LEDGDATE.pgc b/app/src/vl/svc/VL_LEDGDATE.pgc new file mode 100644 index 0000000..ca82504 --- /dev/null +++ b/app/src/vl/svc/VL_LEDGDATE.pgc @@ -0,0 +1,25 @@ +/* + * VL_LEDGDATE.pgc - vl 모듈 서비스 VL_LEDGDATE (one service = one file). + * 기능: 건별 원장 일자 역전 검출 (ledger.biz_date < purchase.biz_date 엔트리 수, RC 1=역전 존재). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_LEDGDATE.h" + +void VL_LEDGDATE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_rev, h_first; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + EXEC SQL SELECT count(*), coalesce(min(l.ledger_id),0) INTO :h_rev, :h_first + FROM ledger l JOIN purchase p ON p.purchase_id = l.purchase_id + WHERE p.purchase_id = :h_pid AND l.biz_date < p.biz_date; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_rev > 0) rc = 1; /* 원장 기표일 역전 */ + setl(b, T_COUNT, h_rev); + setl(b, T_ID1, h_first); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_LEDGENTRY.h b/app/src/vl/svc/VL_LEDGENTRY.h new file mode 100644 index 0000000..84b1820 --- /dev/null +++ b/app/src/vl/svc/VL_LEDGENTRY.h @@ -0,0 +1,12 @@ +/* + * VL_LEDGENTRY.h - vl 서비스 VL_LEDGENTRY 카피북 (copybook / record header). + * 사용 UBF 필드: T_PURCHASE_ID, T_AMOUNT, T_AMT1, T_COUNT, T_RC + */ +#ifndef VL_LEDGENTRY_H +#define VL_LEDGENTRY_H + +#include "vl_svc.h" + +void VL_LEDGENTRY(TPSVCINFO *p); + +#endif /* VL_LEDGENTRY_H */ diff --git a/app/src/vl/svc/VL_LEDGENTRY.pgc b/app/src/vl/svc/VL_LEDGENTRY.pgc new file mode 100644 index 0000000..6c2cdb3 --- /dev/null +++ b/app/src/vl/svc/VL_LEDGENTRY.pgc @@ -0,0 +1,29 @@ +/* + * VL_LEDGENTRY.pgc - vl 모듈 서비스 VL_LEDGENTRY (one service = one file). + * 기능: 건별 원장 대사 엔트리 검증 (purchase.amount vs ledger RECON 합, RC 1=금액불일치 2=원장없음 3=매입없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_LEDGENTRY.h" + +void VL_LEDGENTRY(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt, h_lcnt, h_lamt; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + EXEC SQL SELECT amount INTO :h_amt FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); } /* 3=매입 없음 */ + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_lcnt, :h_lamt + FROM ledger WHERE purchase_id = :h_pid AND entry_type = 'RECON'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_lcnt == 0) rc = 2; /* RECON 원장 미기표 */ + else if (h_lamt != h_amt) rc = 1; /* 기표 금액 불일치 */ + setl(b, T_AMOUNT, h_amt); + setl(b, T_AMT1, h_lamt); + setl(b, T_COUNT, h_lcnt); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_LIMITNEAR.h b/app/src/vl/svc/VL_LIMITNEAR.h new file mode 100644 index 0000000..918ca36 --- /dev/null +++ b/app/src/vl/svc/VL_LIMITNEAR.h @@ -0,0 +1,12 @@ +/* + * VL_LIMITNEAR.h - vl 서비스 VL_LIMITNEAR 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_BIZDATE, T_ARG1, T_AMT1, T_GROSS, T_ARG2, T_RC + */ +#ifndef VL_LIMITNEAR_H +#define VL_LIMITNEAR_H + +#include "vl_svc.h" + +void VL_LIMITNEAR(TPSVCINFO *p); + +#endif /* VL_LIMITNEAR_H */ diff --git a/app/src/vl/svc/VL_LIMITNEAR.pgc b/app/src/vl/svc/VL_LIMITNEAR.pgc new file mode 100644 index 0000000..bde6303 --- /dev/null +++ b/app/src/vl/svc/VL_LIMITNEAR.pgc @@ -0,0 +1,40 @@ +/* + * VL_LIMITNEAR.pgc - vl 모듈 서비스 VL_LIMITNEAR (one service = one file). + * 기능: 가맹점 일한도 임박 검출 (사용률>=T_ARG1% → RC 1, 초과 → RC 2, 3=가맹점 없음). + * 임박 기준 사용률은 T_ARG1(기본 90%). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_LIMITNEAR.h" + +void VL_LIMITNEAR(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0, thr, pct = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16]; + long h_lim, h_used; + EXEC SQL END DECLARE SECTION; + thr = getl(b, T_ARG1); + if (thr <= 0 || thr > 100) thr = 90; + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + if (h_merch[0] == 0) { userlog("VL_LIMITNEAR: T_MERCHANT 누락"); FAIL(b); } + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_bizdate[0] == 0) strcpy(h_bizdate, "2026-07-19"); + EXEC SQL SELECT m.daily_limit, coalesce(sum(p.amount),0) + INTO :h_lim, :h_used + FROM merchant m LEFT JOIN purchase p + ON p.merchant_id = m.merchant_id + AND p.biz_date = :h_bizdate AND p.status <> 'C' + WHERE m.merchant_id = :h_merch + GROUP BY m.daily_limit; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); } /* 3=가맹점 없음 */ + if (sqlca.sqlcode < 0) FAIL(b); + if (h_lim > 0) pct = h_used * 100 / h_lim; + if (h_used > h_lim) rc = 2; /* 이미 한도 초과 */ + else if (pct >= thr) rc = 1; /* 한도 임박 */ + setl(b, T_AMT1, h_lim); + setl(b, T_GROSS, h_used); + setl(b, T_ARG2, pct); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_MDRRATE.h b/app/src/vl/svc/VL_MDRRATE.h new file mode 100644 index 0000000..681f9d8 --- /dev/null +++ b/app/src/vl/svc/VL_MDRRATE.h @@ -0,0 +1,12 @@ +/* + * VL_MDRRATE.h - vl 서비스 VL_MDRRATE 카피북 (copybook / record header). + * 사용 UBF 필드: T_COUNT, T_ARG1, T_STR1, T_RC + */ +#ifndef VL_MDRRATE_H +#define VL_MDRRATE_H + +#include "vl_svc.h" + +void VL_MDRRATE(TPSVCINFO *p); + +#endif /* VL_MDRRATE_H */ diff --git a/app/src/vl/svc/VL_MDRRATE.pgc b/app/src/vl/svc/VL_MDRRATE.pgc new file mode 100644 index 0000000..dfa56d4 --- /dev/null +++ b/app/src/vl/svc/VL_MDRRATE.pgc @@ -0,0 +1,29 @@ +/* + * VL_MDRRATE.pgc - vl 모듈 서비스 VL_MDRRATE (one service = one file). + * 기능: 가맹점 요율 위반 검출 (mdr_bps<0 또는 규칙 MDR_MAX 초과, RC 1=위반 존재). + * 상한은 vl_rule MDR_MAX(없으면 1000bps). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_MDRRATE.h" + +void VL_MDRRATE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + char sev[8]; + EXEC SQL BEGIN DECLARE SECTION; + long h_max, h_cnt; + char h_first[64]; + EXEC SQL END DECLARE SECTION; + h_max = 0; + if (vldb_get_rule("MDR_MAX", &h_max, sev) < 0 || h_max <= 0) h_max = 1000; + EXEC SQL SELECT count(*), coalesce(min(merchant_id),'') INTO :h_cnt, :h_first + FROM merchant WHERE mdr_bps < 0 OR mdr_bps > :h_max; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt > 0) rc = 1; /* 요율 위반 가맹점 존재 */ + setl(b, T_COUNT, h_cnt); + setl(b, T_ARG1, h_max); + Bchg(b, T_STR1, 0, h_first, 0L); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_NEGBAL.h b/app/src/vl/svc/VL_NEGBAL.h new file mode 100644 index 0000000..17fafff --- /dev/null +++ b/app/src/vl/svc/VL_NEGBAL.h @@ -0,0 +1,12 @@ +/* + * VL_NEGBAL.h - vl 서비스 VL_NEGBAL 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_COUNT, T_AMT1, T_STR1, T_RC + */ +#ifndef VL_NEGBAL_H +#define VL_NEGBAL_H + +#include "vl_svc.h" + +void VL_NEGBAL(TPSVCINFO *p); + +#endif /* VL_NEGBAL_H */ diff --git a/app/src/vl/svc/VL_NEGBAL.pgc b/app/src/vl/svc/VL_NEGBAL.pgc new file mode 100644 index 0000000..f6e9e62 --- /dev/null +++ b/app/src/vl/svc/VL_NEGBAL.pgc @@ -0,0 +1,29 @@ +/* + * VL_NEGBAL.pgc - vl 모듈 서비스 VL_NEGBAL (one service = one file). + * 기능: 마이너스 잔액 가맹점 검출 (merchant_summary.net_amount<0, RC 1=존재; 최악치 반환). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_NEGBAL.h" + +void VL_NEGBAL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_worst_m[64]; + long h_cnt, h_worst; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_bizdate[0] == 0) strcpy(h_bizdate, "2026-07-19"); + EXEC SQL SELECT count(*), coalesce(min(net_amount),0), coalesce(min(merchant_id),'') + INTO :h_cnt, :h_worst, :h_worst_m + FROM merchant_summary + WHERE biz_date = :h_bizdate AND net_amount < 0; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt > 0) rc = 1; /* 마이너스 잔액 존재 */ + setl(b, T_COUNT, h_cnt); + setl(b, T_AMT1, h_worst); + Bchg(b, T_STR1, 0, h_worst_m, 0L); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_NEGNET.h b/app/src/vl/svc/VL_NEGNET.h new file mode 100644 index 0000000..1bc1e2d --- /dev/null +++ b/app/src/vl/svc/VL_NEGNET.h @@ -0,0 +1,12 @@ +/* + * VL_NEGNET.h - vl 서비스 VL_NEGNET 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_COUNT, T_ARG1, T_ARG2, T_RC + */ +#ifndef VL_NEGNET_H +#define VL_NEGNET_H + +#include "vl_svc.h" + +void VL_NEGNET(TPSVCINFO *p); + +#endif /* VL_NEGNET_H */ diff --git a/app/src/vl/svc/VL_NEGNET.pgc b/app/src/vl/svc/VL_NEGNET.pgc new file mode 100644 index 0000000..0b85b9e --- /dev/null +++ b/app/src/vl/svc/VL_NEGNET.pgc @@ -0,0 +1,31 @@ +/* + * VL_NEGNET.pgc - vl 모듈 서비스 VL_NEGNET (one service = one file). + * 기능: 일자별 음수 순액/역전 수수료 매입 검출 (net<0, fee<0, fee>amount, RC 1=존재). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_NEGNET.h" + +void VL_NEGNET(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt, h_neg, h_over; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_bizdate[0] == 0) strcpy(h_bizdate, "2026-07-19"); + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN net < 0 THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN fee > amount THEN 1 ELSE 0 END),0) + INTO :h_cnt, :h_neg, :h_over + FROM purchase + WHERE biz_date = :h_bizdate AND (net < 0 OR fee < 0 OR fee > amount); + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt > 0) rc = 1; /* 음수/역전 건 존재 */ + setl(b, T_COUNT, h_cnt); + setl(b, T_ARG1, h_neg); /* 음수 순액 건수 */ + setl(b, T_ARG2, h_over); /* 수수료>금액 건수 */ + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_RATEDEV.h b/app/src/vl/svc/VL_RATEDEV.h new file mode 100644 index 0000000..fe8041f --- /dev/null +++ b/app/src/vl/svc/VL_RATEDEV.h @@ -0,0 +1,12 @@ +/* + * VL_RATEDEV.h - vl 서비스 VL_RATEDEV 카피북 (copybook / record header). + * 사용 UBF 필드: T_PURCHASE_ID, T_ARG1, T_ARG2, T_ARG3, T_DELTA, T_RC + */ +#ifndef VL_RATEDEV_H +#define VL_RATEDEV_H + +#include "vl_svc.h" + +void VL_RATEDEV(TPSVCINFO *p); + +#endif /* VL_RATEDEV_H */ diff --git a/app/src/vl/svc/VL_RATEDEV.pgc b/app/src/vl/svc/VL_RATEDEV.pgc new file mode 100644 index 0000000..cbb1050 --- /dev/null +++ b/app/src/vl/svc/VL_RATEDEV.pgc @@ -0,0 +1,33 @@ +/* + * VL_RATEDEV.pgc - vl 모듈 서비스 VL_RATEDEV (one service = one file). + * 기능: 건별 실적용 요율 편차 검출 (fee*10000/amount vs mdr_bps, 편차>T_ARG1 → RC 1, 2=금액 0 이하, 3=매입없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_RATEDEV.h" + +void VL_RATEDEV(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0, tol, actual, dev; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_amt, h_fee, h_bps; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + tol = getl(b, T_ARG1); + if (tol <= 0) tol = 5; /* 기본 허용편차 5bps */ + EXEC SQL SELECT p.amount, p.fee, m.mdr_bps INTO :h_amt, :h_fee, :h_bps + FROM purchase p JOIN merchant m ON m.merchant_id = p.merchant_id + WHERE p.purchase_id = :h_pid; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); } + if (sqlca.sqlcode < 0) FAIL(b); + if (h_amt <= 0) { setl(b, T_RC, 2); OK(b); } /* 요율 산출 불가 */ + actual = h_fee * 10000 / h_amt; + dev = actual - h_bps; + if (dev < 0) dev = -dev; + if (dev > tol) rc = 1; /* 요율 편차 초과 */ + setl(b, T_ARG2, actual); + setl(b, T_ARG3, h_bps); + setl(b, T_DELTA, dev); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_RILEDGER.h b/app/src/vl/svc/VL_RILEDGER.h new file mode 100644 index 0000000..a3b4b8d --- /dev/null +++ b/app/src/vl/svc/VL_RILEDGER.h @@ -0,0 +1,12 @@ +/* + * VL_RILEDGER.h - vl 서비스 VL_RILEDGER 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_ARG1, T_ARG2, T_ARG3, T_COUNT, T_RC + */ +#ifndef VL_RILEDGER_H +#define VL_RILEDGER_H + +#include "vl_svc.h" + +void VL_RILEDGER(TPSVCINFO *p); + +#endif /* VL_RILEDGER_H */ diff --git a/app/src/vl/svc/VL_RILEDGER.pgc b/app/src/vl/svc/VL_RILEDGER.pgc new file mode 100644 index 0000000..fc5381d --- /dev/null +++ b/app/src/vl/svc/VL_RILEDGER.pgc @@ -0,0 +1,37 @@ +/* + * VL_RILEDGER.pgc - vl 모듈 서비스 VL_RILEDGER (one service = one file). + * 기능: 참조무결성 세분 검증 - 원장→매입 고아 엔트리 (RC 비트합 1=RECON 2=SETTLE 4=XFER 고아). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_RILEDGER.h" + +void VL_RILEDGER(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_recon, h_settle, h_xfer; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_bizdate[0] == 0) strcpy(h_bizdate, "2026-07-19"); + EXEC SQL SELECT + coalesce(sum(CASE WHEN l.entry_type = 'RECON' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN l.entry_type = 'SETTLE' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN l.entry_type = 'XFER' THEN 1 ELSE 0 END),0) + INTO :h_recon, :h_settle, :h_xfer + FROM ledger l + WHERE l.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM purchase p + WHERE p.purchase_id = l.purchase_id); + if (sqlca.sqlcode < 0) FAIL(b); + if (h_recon > 0) rc |= 1; /* RECON 고아 */ + if (h_settle > 0) rc |= 2; /* SETTLE 고아 */ + if (h_xfer > 0) rc |= 4; /* XFER 고아 */ + setl(b, T_ARG1, h_recon); + setl(b, T_ARG2, h_settle); + setl(b, T_ARG3, h_xfer); + setl(b, T_COUNT, h_recon + h_settle + h_xfer); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_RIMERCH.h b/app/src/vl/svc/VL_RIMERCH.h new file mode 100644 index 0000000..b0ff905 --- /dev/null +++ b/app/src/vl/svc/VL_RIMERCH.h @@ -0,0 +1,12 @@ +/* + * VL_RIMERCH.h - vl 서비스 VL_RIMERCH 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_COUNT, T_ID1, T_RC + */ +#ifndef VL_RIMERCH_H +#define VL_RIMERCH_H + +#include "vl_svc.h" + +void VL_RIMERCH(TPSVCINFO *p); + +#endif /* VL_RIMERCH_H */ diff --git a/app/src/vl/svc/VL_RIMERCH.pgc b/app/src/vl/svc/VL_RIMERCH.pgc new file mode 100644 index 0000000..17b9779 --- /dev/null +++ b/app/src/vl/svc/VL_RIMERCH.pgc @@ -0,0 +1,29 @@ +/* + * VL_RIMERCH.pgc - vl 모듈 서비스 VL_RIMERCH (one service = one file). + * 기능: 참조무결성 세분 검증 - 매입→가맹점 (일자별 고아 매입 수, RC 1=고아 존재). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_RIMERCH.h" + +void VL_RIMERCH(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt, h_first; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_bizdate[0] == 0) strcpy(h_bizdate, "2026-07-19"); + EXEC SQL SELECT count(*), coalesce(min(p.purchase_id),0) INTO :h_cnt, :h_first + FROM purchase p + WHERE p.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM merchant m + WHERE m.merchant_id = p.merchant_id); + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt > 0) rc = 1; /* 가맹점 참조 깨진 매입 존재 */ + setl(b, T_COUNT, h_cnt); + setl(b, T_ID1, h_first); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_RIPURCH.h b/app/src/vl/svc/VL_RIPURCH.h new file mode 100644 index 0000000..4f65a2b --- /dev/null +++ b/app/src/vl/svc/VL_RIPURCH.h @@ -0,0 +1,12 @@ +/* + * VL_RIPURCH.h - vl 서비스 VL_RIPURCH 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_COUNT, T_ID1, T_NET, T_RC + */ +#ifndef VL_RIPURCH_H +#define VL_RIPURCH_H + +#include "vl_svc.h" + +void VL_RIPURCH(TPSVCINFO *p); + +#endif /* VL_RIPURCH_H */ diff --git a/app/src/vl/svc/VL_RIPURCH.pgc b/app/src/vl/svc/VL_RIPURCH.pgc new file mode 100644 index 0000000..464a059 --- /dev/null +++ b/app/src/vl/svc/VL_RIPURCH.pgc @@ -0,0 +1,31 @@ +/* + * VL_RIPURCH.pgc - vl 모듈 서비스 VL_RIPURCH (one service = one file). + * 기능: 참조무결성 세분 검증 - 정산→매입 (일자별 고아 정산 수/금액, RC 1=고아 존재). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_RIPURCH.h" + +void VL_RIPURCH(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt, h_first, h_net; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_bizdate[0] == 0) strcpy(h_bizdate, "2026-07-19"); + EXEC SQL SELECT count(*), coalesce(min(s.settlement_id),0), coalesce(sum(s.net),0) + INTO :h_cnt, :h_first, :h_net + FROM settlement s + WHERE s.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM purchase p + WHERE p.purchase_id = s.purchase_id); + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt > 0) rc = 1; /* 매입 없는 정산 존재 */ + setl(b, T_COUNT, h_cnt); + setl(b, T_ID1, h_first); + setl(b, T_NET, h_net); /* 고아 정산 금액 합 */ + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_RISETTLE.h b/app/src/vl/svc/VL_RISETTLE.h new file mode 100644 index 0000000..f804efc --- /dev/null +++ b/app/src/vl/svc/VL_RISETTLE.h @@ -0,0 +1,12 @@ +/* + * VL_RISETTLE.h - vl 서비스 VL_RISETTLE 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_COUNT, T_PURCHASE_ID, T_RC + */ +#ifndef VL_RISETTLE_H +#define VL_RISETTLE_H + +#include "vl_svc.h" + +void VL_RISETTLE(TPSVCINFO *p); + +#endif /* VL_RISETTLE_H */ diff --git a/app/src/vl/svc/VL_RISETTLE.pgc b/app/src/vl/svc/VL_RISETTLE.pgc new file mode 100644 index 0000000..e8e1396 --- /dev/null +++ b/app/src/vl/svc/VL_RISETTLE.pgc @@ -0,0 +1,27 @@ +/* + * VL_RISETTLE.pgc - vl 모듈 서비스 VL_RISETTLE (one service = one file). + * 기능: 참조무결성 세분 검증 - 정산 가맹점 불일치 (settlement.merchant_id <> purchase.merchant_id, RC 1=불일치 존재). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_RISETTLE.h" + +void VL_RISETTLE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt, h_first_p; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_bizdate[0] == 0) strcpy(h_bizdate, "2026-07-19"); + EXEC SQL SELECT count(*), coalesce(min(s.purchase_id),0) INTO :h_cnt, :h_first_p + FROM settlement s JOIN purchase p ON p.purchase_id = s.purchase_id + WHERE s.biz_date = :h_bizdate AND s.merchant_id <> p.merchant_id; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt > 0) rc = 1; /* 정산-매입 가맹점 불일치 */ + setl(b, T_COUNT, h_cnt); + setl(b, T_PURCHASE_ID, h_first_p); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_RULEHIST.h b/app/src/vl/svc/VL_RULEHIST.h new file mode 100644 index 0000000..a3d3a65 --- /dev/null +++ b/app/src/vl/svc/VL_RULEHIST.h @@ -0,0 +1,12 @@ +/* + * VL_RULEHIST.h - vl 서비스 VL_RULEHIST 카피북 (copybook / record header). + * 사용 UBF 필드: T_STR3, T_COUNT, T_ID1, T_RC + */ +#ifndef VL_RULEHIST_H +#define VL_RULEHIST_H + +#include "vl_svc.h" + +void VL_RULEHIST(TPSVCINFO *p); + +#endif /* VL_RULEHIST_H */ diff --git a/app/src/vl/svc/VL_RULEHIST.pgc b/app/src/vl/svc/VL_RULEHIST.pgc new file mode 100644 index 0000000..9ad1ea5 --- /dev/null +++ b/app/src/vl/svc/VL_RULEHIST.pgc @@ -0,0 +1,29 @@ +/* + * VL_RULEHIST.pgc - vl 모듈 서비스 VL_RULEHIST (one service = one file). + * 기능: 검증 규칙 변경이력 조회 (vl_check_log의 VL_RULEMOD 이력 건수/최종 로그 ID, RC 1=이력 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_RULEHIST.h" + +void VL_RULEHIST(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + char rule[32]; + EXEC SQL BEGIN DECLARE SECTION; + char h_pat[40]; + long h_cnt, h_last; + EXEC SQL END DECLARE SECTION; + gets_(b, T_STR3, rule, sizeof(rule)); + if (rule[0] == 0) { userlog("VL_RULEHIST: T_STR3(rule_code) 누락"); FAIL(b); } + snprintf(h_pat, sizeof(h_pat), "%s %%", rule); + EXEC SQL SELECT count(*), coalesce(max(log_id),0) INTO :h_cnt, :h_last + FROM vl_check_log + WHERE check_type = 'VL_RULEMOD' AND detail LIKE :h_pat; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt == 0) rc = 1; /* 변경 이력 없음 */ + setl(b, T_COUNT, h_cnt); + setl(b, T_ID1, h_last); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_RULEMOD.h b/app/src/vl/svc/VL_RULEMOD.h new file mode 100644 index 0000000..f5505d2 --- /dev/null +++ b/app/src/vl/svc/VL_RULEMOD.h @@ -0,0 +1,12 @@ +/* + * VL_RULEMOD.h - vl 서비스 VL_RULEMOD 카피북 (copybook / record header). + * 사용 UBF 필드: T_STR3, T_ARG1, T_STR4, T_BIZDATE, T_ARG2, T_RC + */ +#ifndef VL_RULEMOD_H +#define VL_RULEMOD_H + +#include "vl_svc.h" + +void VL_RULEMOD(TPSVCINFO *p); + +#endif /* VL_RULEMOD_H */ diff --git a/app/src/vl/svc/VL_RULEMOD.pgc b/app/src/vl/svc/VL_RULEMOD.pgc new file mode 100644 index 0000000..1a339a3 --- /dev/null +++ b/app/src/vl/svc/VL_RULEMOD.pgc @@ -0,0 +1,39 @@ +/* + * VL_RULEMOD.pgc - vl 모듈 서비스 VL_RULEMOD (one service = one file). + * 기능: 검증 규칙 변경 + 변경이력 기록 (임계값/심각도 UPDATE 후 vl_check_log에 이력 적재, RC 3=규칙 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_RULEMOD.h" + +void VL_RULEMOD(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16], hist[128]; + EXEC SQL BEGIN DECLARE SECTION; + char h_rule[32], h_sev[8]; + long h_old, h_new; + EXEC SQL END DECLARE SECTION; + gets_(b, T_STR3, h_rule, sizeof(h_rule)); + if (h_rule[0] == 0) { userlog("VL_RULEMOD: T_STR3(rule_code) 누락"); FAIL(b); } + h_new = getl(b, T_ARG1); + gets_(b, T_STR4, h_sev, sizeof(h_sev)); + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) strcpy(bizdate, "2026-07-19"); + EXEC SQL SELECT threshold INTO :h_old FROM vl_rule WHERE rule_code = :h_rule; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); } /* 3=규칙 없음 */ + if (sqlca.sqlcode < 0) FAIL(b); + if (h_sev[0] == 0) { + EXEC SQL UPDATE vl_rule SET threshold = :h_new, updated_at = now() + WHERE rule_code = :h_rule; + } else { + EXEC SQL UPDATE vl_rule SET threshold = :h_new, severity = :h_sev, + updated_at = now() + WHERE rule_code = :h_rule; + } + if (sqlca.sqlcode < 0) FAIL(b); + snprintf(hist, sizeof(hist), "%s THR %ld -> %ld", h_rule, h_old, h_new); + if (vldb_insert_check_log("VL_RULEMOD", 0, 0, hist, bizdate) < 0) FAIL(b); + setl(b, T_ARG2, h_old); /* 변경 전 임계값 */ + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/vl/svc/VL_RULEOFF.h b/app/src/vl/svc/VL_RULEOFF.h new file mode 100644 index 0000000..9c90ffc --- /dev/null +++ b/app/src/vl/svc/VL_RULEOFF.h @@ -0,0 +1,12 @@ +/* + * VL_RULEOFF.h - vl 서비스 VL_RULEOFF 카피북 (copybook / record header). + * 사용 UBF 필드: T_STR3, T_STR5, T_RC + */ +#ifndef VL_RULEOFF_H +#define VL_RULEOFF_H + +#include "vl_svc.h" + +void VL_RULEOFF(TPSVCINFO *p); + +#endif /* VL_RULEOFF_H */ diff --git a/app/src/vl/svc/VL_RULEOFF.pgc b/app/src/vl/svc/VL_RULEOFF.pgc new file mode 100644 index 0000000..85154f4 --- /dev/null +++ b/app/src/vl/svc/VL_RULEOFF.pgc @@ -0,0 +1,28 @@ +/* + * VL_RULEOFF.pgc - vl 모듈 서비스 VL_RULEOFF (one service = one file). + * 기능: 검증 규칙 활성/비활성 전환 (T_STR5 = ACTIVE|INACTIVE, RC 1=규칙 없음 2=상태값 오류). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_RULEOFF.h" + +void VL_RULEOFF(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_rule[32], h_st[16]; + EXEC SQL END DECLARE SECTION; + gets_(b, T_STR3, h_rule, sizeof(h_rule)); + if (h_rule[0] == 0) { userlog("VL_RULEOFF: T_STR3(rule_code) 누락"); FAIL(b); } + gets_(b, T_STR5, h_st, sizeof(h_st)); + if (strcmp(h_st, "ACTIVE") != 0 && strcmp(h_st, "INACTIVE") != 0) { + setl(b, T_RC, 2); /* 허용되지 않는 상태값 */ + OK(b); + } + EXEC SQL UPDATE vl_rule SET status = :h_st, updated_at = now() + WHERE rule_code = :h_rule; + if (sqlca.sqlcode < 0) FAIL(b); + if ((long)sqlca.sqlerrd[2] == 0) rc = 1; /* 대상 규칙 없음 */ + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_RULEREG.h b/app/src/vl/svc/VL_RULEREG.h new file mode 100644 index 0000000..a19749f --- /dev/null +++ b/app/src/vl/svc/VL_RULEREG.h @@ -0,0 +1,12 @@ +/* + * VL_RULEREG.h - vl 서비스 VL_RULEREG 카피북 (copybook / record header). + * 사용 UBF 필드: T_STR3, T_STR1, T_STR4, T_ARG1, T_RC + */ +#ifndef VL_RULEREG_H +#define VL_RULEREG_H + +#include "vl_svc.h" + +void VL_RULEREG(TPSVCINFO *p); + +#endif /* VL_RULEREG_H */ diff --git a/app/src/vl/svc/VL_RULEREG.pgc b/app/src/vl/svc/VL_RULEREG.pgc new file mode 100644 index 0000000..a5fd72d --- /dev/null +++ b/app/src/vl/svc/VL_RULEREG.pgc @@ -0,0 +1,34 @@ +/* + * VL_RULEREG.pgc - vl 모듈 서비스 VL_RULEREG (one service = one file). + * 기능: 검증 규칙 신규 등록 (이미 존재하면 RC 1로 거부, 신규만 INSERT). + * 갱신 겸용 upsert는 VL_RULE이 담당하고, 본 서비스는 순수 신규 등록만 허용한다. + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_RULEREG.h" + +void VL_RULEREG(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_rule[32], h_name[64], h_sev[8]; + long h_thr, h_cnt; + EXEC SQL END DECLARE SECTION; + gets_(b, T_STR3, h_rule, sizeof(h_rule)); + if (h_rule[0] == 0) { userlog("VL_RULEREG: T_STR3(rule_code) 누락"); FAIL(b); } + gets_(b, T_STR1, h_name, sizeof(h_name)); + if (h_name[0] == 0) strcpy(h_name, "신규 검증 규칙"); + gets_(b, T_STR4, h_sev, sizeof(h_sev)); + if (h_sev[0] == 0) strcpy(h_sev, "INFO"); + h_thr = getl(b, T_ARG1); + EXEC SQL SELECT count(*) INTO :h_cnt FROM vl_rule WHERE rule_code = :h_rule; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt > 0) rc = 1; /* 이미 등록된 규칙 */ + else { + EXEC SQL INSERT INTO vl_rule (rule_code, rule_name, threshold, severity) + VALUES (:h_rule, :h_name, :h_thr, :h_sev); + if (sqlca.sqlcode < 0) FAIL(b); + } + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_RULESTAT.h b/app/src/vl/svc/VL_RULESTAT.h new file mode 100644 index 0000000..5eb3830 --- /dev/null +++ b/app/src/vl/svc/VL_RULESTAT.h @@ -0,0 +1,12 @@ +/* + * VL_RULESTAT.h - vl 서비스 VL_RULESTAT 카피북 (copybook / record header). + * 사용 UBF 필드: T_COUNT, T_ARG1, T_ARG2, T_RC + */ +#ifndef VL_RULESTAT_H +#define VL_RULESTAT_H + +#include "vl_svc.h" + +void VL_RULESTAT(TPSVCINFO *p); + +#endif /* VL_RULESTAT_H */ diff --git a/app/src/vl/svc/VL_RULESTAT.pgc b/app/src/vl/svc/VL_RULESTAT.pgc new file mode 100644 index 0000000..6cc4d4e --- /dev/null +++ b/app/src/vl/svc/VL_RULESTAT.pgc @@ -0,0 +1,27 @@ +/* + * VL_RULESTAT.pgc - vl 모듈 서비스 VL_RULESTAT (one service = one file). + * 기능: 검증 규칙 현황 집계 (전체/ACTIVE/CRIT 건수, RC 1=활성 규칙 전무). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_RULESTAT.h" + +void VL_RULESTAT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_tot, h_act, h_crit; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN status = 'ACTIVE' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN severity = 'CRIT' THEN 1 ELSE 0 END),0) + INTO :h_tot, :h_act, :h_crit + FROM vl_rule; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_act == 0) rc = 1; /* 활성 규칙 없음: 검증 공백 */ + setl(b, T_COUNT, h_tot); + setl(b, T_ARG1, h_act); + setl(b, T_ARG2, h_crit); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_SUMFEE.h b/app/src/vl/svc/VL_SUMFEE.h new file mode 100644 index 0000000..1e42ca8 --- /dev/null +++ b/app/src/vl/svc/VL_SUMFEE.h @@ -0,0 +1,12 @@ +/* + * VL_SUMFEE.h - vl 서비스 VL_SUMFEE 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_BIZDATE, T_FEE, T_AMT1, T_NET, T_AMT2, T_DELTA, T_RC + */ +#ifndef VL_SUMFEE_H +#define VL_SUMFEE_H + +#include "vl_svc.h" + +void VL_SUMFEE(TPSVCINFO *p); + +#endif /* VL_SUMFEE_H */ diff --git a/app/src/vl/svc/VL_SUMFEE.pgc b/app/src/vl/svc/VL_SUMFEE.pgc new file mode 100644 index 0000000..7683ce4 --- /dev/null +++ b/app/src/vl/svc/VL_SUMFEE.pgc @@ -0,0 +1,38 @@ +/* + * VL_SUMFEE.pgc - vl 모듈 서비스 VL_SUMFEE (one service = one file). + * 기능: 통계-원천 수수료/순액 대사 (merchant_summary fee/net vs purchase 재집계, RC 비트합 1=수수료 2=순액, 8=통계 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_SUMFEE.h" + +void VL_SUMFEE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16]; + long h_sfee, h_snet, h_pfee, h_pnet; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + if (h_merch[0] == 0) { userlog("VL_SUMFEE: T_MERCHANT 누락"); FAIL(b); } + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_bizdate[0] == 0) strcpy(h_bizdate, "2026-07-19"); + EXEC SQL SELECT fee_amount, net_amount INTO :h_sfee, :h_snet + FROM merchant_summary + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 8); OK(b); } /* 8=통계 없음 */ + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(fee),0), coalesce(sum(net),0) INTO :h_pfee, :h_pnet + FROM purchase + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status <> 'C'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_sfee != h_pfee) rc |= 1; /* 수수료 대사 불일치 */ + if (h_snet != h_pnet) rc |= 2; /* 순액 대사 불일치 */ + setl(b, T_FEE, h_pfee); + setl(b, T_AMT1, h_sfee); + setl(b, T_NET, h_pnet); + setl(b, T_AMT2, h_snet); + setl(b, T_DELTA, (h_sfee - h_pfee) + (h_snet - h_pnet)); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_SUMRECON.h b/app/src/vl/svc/VL_SUMRECON.h new file mode 100644 index 0000000..12df87e --- /dev/null +++ b/app/src/vl/svc/VL_SUMRECON.h @@ -0,0 +1,12 @@ +/* + * VL_SUMRECON.h - vl 서비스 VL_SUMRECON 카피북 (copybook / record header). + * 사용 UBF 필드: T_MERCHANT, T_BIZDATE, T_COUNT, T_ARG1, T_GROSS, T_AMT1, T_RC + */ +#ifndef VL_SUMRECON_H +#define VL_SUMRECON_H + +#include "vl_svc.h" + +void VL_SUMRECON(TPSVCINFO *p); + +#endif /* VL_SUMRECON_H */ diff --git a/app/src/vl/svc/VL_SUMRECON.pgc b/app/src/vl/svc/VL_SUMRECON.pgc new file mode 100644 index 0000000..4d2195f --- /dev/null +++ b/app/src/vl/svc/VL_SUMRECON.pgc @@ -0,0 +1,37 @@ +/* + * VL_SUMRECON.pgc - vl 모듈 서비스 VL_SUMRECON (one service = one file). + * 기능: 통계-원천 대사 검증 (merchant_summary 건수/총액 vs purchase 재집계, RC 비트합 1=건수 2=총액, 8=통계 없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_SUMRECON.h" + +void VL_SUMRECON(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16]; + long h_scnt, h_sgross, h_pcnt, h_pgross; + EXEC SQL END DECLARE SECTION; + gets_(b, T_MERCHANT, h_merch, sizeof(h_merch)); + if (h_merch[0] == 0) { userlog("VL_SUMRECON: T_MERCHANT 누락"); FAIL(b); } + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_bizdate[0] == 0) strcpy(h_bizdate, "2026-07-19"); + EXEC SQL SELECT txn_count, gross_amount INTO :h_scnt, :h_sgross + FROM merchant_summary + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 8); OK(b); } /* 8=통계 없음 */ + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_pcnt, :h_pgross + FROM purchase + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate AND status <> 'C'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_scnt != h_pcnt) rc |= 1; /* 건수 대사 불일치 */ + if (h_sgross != h_pgross) rc |= 2; /* 총액 대사 불일치 */ + setl(b, T_COUNT, h_pcnt); + setl(b, T_ARG1, h_scnt); + setl(b, T_GROSS, h_pgross); + setl(b, T_AMT1, h_sgross); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_SUSPACK.h b/app/src/vl/svc/VL_SUSPACK.h new file mode 100644 index 0000000..2ea80c4 --- /dev/null +++ b/app/src/vl/svc/VL_SUSPACK.h @@ -0,0 +1,12 @@ +/* + * VL_SUSPACK.h - vl 서비스 VL_SUSPACK 카피북 (copybook / record header). + * 사용 UBF 필드: T_ID1, T_STR1, T_ARG1, T_RC + */ +#ifndef VL_SUSPACK_H +#define VL_SUSPACK_H + +#include "vl_svc.h" + +void VL_SUSPACK(TPSVCINFO *p); + +#endif /* VL_SUSPACK_H */ diff --git a/app/src/vl/svc/VL_SUSPACK.pgc b/app/src/vl/svc/VL_SUSPACK.pgc new file mode 100644 index 0000000..92e8dea --- /dev/null +++ b/app/src/vl/svc/VL_SUSPACK.pgc @@ -0,0 +1,29 @@ +/* + * VL_SUSPACK.pgc - vl 모듈 서비스 VL_SUSPACK (one service = one file). + * 기능: 의심거래 워크플로 1단계 확인 (OPEN→CONFIRMED, 확인자 기록, RC 1=OPEN 상태 아님/없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_SUSPACK.h" + +void VL_SUSPACK(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0, rows; + EXEC SQL BEGIN DECLARE SECTION; + long h_sid; + char h_who[32]; + EXEC SQL END DECLARE SECTION; + h_sid = getl(b, T_ID1); + gets_(b, T_STR1, h_who, sizeof(h_who)); + if (h_who[0] == 0) strcpy(h_who, "MISC"); + EXEC SQL UPDATE vl_suspect + SET status = 'CONFIRMED', + detail = detail || ' [확인:' || :h_who || ']' + WHERE suspect_id = :h_sid AND status = 'OPEN'; + if (sqlca.sqlcode < 0) FAIL(b); + rows = (long)sqlca.sqlerrd[2]; + if (rows == 0) rc = 1; /* OPEN 아님 또는 미존재 */ + setl(b, T_ARG1, rows); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_SUSPACT.h b/app/src/vl/svc/VL_SUSPACT.h new file mode 100644 index 0000000..0744dab --- /dev/null +++ b/app/src/vl/svc/VL_SUSPACT.h @@ -0,0 +1,12 @@ +/* + * VL_SUSPACT.h - vl 서비스 VL_SUSPACT 카피북 (copybook / record header). + * 사용 UBF 필드: T_ID1, T_REASON, T_BIZDATE, T_PURCHASE_ID, T_RC + */ +#ifndef VL_SUSPACT_H +#define VL_SUSPACT_H + +#include "vl_svc.h" + +void VL_SUSPACT(TPSVCINFO *p); + +#endif /* VL_SUSPACT_H */ diff --git a/app/src/vl/svc/VL_SUSPACT.pgc b/app/src/vl/svc/VL_SUSPACT.pgc new file mode 100644 index 0000000..3e2eb9f --- /dev/null +++ b/app/src/vl/svc/VL_SUSPACT.pgc @@ -0,0 +1,34 @@ +/* + * VL_SUSPACT.pgc - vl 모듈 서비스 VL_SUSPACT (one service = one file). + * 기능: 의심거래 워크플로 2단계 조치 등록 (조치내용 detail 부기 + 검증로그 적재, RC 2=이미 종결 3=없음 4=조치내용 누락). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_SUSPACT.h" + +void VL_SUSPACT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + char bizdate[16]; + EXEC SQL BEGIN DECLARE SECTION; + long h_sid, h_pid; + char h_st[16], h_act[128]; + EXEC SQL END DECLARE SECTION; + h_sid = getl(b, T_ID1); + gets_(b, T_REASON, h_act, sizeof(h_act)); + if (h_act[0] == 0) { setl(b, T_RC, 4); OK(b); } /* 조치내용 필수 */ + gets_(b, T_BIZDATE, bizdate, sizeof(bizdate)); + if (bizdate[0] == 0) strcpy(bizdate, "2026-07-19"); + EXEC SQL SELECT purchase_id, status INTO :h_pid, :h_st + FROM vl_suspect WHERE suspect_id = :h_sid; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); } /* 3=의심거래 없음 */ + if (sqlca.sqlcode < 0) FAIL(b); + if (strcmp(h_st, "CLOSED") == 0) { setl(b, T_RC, 2); OK(b); } /* 종결건 조치 불가 */ + EXEC SQL UPDATE vl_suspect + SET detail = detail || ' [조치:' || :h_act || ']' + WHERE suspect_id = :h_sid; + if (sqlca.sqlcode < 0) FAIL(b); + if (vldb_insert_check_log("VL_SUSPACT", h_pid, 0, h_act, bizdate) < 0) FAIL(b); + setl(b, T_PURCHASE_ID, h_pid); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/vl/svc/VL_SUSPFINAL.h b/app/src/vl/svc/VL_SUSPFINAL.h new file mode 100644 index 0000000..3a725db --- /dev/null +++ b/app/src/vl/svc/VL_SUSPFINAL.h @@ -0,0 +1,12 @@ +/* + * VL_SUSPFINAL.h - vl 서비스 VL_SUSPFINAL 카피북 (copybook / record header). + * 사용 UBF 필드: T_ID1, T_ARG1, T_RC + */ +#ifndef VL_SUSPFINAL_H +#define VL_SUSPFINAL_H + +#include "vl_svc.h" + +void VL_SUSPFINAL(TPSVCINFO *p); + +#endif /* VL_SUSPFINAL_H */ diff --git a/app/src/vl/svc/VL_SUSPFINAL.pgc b/app/src/vl/svc/VL_SUSPFINAL.pgc new file mode 100644 index 0000000..027c1f1 --- /dev/null +++ b/app/src/vl/svc/VL_SUSPFINAL.pgc @@ -0,0 +1,27 @@ +/* + * VL_SUSPFINAL.pgc - vl 모듈 서비스 VL_SUSPFINAL (one service = one file). + * 기능: 의심거래 워크플로 3단계 종결 (CONFIRMED→CLOSED만 허용, RC 1=이미 종결 2=확인 전 종결 불가 3=없음). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_SUSPFINAL.h" + +void VL_SUSPFINAL(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + EXEC SQL BEGIN DECLARE SECTION; + long h_sid; + char h_st[16]; + EXEC SQL END DECLARE SECTION; + h_sid = getl(b, T_ID1); + EXEC SQL SELECT status INTO :h_st FROM vl_suspect WHERE suspect_id = :h_sid; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); } /* 3=의심거래 없음 */ + if (sqlca.sqlcode < 0) FAIL(b); + if (strcmp(h_st, "CLOSED") == 0) { setl(b, T_RC, 1); OK(b); } /* 이미 종결 */ + if (strcmp(h_st, "OPEN") == 0) { setl(b, T_RC, 2); OK(b); } /* 확인 단계 미경유 */ + EXEC SQL UPDATE vl_suspect SET status = 'CLOSED' + WHERE suspect_id = :h_sid AND status = 'CONFIRMED'; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_ARG1, (long)sqlca.sqlerrd[2]); + setl(b, T_RC, 0); + OK(b); +} diff --git a/app/src/vl/svc/VL_SUSPREOPEN.h b/app/src/vl/svc/VL_SUSPREOPEN.h new file mode 100644 index 0000000..46dc1cb --- /dev/null +++ b/app/src/vl/svc/VL_SUSPREOPEN.h @@ -0,0 +1,12 @@ +/* + * VL_SUSPREOPEN.h - vl 서비스 VL_SUSPREOPEN 카피북 (copybook / record header). + * 사용 UBF 필드: T_ID1, T_REASON, T_RC + */ +#ifndef VL_SUSPREOPEN_H +#define VL_SUSPREOPEN_H + +#include "vl_svc.h" + +void VL_SUSPREOPEN(TPSVCINFO *p); + +#endif /* VL_SUSPREOPEN_H */ diff --git a/app/src/vl/svc/VL_SUSPREOPEN.pgc b/app/src/vl/svc/VL_SUSPREOPEN.pgc new file mode 100644 index 0000000..8078dfd --- /dev/null +++ b/app/src/vl/svc/VL_SUSPREOPEN.pgc @@ -0,0 +1,27 @@ +/* + * VL_SUSPREOPEN.pgc - vl 모듈 서비스 VL_SUSPREOPEN (one service = one file). + * 기능: 종결 의심거래 재오픈 (CLOSED→OPEN, 재오픈 사유 필수, RC 1=종결건 아님/없음 2=사유 누락). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_SUSPREOPEN.h" + +void VL_SUSPREOPEN(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_sid; + char h_rsn[128]; + EXEC SQL END DECLARE SECTION; + h_sid = getl(b, T_ID1); + gets_(b, T_REASON, h_rsn, sizeof(h_rsn)); + if (h_rsn[0] == 0) { setl(b, T_RC, 2); OK(b); } /* 재오픈 사유 필수 */ + EXEC SQL UPDATE vl_suspect + SET status = 'OPEN', + detail = detail || ' [재오픈:' || :h_rsn || ']' + WHERE suspect_id = :h_sid AND status = 'CLOSED'; + if (sqlca.sqlcode < 0) FAIL(b); + if ((long)sqlca.sqlerrd[2] == 0) rc = 1; /* CLOSED 상태 아님 */ + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_SUSPSTAT.h b/app/src/vl/svc/VL_SUSPSTAT.h new file mode 100644 index 0000000..17c01f3 --- /dev/null +++ b/app/src/vl/svc/VL_SUSPSTAT.h @@ -0,0 +1,12 @@ +/* + * VL_SUSPSTAT.h - vl 서비스 VL_SUSPSTAT 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_COUNT, T_ARG1, T_ARG2, T_ARG3, T_RC + */ +#ifndef VL_SUSPSTAT_H +#define VL_SUSPSTAT_H + +#include "vl_svc.h" + +void VL_SUSPSTAT(TPSVCINFO *p); + +#endif /* VL_SUSPSTAT_H */ diff --git a/app/src/vl/svc/VL_SUSPSTAT.pgc b/app/src/vl/svc/VL_SUSPSTAT.pgc new file mode 100644 index 0000000..8b1549d --- /dev/null +++ b/app/src/vl/svc/VL_SUSPSTAT.pgc @@ -0,0 +1,32 @@ +/* + * VL_SUSPSTAT.pgc - vl 모듈 서비스 VL_SUSPSTAT (one service = one file). + * 기능: 의심거래 워크플로 상태별 현황 (일자별 OPEN/CONFIRMED/CLOSED 건수, RC 1=미결 존재). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_SUSPSTAT.h" + +void VL_SUSPSTAT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_tot, h_open, h_conf, h_closed; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_bizdate[0] == 0) strcpy(h_bizdate, "2026-07-19"); + EXEC SQL SELECT count(*), + coalesce(sum(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN status = 'CONFIRMED' THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END),0) + INTO :h_tot, :h_open, :h_conf, :h_closed + FROM vl_suspect WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_open > 0) rc = 1; /* 미결(OPEN) 잔존 */ + setl(b, T_COUNT, h_tot); + setl(b, T_ARG1, h_open); + setl(b, T_ARG2, h_conf); + setl(b, T_ARG3, h_closed); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_THRAUDIT.h b/app/src/vl/svc/VL_THRAUDIT.h new file mode 100644 index 0000000..75eabb5 --- /dev/null +++ b/app/src/vl/svc/VL_THRAUDIT.h @@ -0,0 +1,12 @@ +/* + * VL_THRAUDIT.h - vl 서비스 VL_THRAUDIT 카피북 (copybook / record header). + * 사용 UBF 필드: T_ARG1, T_ARG2, T_RC + */ +#ifndef VL_THRAUDIT_H +#define VL_THRAUDIT_H + +#include "vl_svc.h" + +void VL_THRAUDIT(TPSVCINFO *p); + +#endif /* VL_THRAUDIT_H */ diff --git a/app/src/vl/svc/VL_THRAUDIT.pgc b/app/src/vl/svc/VL_THRAUDIT.pgc new file mode 100644 index 0000000..a00d3bc --- /dev/null +++ b/app/src/vl/svc/VL_THRAUDIT.pgc @@ -0,0 +1,28 @@ +/* + * VL_THRAUDIT.pgc - vl 모듈 서비스 VL_THRAUDIT (one service = one file). + * 기능: 임계값 설정 감사 (RC 비트합 1=ACTIVE ERROR/CRIT 규칙의 임계값 미설정, 2=CRIT 규칙 비활성 방치). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_THRAUDIT.h" + +void VL_THRAUDIT(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_bad, h_off; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_bad FROM vl_rule + WHERE status = 'ACTIVE' AND severity IN ('ERROR','CRIT') + AND threshold <= 0; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*) INTO :h_off FROM vl_rule + WHERE status <> 'ACTIVE' AND severity = 'CRIT'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_bad > 0) rc |= 1; /* 임계값 미설정 고위험 규칙 */ + if (h_off > 0) rc |= 2; /* 비활성화된 CRIT 규칙 */ + setl(b, T_ARG1, h_bad); + setl(b, T_ARG2, h_off); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_THRBUMP.h b/app/src/vl/svc/VL_THRBUMP.h new file mode 100644 index 0000000..630eb08 --- /dev/null +++ b/app/src/vl/svc/VL_THRBUMP.h @@ -0,0 +1,12 @@ +/* + * VL_THRBUMP.h - vl 서비스 VL_THRBUMP 카피북 (copybook / record header). + * 사용 UBF 필드: T_STR3, T_DELTA, T_ARG2, T_ARG3, T_RC + */ +#ifndef VL_THRBUMP_H +#define VL_THRBUMP_H + +#include "vl_svc.h" + +void VL_THRBUMP(TPSVCINFO *p); + +#endif /* VL_THRBUMP_H */ diff --git a/app/src/vl/svc/VL_THRBUMP.pgc b/app/src/vl/svc/VL_THRBUMP.pgc new file mode 100644 index 0000000..c0b5314 --- /dev/null +++ b/app/src/vl/svc/VL_THRBUMP.pgc @@ -0,0 +1,31 @@ +/* + * VL_THRBUMP.pgc - vl 모듈 서비스 VL_THRBUMP (one service = one file). + * 기능: 규칙 임계값 증감 조정 (T_DELTA 만큼 가감, 음수는 0으로 클램프 RC 2, 규칙 없으면 RC 3). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_THRBUMP.h" + +void VL_THRBUMP(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0, delta; + EXEC SQL BEGIN DECLARE SECTION; + char h_rule[32]; + long h_old, h_new; + EXEC SQL END DECLARE SECTION; + gets_(b, T_STR3, h_rule, sizeof(h_rule)); + if (h_rule[0] == 0) { userlog("VL_THRBUMP: T_STR3(rule_code) 누락"); FAIL(b); } + delta = getl(b, T_DELTA); + EXEC SQL SELECT threshold INTO :h_old FROM vl_rule WHERE rule_code = :h_rule; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 3); OK(b); } /* 3=규칙 없음 */ + if (sqlca.sqlcode < 0) FAIL(b); + h_new = h_old + delta; + if (h_new < 0) { h_new = 0; rc = 2; } /* 하한 0 클램프 */ + EXEC SQL UPDATE vl_rule SET threshold = :h_new, updated_at = now() + WHERE rule_code = :h_rule; + if (sqlca.sqlcode < 0) FAIL(b); + setl(b, T_ARG2, h_old); + setl(b, T_ARG3, h_new); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_THRSET.h b/app/src/vl/svc/VL_THRSET.h new file mode 100644 index 0000000..89b9f24 --- /dev/null +++ b/app/src/vl/svc/VL_THRSET.h @@ -0,0 +1,12 @@ +/* + * VL_THRSET.h - vl 서비스 VL_THRSET 카피북 (copybook / record header). + * 사용 UBF 필드: T_STR3, T_ARG1, T_ARG2, T_RC + */ +#ifndef VL_THRSET_H +#define VL_THRSET_H + +#include "vl_svc.h" + +void VL_THRSET(TPSVCINFO *p); + +#endif /* VL_THRSET_H */ diff --git a/app/src/vl/svc/VL_THRSET.pgc b/app/src/vl/svc/VL_THRSET.pgc new file mode 100644 index 0000000..4206dd1 --- /dev/null +++ b/app/src/vl/svc/VL_THRSET.pgc @@ -0,0 +1,27 @@ +/* + * VL_THRSET.pgc - vl 모듈 서비스 VL_THRSET (one service = one file). + * 기능: 규칙 임계값 직접 설정 (음수 거부 RC 2, 대상 규칙 없으면 RC 3). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_THRSET.h" + +void VL_THRSET(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_rule[32]; + long h_thr; + EXEC SQL END DECLARE SECTION; + gets_(b, T_STR3, h_rule, sizeof(h_rule)); + if (h_rule[0] == 0) { userlog("VL_THRSET: T_STR3(rule_code) 누락"); FAIL(b); } + h_thr = getl(b, T_ARG1); + if (h_thr < 0) { setl(b, T_RC, 2); OK(b); } /* 음수 임계값 거부 */ + EXEC SQL UPDATE vl_rule SET threshold = :h_thr, updated_at = now() + WHERE rule_code = :h_rule; + if (sqlca.sqlcode < 0) FAIL(b); + if ((long)sqlca.sqlerrd[2] == 0) rc = 3; /* 대상 규칙 없음 */ + setl(b, T_ARG2, h_thr); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_TRIAGG.h b/app/src/vl/svc/VL_TRIAGG.h new file mode 100644 index 0000000..0ed6089 --- /dev/null +++ b/app/src/vl/svc/VL_TRIAGG.h @@ -0,0 +1,12 @@ +/* + * VL_TRIAGG.h - vl 서비스 VL_TRIAGG 카피북 (copybook / record header). + * 사용 UBF 필드: T_BIZDATE, T_AMT1, T_AMT2, T_AMT3, T_DELTA, T_RC + */ +#ifndef VL_TRIAGG_H +#define VL_TRIAGG_H + +#include "vl_svc.h" + +void VL_TRIAGG(TPSVCINFO *p); + +#endif /* VL_TRIAGG_H */ diff --git a/app/src/vl/svc/VL_TRIAGG.pgc b/app/src/vl/svc/VL_TRIAGG.pgc new file mode 100644 index 0000000..0824f12 --- /dev/null +++ b/app/src/vl/svc/VL_TRIAGG.pgc @@ -0,0 +1,36 @@ +/* + * VL_TRIAGG.pgc - vl 모듈 서비스 VL_TRIAGG (one service = one file). + * 기능: 일자별 매입-정산-지급 3단 총액 대사 (RC 비트합 1=매입vs정산 불일치 2=정산vs지급 불일치). + * 정산완료(S) 매입 순액 합 / 정산 순액 합 / 지급(XFER) 원장 합을 서로 대조한다. + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_TRIAGG.h" + +void VL_TRIAGG(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_pnet, h_snet, h_xamt; + EXEC SQL END DECLARE SECTION; + gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate)); + if (h_bizdate[0] == 0) strcpy(h_bizdate, "2026-07-19"); + EXEC SQL SELECT coalesce(sum(net),0) INTO :h_pnet FROM purchase + WHERE biz_date = :h_bizdate AND status = 'S'; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(net),0) INTO :h_snet FROM settlement + WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_xamt FROM ledger + WHERE biz_date = :h_bizdate AND entry_type = 'XFER'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_pnet != h_snet) rc |= 1; /* 매입 vs 정산 총액 불일치 */ + if (h_snet != h_xamt) rc |= 2; /* 정산 vs 지급 총액 불일치 */ + setl(b, T_AMT1, h_pnet); + setl(b, T_AMT2, h_snet); + setl(b, T_AMT3, h_xamt); + setl(b, T_DELTA, h_pnet - h_xamt); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_TRISTAGE.h b/app/src/vl/svc/VL_TRISTAGE.h new file mode 100644 index 0000000..45a306d --- /dev/null +++ b/app/src/vl/svc/VL_TRISTAGE.h @@ -0,0 +1,12 @@ +/* + * VL_TRISTAGE.h - vl 서비스 VL_TRISTAGE 카피북 (copybook / record header). + * 사용 UBF 필드: T_PURCHASE_ID, T_NET, T_AMT1, T_AMT2, T_RC + */ +#ifndef VL_TRISTAGE_H +#define VL_TRISTAGE_H + +#include "vl_svc.h" + +void VL_TRISTAGE(TPSVCINFO *p); + +#endif /* VL_TRISTAGE_H */ diff --git a/app/src/vl/svc/VL_TRISTAGE.pgc b/app/src/vl/svc/VL_TRISTAGE.pgc new file mode 100644 index 0000000..373e10e --- /dev/null +++ b/app/src/vl/svc/VL_TRISTAGE.pgc @@ -0,0 +1,35 @@ +/* + * VL_TRISTAGE.pgc - vl 모듈 서비스 VL_TRISTAGE (one service = one file). + * 기능: 건별 매입-정산-지급 3단 정합 검증 (RC 비트합 1=정산누락 2=지급누락 4=정산액불일치 8=지급액불일치, 32=매입없음). + * 지급은 ledger의 XFER 엔트리로 판정한다. + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_TRISTAGE.h" + +void VL_TRISTAGE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_pid, h_net, h_scnt, h_snet, h_xcnt, h_xamt; + EXEC SQL END DECLARE SECTION; + h_pid = getl(b, T_PURCHASE_ID); + EXEC SQL SELECT net INTO :h_net FROM purchase WHERE purchase_id = :h_pid; + if (sqlca.sqlcode == 100) { setl(b, T_RC, 32); OK(b); } /* 32=매입 없음 */ + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_scnt, :h_snet + FROM settlement WHERE purchase_id = :h_pid; + if (sqlca.sqlcode < 0) FAIL(b); + EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_xcnt, :h_xamt + FROM ledger WHERE purchase_id = :h_pid AND entry_type = 'XFER'; + if (sqlca.sqlcode < 0) FAIL(b); + if (h_scnt == 0) rc |= 1; /* 정산 누락 */ + if (h_xcnt == 0) rc |= 2; /* 지급 누락 */ + if (h_scnt > 0 && h_snet != h_net) rc |= 4; /* 정산 금액 불일치 */ + if (h_xcnt > 0 && h_xamt != h_net) rc |= 8; /* 지급 금액 불일치 */ + setl(b, T_NET, h_net); + setl(b, T_AMT1, h_snet); + setl(b, T_AMT2, h_xamt); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_UNACQ.h b/app/src/vl/svc/VL_UNACQ.h new file mode 100644 index 0000000..fd7de08 --- /dev/null +++ b/app/src/vl/svc/VL_UNACQ.h @@ -0,0 +1,12 @@ +/* + * VL_UNACQ.h - vl 서비스 VL_UNACQ 카피북 (copybook / record header). + * 사용 UBF 필드: T_COUNT, T_ID1, T_RC + */ +#ifndef VL_UNACQ_H +#define VL_UNACQ_H + +#include "vl_svc.h" + +void VL_UNACQ(TPSVCINFO *p); + +#endif /* VL_UNACQ_H */ diff --git a/app/src/vl/svc/VL_UNACQ.pgc b/app/src/vl/svc/VL_UNACQ.pgc new file mode 100644 index 0000000..ba2abdf --- /dev/null +++ b/app/src/vl/svc/VL_UNACQ.pgc @@ -0,0 +1,25 @@ +/* + * VL_UNACQ.pgc - vl 모듈 서비스 VL_UNACQ (one service = one file). + * 기능: 미매입 승인 검출 (매입 원장에 없는 승인 레코드 수, RC 1=고아 승인 존재). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_UNACQ.h" + +void VL_UNACQ(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_cnt, h_first; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*), coalesce(min(a.approval_id),0) INTO :h_cnt, :h_first + FROM approval a + WHERE NOT EXISTS (SELECT 1 FROM purchase p + WHERE p.purchase_id = a.purchase_id); + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt > 0) rc = 1; /* 매입 없는 승인 존재 */ + setl(b, T_COUNT, h_cnt); + setl(b, T_ID1, h_first); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/svc/VL_UNACQAGE.h b/app/src/vl/svc/VL_UNACQAGE.h new file mode 100644 index 0000000..83bb8d0 --- /dev/null +++ b/app/src/vl/svc/VL_UNACQAGE.h @@ -0,0 +1,12 @@ +/* + * VL_UNACQAGE.h - vl 서비스 VL_UNACQAGE 카피북 (copybook / record header). + * 사용 UBF 필드: T_ARG1, T_COUNT, T_ID1, T_ARG2, T_RC + */ +#ifndef VL_UNACQAGE_H +#define VL_UNACQAGE_H + +#include "vl_svc.h" + +void VL_UNACQAGE(TPSVCINFO *p); + +#endif /* VL_UNACQAGE_H */ diff --git a/app/src/vl/svc/VL_UNACQAGE.pgc b/app/src/vl/svc/VL_UNACQAGE.pgc new file mode 100644 index 0000000..dc4acd9 --- /dev/null +++ b/app/src/vl/svc/VL_UNACQAGE.pgc @@ -0,0 +1,29 @@ +/* + * VL_UNACQAGE.pgc - vl 모듈 서비스 VL_UNACQAGE (one service = one file). + * 기능: 장기 미확정 매입 검출 (접수 A/보류 H 상태로 N일 초과 경과, RC 1=존재). + * 경과일 기준은 T_ARG1(기본 3일). + * vl_svr가 tpsvrinit에서 이 서비스를 tpadvertise 한다. + */ +#include "VL_UNACQAGE.h" + +void VL_UNACQAGE(TPSVCINFO *p) +{ + UBFH *b = (UBFH *)p->data; + long rc = 0; + EXEC SQL BEGIN DECLARE SECTION; + long h_days, h_cnt, h_first; + EXEC SQL END DECLARE SECTION; + h_days = getl(b, T_ARG1); + if (h_days <= 0) h_days = 3; + EXEC SQL SELECT count(*), coalesce(min(purchase_id),0) INTO :h_cnt, :h_first + FROM purchase + WHERE status IN ('A','H') + AND biz_date < current_date - CAST(:h_days AS int); + if (sqlca.sqlcode < 0) FAIL(b); + if (h_cnt > 0) rc = 1; /* 장기 미확정 매입 존재 */ + setl(b, T_COUNT, h_cnt); + setl(b, T_ID1, h_first); + setl(b, T_ARG2, h_days); + setl(b, T_RC, rc); + OK(b); +} diff --git a/app/src/vl/vl_svr.pgc b/app/src/vl/vl_svr.pgc index 5c9ae66..d36fc38 100644 --- a/app/src/vl/vl_svr.pgc +++ b/app/src/vl/vl_svr.pgc @@ -2,7 +2,7 @@ * vl_svr.pgc - vl (정합성검증/validation) MODULE SERVER (thin dispatcher). * * The legacy "one module server, many services" pattern: this binary owns NO - * business logic. Each of vl's ~30 online 검증 services now lives in its own file + * business logic. Each of vl's ~75 online 검증 services now lives in its own file * (app/src/vl/svc/.pgc) with its own copybook header * (app/src/vl/svc/.h); each SELECTs the target rows, applies a 검증 * branch, and returns an 에러코드 in T_RC. Objects are archived into libvlsvc.a @@ -47,6 +47,51 @@ #include "VL_CROSSCHK.h" #include "VL_STATUS.h" #include "VL_CONFIRM.h" +#include "VL_APPRAMT.h" +#include "VL_APPRAGG.h" +#include "VL_TRISTAGE.h" +#include "VL_TRIAGG.h" +#include "VL_LEDGBAL.h" +#include "VL_LEDGENTRY.h" +#include "VL_FEERECALC.h" +#include "VL_FEEADJNET.h" +#include "VL_DUPPAY.h" +#include "VL_DUPSETTLE.h" +#include "VL_DUPPAYDAY.h" +#include "VL_UNACQ.h" +#include "VL_UNACQAGE.h" +#include "VL_AGEDOPEN.h" +#include "VL_AGEDCONF.h" +#include "VL_NEGBAL.h" +#include "VL_NEGNET.h" +#include "VL_MDRRATE.h" +#include "VL_RATEDEV.h" +#include "VL_DAYLIMIT.h" +#include "VL_LIMITNEAR.h" +#include "VL_DATEREV.h" +#include "VL_LEDGDATE.h" +#include "VL_RIMERCH.h" +#include "VL_RIPURCH.h" +#include "VL_RISETTLE.h" +#include "VL_RILEDGER.h" +#include "VL_SUMRECON.h" +#include "VL_SUMFEE.h" +#include "VL_RULEREG.h" +#include "VL_RULEMOD.h" +#include "VL_RULEHIST.h" +#include "VL_RULEOFF.h" +#include "VL_RULESTAT.h" +#include "VL_SUSPACK.h" +#include "VL_SUSPACT.h" +#include "VL_SUSPFINAL.h" +#include "VL_SUSPREOPEN.h" +#include "VL_SUSPSTAT.h" +#include "VL_THRSET.h" +#include "VL_THRBUMP.h" +#include "VL_THRAUDIT.h" +#include "VL_CANCELMIS.h" +#include "VL_INSTCHK.h" +#include "VL_FXCHK.h" /* ----- advertise table: {service name, function} (fn is extern from its file) */ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { @@ -80,6 +125,51 @@ static struct { const char *name; void (*fn)(TPSVCINFO *); } SVCS[] = { {"VL_CROSSCHK", VL_CROSSCHK}, {"VL_STATUS", VL_STATUS}, {"VL_CONFIRM", VL_CONFIRM}, + {"VL_APPRAMT", VL_APPRAMT}, + {"VL_APPRAGG", VL_APPRAGG}, + {"VL_TRISTAGE", VL_TRISTAGE}, + {"VL_TRIAGG", VL_TRIAGG}, + {"VL_LEDGBAL", VL_LEDGBAL}, + {"VL_LEDGENTRY", VL_LEDGENTRY}, + {"VL_FEERECALC", VL_FEERECALC}, + {"VL_FEEADJNET", VL_FEEADJNET}, + {"VL_DUPPAY", VL_DUPPAY}, + {"VL_DUPSETTLE", VL_DUPSETTLE}, + {"VL_DUPPAYDAY", VL_DUPPAYDAY}, + {"VL_UNACQ", VL_UNACQ}, + {"VL_UNACQAGE", VL_UNACQAGE}, + {"VL_AGEDOPEN", VL_AGEDOPEN}, + {"VL_AGEDCONF", VL_AGEDCONF}, + {"VL_NEGBAL", VL_NEGBAL}, + {"VL_NEGNET", VL_NEGNET}, + {"VL_MDRRATE", VL_MDRRATE}, + {"VL_RATEDEV", VL_RATEDEV}, + {"VL_DAYLIMIT", VL_DAYLIMIT}, + {"VL_LIMITNEAR", VL_LIMITNEAR}, + {"VL_DATEREV", VL_DATEREV}, + {"VL_LEDGDATE", VL_LEDGDATE}, + {"VL_RIMERCH", VL_RIMERCH}, + {"VL_RIPURCH", VL_RIPURCH}, + {"VL_RISETTLE", VL_RISETTLE}, + {"VL_RILEDGER", VL_RILEDGER}, + {"VL_SUMRECON", VL_SUMRECON}, + {"VL_SUMFEE", VL_SUMFEE}, + {"VL_RULEREG", VL_RULEREG}, + {"VL_RULEMOD", VL_RULEMOD}, + {"VL_RULEHIST", VL_RULEHIST}, + {"VL_RULEOFF", VL_RULEOFF}, + {"VL_RULESTAT", VL_RULESTAT}, + {"VL_SUSPACK", VL_SUSPACK}, + {"VL_SUSPACT", VL_SUSPACT}, + {"VL_SUSPFINAL", VL_SUSPFINAL}, + {"VL_SUSPREOPEN", VL_SUSPREOPEN}, + {"VL_SUSPSTAT", VL_SUSPSTAT}, + {"VL_THRSET", VL_THRSET}, + {"VL_THRBUMP", VL_THRBUMP}, + {"VL_THRAUDIT", VL_THRAUDIT}, + {"VL_CANCELMIS", VL_CANCELMIS}, + {"VL_INSTCHK", VL_INSTCHK}, + {"VL_FXCHK", VL_FXCHK}, {NULL, NULL} }; diff --git a/app/ui/index.html b/app/ui/index.html index 2c2b8fc..6de5a3f 100644 --- a/app/ui/index.html +++ b/app/ui/index.html @@ -142,6 +142,22 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_ALERTCHK", + "desc": "매입 알림 점검: 가맹점의 당일 고액거래(임계금액 이상)와 보류 적체 건수를", + "in": [ + "T_AMT1", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_ARG2" + ] + }, { "mod": "ac", "modnm": "매입", @@ -157,6 +173,21 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_APPRGAP", + "desc": "승인-매입 갭 점검: 승인 없는 매입(무승인 매입)과 매입 없는 승인(고아", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_AMT1", + "T_ARG2", + "T_RC" + ] + }, { "mod": "ac", "modnm": "매입", @@ -179,6 +210,22 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_BULKVERIFY", + "desc": "대량매입 검증: 가맹점·영업일 단위로 전 건의 수수료(MDR 재계산 대비)와", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG1", + "T_ARG2" + ] + }, { "mod": "ac", "modnm": "매입", @@ -194,6 +241,138 @@ const SVC_SCREENS = [ "T_CANCEL_ID" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CB_DEFEND", + "desc": "지불거절 방어: 접수된 CB 건에 방어 증빙 사유를 덧붙이고, 보류 중이던", + "in": [ + "T_CANCEL_ID", + "T_REASON" + ], + "out": [ + "T_RC", + "T_PURCHASE_ID" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CB_OPEN", + "desc": "지불거절(chargeback) 접수: 대사완료/정산완료 매입만 접수 가능, ac_cancel 에", + "in": [ + "T_PURCHASE_ID", + "T_AMOUNT", + "T_BIZDATE", + "T_REASON" + ], + "out": [ + "T_RC", + "T_CANCEL_ID" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CB_QRY", + "desc": "지불거절 목록조회: 가맹점·영업일 기준 CB 접수 건을 커서로 훑어 건수/합계를", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_AMOUNT" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CB_STAT", + "desc": "지불거절 일별 통계: 영업일 기준 CB 건수·합계·평균·최대 금액과 방어 완료", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_AMT1", + "T_AMT2", + "T_ARG1" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CCYSUM", + "desc": "통화별 매입 집계: 통화코드를 지정하면 해당 통화의 원통화/원화 합계를,", + "in": [ + "T_CCY", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_FX_AMT", + "T_AMOUNT", + "T_ARG1" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CHANSTAT", + "desc": "매입 채널별 통계: 영업일 채널 그룹 집계를 커서로 순회하며 채널 종수와", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_ARG1" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CLAIMPROC", + "desc": "클레임 처리(종결): 등록된 클레임 행을 삭제하고 처리 결과를 원장에", + "in": [ + "T_ID1", + "T_STR1", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_PURCHASE_ID" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CLAIMQRY", + "desc": "클레임 목록조회: 영업일의 미종결 클레임을 커서로 순회, 총건수와 함께", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CLAIMREG", + "desc": "클레임 등록: 가맹점 이의신청을 미매입 원장(ac_unmatch)에 CLAIM 사유로", + "in": [ + "T_PURCHASE_ID", + "T_REASON", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID1" + ] + }, { "mod": "ac", "modnm": "매입", @@ -209,6 +388,35 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CORRHIST", + "desc": "정정 이력조회: 매입 건에 붙은 수수료 조정 이력(최근 10건)을 역순 커서로", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_COUNT", + "T_ARG1" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CTRYSUM", + "desc": "국가별 매입 집계: 통화코드를 국가권역(미주/일본/유럽/기타)으로 매핑하는", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_ARG4", + "T_GROSS" + ] + }, { "mod": "ac", "modnm": "매입", @@ -220,6 +428,25 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_DAYSTAT", + "desc": "매입 일별 상태 통계: 영업일 전 건을 상태별(접수/대사/정산/취소/보류/무효)", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_ARG4", + "T_ARG5", + "T_ARG6", + "T_COUNT", + "T_GROSS" + ] + }, { "mod": "ac", "modnm": "매입", @@ -249,6 +476,21 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_DUPSCAN", + "desc": "중복매입 스캔: 영업일 내 (가맹점, 금액) 조합이 2건 이상인 그룹을 부질의로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_AMT1", + "T_RC" + ] + }, { "mod": "ac", "modnm": "매입", @@ -278,6 +520,20 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_EDIRESEND", + "desc": "EDI 재전송: 문서번호의 최신 EDI 원장을 찾아 재전송 사본(-R 접미 문서번호)", + "in": [ + "T_DOCNO" + ], + "out": [ + "T_RC", + "T_ID1", + "T_AMOUNT" + ] + }, { "mod": "ac", "modnm": "매입", @@ -293,6 +549,54 @@ const SVC_SCREENS = [ "T_ADJ" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_FEEHIST", + "desc": "수수료 조정 이력 집계: 매입 건의 조정 건수, 순증감 합계, 증액분/감액분을", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_COUNT", + "T_DELTA", + "T_AMT1", + "T_AMT2", + "T_RC" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_FEERECALC", + "desc": "수수료 재산정: 가맹점의 현행 MDR 로 수수료를 다시 계산해 기존 수수료와의", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_ADJ", + "T_FEE", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_FEEWAIVE", + "desc": "수수료 감면: 요청 감면율(bps)만큼 수수료를 깎아 가맹점 정산금에 돌려준다.", + "in": [ + "T_PURCHASE_ID", + "T_ARG1", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_AMT1", + "T_ADJ" + ] + }, { "mod": "ac", "modnm": "매입", @@ -309,6 +613,41 @@ const SVC_SCREENS = [ "T_FEE" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_FXCONVERT", + "desc": "통화 환산 등록: 해외매입의 원통화 금액에 적용환율(bps)을 곱해 원화를", + "in": [ + "T_PURCHASE_ID", + "T_FX_AMT", + "T_ARG1", + "T_CCY", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID1", + "T_AMOUNT" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_FXRATE", + "desc": "통화 적용환율 조회: 해외매입 원장(ac_fx)에서 특정 통화의 최근 영업일", + "in": [ + "T_CCY", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_COUNT" + ] + }, { "mod": "ac", "modnm": "매입", @@ -321,6 +660,20 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_HOLDQRY", + "desc": "보류 매입 조회: 가맹점의 보류(H) 건을 최근 갱신순 커서로 훑어 건수·총액을", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, { "mod": "ac", "modnm": "매입", @@ -336,6 +689,22 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_ISSSTAT", + "desc": "매입 발급사별 통계: 영업일 발급사 그룹 집계에서 1위 발급사(건수·총액)와", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_GROSS", + "T_ARG1", + "T_ARG2" + ] + }, { "mod": "ac", "modnm": "매입", @@ -361,6 +730,23 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_LIMITUSAGE", + "desc": "가맹점 한도소진 조회: 영업일 유효 매입(취소/무효 제외) 합계를 일한도와", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_AMT1", + "T_AMT2", + "T_AMT3", + "T_ARG1" + ] + }, { "mod": "ac", "modnm": "매입", @@ -375,6 +761,70 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_MONSTAT", + "desc": "매입 월간 통계: 기준일이 속한 달 전체의 건수/총액/평균 거래액과 영업일수", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_AMT1", + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_NETCALC", + "desc": "정산금 재계산: 매입금액에서 수수료와 원천세 누계를 차감한 실정산금을", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_NET", + "T_TAX", + "T_DELTA" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_NOINTSHARE", + "desc": "무이자 할부 분담금 산정: 할부 매입의 개월수에 월당 15bps 를 곱한 발급사", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_AMT1", + "T_INSTALL_N", + "T_ADJ" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_PARTCXL", + "desc": "부분취소: 취소액이 잔여 매입금액 미만인 경우에만 허용. 수수료를 비례", + "in": [ + "T_PURCHASE_ID", + "T_AMOUNT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_CANCEL_ID", + "T_AMT1", + "T_FEE" + ] + }, { "mod": "ac", "modnm": "매입", @@ -389,6 +839,55 @@ const SVC_SCREENS = [ "T_CANCEL_ID" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_PARTRFND", + "desc": "부분환불: 기존 환불 누계 + 요청액이 매입금액을 넘지 않도록 검증하고", + "in": [ + "T_PURCHASE_ID", + "T_AMOUNT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_CANCEL_ID", + "T_AMT2" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_PROMO", + "desc": "프로모션 매입 등록: 행사 코드가 유효하면 수수료 0 의 프로모션 매입을", + "in": [ + "T_AMOUNT", + "T_MERCHANT", + "T_STR1", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_PURCHASE_ID", + "T_NET" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_REBUY", + "desc": "재매입: 취소(C)된 원매입의 가맹점/금액/채널을 승계해 신규 매입을 생성하고", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID1", + "T_FEE", + "T_NET" + ] + }, { "mod": "ac", "modnm": "매입", @@ -429,6 +928,20 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_REMATCH", + "desc": "승인미매칭 재처리: 미매입 원장에 남은 건에 유효 승인이 도착했는지 다시", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_ARG2" + ] + }, { "mod": "ac", "modnm": "매입", @@ -439,6 +952,80 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_RESERVE", + "desc": "매입 예약: 활성(ACTIVE) 가맹점에 대해 미래 영업일 예약 매입(status P)을", + "in": [ + "T_AMOUNT", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_PURCHASE_ID", + "T_FEE" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_RESERVECXL", + "desc": "매입 예약취소: 아직 접수 전환되지 않은 예약(P) 건만 물리 삭제한다.", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_AMOUNT" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_RESERVEQRY", + "desc": "매입 예약 조회: 가맹점의 대기 중 예약(P)을 예약일 순 커서로 훑어 건수와", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_RETURN", + "desc": "매입 반송: 접수(A) 상태의 매입을 전액 반송 처리한다. 상태를 취소(C)로", + "in": [ + "T_PURCHASE_ID", + "T_REASON", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_CANCEL_ID", + "T_AMOUNT" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_SETTLEDUE", + "desc": "정산예정 조회: 대사완료(M) 상태이면서 아직 정산행이 만들어지지 않은", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_NET", + "T_RC" + ] + }, { "mod": "ac", "modnm": "매입", @@ -454,6 +1041,39 @@ const SVC_SCREENS = [ "T_SETTLE_ID" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_SPLITPAY", + "desc": "매입 분할납부 등록: 매입금액을 요청 회차수로 균등 분할(잔액은 마지막", + "in": [ + "T_PURCHASE_ID", + "T_INSTALL_N", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_AMT1", + "T_AMT2" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_SPLITQRY", + "desc": "분할납부 회차 조회: 매입 건의 회차 스케줄을 커서로 순회하며 회차수·합계와", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_AMT1", + "T_AMT2", + "T_RC" + ] + }, { "mod": "ac", "modnm": "매입", @@ -464,6 +1084,20 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_TAXCXL", + "desc": "세금계산서 취소: 원본 계산서를 상쇄하는 음수(공급가/부가세 부호 반전)", + "in": [ + "T_ID1" + ], + "out": [ + "T_RC", + "T_ID2", + "T_TAX" + ] + }, { "mod": "ac", "modnm": "매입", @@ -479,6 +1113,36 @@ const SVC_SCREENS = [ "T_TAX" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_TAXREISSUE", + "desc": "세금계산서 재발행: 원본 계산서의 공급가/부가세를 승계한 신규 계산서를", + "in": [ + "T_ID1", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID2", + "T_TAX" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_TOPMERCH", + "desc": "상위 가맹점 조회: 영업일 매출 상위 N개 가맹점을 커서로 뽑아 가맹점 ID와", + "in": [ + "T_ARG1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, { "mod": "ac", "modnm": "매입", @@ -491,6 +1155,21 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_VELOCITY", + "desc": "매입 유입속도 점검: 가맹점의 최근 10분간 접수 건수를 재어 허용치 초과", + "in": [ + "T_ARG2", + "T_MERCHANT" + ], + "out": [ + "T_ARG1", + "T_ARG3", + "T_RC" + ] + }, { "mod": "ac", "modnm": "매입", @@ -505,6 +1184,21 @@ const SVC_SCREENS = [ "T_CANCEL_ID" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_WEEKSTAT", + "desc": "매입 주간 통계: 기준일 포함 직전 7일 구간의 건수/총액/수수료 합계와", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_FEE", + "T_AMT1" + ] + }, { "mod": "ac", "modnm": "매입", @@ -530,6 +1224,22 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "APPROVAL_RATE", + "desc": "가맹점 승인율 조회: 영업일 접수 대비 승인 성공(A/M/S) 비율을 bps 로", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_COUNT", + "T_ARG2", + "T_RC" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -546,6 +1256,20 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "AUTH_AMT_ADJUST", + "desc": "승인금액 조정(incremental/decremental auth): 호텔·주유소처럼 최종 금액이", + "in": [ + "T_ID1", + "T_DELTA" + ], + "out": [ + "T_AMOUNT", + "T_RC" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -595,6 +1319,53 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "AUTH_DELAY_MON", + "desc": "승인 지연 모니터: 승인(A) 상태로 기준 시간(분)을 넘겨 매입확정이 안 된", + "in": [ + "T_BIZDATE", + "T_ARG1" + ], + "out": [ + "T_COUNT", + "T_ARG2", + "T_ARG3", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "AUTH_DUP_CHK", + "desc": "중복승인 검출: 단말 재시도 등으로 동일 카드+가맹점+금액 조합이 직전 5분내", + "in": [ + "T_KEY1", + "T_MERCHANT", + "T_AMOUNT" + ], + "out": [ + "T_ARG1", + "T_ID1", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "AUTH_EXPIRE_CHK", + "desc": "미매입 승인 만기 점검: 기준일수를 넘겨도 매입확정되지 않은 승인(A) 건의", + "in": [ + "T_ARG1" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC", + "T_ID1" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -635,6 +1406,23 @@ const SVC_SCREENS = [ "T_AMOUNT" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "AUTH_PARTIAL", + "desc": "부분승인: 잔여한도가 요청금액에 못 미치면 잔여한도만큼만 승인(RC=2)하고,", + "in": [ + "T_KEY1", + "T_MERCHANT", + "T_BIZDATE", + "T_AMOUNT" + ], + "out": [ + "T_ID1", + "T_AMT1", + "T_RC" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -647,6 +1435,19 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "AUTH_RESEND", + "desc": "승인 재전송: 망 단절 등으로 응답을 못 받은 단말이 원승인의 승인코드를", + "in": [ + "T_ID1" + ], + "out": [ + "T_AMOUNT", + "T_RC" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -724,6 +1525,33 @@ const SVC_SCREENS = [ "T_CANCEL_ID" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "BIN_ROUTE_INQ", + "desc": "BIN별 라우팅 조회: 카드번호 선두 6자리(BIN)의 첫 자리로 매입 게이트웨이를", + "in": [ + "T_KEY1" + ], + "out": [ + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "BLACKLIST_ADD", + "desc": "블랙리스트 등재: 카드를 블랙리스트에 올리고(기등재 건은 사유만 갱신),", + "in": [ + "T_KEY1", + "T_REASON" + ], + "out": [ + "T_COUNT", + "T_RC" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -736,6 +1564,136 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "BLACKLIST_DEL", + "desc": "블랙리스트 해제: 등재 행을 삭제하고, 옵션(T_ARG1=1)에 따라 함께 정지됐던", + "in": [ + "T_KEY1", + "T_ARG1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "BLACKLIST_INQ", + "desc": "블랙리스트 조회: 특정 카드의 등재 여부/사유/등재일시(문자열 포맷)를", + "in": [ + "T_KEY1" + ], + "out": [ + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "CANCEL_REPROC", + "desc": "승인취소 재처리: au_cancel 에 접수됐지만 원승인 원장에 반영되지 못한 건을", + "in": [ + "T_CANCEL_ID" + ], + "out": [ + "T_ID1", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "CANCEL_STATUS", + "desc": "취소 처리현황 조회: 영업일 기준 취소 접수를 유형(FULL/PARTIAL/VOID)별로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_GROSS" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "CARD_DAILY_RPT", + "desc": "카드 일일 승인 리포트: 카드+영업일 기준 상태별(승인/거절/취소류/매입) 건수,", + "in": [ + "T_KEY1", + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_ARG4", + "T_GROSS", + "T_AMT1", + "T_ARG5" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "CARD_REISSUE", + "desc": "카드 재발급 연계: 구 카드의 한도원장을 신 카드번호로 이관 생성하고", + "in": [ + "T_KEY1", + "T_KEY2" + ], + "out": [ + "T_RC", + "T_AMT1" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "CARD_RESUME", + "desc": "카드 정지 해제: 블랙리스트에 남아 있으면 해제를 거부하고,", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "CARD_STATE_INQ", + "desc": "카드 상태 종합조회: 한도원장(상태/기본·임시한도/사용액)에", + "in": [ + "T_KEY1" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_AMT3", + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "CARD_SUSPEND", + "desc": "카드 정지: ACTIVE 상태의 카드만 SUSPEND 로 전환하고, 참고용으로", + "in": [ + "T_KEY1", + "T_REASON" + ], + "out": [ + "T_COUNT", + "T_RC" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -762,6 +1720,33 @@ const SVC_SCREENS = [ "T_ARG1" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "DECLINE_ANALYZE", + "desc": "승인 거절사유 분석: 영업일 거절(D) 건을 사유별로 그룹핑해 상위 3개 사유를", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "FRAUD_CASE_INQ", + "desc": "이상거래 케이스 조회: 카드의 부정거래 적발 이력을 집계(건수/최고/평균점수)", + "in": [ + "T_KEY1" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -779,6 +1764,54 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "FRAUD_RULE_AMT", + "desc": "이상거래 고액 룰: 거래금액이 카드의 30일 평균 사용액의 3배를 넘고", + "in": [ + "T_KEY1", + "T_BIZDATE", + "T_AMOUNT" + ], + "out": [ + "T_AMT1", + "T_RC", + "T_ARG1", + "T_ID1" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "FRAUD_RULE_DUP", + "desc": "이상거래 동일금액 반복 룰: 당일 카드 승인 가운데 같은 금액이 3회 이상", + "in": [ + "T_KEY1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_AMT1", + "T_RC", + "T_ID1" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "FRAUD_RULE_FREQ", + "desc": "이상거래 다빈도 룰: 직전 10분간 승인 시도(거절 포함)가 5건 이상이면", + "in": [ + "T_KEY1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_RC", + "T_ID1" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -821,6 +1854,21 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "LIMIT_HIST", + "desc": "한도 사용 이력조회: 최근 N일 일자별 승인합계를 커서로 훑어", + "in": [ + "T_KEY1", + "T_ARG1" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_AMT1" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -835,6 +1883,48 @@ const SVC_SCREENS = [ "T_AMT3" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "LIMIT_RAISE_REQ", + "desc": "한도 영구상향 심사: 최근 30일 승인실적 합계와 거절 건수를 심사해", + "in": [ + "T_KEY1", + "T_AMT1" + ], + "out": [ + "T_GROSS", + "T_RC", + "T_AMT2" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "LIMIT_REMAIN", + "desc": "잔여한도 조회: (기본한도 + 임시한도 - 사용액) 을 계산해 내려준다.", + "in": [ + "T_KEY1" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "LIMIT_RESTORE", + "desc": "임시한도 자동복원: LIMIT_TEMP 로 상향됐던 temp_limit 를 걷어내고", + "in": [ + "T_KEY1" + ], + "out": [ + "T_AMT1", + "T_RC" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -867,6 +1957,83 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "LIMIT_USAGE", + "desc": "한도 사용률 조회: 사용액/기본한도를 bps(만분율)로 환산하고", + "in": [ + "T_KEY1" + ], + "out": [ + "T_ARG1", + "T_AMT1", + "T_AMT2" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "MERCH_AUTH_RANK", + "desc": "가맹점 승인 순위: 영업일의 가맹점별 승인금액을 rank() 윈도우로 서열화한", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "NIGHT_POLICY", + "desc": "심야 승인정책 점검: 최근 7일간 심야시간대(23시~06시) 승인 이력을 집계해", + "in": [ + "T_KEY1", + "T_AMOUNT" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "NOAUTH_CAPTURE", + "desc": "무승인 매입 검출: 매입원장(purchase)에는 있는데 같은 가맹점/영업일/금액의", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "OVERSEAS_POLICY", + "desc": "해외 승인정책: 제재국(간이 목록) 거래는 즉시 차단(RC=9)하고,", + "in": [ + "T_KEY1", + "T_MERCHANT", + "T_STR1", + "T_BIZDATE", + "T_AMOUNT" + ], + "out": [ + "T_RC", + "T_ID1" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -882,6 +2049,19 @@ const SVC_SCREENS = [ "T_ID2" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "PREAUTH_CANCEL", + "desc": "선승인 취소: 미확정(P) 선승인을 X 로 전환해 홀드 금액을 해제한다.", + "in": [ + "T_ID2" + ], + "out": [ + "T_RC", + "T_AMT1" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -895,6 +2075,49 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "PREAUTH_EXTEND", + "desc": "선승인 연장: 아직 확정되지 않은(P) 선승인의 기준일(biz_date)을 지정 일수만큼", + "in": [ + "T_ID2", + "T_ARG1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "PREAUTH_INQ", + "desc": "선승인 조회: 카드의 선승인 현황을 상태(P/C/X)별 FILTER 집계로 나눠 주고", + "in": [ + "T_KEY1" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_AMT1", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "RISK_SCORE_INQ", + "desc": "카드 리스크 점수 산출: 부정거래 적발 이력(평균점수/건수), 30일 거절률,", + "in": [ + "T_KEY1" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -911,6 +2134,93 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "STANDIN_INQ", + "desc": "대행승인 현황 조회: 영업일의 STANDIN 승인 건을 미정산(A)/정산완료(S)로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_AMT1", + "T_AMT2", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "STANDIN_SETTLE", + "desc": "대행승인(stand-in) 정산: 이슈어 복구 후, 대행으로 승인(A)해 둔 STANDIN", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "STAT_BYTYPE", + "desc": "승인유형(채널)별 통계: 영업일 승인 접수를 auth_type", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_ARG4", + "T_ARG5", + "T_ARG6", + "T_COUNT" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "STAT_HOURLY", + "desc": "시간대별 승인 통계: 영업일의 승인 접수를 시(HH24) 단위로 그룹핑한 커서를", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_COUNT", + "T_ARG2" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "STAT_ISSUER", + "desc": "발급사(카드망)별 승인 통계: 카드번호 선두 자리로 발급사망을 판별", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_COUNT" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "TOKEN_VERIFY", + "desc": "토큰 검증: 결제 토큰(PAN)을 4단계로 검증해 비트마스크 RC 로 응답한다.", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -926,6 +2236,49 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "VELOCITY_HOUR", + "desc": "시간 단위 velocity 점검: 직전 60분 슬라이딩 윈도우의 카드 승인", + "in": [ + "T_KEY1" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "VELOCITY_MERCH", + "desc": "가맹점 velocity 점검: 직전 10분간 가맹점 승인 요청 폭주를 감시한다.", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "VELOCITY_WEEK", + "desc": "주간 velocity 점검: 최근 7일 카드 승인 건수/금액에 더해 방문 가맹점의", + "in": [ + "T_KEY1" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_ARG1", + "T_RC" + ] + }, { "mod": "cl", "modnm": "마감", @@ -953,6 +2306,37 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_AMTXCHECK", + "desc": "마감 금액 크로스체크: 마감이력 total_amount vs 매입 amount 합계 vs 스냅샷", + "in": [ + "T_ID1" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_AMT3", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_APPRHIST", + "desc": "마감 승인 이력: 기간 프리픽스(YYYY-MM 등)의 마감을 승인(A/L)/완료(C)/취소(X)", + "in": [ + "T_STR1" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_COUNT" + ] + }, { "mod": "cl", "modnm": "마감", @@ -963,6 +2347,32 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_APPRREJECT", + "desc": "마감 승인 반려: 승인(A) 상태 마감을 완료(C)로 되돌리며 반려 사유를 기록.", + "in": [ + "T_ID1", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_APPRREQ", + "desc": "마감 승인 요청: C 상태이며 집계건수>0 인 마감에 승인요청(APPR_REQ) 표시를", + "in": [ + "T_ID1" + ], + "out": [ + "T_ARG1", + "T_RC" + ] + }, { "mod": "cl", "modnm": "마감", @@ -975,6 +2385,19 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_AUDITTRAIL", + "desc": "마감 이력 감사: 마감건의 생성-수정 시각 간격(초)을 산출하고, 잠금(L)", + "in": [ + "T_ID1" + ], + "out": [ + "T_ARG1", + "T_RC" + ] + }, { "mod": "cl", "modnm": "마감", @@ -989,6 +2412,18 @@ const SVC_SCREENS = [ "T_DELTA" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_CALENDAR", + "desc": "마감 캘린더: 월(YYYY-MM)의 1~31일 각 일자 마감 상태를 한 글자씩 표현한", + "in": [ + "T_STR1" + ], + "out": [ + "T_COUNT" + ] + }, { "mod": "cl", "modnm": "마감", @@ -999,6 +2434,67 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_CHNCLOSE", + "desc": "채널별 마감: 채널(POS/EDC/EDI/FOREIGN) 단위로 당일 매입을 집계하고", + "in": [ + "T_BIZDATE", + "T_CHANNEL" + ], + "out": [ + "T_ID1", + "T_COUNT", + "T_GROSS" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_CHNVERIFY", + "desc": "채널별 마감 검증: 영업일+채널의 매입건수와 해당 매입에 연결된 ledger", + "in": [ + "T_BIZDATE", + "T_CHANNEL" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_CLOSERATE", + "desc": "마감율 산출: 월 프리픽스(YYYY-MM)에 대해 매입이 발생한 영업일수 대비", + "in": [ + "T_STR1" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_CNTXCHECK", + "desc": "마감 건수 크로스체크: 마감이력 txn_count vs 실매입 count vs 스냅샷", + "in": [ + "T_ID1" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_RC" + ] + }, { "mod": "cl", "modnm": "마감", @@ -1009,6 +2505,31 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_CORPCLOSE", + "desc": "법인별 마감: 당일 매입을 발급사(issuer) 법인 단위로 그룹 커서 순회하며", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ID1" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_CXLREASON", + "desc": "마감 취소 사유별 집계: 기간 프리픽스의 취소(X) 마감을 memo(사유)별로 그룹", + "in": [ + "T_STR1" + ], + "out": [ + "T_COUNT" + ] + }, { "mod": "cl", "modnm": "마감", @@ -1021,6 +2542,30 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_DELAYLIST", + "desc": "지연 마감 목록: 생성 후 24시간이 지나도록 개시(O) 상태로 남은 마감을", + "in": [], + "out": [ + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_DELAYREG", + "desc": "마감 지연 사유 등록: 개시(O) 상태의 마감건에만 지연 사유를 memo 로", + "in": [ + "T_ID1", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, { "mod": "cl", "modnm": "마감", @@ -1034,6 +2579,132 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_DIVCLOSE", + "desc": "부문별 마감: 업종(category) 부문 단위로 당일 매입 건수/정산액(net)을 집계하고", + "in": [ + "T_BIZDATE", + "T_CATEGORY" + ], + "out": [ + "T_ID1", + "T_COUNT", + "T_NET" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_DUPCHK", + "desc": "중복 마감 탐지: 취소(X) 제외 동일 (close_type, period_key) 에 마감이 2건", + "in": [], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_ESCALATE", + "desc": "승인 지연 에스컬레이션: 기준일수(T_ARG1, 기본 3일) 초과 승인 대기(C)", + "in": [ + "T_ARG1" + ], + "out": [ + "T_COUNT", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_FEEVERIFY", + "desc": "수수료 검증: 마감 스냅샷의 fee 합계와 해당 영업일 매입 fee 합계에", + "in": [ + "T_ID1" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_AMT3", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_FINALIZE", + "desc": "본마감: 해당 영업일의 가마감(PROV, status O)을 찾아 매입을 재집계한 뒤", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ID1", + "T_COUNT", + "T_DELTA" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_FORCECLOSE", + "desc": "강제 마감: 체크리스트를 우회해 해당 영업일의 미확정 개시(O) 마감을 제거한", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ID1", + "T_ARG1", + "T_COUNT" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_GAPDAYS", + "desc": "마감 누락일 탐지: 월 프리픽스 내 매입은 존재하나 일마감이 없는 일자를", + "in": [ + "T_STR1" + ], + "out": [ + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_HALFCLOSE", + "desc": "반기 결산 마감: 연도(T_STR1=YYYY)와 반기(T_ARG1=1|2)를 받아 상/하반기", + "in": [ + "T_STR1", + "T_ARG1" + ], + "out": [ + "T_ID1", + "T_COUNT", + "T_GROSS" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_HOLIDAY", + "desc": "휴일 마감 정책: 영업일의 요일을 판정해 주말이면 마감 SKIP(RC=1) 정책을,", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, { "mod": "cl", "modnm": "마감", @@ -1059,6 +2730,62 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_LOCKAUDIT", + "desc": "잠금 감사: 잠금(locked=true) 기간 중 승인(A) 또는 잠금(L) 마감이 하나도", + "in": [], + "out": [ + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_LOCKSWEEP", + "desc": "기간 잠금 일괄: 프리픽스 구간의 기존 잠금 레코드를 locked=true 로 재잠금하고", + "in": [ + "T_STR1" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_COUNT" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_MEMOSEARCH", + "desc": "마감 사유 검색: memo 에 키워드(T_STR1)가 포함된 마감을 검색해 총 건수와", + "in": [ + "T_STR1" + ], + "out": [ + "T_COUNT", + "T_RC", + "T_ID1" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_MERCLOSE", + "desc": "가맹점별 마감: 단일 가맹점의 당일 매입(gross/fee/net/건수)을 집계해 해당", + "in": [ + "T_BIZDATE", + "T_MERCHANT" + ], + "out": [ + "T_ID1", + "T_ID2", + "T_GROSS", + "T_NET", + "T_COUNT" + ] + }, { "mod": "cl", "modnm": "마감", @@ -1085,6 +2812,33 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_NETVERIFY", + "desc": "정산액 검증: 마감 스냅샷에서 gross - fee <> net 인 내부 모순 행을 찾아", + "in": [ + "T_ID1" + ], + "out": [ + "T_COUNT", + "T_RC", + "T_DELTA" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_PENDLIST", + "desc": "승인 대기 목록: 승인요청(memo APPR_REQ) 상태로 대기 중인 완료(C) 마감의", + "in": [], + "out": [ + "T_COUNT", + "T_RC", + "T_ID1", + "T_ARG1" + ] + }, { "mod": "cl", "modnm": "마감", @@ -1099,6 +2853,46 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_POSTTRIG", + "desc": "마감 후속작업 트리거: 마감(C/A) 확인 후 해당 영업일의 정산건을 ledger 에", + "in": [ + "T_ID1" + ], + "out": [ + "T_COUNT" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_PRECHECK", + "desc": "마감 전 체크리스트 검증: (1)미마감 M 매입 (2)미매칭 ac_unmatch (3)기간 기잠금", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_PROVCLOSE", + "desc": "가마감: 당일 매입 집계 후 status O + memo 'PROV' 가마감을 생성한다.", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID1", + "T_COUNT", + "T_GROSS" + ] + }, { "mod": "cl", "modnm": "마감", @@ -1152,6 +2946,19 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_REOPENREQ", + "desc": "마감 재개시: 완료(C) 상태이고 기간이 잠금되지 않은 마감을 개시(O)로", + "in": [ + "T_ID1", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, { "mod": "cl", "modnm": "마감", @@ -1187,6 +2994,69 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_RPTCAT", + "desc": "업종별 마감 리포트: 영업일 매입을 업종(category) 그룹으로 집계하며 업종별", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG4" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_RPTCHN", + "desc": "채널별 마감 리포트: 영업일의 매입을 채널 그룹으로 집계해 occurrence", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_RPTISSUER", + "desc": "발급사별 마감 리포트: 영업일 매입을 발급사 그룹으로 집계하되 임계금액", + "in": [ + "T_BIZDATE", + "T_AMT1" + ], + "out": [ + "T_COUNT" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_RPTTOPMER", + "desc": "상위 가맹점 마감 리포트: 마감 스냅샷에서 net 상위 N(T_COUNT 입력, 기본 5)", + "in": [ + "T_ID1", + "T_COUNT" + ], + "out": [ + "T_COUNT" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_SCHEDULE", + "desc": "마감 일정 관리: 최종 일마감(biz_date 최대) 기준으로 차기 마감 예정일과", + "in": [], + "out": [ + "T_RC", + "T_ARG1" + ] + }, { "mod": "cl", "modnm": "마감", @@ -1199,6 +3069,49 @@ const SVC_SCREENS = [ "T_ID2" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_SEQAUDIT", + "desc": "마감 순번 감사: cl_seq_reg 의 유형별 last_seq 와 실제 cl_close_log 의", + "in": [], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_SLAMON", + "desc": "마감 SLA 모니터: 기간 프리픽스의 일마감 중 기준시각(T_STR2=HHMI, 기본 1000)", + "in": [ + "T_STR1", + "T_STR2" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_SNAPDIFF", + "desc": "스냅샷 비교: 두 마감(T_ID1, T_ID2)의 스냅샷 gross/net 합계와 가맹점 수를", + "in": [ + "T_ID1", + "T_ID2" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_ARG1", + "T_RC" + ] + }, { "mod": "cl", "modnm": "마감", @@ -1213,6 +3126,22 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_SNAPXCHECK", + "desc": "스냅샷-원장 크로스체크: 마감 스냅샷 net 합계 vs 같은 영업일 ledger SETTLE", + "in": [ + "T_ID1" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_DELTA", + "T_ARG1", + "T_RC" + ] + }, { "mod": "cl", "modnm": "마감", @@ -1250,6 +3179,38 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_TREND", + "desc": "마감 금액 추이: 최근 N회(T_ARG1, 기본 7) 일마감의 총액 평균/최소/최대를", + "in": [ + "T_ARG1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_AMT1", + "T_AMT2", + "T_AMT3" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_TRIGSTAT", + "desc": "후속작업 상태: 영업일 정산 건수 대비 ledger XFER 전기 건수를 비교해", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_DELTA", + "T_RC" + ] + }, { "mod": "cl", "modnm": "마감", @@ -1299,6 +3260,21 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_YEARPREP", + "desc": "연말 결산 사전점검: 연도 내 완료된 월마감(MONTHLY, C/A/L)의 distinct", + "in": [ + "T_STR1" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_RC" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1312,6 +3288,19 @@ const SVC_SCREENS = [ "T_STR3" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_AMT_HANGUL", + "desc": "금액 한글변환(순수 계산): 정수 금액을 4자리(만 단위) 그룹으로 쪼개", + "in": [ + "T_AMOUNT" + ], + "out": [ + "T_RC", + "T_STR4" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1340,6 +3329,114 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_AUDIT_BYLVL", + "desc": "감사로그 레벨별 조회(조회 세분): 레벨(I/W/E)을 검증한 뒤 기준일 해당 레벨", + "in": [ + "T_STR1", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_MSG" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_AUDIT_BYSVC", + "desc": "감사로그 서비스별 조회(조회 세분): 서비스명 기준으로 기준일 로그 건수와", + "in": [ + "T_SVCNAME", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_ID2" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_AUDIT_RANGE", + "desc": "감사로그 ID 구간 조회(조회 세분): log_id 구간(id1~id2, 최대 폭 10000)의", + "in": [ + "T_ID1", + "T_ID2" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG1", + "T_ARG2" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_BATHIST_CNT", + "desc": "배치 실행 이력 집계: 기준일 해당 잡(svc_name)의 로그를 레벨별로 집계해", + "in": [ + "T_KEY1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_ID1" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_BATRERUN_REQ", + "desc": "배치 재실행 요청: 기준일에 실행 이력이 실제로 있는 잡만 재실행 요청을 받아", + "in": [ + "T_KEY1", + "T_BIZDATE", + "T_REASON" + ], + "out": [ + "T_RC", + "T_ID1", + "T_COUNT" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_BATSCHD_GET", + "desc": "배치 스케줄 조회: SCHD: 파라미터를 읽어 주기와 실행시각을 분리해", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC", + "T_STR4", + "T_ARG1" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_BATSCHD_REG", + "desc": "배치 스케줄 등록: 잡 이름과 주기(DAILY/WEEKLY/MONTHLY) + 실행시각(HHMM)을", + "in": [ + "T_KEY1", + "T_STR1", + "T_ARG1", + "T_BIZDATE" + ], + "out": [ + "T_RC" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1350,6 +3447,22 @@ const SVC_SCREENS = [ "T_BIZDATE" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_BIZDAY_CNT", + "desc": "영업일수 계산: from~to 사이를 하루씩 진행하며 주말은 순수 요일 공식", + "in": [ + "T_STR1", + "T_STR2" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG1", + "T_ARG2" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1401,6 +3514,83 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_CODEGRP_CLOSE", + "desc": "코드그룹 코드 폐기: 사용중(use_yn='Y')인 코드만 use_yn='N' 으로 논리 삭제하고,", + "in": [ + "T_KEY1", + "T_KEY2", + "T_REASON", + "T_BIZDATE" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_CODEGRP_CNT", + "desc": "코드그룹 현황: 그룹의 전체 코드 수와 사용중(use_yn='Y') 코드 수를 한 번의", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG1", + "T_ARG2" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_CODEGRP_HIST", + "desc": "코드그룹 변경 이력 조회: CODEGRP 이력 로그에서 그룹 접두 패턴으로 건수와", + "in": [ + "T_KEY1" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_MSG" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_CODEGRP_REG", + "desc": "코드그룹 코드 등록: 코드 중복을 검사한 뒤 cm_code 에 신규 코드를 등록하고,", + "in": [ + "T_KEY1", + "T_KEY2", + "T_STR1", + "T_ARG1", + "T_BIZDATE" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_CODEGRP_UPD", + "desc": "코드그룹 코드 변경: 변경 전 코드명을 먼저 조회해 이력 메시지에 담고,", + "in": [ + "T_KEY1", + "T_KEY2", + "T_STR1", + "T_ARG1", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_STR4" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1427,6 +3617,36 @@ const SVC_SCREENS = [ "T_ARG1" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_DATEDIFF", + "desc": "기간 계산 유틸(순수 계산): 두 날짜를 역법 공식으로 절대 일련일수로 바꿔", + "in": [ + "T_STR1", + "T_STR2" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_ARG2" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_ENC_HEX", + "desc": "간이 암복호화(암호화 유틸 세분, 순수 계산): 키 문자열을 순환시키며 XOR 한", + "in": [ + "T_STR1", + "T_KEY1", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_STR4" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1440,6 +3660,21 @@ const SVC_SCREENS = [ "T_MSG" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_FILE_CKSUM", + "desc": "파일 체크섬(순수 계산): 파일 내용 문자열에 대해 Adler-32 체크섬(mod 65521", + "in": [ + "T_STR1" + ], + "out": [ + "T_STR4", + "T_ARG1", + "T_ARG2", + "T_COUNT" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1477,6 +3712,21 @@ const SVC_SCREENS = [ "T_ARG1" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_FX_AGE", + "desc": "환율 최신성 조회: 통화의 마지막 고시일과 오늘 사이 경과일수를 DB 날짜", + "in": [ + "T_CCY", + "T_ARG2" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_STR4" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1492,6 +3742,114 @@ const SVC_SCREENS = [ "T_ARG1" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_FX_CROSS", + "desc": "크로스 환율 계산: 두 통화의 같은 기준일 원화 환율을 조회해 ccy1 1단위당", + "in": [ + "T_CCY", + "T_STR1", + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_RC", + "T_AMT1", + "T_AMT2" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_FX_HIST", + "desc": "환율 이력 조회: 통화의 기간(from~to) 내 고시 건수, 최소/최대 환율, 마지막", + "in": [ + "T_CCY", + "T_STR1", + "T_STR2" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_AMT1", + "T_AMT2", + "T_STR4" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_FX_PUBLISH", + "desc": "환율 고시: 기준일/통화의 환율(rate_bps>0)을 고시한다. 이미 고시된 날이면", + "in": [ + "T_CCY", + "T_BIZDATE", + "T_ARG1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_FX_VALID", + "desc": "환율 유효성 검증: 입력 환율을 해당 통화의 직전 고시가와 비교해 변동폭(bps)", + "in": [ + "T_CCY", + "T_BIZDATE", + "T_ARG1" + ], + "out": [ + "T_ARG2", + "T_ARG3", + "T_RC" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_HEALTH_DB", + "desc": "시스템 헬스(DB 세분): DB 왕복(SELECT)으로 연결을 확인하고 DB 서버 일자와", + "in": [], + "out": [ + "T_STATUS", + "T_STR4", + "T_STR5", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_HEALTH_LOG", + "desc": "시스템 헬스(로그 세분): 금일 로그 총량과 오류(E) 비율(bps)을 계산해 등급을", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_STATUS", + "T_COUNT", + "T_ARG1", + "T_ARG2" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_HEALTH_SEQ", + "desc": "시스템 헬스(채번 세분): 카운터 개수/최대값과 음수로 굴러간 비정상 카운터", + "in": [], + "out": [ + "T_STATUS", + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_RC" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1504,6 +3862,49 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_HOLIDAY_CNT", + "desc": "기간 휴일 조회(캘린더 조회 세분): 국가별로 기간 내 휴일 수와 첫/마지막", + "in": [ + "T_STR1", + "T_STR2", + "T_STR3" + ], + "out": [ + "T_COUNT", + "T_STR4", + "T_STR5" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_HOLIDAY_DEL", + "desc": "휴일 삭제(캘린더 관리): 삭제 전에 휴일명을 읽어 이력에 남기고 cm_holiday", + "in": [ + "T_STR1" + ], + "out": [ + "T_RC", + "T_STR4" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_HOLIDAY_REG", + "desc": "휴일 등록(캘린더 관리): 날짜 형식을 검증하고 중복 등록을 거른 뒤 cm_holiday", + "in": [ + "T_STR1", + "T_STR2", + "T_STR3" + ], + "out": [ + "T_RC" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1556,6 +3957,48 @@ const SVC_SCREENS = [ "T_STR4" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_MASK_CARD", + "desc": "카드번호 마스킹(마스킹 세분, 순수 계산): 전체가 숫자(13~19자리)인지 먼저", + "in": [ + "T_STR1" + ], + "out": [ + "T_RC", + "T_STR4", + "T_ARG1" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_MASK_NAME", + "desc": "성명 마스킹(마스킹 세분, 순수 계산): UTF-8 문자 단위로 첫 글자만 남기고", + "in": [ + "T_STR1" + ], + "out": [ + "T_RC", + "T_STR4", + "T_ARG1" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_MASK_PHONE", + "desc": "전화번호 마스킹(마스킹 세분, 순수 계산): 하이픈 등 구분자는 위치를 그대로", + "in": [ + "T_STR1" + ], + "out": [ + "T_RC", + "T_STR4", + "T_ARG1" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1569,6 +4012,49 @@ const SVC_SCREENS = [ "T_ARG1" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_MSG_FALLBACK", + "desc": "다국어 메시지 폴백 조회: 요청 언어 그룹에서 먼저 찾고, 없으면 기본 언어", + "in": [ + "T_STR1", + "T_KEY1" + ], + "out": [ + "T_MSG", + "T_RC" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_MSG_GET", + "desc": "다국어 메시지 조회: 언어코드(KO/EN/JA...)로 메시지 그룹 MSG_ 을 정해", + "in": [ + "T_STR1", + "T_KEY1" + ], + "out": [ + "T_RC", + "T_MSG", + "T_STR4" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_MSG_SET", + "desc": "다국어 메시지 등록/수정(upsert): MSG_ 그룹에 UPDATE 를 먼저 시도하고", + "in": [ + "T_STR1", + "T_KEY1", + "T_MSG" + ], + "out": [ + "T_RC" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1595,6 +4081,37 @@ const SVC_SCREENS = [ "T_STR3" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_NOTI_HIST", + "desc": "알림 발송 이력: 기준일 NOTI 로그에서 채널 접두 패턴([SMS>...] 등)으로 발송", + "in": [ + "T_CHANNEL", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_MSG" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_NOTI_SEND", + "desc": "알림 발송: 채널(SMS/EMAIL/PUSH 검증) + 수신자 + 본문을 발송 큐 성격의", + "in": [ + "T_CHANNEL", + "T_STR1", + "T_MSG", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID1" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1607,6 +4124,64 @@ const SVC_SCREENS = [ "T_STR3" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_PARAM_APPROVE", + "desc": "파라미터 변경 승인(승인 워크플로 2단계): 대기 키(REQ:)의 요청 값을 읽어", + "in": [ + "T_STR1", + "T_STR3", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_STR4" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_PARAM_PENDING", + "desc": "파라미터 승인 대기 현황: 대기 키(REQ:%) 건수, 가장 오래된 요청 키, 최장", + "in": [], + "out": [ + "T_COUNT", + "T_STR4", + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_PARAM_REJECT", + "desc": "파라미터 변경 반려(승인 워크플로 종결): 대기 키(REQ:)를 삭제만 하고 본", + "in": [ + "T_STR1", + "T_REASON", + "T_BIZDATE" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_PARAM_REQ", + "desc": "파라미터 변경 요청(승인 워크플로 1단계): 본 키는 건드리지 않고 대기 키", + "in": [ + "T_STR1", + "T_STR2", + "T_STR3", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_STR4" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1629,6 +4204,19 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_PREVBIZ", + "desc": "직전 영업일 조회: 기준일에서 하루씩 거슬러 내려가며 토/일(순수 요일 공식)과", + "in": [ + "T_STR1" + ], + "out": [ + "T_STR3", + "T_ARG1" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1655,6 +4243,63 @@ const SVC_SCREENS = [ "T_ARG1" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_SEQ_DAILY", + "desc": "일자별 채번(채번 세분): 이름+영업일을 합친 키(:)로 매일 1부터", + "in": [ + "T_KEY1", + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_STR4" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_SEQ_PEEK", + "desc": "채번 현재값 조회(채번 세분): 카운터를 증가시키지 않고 cm_seq 의 현재값과", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_STR4" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_SEQ_RESET", + "desc": "채번 초기화(채번 세분): 운영 파라미터 SEQ_RESET_YN 이 'Y' 일 때만 카운터를", + "in": [ + "T_KEY1", + "T_ARG1", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ARG2" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_SEQ_TYPED", + "desc": "유형별 채번(채번 세분): 허용된 채번 유형(SLIP/TICKET/BATCH/NOTI)만 유형별", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_STR4" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1681,6 +4326,71 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_ACCRUEREV", + "desc": "계상 환입 분개: 기준일(T_BIZDATE) 이전의 미결 '계상' 전표를 커서로 찾아", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ID2" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_ACCTHIST", + "desc": "계정 잔액 이력 조회: [T_BIZDATE ~ T_STR1] 구간의 계정잔액(lg_account_bal)", + "in": [ + "T_KEY1", + "T_BIZDATE", + "T_STR1" + ], + "out": [ + "T_COUNT", + "T_AMT2", + "T_AMT3", + "T_AMT4" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_ACCTLEDGER", + "desc": "계정과목별 원장조회: 지정 계정/영업일의 라인을 커서로 훑어 차변/대변", + "in": [ + "T_KEY1", + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_AMT2", + "T_AMT3", + "T_DELTA", + "T_ID1" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_ACCTRANGE", + "desc": "계정과목 기간별 원장집계: [T_BIZDATE ~ T_STR1] 구간의 차변/대변 합계와", + "in": [ + "T_KEY1", + "T_BIZDATE", + "T_STR1" + ], + "out": [ + "T_AMT2", + "T_AMT3", + "T_COUNT", + "T_DELTA" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1738,6 +4448,63 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_APPROVE", + "desc": "전표 승인 워크플로: 작성(D) 전표만 대상으로 차변합=대변합 균형을 검증한 뒤", + "in": [ + "T_ID1" + ], + "out": [ + "T_DELTA", + "T_AMT2" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_APPRQUEUE", + "desc": "전표 승인 대기 큐 조회: 미결(D) 전표의 대기 건수와 전표 ID 범위(최소/최대),", + "in": [], + "out": [ + "T_COUNT", + "T_ID1", + "T_ID2", + "T_ARG1", + "T_ARG2" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_BADDEBT", + "desc": "대손 처리 분개: 가맹점(T_MERCHANT) 채권 T_AMOUNT 를 상각 —", + "in": [ + "T_AMOUNT", + "T_MERCHANT", + "T_BIZDATE", + "T_REASON" + ], + "out": [ + "T_ID1" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_BADDEBTRCV", + "desc": "대손 회수(환입) 분개: 상각했던 채권이 회수되면 D:CASH / C:BADDEBT_RCV 로", + "in": [ + "T_AMOUNT", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_AMT2", + "T_ID1" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1752,6 +4519,54 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_BALPROOF", + "desc": "잔액 증명: 계정잔액 테이블(lg_account_bal)의 기록 잔액과 원장 라인에서", + "in": [ + "T_KEY1", + "T_BIZDATE" + ], + "out": [ + "T_AMT2", + "T_AMT3", + "T_DELTA" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_BIGLINE", + "desc": "고액 분개 조회: 임계금액(T_AMOUNT, 기본 1000만) 이상 라인의 건수·합계와", + "in": [ + "T_AMOUNT", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_ID1", + "T_ID2", + "T_AMT2" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_BLACKENTRY", + "desc": "흑색 정정 분개: 원전표(T_ID1)를 반전 상쇄한 뒤 정정 금액(T_AMOUNT)으로", + "in": [ + "T_ID1", + "T_AMOUNT", + "T_BIZDATE", + "T_MERCHANT" + ], + "out": [ + "T_ID2", + "T_ARG1" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1764,6 +4579,22 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_CLOSECHK", + "desc": "결산 체크리스트: 당일 (1)미결 전표 (2)불균형 전표 (3)0원 라인", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_ARG4", + "T_RC" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1825,6 +4656,19 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_DAYCLOSE", + "desc": "일마감: 당일 균형(차변합=대변합) 미결 전표를 일괄 P 승격한 뒤, 당일 사용된", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_COUNT" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1839,6 +4683,20 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_DEPTPL", + "desc": "부문별 손익 조회: 당일 매입을 업종/부문(category)별로 묶어 수수료 손익을", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_AMT2" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1871,6 +4729,34 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_DUPSCAN", + "desc": "중복 분개 스캔: 당일 동일 (전표, 계정, 차대, 금액) 조합이 2건 이상인 중복", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ID1", + "T_ARG2" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_FEEACCRUE", + "desc": "수수료 미수 계상: 대사완료(M)했지만 아직 미정산인 매입의 수수료를", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_FEE", + "T_ID1" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1886,6 +4772,36 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_FEERECOG", + "desc": "수수료 수익 인식: 당일 정산완료(S) 매입 수수료 합계를", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_FEE", + "T_ID1", + "T_COUNT" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_FEESPLIT", + "desc": "수수료 부문 배부: 당일 FEE_INC 대변 합계를 배부율(T_ARG1 bps)로 부문 A/B 에", + "in": [ + "T_ARG1", + "T_BIZDATE" + ], + "out": [ + "T_GROSS", + "T_ID1", + "T_AMT2", + "T_AMT3" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1908,6 +4824,49 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_LINECOUNT", + "desc": "원장 라인 집계: 당일 라인의 posted/closed 상태별 건수, 관련 전표 수,", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_COUNT", + "T_ARG3" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_MASKINQ", + "desc": "원장 마스킹 조회: 라인 ID(T_ID1)의 원장 라인을 가맹점 ID 마스킹(앞 2자리", + "in": [ + "T_ID1" + ], + "out": [ + "T_ID2", + "T_AMOUNT" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_MEMOAUDIT", + "desc": "전표 메모 감사: 당일 전기(P) 전표 중 메모가 비어 있는 건수와 표본 전표 ID,", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ID1", + "T_ARG2" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1919,6 +4878,36 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_MERCHPROOF", + "desc": "가맹점 잔액 증명: 가맹점 잔액 테이블(lg_merch_bal)의 차변/대변 누계와", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_AMT2", + "T_AMT3", + "T_AMT4", + "T_ARG1" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_MONTHCLOSE", + "desc": "월차 결산 분개: [T_BIZDATE=월초 ~ T_STR1=월말] 구간 FEE_INC 순액(대변-차변)을", + "in": [ + "T_BIZDATE", + "T_STR1" + ], + "out": [ + "T_AMT2", + "T_ID1" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1933,6 +4922,104 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_NETPOS", + "desc": "순포지션 조회: 당일 채권(RECV) 잔액과 가맹점미지급(MERCH_PAY) 잔액을", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_AMT2", + "T_AMT3", + "T_NET" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_PAYACCRUE", + "desc": "미지급 계상 분개: 당일 정산 순액 합계와 원장 MERCH_PAY 대변 합계의 갭을", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_NET", + "T_AMT3", + "T_DELTA", + "T_ID1" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_PAYRECON", + "desc": "원장-지급 대사: 가맹점(T_MERCHANT)의 원장 라인 순액(대변-차변)과 정산", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_AMT2", + "T_NET", + "T_DELTA", + "T_COUNT" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_PENDAGE", + "desc": "미결전표 경과일 집계: 작성(D) 전표를 경과일 구간(0-1일 / 2-7일 / 8일+)별로", + "in": [], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_ARG4", + "T_COUNT" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_PENDLIST", + "desc": "미결전표 관리(목록 집계): 작성(D) 상태 전표의 건수, 연계 라인 총차변액,", + "in": [], + "out": [ + "T_COUNT", + "T_ID1", + "T_AMT2" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_PERIODTB", + "desc": "기간별 시산표: [T_BIZDATE ~ T_STR1] 구간을 계정별 GROUP BY 커서로 집계해", + "in": [ + "T_BIZDATE", + "T_STR1" + ], + "out": [ + "T_COUNT", + "T_DELTA" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_PLCLOSE", + "desc": "손익 대체 분개(일 단위): 당일 손익계정(FEE_INC, BADDEBT_EXP)의 순액을 각각", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_NET" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1950,6 +5037,20 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_QTRCLOSE", + "desc": "분기 결산 분개: 연도(T_ARG1)/분기(T_ARG2)로 분기 구간을 산출해 그 구간의", + "in": [ + "T_ARG1", + "T_ARG2" + ], + "out": [ + "T_ID1", + "T_AMT2" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1961,6 +5062,57 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_RECVACCRUE", + "desc": "미수 계상 분개: 접수(A) 상태 매입 총액을 D:RECV_ACCR / C:SUSP 로 미결 계상", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_AMT2" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_REDENTRY", + "desc": "적색 정정 분개: 원전표(T_ID1)의 모든 라인을 차/대변 반전(INSERT..SELECT CASE)", + "in": [ + "T_ID1", + "T_BIZDATE" + ], + "out": [ + "T_ID2", + "T_COUNT" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_REJECT", + "desc": "전표 반려 워크플로: 작성(D) 전표를 X(취소) 처리하고 반려 사유(T_REASON)를", + "in": [ + "T_ID1", + "T_REASON" + ], + "out": [] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_REOPEN", + "desc": "취소 전표 재개: X(취소) 전표를 D(작성)로 되돌리고 closed 라인을 posted 로", + "in": [ + "T_ID1" + ], + "out": [ + "T_COUNT" + ] + }, { "mod": "lg", "modnm": "원장", @@ -2002,6 +5154,67 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_SETTLERECON", + "desc": "원장-정산 대사: 당일 정산(settlement) 순액 합계와 원장 MERCH_PAY 대변", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_NET", + "T_AMT3", + "T_DELTA", + "T_COUNT" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_STATACCT", + "desc": "원장 통계 세분(계정): 당일 사용된 계정과목 수와 잔액 절대값이 가장 큰", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_AMT2" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_STATDC", + "desc": "원장 통계 세분(차대변): 당일 차변/대변 각각의 건수·합계·최대금액을 구하고", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_AMT2", + "T_AMT3", + "T_ARG3", + "T_ARG4", + "T_AMT4" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_STATMERCH", + "desc": "원장 통계 세분(가맹점): 특정 가맹점의 원장 라인 건수, 최초/최종 영업일,", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_COUNT", + "T_AMT2", + "T_AMT3", + "T_DELTA" + ] + }, { "mod": "lg", "modnm": "원장", @@ -2015,6 +5228,20 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_STATTYPE", + "desc": "원장 통계 세분(전표유형): 당일 전표를 유형(voucher_type)별로 집계해", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_COUNT", + "T_ARG2" + ] + }, { "mod": "lg", "modnm": "원장", @@ -2026,6 +5253,34 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_SUSPENSE", + "desc": "가지급(SUSP) 정리 분개: 당일 SUSP 계정 순액(차-대)을 0 으로 만드는 반대", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_DELTA", + "T_ID1", + "T_AMT2" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_TBDIFF", + "desc": "시산표 차액 조회: 당일 시산표(lg_trial_bal)에서 불균형(diff<>0) 계정 수,", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_DELTA" + ] + }, { "mod": "lg", "modnm": "원장", @@ -2069,6 +5324,18 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_VOIDVOUCHER", + "desc": "전표 취소: 마감연계(S) 상태가 아닌 전표를 X 처리하고 소속 라인 전부를", + "in": [ + "T_ID1" + ], + "out": [ + "T_COUNT" + ] + }, { "mod": "lg", "modnm": "원장", @@ -2084,6 +5351,35 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_VOUCHTRACE", + "desc": "전표 추적: 전표(T_ID1)의 헤더(유형/상태/연계 ID)와 라인 통계(건수, 차변합,", + "in": [ + "T_ID1" + ], + "out": [ + "T_ID2", + "T_COUNT", + "T_AMT2", + "T_AMT3", + "T_RC" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_ZEROLINE", + "desc": "0원 분개 검출/정리: 당일 금액 0 인 posted 라인을 검출해 closed 처리하고", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2096,6 +5392,73 @@ const SVC_SCREENS = [ "T_STATUS" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_ADVICE", + "desc": "0220 매입 어드바이스 수신: 응답이 필요 없는 통지 전문이라", + "in": [ + "T_CHANNEL", + "T_STR3", + "T_BIZDATE", + "T_AMOUNT" + ], + "out": [ + "T_ID1", + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_AMTCHECK", + "desc": "전문 금액/카드 사전검증: 금액 범위(0 < 금액 <= 한도)와", + "in": [ + "T_AMOUNT", + "T_AMT4", + "T_STR5" + ], + "out": [ + "T_AMT4", + "T_RC" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_AUTHREQ", + "desc": "PAN 앞6/뒤4 만 남기고 '*' 마스킹 (승인요청 로그 저장용). */", + "in": [ + "T_CHANNEL", + "T_STR3", + "T_BIZDATE", + "T_AMOUNT" + ], + "out": [ + "T_STR2", + "T_STR5", + "T_ID1", + "T_ARG1" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_AUTHRSP", + "desc": "0110 승인응답 생성: 승인한도(5천만원) 초과 시 rc=61 거절,", + "in": [ + "T_CHANNEL", + "T_BIZDATE", + "T_ID1", + "T_AMOUNT" + ], + "out": [ + "T_STR2", + "T_ID2", + "T_RC" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2109,6 +5472,41 @@ const SVC_SCREENS = [ "T_STR6" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_CAPRSP", + "desc": "0210 매입응답 처리: STAN 으로 원 0200 전문을 찾아", + "in": [ + "T_CHANNEL", + "T_BIZDATE", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_ID1", + "T_ID2" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_CAPTURE", + "desc": "0200 매입요청 전문 처리: MDR(bps) 수수료/정산액을 산출해", + "in": [ + "T_CHANNEL", + "T_STR3", + "T_BIZDATE", + "T_AMOUNT", + "T_ARG2" + ], + "out": [ + "T_RC", + "T_FEE", + "T_NET", + "T_ID1" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2122,6 +5520,51 @@ const SVC_SCREENS = [ "T_STATUS" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_CHANCLOSE", + "desc": "채널 폐쇄: 채널을 DOWN 으로 내리고 세션키를 폐기하되,", + "in": [ + "T_CHANNEL" + ], + "out": [ + "T_RC", + "T_STATUS", + "T_COUNT" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_CHANHEALTH", + "desc": "채널 종합 건강점검: 상태(UP 여부) + 당일 오류율(bps) +", + "in": [ + "T_CHANNEL", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_STATUS", + "T_ARG3", + "T_ARG7", + "T_ARG8" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_CHANOPEN", + "desc": "채널 개통: DOWN 채널만 UP 으로 올리고 sign-on 시각을 기록.", + "in": [ + "T_CHANNEL" + ], + "out": [ + "T_RC", + "T_STATUS", + "T_STR5" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2134,6 +5577,22 @@ const SVC_SCREENS = [ "T_STATUS" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_CHANSWITCH", + "desc": "채널 절체: 주채널을 DOWN, 예비채널을 UP 으로 한 트랜잭션", + "in": [ + "T_CHANNEL", + "T_STR2" + ], + "out": [ + "T_RC", + "T_STR5", + "T_STR2", + "T_COUNT" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2177,6 +5636,37 @@ const SVC_SCREENS = [ "T_STR3" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_DIRECTADPT", + "desc": "구간 [s, s+n) 이 전부 십진 숫자인지 검사 (직연동 전문 필드 검증). */", + "in": [ + "T_STR3" + ], + "out": [ + "T_RC", + "T_STR1", + "T_AMOUNT", + "T_CHANNEL", + "T_COUNT" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_DUPCHK", + "desc": "중복전문 검출: 동일 STAN 이 당일 원장에 이미 몇 건인지 세어", + "in": [ + "T_BIZDATE", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG2" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2191,6 +5681,22 @@ const SVC_SCREENS = [ "T_STATUS" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_ERRLIST", + "desc": "오류전문 대기목록 요약: 당일 NACK(N) 전문의 건수, 금액합,", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_ID1", + "T_ID2", + "T_RC" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2207,6 +5713,66 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_ERRPURGE", + "desc": "오류전문 폐기: 재처리를 포기한 당일 NACK(N) 전문 중", + "in": [ + "T_BIZDATE", + "T_STR2" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_ERRRETRY", + "desc": "오류전문 단건 재처리: NACK(N) 된 전문을 rc=00/R 로 리셋하고", + "in": [ + "T_ID1" + ], + "out": [ + "T_RC", + "T_ID2" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_FROMISO", + "desc": "ISO -> 내부전문 변환: 고정길이 ISO 전문을 잘라", + "in": [ + "T_STR3" + ], + "out": [ + "T_RC", + "T_STR3", + "T_STR1", + "T_STR4", + "T_AMOUNT", + "T_COUNT" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_GAPSCAN", + "desc": "STAN 순번 갭 검출: 당일 최소~최대 순번 구간 폭에서 실제", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_COUNT", + "T_ARG6", + "T_RC" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2249,6 +5815,35 @@ const SVC_SCREENS = [ "T_ARG2" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_KEYROTATE", + "desc": "키 전환(교체): KEYVER 카운터를 올려 새 세대키 번호를 따고,", + "in": [ + "T_CHANNEL" + ], + "out": [ + "T_KEY1", + "T_ARG3", + "T_ARG2" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_KEYSTATUS", + "desc": "키 상태 조회: 현재 키버전(KEYVER)과 채널의 세션키/사인온", + "in": [ + "T_CHANNEL" + ], + "out": [ + "T_RC", + "T_KEY1", + "T_ARG3", + "T_ARG4" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2275,6 +5870,33 @@ const SVC_SCREENS = [ "T_KEY1" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_MACGEN", + "desc": "XOR-회전 8-hex MAC: 키버전을 시드로 섞어 세대별로 값이 달라진다. */", + "in": [ + "T_STR3" + ], + "out": [ + "T_RC", + "T_KEY1", + "T_ARG3" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_MACVERIFY", + "desc": "MG_MAC 과 동일 계열의 가산 체크섬 (검증측 독립 구현, 4-hex). */", + "in": [ + "T_ID1" + ], + "out": [ + "T_RC", + "T_KEY2" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2288,6 +5910,22 @@ const SVC_SCREENS = [ "T_STATUS" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_NETMGMT", + "desc": "0800 망관리 전문 라우터: 처리코드 001=사인온(UP),", + "in": [ + "T_CHANNEL", + "T_STR4", + "T_BIZDATE" + ], + "out": [ + "T_STR1", + "T_STR2", + "T_ID1" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2303,6 +5941,20 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_PANMASK", + "desc": "BIN(앞6)/뒤4 보존 마스킹. 12자리 미만 짧은 PAN 은 전체 마스킹. */", + "in": [ + "T_STR5", + "T_ID1" + ], + "out": [ + "T_RC", + "T_STR5" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2317,6 +5969,23 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_QDEPTH", + "desc": "송수신 큐 깊이 조회: 대기(Q)/송신(S)/완료(D)/실패(F) 상태별", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_ARG4", + "T_ARG7", + "T_RC" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2334,6 +6003,21 @@ const SVC_SCREENS = [ "T_ARG1" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_REASM", + "desc": "분할전문 재조립: 세그먼트 T_STR5~T_STR8 (최대 4편) 을 순서대로", + "in": [ + "T_ARG1" + ], + "out": [ + "T_RC", + "T_STR3", + "T_COUNT", + "T_ARG2" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2380,6 +6064,40 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_REVADVICE", + "desc": "0420 취소 어드바이스 수신: 통지성 전문이므로 무조건 수용(A),", + "in": [ + "T_CHANNEL", + "T_BIZDATE", + "T_STR3", + "T_AMOUNT", + "T_ARG1" + ], + "out": [ + "T_ID1", + "T_ARG1" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_REVERSAL", + "desc": "0400 취소요청 처리: 원전문을 STAN 으로 찾아 rc=17(고객취소)", + "in": [ + "T_CHANNEL", + "T_BIZDATE", + "T_ARG1", + "T_AMOUNT" + ], + "out": [ + "T_RC", + "T_ID1", + "T_ID2" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2394,6 +6112,69 @@ const SVC_SCREENS = [ "T_STR3" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_RTADD", + "desc": "라우팅 룰 등록: 채널+MTI -> 목적지 룰을 신규 채번해 추가.", + "in": [ + "T_CHANNEL", + "T_STR1", + "T_STR3", + "T_BIZDATE", + "T_ARG4" + ], + "out": [ + "T_RC", + "T_ID1" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_RTDROP", + "desc": "라우팅 룰 비활성: 채널+MTI 의 활성 룰을 전부 내려서", + "in": [ + "T_CHANNEL", + "T_STR1" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_RTLIST", + "desc": "라우팅 룰 열람 요약: 채널의 활성/비활성 룰 수와 최우선(최소)", + "in": [ + "T_CHANNEL" + ], + "out": [ + "T_STR3", + "T_ARG4", + "T_COUNT", + "T_ARG7", + "T_RC" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_RTSWITCH", + "desc": "라우팅 목적지 절체: 채널+MTI 활성 룰의 dest 를 새 목적지로", + "in": [ + "T_CHANNEL", + "T_STR1", + "T_STR3" + ], + "out": [ + "T_RC", + "T_STR5", + "T_COUNT" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2411,6 +6192,22 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_SEQAUDIT", + "desc": "채널 순번 감사: 채널이 기억하는 last_stan 과 원장의 실제", + "in": [ + "T_CHANNEL", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_ARG2", + "T_ARG6" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2421,6 +6218,41 @@ const SVC_SCREENS = [ "T_ARG2" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_SETTLEREQ", + "desc": "0500 정산집계 요청 생성: 당일 채널의 수신 0200 건수/금액을", + "in": [ + "T_CHANNEL", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_ID1", + "T_STR3" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_SETTLERSP", + "desc": "0510 정산집계 응답 대사: 상대기관이 보낸 건수/합계를 당일", + "in": [ + "T_CHANNEL", + "T_BIZDATE", + "T_COUNT", + "T_GROSS" + ], + "out": [ + "T_STR2", + "T_ARG1", + "T_AMT1", + "T_ID1", + "T_RC" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2456,6 +6288,71 @@ const SVC_SCREENS = [ "T_ARG1" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_STATDIR", + "desc": "송수신 방향별 통계: 당일 IN/OUT 각각의 건수와 금액합,", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_AMT1", + "T_ARG2", + "T_AMT2", + "T_ARG8", + "T_RC" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_STATHOUR", + "desc": "시간대별 통계: 요청 시(0~23)의 당일 건수/금액합과 함께", + "in": [ + "T_BIZDATE", + "T_ARG5" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_ARG5", + "T_ARG6" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_STATMTI", + "desc": "전문유형(MTI)별 당일 통계: 지정 MTI 의 건수/금액합/최대금액과", + "in": [ + "T_STR1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_AMT1", + "T_ARG8" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_STATRC", + "desc": "응답코드 분포 조회: 당일 정상(00)/거절 건수, 고유 거절코드", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG7", + "T_STR2", + "T_ARG6" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2484,6 +6381,79 @@ const SVC_SCREENS = [ "T_STATUS" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_THROTTLE", + "desc": "채널 트래픽 제어: 당일 수신량이 채널 한도(기본 10000건)의", + "in": [ + "T_CHANNEL", + "T_BIZDATE", + "T_ARG4" + ], + "out": [ + "T_COUNT", + "T_ARG4", + "T_ARG8", + "T_RC" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_TIMEOUT", + "desc": "전문 타임아웃 스윕: 임계초(TMOUTSEC, 기본 60초)를 넘겨도", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG5" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_TIMEOUTSET", + "desc": "타임아웃 임계초 설정: 운영자가 지정한 초(5~600 범위 강제)를", + "in": [ + "T_ARG5" + ], + "out": [ + "T_ARG5", + "T_RC" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_TOISO", + "desc": "내부전문 -> ISO 변환: 콜론 구분 내부포맷 \"MTI:처리코드:금액\" 을", + "in": [ + "T_STR3" + ], + "out": [ + "T_RC", + "T_STR3", + "T_STR6", + "T_STR1", + "T_AMOUNT" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_TPSCHECK", + "desc": "순간 처리율 점검: 최근 60초 유입 전문 수로 근사 TPS(x100)를", + "in": [], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG8", + "T_ARG6", + "T_RC" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2498,6 +6468,184 @@ const SVC_SCREENS = [ "T_AMOUNT" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_VAN1ADPT", + "desc": "VAN1 채널 어댑터: 파이프 구분 전문 \"V1|MTI|금액|처리코드\" 를", + "in": [ + "T_STR3" + ], + "out": [ + "T_RC", + "T_STR3", + "T_STR1", + "T_AMOUNT", + "T_CHANNEL" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_VAN2ADPT", + "desc": "VAN2 채널 어댑터: 키=값 세미콜론 전문 \"M=0200;A=1000;P=000000\"", + "in": [ + "T_STR3" + ], + "out": [ + "T_RC", + "T_STR3", + "T_STR1", + "T_AMOUNT", + "T_CHANNEL" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_ACCT_VERIFY", + "desc": "기능: 정산계좌 검증 실행 (형식 검사 + ACCT_VRF 검증 이력 적재).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_ACCT_VRF_HIST", + "desc": "기능: 정산계좌 검증 이력 조회 (누적 횟수 + 최근 결과/일시).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_ACCT_VRF_STAT", + "desc": "기능: 정산계좌 검증 전사 통계 (총 검증 수, 검증 대상 가맹점 수, 실패 건수).", + "in": [], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_APPLY_APPROVE", + "desc": "기능: 가입 심사 승인 (REVIEW -> ACTIVE, 기본 수수료율/한도 자동 개설).", + "in": [ + "T_MERCHANT", + "T_ARG1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_APPLY_REG", + "desc": "기능: 가맹점 가입 신청 접수 (심사 워크플로 시작, 상태 APPLIED).", + "in": [ + "T_MERCHANT", + "T_STR1", + "T_STR2", + "T_STR3" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_APPLY_REJECT", + "desc": "기능: 가입 심사 반려 (APPLIED/REVIEW -> REJECTED, 반려 사유 필수).", + "in": [ + "T_MERCHANT", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_APPLY_REVIEW", + "desc": "기능: 가입 신청 심사 개시 (APPLIED -> REVIEW, MCC 유효성 사전점검).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_ARG1" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_APPLY_STATUS", + "desc": "기능: 가입 심사 진행 상태 조회 (단계 코드 + 워크플로 이력 건수).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_COUNT" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_AUDIT_PURGE", + "desc": "기능: 감사 이력 보존기한 경과분 삭제 (삭제 내역 자체도 감사 기록으로 남김).", + "in": [ + "T_ARG1" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_AUDIT_RECENT", + "desc": "기능: 가맹점 최근 변경 감사 조회 (최신 이력 1건: 액션/변경값/메모/일자).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_AUDIT_TRAIL", + "desc": "기능: 가맹점 마스터 변경 감사 추적 (액션 필터별 이력 건수 + 발생 액션 종류 수).", + "in": [ + "T_MERCHANT", + "T_STR1" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2520,6 +6668,110 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_BIZNO_CHECK", + "desc": "기능: 사업자등록번호 검증 (10자리 형식 + 국세청 검증공식 checksum + 마스터 중복 사용 확인).", + "in": [ + "T_STR2" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_BIZNO_UPD", + "desc": "기능: 사업자등록번호 변경 (형식 검사 + 타 가맹점 중복 차단 + 변경 이력).", + "in": [ + "T_MERCHANT", + "T_STR2" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CHK_FEERATE", + "desc": "기능: 수수료율 마스터 정합성 점검 (활성 가맹점 요율 누락 / bps 범위 이탈 / 고아 요율).", + "in": [], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CHK_LIMIT", + "desc": "기능: 한도 마스터 정합성 점검 (활성 가맹점 한도 누락 + 한도 체계 역전 검출).", + "in": [], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CHK_TERM", + "desc": "기능: 단말 마스터 정합성 점검 (소속 가맹점 없는 고아 단말 + 폐업 가맹점의 가동 단말).", + "in": [], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CODEMAP_DEL", + "desc": "기능: 코드 매핑 삭제 (단건 또는 네임스페이스 전체 일괄 삭제).", + "in": [ + "T_KEY1", + "T_STR1" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CODEMAP_GET", + "desc": "기능: 코드 매핑 변환 조회 (네임스페이스의 원본 코드를 대상 코드로 변환).", + "in": [ + "T_KEY1", + "T_STR1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CODEMAP_SET", + "desc": "기능: 코드 매핑 등록/갱신 (외부코드->내부코드 변환표, MAP_<네임스페이스> 코드그룹 upsert).", + "in": [ + "T_KEY1", + "T_STR1", + "T_STR2" + ], + "out": [ + "T_RC" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2565,6 +6817,59 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CONTRACT_END", + "desc": "기능: 가맹 계약 해지 (CONT_END 이력 + 가맹점 상태 HOLD 전환).", + "in": [ + "T_MERCHANT", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CONTRACT_INQ", + "desc": "기능: 가맹 계약 현황 조회 (체결/갱신 횟수, 최종 만기일, 유효 여부).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG1" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CONTRACT_NEW", + "desc": "기능: 가맹 계약 체결 (기간 개월수로 만기일 산출, CONTRACT 이력 기록).", + "in": [ + "T_MERCHANT", + "T_ARG1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CONTRACT_RENEW", + "desc": "기능: 가맹 계약 갱신 (직전 만기일 기준 개월수 연장).", + "in": [ + "T_MERCHANT", + "T_ARG1" + ], + "out": [ + "T_RC" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2627,6 +6932,49 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_GRADE_ADJUST", + "desc": "기능: 가맹점 등급 수동 보정 (산정 결과 예외 조정, 조정 사유 필수 감사 기록).", + "in": [ + "T_MERCHANT", + "T_STR2", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_GRADE_DIST", + "desc": "기능: 가맹점 등급 분포 조회 (최근 등급 통계일 기준 S/A/B/C 세분 현황).", + "in": [], + "out": [ + "T_RC", + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_ARG4", + "T_COUNT" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_GRADE_PREVIEW", + "desc": "기능: 가맹점 등급 산정 시뮬레이션 (누적 매출로 등급 산출, 마스터는 불변).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_GROSS", + "T_ARG1" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2687,6 +7035,20 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_MCC_INQ", + "desc": "기능: MCC 업종 조회 (명칭/위험도 + 해당 업종 활성 가맹점 수).", + "in": [ + "T_CATEGORY" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_COUNT" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2699,6 +7061,35 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_MCC_RISKLIST", + "desc": "기능: 고위험 MCC 현황 (기준 위험도 이상 MCC 수, 최고 위험도, 소속 활성 가맹점 수).", + "in": [ + "T_ARG1" + ], + "out": [ + "T_COUNT", + "T_ARG2", + "T_ARG3", + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_MCC_UPD", + "desc": "기능: MCC 업종 마스터 변경 (명칭 선택 변경 + 위험도 1~5 재설정).", + "in": [ + "T_CATEGORY", + "T_STR1", + "T_ARG1" + ], + "out": [ + "T_RC" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2724,6 +7115,20 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_MERCH_CLOSE", + "desc": "기능: 가맹점 폐업 처리 (DELETED 전환 + 전체 단말 폐기 + 하위 관계 해제).", + "in": [ + "T_MERCHANT", + "T_REASON" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2755,6 +7160,20 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_MERCH_PAUSE", + "desc": "기능: 가맹점 휴업 처리 (ACTIVE -> HOLD, 가동 단말 일괄 SUSPENDED).", + "in": [ + "T_MERCHANT", + "T_REASON" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2768,6 +7187,19 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_MERCH_RESUME", + "desc": "기능: 가맹점 영업 재개 (HOLD -> ACTIVE, 정지 단말 재가동).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2803,6 +7235,126 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_PARTNER_INQ", + "desc": "기능: 제휴사 마스터 단건 조회 (명칭/상태/표시순서).", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC", + "T_ARG1" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_PARTNER_REG", + "desc": "기능: 제휴사 마스터 등록/갱신 (공통코드 PARTNER 그룹 upsert).", + "in": [ + "T_KEY1", + "T_STR1", + "T_ARG1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_PARTNER_STATUS", + "desc": "기능: 제휴사 사용 상태 변경 (ACTIVE/INACTIVE 만 허용).", + "in": [ + "T_KEY1", + "T_STATUS" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_RATE_APPROVE", + "desc": "기능: 수수료율 변경 승인 (대기 요청을 실제 요율 마스터에 반영, RATE_APV 이력).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_AMT1" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_RATE_REJECT", + "desc": "기능: 수수료율 변경 반려 (대기 요청을 반려 처리, 요율 마스터는 불변).", + "in": [ + "T_MERCHANT", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_RATE_REQ", + "desc": "기능: 수수료율 변경 요청 접수 (요율 승인 워크플로 1단계, RATE_REQ 이력 대기건 생성).", + "in": [ + "T_MERCHANT", + "T_STR2", + "T_REASON", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_AMT1" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_REL_LINK", + "desc": "기능: 대표가맹점-하위가맹점 관계 연결 (MERCH_REL 코드그룹, 하위=code, 대표=code_name).", + "in": [ + "T_MERCHANT", + "T_KEY1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_REL_LIST", + "desc": "기능: 대표가맹점의 하위가맹점 현황 (활성 연결 수 + 최초 하위 ID).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_REL_UNLINK", + "desc": "기능: 대표가맹점-하위가맹점 관계 해제 (관계 행 INACTIVE 전환 + 이력).", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2836,6 +7388,59 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_TERM_CNT", + "desc": "기능: 가맹점별 단말기 보유 현황 집계 (전체/가동/폐기 건수).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_TERM_MOVE", + "desc": "기능: 단말기 가맹점간 이동 (소속 변경 + 반입 이력).", + "in": [ + "T_KEY1", + "T_MERCHANT" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_TERM_SCRAP", + "desc": "기능: 단말기 폐기 (SCRAPPED 전환 + 폐기 사유 이력).", + "in": [ + "T_KEY1", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_TERM_SUSPEND", + "desc": "기능: 단말기 사용정지/재개 토글 (ACTIVE<->SUSPENDED).", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2848,6 +7453,19 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_ACCTCHANGE", + "desc": "지급계좌 변경: 미지급(R/H/J) 건만 대상. 새 계좌가 동일 가맹점의", + "in": [ + "T_ID1", + "T_KEY1" + ], + "out": [ + "T_RC" + ] + }, { "mod": "py", "modnm": "지급", @@ -2861,6 +7479,21 @@ const SVC_SCREENS = [ "T_AMT1" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_ACCTLIST", + "desc": "가맹점 지급계좌 현황: 가맹점의 모든 지급계좌를 커서로 훑어 계좌/은행/", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_GROSS", + "T_RC" + ] + }, { "mod": "py", "modnm": "지급", @@ -2893,6 +7526,70 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_ADVCALC", + "desc": "선지급 가능액 산정: 가맹점 미지급(R/A) 잔액에 지급률(bps)을 곱한 한도에서", + "in": [ + "T_MERCHANT", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_GROSS", + "T_AMT1", + "T_AMT2", + "T_ARG1" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_ADVREPAY", + "desc": "선지급 상환: ADVANCE 채널 지급건의 잔여 net 에서 상환액을 차감하고", + "in": [ + "T_ID1", + "T_AMOUNT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_AMOUNT", + "T_NET" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_APPRBULK", + "desc": "소액 일괄 승인: 당일 요청(R)건 중 net 이 승인한도 이하인 건을 한 번의", + "in": [ + "T_BIZDATE", + "T_AMT1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG1", + "T_AMT1" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_APPRLIMIT", + "desc": "고액 승인 통제: 한도 초과 건은 승인자 지정 없이는 거절(2). 승인 시", + "in": [ + "T_ID1", + "T_KEY2", + "T_AMT1" + ], + "out": [ + "T_RC", + "T_NET" + ] + }, { "mod": "py", "modnm": "지급", @@ -2906,6 +7603,36 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_APPRREQ", + "desc": "승인 대기 큐 조회: 당일 요청(R)건을 금액 내림차순 상위 N건 커서로 훑어", + "in": [ + "T_BIZDATE", + "T_ARG1" + ], + "out": [ + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_BANKFILE", + "desc": "은행별 지급파일 생성: 지정 은행코드의 승인(A) 지급건만 py_account 조인으로", + "in": [ + "T_KEY1", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ID2", + "T_GROSS" + ] + }, { "mod": "py", "modnm": "지급", @@ -2937,6 +7664,24 @@ const SVC_SCREENS = [ "T_ADJ" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_CANCELHIST", + "desc": "취소/환입 이력 집계: 가맹점 당일 취소(C)와 역분개/환입(V)을 각각", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_AMT1", + "T_ARG2", + "T_AMT2", + "T_COUNT", + "T_RC" + ] + }, { "mod": "py", "modnm": "지급", @@ -2959,6 +7704,79 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_DORMANT", + "desc": "휴면 가맹점 검출: 최근 N일간 지급 이력이 전혀 없는 정상(ACTIVE) 가맹점", + "in": [ + "T_ARG1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG1" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_DORMSTOP", + "desc": "휴면 가맹점 지급정지: 대상 가맹점의 정상 계좌를 DORMANT 로 내리고", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_COUNT" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_FAILRATE", + "desc": "지급 실패율 산출: 가맹점 당일 전체 지급건 대비 반려(J) 비중을 bps 로", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG1", + "T_ARG2" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_FAILSTAT", + "desc": "실패사유별 통계: 당일 반려(J)건을 사유별 커서로 집계해 상위 8개 사유를", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_FAILTOP", + "desc": "최다 실패 가맹점: 당일 반려(J)건이 가장 많은 가맹점 1곳을 ORDER BY +", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_GROSS" + ] + }, { "mod": "py", "modnm": "지급", @@ -2974,6 +7792,52 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_FEEMIN", + "desc": "최소수수료 보정: 당일 요청(R) 건 중 수수료가 최소수수료 미만인 건을", + "in": [ + "T_BIZDATE", + "T_AMT1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_AMT1" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_FEERATE", + "desc": "수수료율 재적용: 가맹점 계약 요율(merchant.mdr_bps)을 조인으로 가져와", + "in": [ + "T_ID1" + ], + "out": [ + "T_RC", + "T_FEE", + "T_NET", + "T_ARG1" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_FEEWAIVE", + "desc": "수수료 면제: 미지급(R/A) 건의 수수료를 0 으로 하고 net 을 amount 로 복원.", + "in": [ + "T_ID1", + "T_REASON", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_FEE", + "T_NET" + ] + }, { "mod": "py", "modnm": "지급", @@ -2988,6 +7852,52 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_FILEREBUILD", + "desc": "지급파일 재생성: 아직 미확정(G)인 파일 헤더를 실제 편성 상세 기준으로", + "in": [ + "T_ID2" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_GROSS" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_FILESTAT", + "desc": "지급파일 현황: 당일 파일을 생성(G)/확정(C) 상태별로 세고 총 레코드수와", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_FILEVERIFY", + "desc": "지급파일 검증: py_file 헤더의 건수/총액과 실제 편성된 py_payout 상세 집계를", + "in": [ + "T_ID2" + ], + "out": [ + "T_ARG1", + "T_AMT1", + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, { "mod": "py", "modnm": "지급", @@ -3012,6 +7922,48 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_HOLDAGING", + "desc": "장기 보류 점검: 보류(H) 상태로 N일 이상 경과한 건수/금액과 가장 오래된", + "in": [ + "T_ARG1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_GROSS", + "T_ARG1" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_HOLDLIST", + "desc": "보류 사유별 집계: 당일 보류(H)건을 사유별로 커서 집계해 상위 10개 사유를", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_HOLDREASON", + "desc": "사유부 지급보류: 요청(R)/승인(A) 건을 보류(H)로 전이하면서 보류 사유를", + "in": [ + "T_ID1", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, { "mod": "py", "modnm": "지급", @@ -3041,6 +7993,50 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_LEDGERSUM", + "desc": "지급원장 유형별 합계: 당일 py_ledger 를 기표유형별 커서 집계해", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_LIMITCHK", + "desc": "지급한도 점검: 가맹점 일한도(merchant.daily_limit) 대비 당일 확정성", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_AMT3", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_LIMITHOLD", + "desc": "한도 초과 자동 보류: 당일 사용액이 일한도를 넘은 가맹점을 HAVING 커서로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_COUNT" + ] + }, { "mod": "py", "modnm": "지급", @@ -3055,6 +8051,22 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_MERGE", + "desc": "지급 합산: 가맹점 당일 요청(R) STD 건이 2건 이상이면 전부 취소(C) 표시 후", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ID2", + "T_NET" + ] + }, { "mod": "py", "modnm": "지급", @@ -3068,6 +8080,64 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_NETRECALC", + "desc": "순지급액 정합 검증: 당일 건 중 amount - fee != net 인 불일치 건수와", + "in": [ + "T_BIZDATE", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_DELTA", + "T_ARG2" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_NOTIFYEMAIL", + "desc": "지급내역 이메일 통지(시뮬): 가맹점의 당일 지급완료(P) 건수/합계를 집계해", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_GROSS" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_NOTIFYPLAN", + "desc": "지급 예정 통보: 지정 실행일의 예약(S) 건을 py_payout 과 조인 집계해", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_GROSS" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_NOTIFYSMS", + "desc": "지급완료 SMS 통지(시뮬): 지급완료(P)건만 대상. 가맹점/금액을 조회해", + "in": [ + "T_ID1" + ], + "out": [ + "T_RC", + "T_NET" + ] + }, { "mod": "py", "modnm": "지급", @@ -3081,6 +8151,20 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_REFUND", + "desc": "지급 환입: 지급완료(P) 건을 역분개(V)하며 지급계좌에 net 을 복원하고", + "in": [ + "T_ID1", + "T_REASON" + ], + "out": [ + "T_RC", + "T_NET" + ] + }, { "mod": "py", "modnm": "지급", @@ -3118,6 +8202,19 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_REPAYNEW", + "desc": "계좌 변경 재지급: 반려(J)된 지급건을 가맹점의 최신 정상(ACTIVE) 계좌로", + "in": [ + "T_ID1" + ], + "out": [ + "T_RC", + "T_ID2" + ] + }, { "mod": "py", "modnm": "지급", @@ -3166,6 +8263,19 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_SCHEDCANCEL", + "desc": "지급 예약 취소: 아직 미실행(S)인 예약만 DELETE 로 제거한다. 이미", + "in": [ + "T_ID1" + ], + "out": [ + "T_RC", + "T_ID2" + ] + }, { "mod": "py", "modnm": "지급", @@ -3179,6 +8289,47 @@ const SVC_SCREENS = [ "T_ID2" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_SEIZE", + "desc": "압류/가압류 등록: 대상 계좌를 SEIZED 로 전환하고 그 계좌의 미지급(R/A)", + "in": [ + "T_KEY1", + "T_REASON" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_SEIZECHK", + "desc": "압류계좌 지급 차단 점검: 당일 미지급(R/A)건 중 압류(SEIZED) 계좌로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_SEIZEREL", + "desc": "압류 해제: SEIZED 계좌를 ACTIVE 로 되돌리고, 압류 사유로 보류됐던", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, { "mod": "py", "modnm": "지급", @@ -3195,6 +8346,67 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_SPLITRATIO", + "desc": "비율 분할: 요청(R) 건을 bps 비율로 두 건의 SPLIT 지급건으로 쪼개고", + "in": [ + "T_ID1", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_ID2", + "T_ARG2", + "T_AMT1", + "T_AMT2" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_STATBANK", + "desc": "은행별 지급 통계: 당일 지급건을 py_account 조인으로 은행코드별 커서", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_STATCHAN", + "desc": "채널별 지급 통계: 당일 지급건을 채널(STD/SPLIT/BULK/ADVANCE/URGENT)별", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_STATDAILY", + "desc": "일자별 지급 추이: 시작일부터 N일 구간을 일자별 커서 집계해 occurrence 로", + "in": [ + "T_BIZDATE", + "T_ARG1" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_AMT2", + "T_RC" + ] + }, { "mod": "py", "modnm": "지급", @@ -3211,6 +8423,22 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_STATRANGE", + "desc": "금액 구간별 분포: 당일 지급건을 소액(<10만)/중액(10만~100만)/고액(100만+)", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_COUNT", + "T_RC" + ] + }, { "mod": "py", "modnm": "지급", @@ -3235,6 +8463,24 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_URGENT", + "desc": "긴급지급: 긴급 수수료(0.5%) 공제 후 잔액 확인 -> 즉시 지급완료(P) 건을", + "in": [ + "T_MERCHANT", + "T_KEY1", + "T_AMOUNT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID1", + "T_FEE", + "T_NET" + ] + }, { "mod": "py", "modnm": "지급", @@ -3247,6 +8493,35 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_XFERMATCH", + "desc": "이체결과 대사(단건): 지급건 net 과 py_ledger 의 XFER 기표 합계를 비교해", + "in": [ + "T_ID1" + ], + "out": [ + "T_NET", + "T_AMT1", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_XFERMISS", + "desc": "이체 기표 누락 검출: 당일 지급완료(P)인데 py_ledger 에 XFER 기표가 없는", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3261,6 +8536,21 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_4WAY", + "desc": "승인-매입-입금-지급 4-way 대사: 승인(approval), 입금(settlement), 지급", + "in": [ + "T_PURCHASE_ID", + "T_NET", + "T_BIZDATE" + ], + "out": [ + "T_ID1", + "T_RC" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3276,6 +8566,21 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_AGEING", + "desc": "미대사 에이징: 미해결 예외를 당일 / 최근 7일 / 7일 초과 세 버킷으로 나눠", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_COUNT" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3320,6 +8625,34 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_AUDITLOG", + "desc": "대사 감사 로그 기록: 호출 서비스명과 사유를 받아 원장에 RC_AUDIT 증적을", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE", + "T_SVCNAME", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_AUDITTRAIL", + "desc": "대사 감사추적 조회: 특정 매입의 대사 이력 건수, 대사 관련 원장 증적 건수,", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_ARG1", + "T_ARG2" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3332,6 +8665,36 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_BYCATEGORY", + "desc": "업종(카테고리)별 대사 세분: 요청 업종의 당일 MATCHED 건수와 EXCEPTION", + "in": [ + "T_BIZDATE", + "T_CATEGORY" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_BYCHANNEL", + "desc": "채널별 대사 세분 집계: 요청 채널(POS/EDC/EDI/FOREIGN)의 당일 MATCHED 대사", + "in": [ + "T_BIZDATE", + "T_CHANNEL" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_FEE" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3371,6 +8734,64 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_BYMGROUP", + "desc": "가맹점그룹별 대사 세분: 가맹점 ID prefix (그룹 키) 로 묶어 당일 MATCHED", + "in": [ + "T_BIZDATE", + "T_KEY1" + ], + "out": [ + "T_COUNT", + "T_GROSS" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_CANCELREC", + "desc": "취소분 대사: 매입 금액과 취소(ac_cancel) 누적 금액을 비교하여 전액 취소면", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_ID1" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_CARRYOVER", + "desc": "이월 대사: 이전 영업일의 EXCEPTION 대사 건들을 다음 영업일 HOLD 건으로", + "in": [ + "T_STR1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_CLOSEDAY", + "desc": "일마감 대사 확정: 미해결 예외나 보류가 남아 있으면 마감을 거부하고, 모두", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_RC", + "T_COUNT", + "T_GROSS" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3407,6 +8828,36 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_DEPMATCH", + "desc": "입금(정산) 대사: 매입 net 금액과 settlement 입금 합계를 비교하여 일치 시", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_ID1" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_DIFFCAUSE", + "desc": "대사 차이 원인분석: 당일 차이를 (1) 수수료 계산 불일치, (2) 취소 존재,", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_COUNT" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3420,6 +8871,60 @@ const SVC_SCREENS = [ "T_DELTA" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_DIFFTREND", + "desc": "대사 차이 추이 분석: 당일 평균 |차이금액| 과 직전 7영업일 평균을 비교하여", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_RC" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_DUPCHK", + "desc": "중복 대사 검출: 당일 대사 로그에서 같은 매입이 두 번 이상 대사된 매입", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_EDIRECON", + "desc": "EDI 대사: 가맹점의 당일 EDI 전문 금액 합계와 매입(취소 제외) 금액 합계를", + "in": [ + "T_BIZDATE", + "T_MERCHANT" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_DELTA", + "T_COUNT" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_ESCALATE", + "desc": "예외 에스컬레이션: 미해결 예외가 걸린 매입의 당일 WARN 경보들을 IN-서브", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3434,6 +8939,77 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_FEEDIFF", + "desc": "수수료 차이 일괄 집계: 당일 매입 전체에서 MDR 기대치와 다른 수수료 건수와", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_DELTA" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_FEERECON", + "desc": "수수료 대사: 매입 수수료를 가맹점 MDR(bp) 기준 기대 수수료와 비교한다.", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_FEE", + "T_DELTA", + "T_ID1" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_FXRATECHK", + "desc": "외화 환율 검증: 최신 외화 거래의 원통화 금액 x 환율(bp) 재계산 값이 저장된", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_FX_AMT", + "T_AMT1", + "T_AMT2", + "T_RC" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_FXRECON", + "desc": "외화 대사: 외화 거래(ac_fx)의 원화 환산 금액 합계와 매입 금액을 비교하여", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_ID1" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_GAPSCAN", + "desc": "대사 누락 스캔: 대사완료(M) 상태인데 대사 로그가 하나도 없는 당일 매입", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ID1" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3446,6 +9022,49 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_HOLDAGE", + "desc": "보류 장기화 조회: 기준일 이전부터 남아 있는 HOLD 대사 건수와 가장 오래된", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_HOLDRSN", + "desc": "대사 보류 사유별 현황: 특정 사유의 당일 예외 건수를 미해결/해결로 나누어", + "in": [ + "T_BIZDATE", + "T_REASON" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_COUNT" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_INSTRECON", + "desc": "할부 대사: 할부 개월수(months) 대비 등록된 회차(seq_no) 수를 비교하여", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_INSTALL_N", + "T_COUNT", + "T_GROSS", + "T_ID1" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3458,6 +9077,65 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_LIMITCHK", + "desc": "가맹점 한도 대비 대사: 가맹점 일한도(daily_limit)와 당일 매입(취소 제외)", + "in": [ + "T_BIZDATE", + "T_MERCHANT" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_RC" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_MANAPPRV", + "desc": "수작업 대사 승인: HOLD 상태의 MANREG 수작업 대사 1건을 MATCHED 로 확정하고", + "in": [ + "T_ID1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_PURCHASE_ID" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_MANREG", + "desc": "수작업 대사 등록: 담당자가 입력한 수작업 대사를 HOLD 상태(승인 대기)로", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE", + "T_REASON", + "T_NET" + ], + "out": [ + "T_ID1" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_MANREJECT", + "desc": "수작업 대사 반려: 승인 대기(HOLD) 중인 수작업 대사를 ROLLBACK 처리하고,", + "in": [ + "T_ID1", + "T_BIZDATE", + "T_REASON" + ], + "out": [ + "T_ID2", + "T_PURCHASE_ID" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3472,6 +9150,47 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_NOTIFY", + "desc": "대사 결과 통보: 당일 대사 요약(rc_summary)을 읽어 통보 메시지를 구성하고,", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_GROSS" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_ORPHAN", + "desc": "고아 매입 검출: 승인(approval) 이 전혀 없는 당일 매입을 NOT EXISTS 로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_PARTACQ", + "desc": "부분매입 대사: 분할 매입(ac_install 회차) 합계가 원매입 금액과 일치하는지", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_AMT2", + "T_ID1" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3486,6 +9205,20 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_PAYMATCH", + "desc": "지급 대사: 매입 net 대비 원장(ledger) SETTLE/XFER 지급 합계를 비교하고,", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_DELTA", + "T_ID1" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3498,6 +9231,35 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_RATEBYISS", + "desc": "발급사별 대사율: 요청 발급사의 당일 대사 로그를 매입과 조인하여 총건수와", + "in": [ + "T_BIZDATE", + "T_ISSUER" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_RATESTAT", + "desc": "대사율 통계: 당일 전체 대사 로그 대비 MATCHED 비율을 bp(1/10000) 단위로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3520,6 +9282,18 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_REOPEN", + "desc": "마감 재개: 확정(CONFIRMED)된 당일 대사 건을 다시 MATCHED 로 되돌려 마감을", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3530,6 +9304,34 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_RERUN", + "desc": "임계초과 재대사 실행: 임계값을 넘는 당일 차이 건들을 커서로 순회하며", + "in": [ + "T_AMT1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_RETRO", + "desc": "소급 대사: 과거 영업일에 보류/예외로 남은 특정 매입의 대사를 새 영업일로", + "in": [ + "T_PURCHASE_ID", + "T_NET", + "T_STR1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3554,6 +9356,21 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_SNAPSHOT", + "desc": "대사 스냅샷: 당일 대사 로그의 총건수/MATCHED 건수/MATCHED 금액을 CASE", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_GROSS" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3577,6 +9394,20 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_SUMCHK", + "desc": "요약-상세 정합성 검증: rc_summary 의 확정 matched 건수와 대사 로그 실측", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_RC" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3590,6 +9421,33 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_TAXRECON", + "desc": "세금계산서 대사: 발행된 세금계산서(공급가+부가세) 합계와 매입 금액을", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_DELTA", + "T_ID1" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_THRESHOLD", + "desc": "임계초과 검출: 당일 차이 중 |차이금액| 이 임계값을 초과하는 건수를 세고,", + "in": [ + "T_AMT1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3605,6 +9463,20 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_TOPDIFF", + "desc": "차이 상위 조회: 당일 |차이금액| 의 최대값과 평균값을 구한 뒤 평균을 넘는", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_COUNT" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3615,6 +9487,48 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_VOIDREC", + "desc": "VOID 매입 대사: 매입 상태가 V(void) 인지와 VOID 유형 취소 증빙 존재를", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_ID1", + "T_RC" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_WHTRECON", + "desc": "원천세 대사: 매입의 원천징수(ac_wht) 세액 합계가 정산 net 금액을 넘지", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_TAX", + "T_NET", + "T_RC", + "T_ID1" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_ZEROAMT", + "desc": "이상금액 검출: 당일 매입 중 금액 0원 또는 음수 net 인 이상 거래를 찾아", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3642,6 +9556,20 @@ const SVC_SCREENS = [ "T_SETTLE_ID" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_ADJHIST", + "desc": "조정 이력 조회: 매입에 걸린 정산 조정을 최신순 커서로 순회해 건수·증감", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_DELTA" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3673,6 +9601,18 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_APPROVE", + "desc": "정산 승인 워크플로: 최신 정산 상태를 검증해 CONFIRMED 인 건만 APPROVED 로", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3728,6 +9668,52 @@ const SVC_SCREENS = [ "T_DELTA" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_CAPCHECK", + "desc": "수수료 상한 도달 점검: 수수료가 상한을 초과하는지 판정만 하고 초과분을", + "in": [ + "T_FEE", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_DELTA", + "T_AMT1" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_CARRYOVER", + "desc": "이월 정산: 대사완료(M) 상태이지만 정산 레코드가 없는 매입(NOT EXISTS)을", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ID1", + "T_NET" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_CHNLSTAT", + "desc": "채널별 정산 통계 세분: 정산-매입 조인 커서로 채널(POS/EDC/EDI/FOREIGN)별", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_GROSS", + "T_AMT1" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3738,6 +9724,113 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_CXLRECALC", + "desc": "정산 취소분 재계산: ac_cancel 누계를 매입 net 에서 차감해 정산 net 을", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_NET", + "T_DELTA" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_CYCLED1", + "desc": "D+1 정산주기: 당일 SETTLED 정산 net 합계를 산출해 익영업일(pay_date=D+1)", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID1", + "T_NET" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_CYCLED2", + "desc": "D+2 정산주기: 당일 CONFIRMED 확정 정산만 집계해 2영업일 뒤(D+2, 주말 스킵)", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ID1", + "T_NET" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_CYCLEMON", + "desc": "월간 정산주기: 대상 월(YYYY-MM)의 가맹점 정산 net 을 합산해 지급일 기준으로", + "in": [ + "T_MERCHANT", + "T_BIZDATE", + "T_KEY1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ID1", + "T_GROSS" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_CYCLEWEEK", + "desc": "주간 정산주기: 주 종료일 기준 직전 7일(취소 제외) 정산 net 을 합산해", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID1", + "T_COUNT", + "T_GROSS" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_DAILYCLOSE", + "desc": "정산 일마감: 당일 정산·지급·조정 건수를 각각 검증 집계하고 정산 net 총액으로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_GROSS" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_DEBTCOLLECT", + "desc": "채권 회수 정산: 회수 입금액을 양수 조정(COLLECT)으로 기록하고 원장에 COLLECT", + "in": [ + "T_AMOUNT", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_ADJ", + "T_DELTA" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3756,6 +9849,22 @@ const SVC_SCREENS = [ "T_AMOUNT" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_DEPOSITCUT", + "desc": "보증금 차감: 보증금 납부 예정액과 SCHEDULED 지급 가용액 중 작은 금액을", + "in": [ + "T_AMT1", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_DELTA", + "T_ADJ" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3769,6 +9878,88 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_DISPUTE", + "desc": "정산 이의신청 처리: 대상 매입의 가맹점을 역추적해 예정 지급을 보류(HELD)로", + "in": [ + "T_DELTA", + "T_PURCHASE_ID", + "T_BIZDATE", + "T_REASON" + ], + "out": [ + "T_ADJ" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_DTLSUM", + "desc": "정산 상세 합계: 가맹점+수수료종류+영업일 단위로 상세 라인의 건수·합계·", + "in": [ + "T_MERCHANT", + "T_STR1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_AMT1", + "T_AMT2", + "T_AMT3" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_EDIRECON", + "desc": "EDI 정산 대사: 가맹점 당일 EDI 문서(ac_edi) 청구 합계와 정산 집계", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_NET", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_FEEPREVIEW", + "desc": "수수료 사전 미리보기: 가맹점 등록 요율(mdr_bps)로 수수료·부가세·원천세와", + "in": [ + "T_AMOUNT", + "T_MERCHANT" + ], + "out": [ + "T_FEE", + "T_TAX", + "T_AMT1", + "T_NET", + "T_RC" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_FEERATIO", + "desc": "수수료율 실효 검증: 매입-가맹점 조인 한 번으로 실제 부과 수수료의 실효", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_DELTA", + "T_RC" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3782,6 +9973,39 @@ const SVC_SCREENS = [ "T_AMOUNT" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_FXSETTLE", + "desc": "해외 매입 정산: 매입에 걸린 외화 내역(ac_fx)의 원화 환산액을 합산해 FX", + "in": [ + "T_SETTLE_ID", + "T_PURCHASE_ID", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_AMOUNT", + "T_COUNT" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_GRPSETTLE", + "desc": "가맹점그룹 통합정산: 그룹 prefix(LIKE) 소속 가맹점별 정산 net 을 커서로", + "in": [ + "T_KEY1", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID1", + "T_COUNT", + "T_GROSS" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3793,6 +10017,34 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_HOLDAMT", + "desc": "금액 조건부 보류: 임계액(threshold) 이상의 SCHEDULED 지급을 집합 UPDATE 로", + "in": [ + "T_AMT1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_ISSUERSTAT", + "desc": "발급사별 수수료 통계 세분: 당일 정산완료(S) 매입을 발급사(issuer) 단위로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_FEE" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3805,6 +10057,36 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_LEDGERSNAP", + "desc": "정산 스냅샷: 당일 정산 건수·net 총액을 집계해 원장에 STSNAP 스냅샷 엔트리로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ID1", + "T_COUNT", + "T_GROSS" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_LIMITCHK", + "desc": "가맹점 한도 점검: 가맹점 일 한도(daily_limit)와 당일 매입 누계(취소 제외)를", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_GROSS", + "T_DELTA", + "T_RC" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3817,6 +10099,41 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_MERCHRATE", + "desc": "가맹점 개별 요율 적용: 구간요율표 대신 가맹점 마스터의 계약 요율(mdr_bps)로", + "in": [ + "T_SETTLE_ID", + "T_PURCHASE_ID", + "T_AMOUNT", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_FEE", + "T_RC" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_MINFEE", + "desc": "최저 수수료 보정: 산출 수수료가 최저 수수료에 미달하면 부족분을 ADJ 정산", + "in": [ + "T_SETTLE_ID", + "T_PURCHASE_ID", + "T_FEE", + "T_ARG1", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_FEE", + "T_DELTA" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3830,6 +10147,20 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_NEGSWEEP", + "desc": "음수 정산 정리: 당일 net 이 음수인 SETTLED 정산의 합계를 먼저 파악한 뒤", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3858,6 +10189,23 @@ const SVC_SCREENS = [ "T_ADJ" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_PARTIAL", + "desc": "부분정산: 매입 net 에서 기정산 합계를 뺀 잔여 한도 내에서 요청 금액만큼", + "in": [ + "T_PURCHASE_ID", + "T_AMOUNT", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_NET", + "T_SETTLE_ID" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3869,6 +10217,53 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_PAYRETRY", + "desc": "지급 재시도: 이체 실패 등으로 CANCELLED 된 지급을 익영업일 지급일로 갱신해", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_PAYSTATUS", + "desc": "지급 상태 조회: 가맹점의 가장 최근 지급 레코드에서 상태·금액·지급 예정일을", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_NET" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_PROMOCALC", + "desc": "프로모션/이벤트 정산: 이벤트 코드와 할인율(%)을 받아 수수료 할인액을 계산하고", + "in": [ + "T_SETTLE_ID", + "T_PURCHASE_ID", + "T_FEE", + "T_ARG1", + "T_BIZDATE", + "T_STR1" + ], + "out": [ + "T_RC", + "T_DELTA", + "T_ADJ", + "T_FEE" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3887,6 +10282,21 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_RATESIM", + "desc": "요율 시뮬레이션: 요율표(st_fee_rate)의 전 구간을 커서로 순회하며 입력 금액에", + "in": [ + "T_AMOUNT", + "T_STR1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_FEE" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3899,6 +10309,21 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_RECVOFFSET", + "desc": "미수금 상계: 매입 조인으로 가맹점의 누적 미수금(음수 조정 절대값)을 산출하고", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_AMOUNT", + "T_AMT1" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3926,6 +10351,19 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_REOPEN", + "desc": "정산 마감 해제: 당일 마감 원장(STCLOSE)을 삭제해 마감을 철회한다.", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_RC" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3939,6 +10377,54 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_RERUN", + "desc": "정산 재실행: 해당 매입의 정산 상세를 전량 삭제하고 매입 원본(fee)을 기준으로", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_COUNT", + "T_FEE" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_RETRORATE", + "desc": "요율 소급적용: 신규 요율(bps)로 매입 금액의 수수료를 재계산하고, 기계상된", + "in": [ + "T_SETTLE_ID", + "T_ARG1", + "T_PURCHASE_ID", + "T_BIZDATE", + "T_STR1" + ], + "out": [ + "T_RC", + "T_DELTA", + "T_ADJ", + "T_FEE" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_ROUNDING", + "desc": "원단위 절사: 최신 정산 net 의 10원 미만 끝자리를 절사해 갱신하고 절사분을", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_DELTA", + "T_ADJ", + "T_NET" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3954,6 +10440,38 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_SPLITEVEN", + "desc": "분할 지급 잔여 보정: net 을 n 회로 균등 분할하되 나눗셈 끝수를 마지막", + "in": [ + "T_NET", + "T_INSTALL_N", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_AMT1", + "T_AMT2" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_STATDETAIL", + "desc": "정산 통계 세분: 당일 정산 상세를 수수료 종류(fee_type)별로 분해 집계하는", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_GROSS", + "T_AMT1" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3978,6 +10496,66 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_TAXINVCHK", + "desc": "세금계산서 대사: 가맹점 당일 세금계산서(ac_tax_invoice) 공급가·세액 합계와", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_GROSS", + "T_AMT1", + "T_TAX", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_TIERINCENT", + "desc": "구간별 인센티브: 가맹점 일 거래대금(gross) 구간에 따라 인센티브 요율을", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_AMOUNT", + "T_ADJ" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_TOPMERCH", + "desc": "상위 가맹점 조회: 당일 정산 net 합계 1위 가맹점을 GROUP BY + ORDER BY", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_GROSS" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_UNPAIDSUM", + "desc": "미지급 집계: 기준일 이전이 지급 예정일인데 아직 완료(PAID)되지 않은", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_GROSS" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -4008,6 +10586,22 @@ const SVC_SCREENS = [ "T_TAX" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_VATREPORT", + "desc": "부가세 신고자료: 대상 월의 정산 상세 VAT 합계와 세금계산서(ac_tax_invoice)", + "in": [ + "T_KEY1" + ], + "out": [ + "T_TAX", + "T_COUNT", + "T_GROSS", + "T_AMT1", + "T_DELTA" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -4024,6 +10618,22 @@ const SVC_SCREENS = [ "T_TAX" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_WHTREPORT", + "desc": "원천세 명세: 가맹점의 대상 월 정산 상세(WHT) 합계와 원천 대장(ac_wht) 합계를", + "in": [ + "T_MERCHANT", + "T_KEY1" + ], + "out": [ + "T_TAX", + "T_AMT1", + "T_DELTA", + "T_RC" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -4036,6 +10646,35 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_AGEDCONF", + "desc": "기능: 장기 미종결 확인건 검출 (CONFIRMED N일 초과, RC 1=존재; T_ARG2=1이면 severity CRIT 승격).", + "in": [ + "T_ARG1", + "T_ARG2" + ], + "out": [ + "T_COUNT", + "T_ARG3", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_AGEDOPEN", + "desc": "기능: 장기미결 의심거래 검출 (OPEN 상태로 N일 초과 경과, RC 1=존재).", + "in": [ + "T_ARG1" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_RC" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4063,6 +10702,36 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_APPRAGG", + "desc": "기능: 일자별 매입-승인 금액 대사 (승인보유 매입금액 vs 전체 매입금액, RC 1=미승인 금액 존재).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_GROSS", + "T_AMT1", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_APPRAMT", + "desc": "기능: 건별 매입-승인 금액/존재 정합 검증 (RC 1=승인없음, 2=거절승인만 존재, 3=매입없음).", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_AMOUNT", + "T_COUNT", + "T_ARG1" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4118,6 +10787,21 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_CANCELMIS", + "desc": "기능: 취소 정합 검증 (RC 비트합 1=취소합>매입금액, 2=취소 존재하나 매입상태 미반영, 3=매입없음).", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_AMOUNT", + "T_AMT1", + "T_COUNT" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4179,6 +10863,35 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_DATEREV", + "desc": "기능: 건별 정산 일자 역전 검출 (settlement.biz_date < purchase.biz_date → RC 1, 3=매입없음).", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_DAYLIMIT", + "desc": "기능: 가맹점 일한도 위반 검증 (일자 매입합 > daily_limit → RC 1, 3=가맹점 없음).", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_AMT1", + "T_GROSS", + "T_DELTA" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4207,6 +10920,51 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_DUPPAY", + "desc": "기능: 건별 중복지급 검출 (ledger XFER 엔트리 2건 이상 → RC 1, 검증로그 기록).", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_DUPPAYDAY", + "desc": "기능: 일자별 중복지급 그룹 집계 (XFER 2건 이상 매입 그룹수/초과건수, RC 1=중복지급 존재).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_DUPSETTLE", + "desc": "기능: 건별 중복 정산 검출 (동일 매입에 정산 2건 이상 → RC 1, 최초/최종 정산 ID 반환).", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_COUNT", + "T_NET", + "T_ID1", + "T_ID2", + "T_RC" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4234,6 +10992,37 @@ const SVC_SCREENS = [ "T_DELTA" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_FEEADJNET", + "desc": "기능: 수수료 조정 반영 검증 (fee+sum(ac_fee_adj.delta) 유효범위 점검, RC 1=음수 수수료 2=금액 초과 3=매입없음)", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_FEE", + "T_DELTA", + "T_COUNT" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_FEERECALC", + "desc": "기능: 수수료 재계산 검증 (가맹점 요율 mdr_bps로 기대수수료 재산출, |fee-기대|>허용오차 → RC 1, 3=매입/가맹점 없음).", + "in": [ + "T_PURCHASE_ID", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_FEE", + "T_AMT1", + "T_DELTA" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4247,6 +11036,84 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_FXCHK", + "desc": "기능: 해외 매입 환산 정합 검증 (krw vs fx*rate_bps/10000, 오차>T_ARG1 → RC 1, 2=해외 내역 없음).", + "in": [ + "T_PURCHASE_ID", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_FX_AMT", + "T_AMT1", + "T_DELTA" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_INSTCHK", + "desc": "기능: 할부 정합 검증 (sum(월부금) vs 매입금액, 오차>T_ARG1 → RC 1, 2=할부 없음 3=매입없음).", + "in": [ + "T_PURCHASE_ID", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_AMT1", + "T_INSTALL_N", + "T_COUNT", + "T_DELTA" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_LEDGBAL", + "desc": "기능: 일자별 원장 대차 정합 검증 (차변 RECON 합 vs 대변 SETTLE+XFER 합, RC 1=대차 불일치).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_COUNT", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_LEDGDATE", + "desc": "기능: 건별 원장 일자 역전 검출 (ledger.biz_date < purchase.biz_date 엔트리 수, RC 1=역전 존재).", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_LEDGENTRY", + "desc": "기능: 건별 원장 대사 엔트리 검증 (purchase.amount vs ledger RECON 합, RC 1=금액불일치 2=원장없음 3=매입없음", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_AMOUNT", + "T_AMT1", + "T_COUNT" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4274,6 +11141,35 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_LIMITNEAR", + "desc": "기능: 가맹점 일한도 임박 검출 (사용률>=T_ARG1% → RC 1, 초과 → RC 2, 3=가맹점 없음).", + "in": [ + "T_ARG1", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_AMT1", + "T_GROSS", + "T_ARG2" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_MDRRATE", + "desc": "기능: 가맹점 요율 위반 검출 (mdr_bps<0 또는 규칙 MDR_MAX 초과, RC 1=위반 존재).", + "in": [], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4286,6 +11182,51 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_NEGBAL", + "desc": "기능: 마이너스 잔액 가맹점 검출 (merchant_summary.net_amount<0, RC 1=존재; 최악치 반환).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_AMT1", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_NEGNET", + "desc": "기능: 일자별 음수 순액/역전 수수료 매입 검출 (net<0, fee<0, fee>amount, RC 1=존재).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RATEDEV", + "desc": "기능: 건별 실적용 요율 편차 검출 (fee*10000/amount vs mdr_bps, 편차>T_ARG1 → RC 1, 2=금액 0 이하, 3", + "in": [ + "T_PURCHASE_ID", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_ARG2", + "T_ARG3", + "T_DELTA" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4324,6 +11265,65 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RILEDGER", + "desc": "기능: 참조무결성 세분 검증 - 원장→매입 고아 엔트리 (RC 비트합 1=RECON 2=SETTLE 4=XFER 고아).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RIMERCH", + "desc": "기능: 참조무결성 세분 검증 - 매입→가맹점 (일자별 고아 매입 수, RC 1=고아 존재).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RIPURCH", + "desc": "기능: 참조무결성 세분 검증 - 정산→매입 (일자별 고아 정산 수/금액, RC 1=고아 존재).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_NET", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RISETTLE", + "desc": "기능: 참조무결성 세분 검증 - 정산 가맹점 불일치 (settlement.merchant_id <> purchase.merchant_id, RC", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_PURCHASE_ID", + "T_RC" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4337,6 +11337,77 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RULEHIST", + "desc": "기능: 검증 규칙 변경이력 조회 (vl_check_log의 VL_RULEMOD 이력 건수/최종 로그 ID, RC 1=이력 없음).", + "in": [ + "T_STR3" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RULEMOD", + "desc": "기능: 검증 규칙 변경 + 변경이력 기록 (임계값/심각도 UPDATE 후 vl_check_log에 이력 적재, RC 3=규칙 없음).", + "in": [ + "T_STR3", + "T_ARG1", + "T_STR4", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ARG2" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RULEOFF", + "desc": "기능: 검증 규칙 활성/비활성 전환 (T_STR5 = ACTIVE|INACTIVE, RC 1=규칙 없음 2=상태값 오류).", + "in": [ + "T_STR3", + "T_STR5" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RULEREG", + "desc": "기능: 검증 규칙 신규 등록 (이미 존재하면 RC 1로 거부, 신규만 INSERT).", + "in": [ + "T_STR3", + "T_STR1", + "T_STR4", + "T_ARG1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RULESTAT", + "desc": "기능: 검증 규칙 현황 집계 (전체/ACTIVE/CRIT 건수, RC 1=활성 규칙 전무).", + "in": [], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_RC" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4386,6 +11457,24 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_SUMFEE", + "desc": "기능: 통계-원천 수수료/순액 대사 (merchant_summary fee/net vs purchase 재집계, RC 비트합 1=수수료 2=순액", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_FEE", + "T_AMT1", + "T_NET", + "T_AMT2", + "T_DELTA" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4399,6 +11488,121 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_SUMRECON", + "desc": "기능: 통계-원천 대사 검증 (merchant_summary 건수/총액 vs purchase 재집계, RC 비트합 1=건수 2=총액, 8=통계 ", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG1", + "T_GROSS", + "T_AMT1" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_SUSPACK", + "desc": "기능: 의심거래 워크플로 1단계 확인 (OPEN→CONFIRMED, 확인자 기록, RC 1=OPEN 상태 아님/없음).", + "in": [ + "T_ID1", + "T_STR1" + ], + "out": [ + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_SUSPACT", + "desc": "기능: 의심거래 워크플로 2단계 조치 등록 (조치내용 detail 부기 + 검증로그 적재, RC 2=이미 종결 3=없음 4=조치내용 누락).", + "in": [ + "T_ID1", + "T_REASON", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_PURCHASE_ID" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_SUSPFINAL", + "desc": "기능: 의심거래 워크플로 3단계 종결 (CONFIRMED→CLOSED만 허용, RC 1=이미 종결 2=확인 전 종결 불가 3=없음).", + "in": [ + "T_ID1" + ], + "out": [ + "T_RC", + "T_ARG1" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_SUSPREOPEN", + "desc": "기능: 종결 의심거래 재오픈 (CLOSED→OPEN, 재오픈 사유 필수, RC 1=종결건 아님/없음 2=사유 누락).", + "in": [ + "T_ID1", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_SUSPSTAT", + "desc": "기능: 의심거래 워크플로 상태별 현황 (일자별 OPEN/CONFIRMED/CLOSED 건수, RC 1=미결 존재).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_THRAUDIT", + "desc": "기능: 임계값 설정 감사 (RC 비트합 1=ACTIVE ERROR/CRIT 규칙의 임계값 미설정, 2=CRIT 규칙 비활성 방치).", + "in": [], + "out": [ + "T_ARG1", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_THRBUMP", + "desc": "기능: 규칙 임계값 증감 조정 (T_DELTA 만큼 가감, 음수는 0으로 클램프 RC 2, 규칙 없으면 RC 3).", + "in": [ + "T_STR3", + "T_DELTA" + ], + "out": [ + "T_RC", + "T_ARG2", + "T_ARG3" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4413,6 +11617,20 @@ const SVC_SCREENS = [ "T_ARG1" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_THRSET", + "desc": "기능: 규칙 임계값 직접 설정 (음수 거부 RC 2, 대상 규칙 없으면 RC 3).", + "in": [ + "T_STR3", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_ARG2" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4426,6 +11644,64 @@ const SVC_SCREENS = [ "T_RC", "T_DELTA" ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_TRIAGG", + "desc": "기능: 일자별 매입-정산-지급 3단 총액 대사 (RC 비트합 1=매입vs정산 불일치 2=정산vs지급 불일치).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_AMT3", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_TRISTAGE", + "desc": "기능: 건별 매입-정산-지급 3단 정합 검증 (RC 비트합 1=정산누락 2=지급누락 4=정산액불일치 8=지급액불일치, 32=매입없음).", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_NET", + "T_AMT1", + "T_AMT2" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_UNACQ", + "desc": "기능: 미매입 승인 검출 (매입 원장에 없는 승인 레코드 수, RC 1=고아 승인 존재).", + "in": [], + "out": [ + "T_COUNT", + "T_ID1", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_UNACQAGE", + "desc": "기능: 장기 미확정 매입 검출 (접수 A/보류 H 상태로 N일 초과 경과, RC 1=존재).", + "in": [ + "T_ARG1" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_ARG2", + "T_RC" + ] } ]; diff --git a/app/ui/screens.js b/app/ui/screens.js index aef51fb..8a9b108 100644 --- a/app/ui/screens.js +++ b/app/ui/screens.js @@ -16,6 +16,22 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_ALERTCHK", + "desc": "매입 알림 점검: 가맹점의 당일 고액거래(임계금액 이상)와 보류 적체 건수를", + "in": [ + "T_AMT1", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_ARG2" + ] + }, { "mod": "ac", "modnm": "매입", @@ -31,6 +47,21 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_APPRGAP", + "desc": "승인-매입 갭 점검: 승인 없는 매입(무승인 매입)과 매입 없는 승인(고아", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_AMT1", + "T_ARG2", + "T_RC" + ] + }, { "mod": "ac", "modnm": "매입", @@ -53,6 +84,22 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_BULKVERIFY", + "desc": "대량매입 검증: 가맹점·영업일 단위로 전 건의 수수료(MDR 재계산 대비)와", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG1", + "T_ARG2" + ] + }, { "mod": "ac", "modnm": "매입", @@ -68,6 +115,138 @@ const SVC_SCREENS = [ "T_CANCEL_ID" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CB_DEFEND", + "desc": "지불거절 방어: 접수된 CB 건에 방어 증빙 사유를 덧붙이고, 보류 중이던", + "in": [ + "T_CANCEL_ID", + "T_REASON" + ], + "out": [ + "T_RC", + "T_PURCHASE_ID" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CB_OPEN", + "desc": "지불거절(chargeback) 접수: 대사완료/정산완료 매입만 접수 가능, ac_cancel 에", + "in": [ + "T_PURCHASE_ID", + "T_AMOUNT", + "T_BIZDATE", + "T_REASON" + ], + "out": [ + "T_RC", + "T_CANCEL_ID" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CB_QRY", + "desc": "지불거절 목록조회: 가맹점·영업일 기준 CB 접수 건을 커서로 훑어 건수/합계를", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_AMOUNT" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CB_STAT", + "desc": "지불거절 일별 통계: 영업일 기준 CB 건수·합계·평균·최대 금액과 방어 완료", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_AMT1", + "T_AMT2", + "T_ARG1" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CCYSUM", + "desc": "통화별 매입 집계: 통화코드를 지정하면 해당 통화의 원통화/원화 합계를,", + "in": [ + "T_CCY", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_FX_AMT", + "T_AMOUNT", + "T_ARG1" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CHANSTAT", + "desc": "매입 채널별 통계: 영업일 채널 그룹 집계를 커서로 순회하며 채널 종수와", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_ARG1" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CLAIMPROC", + "desc": "클레임 처리(종결): 등록된 클레임 행을 삭제하고 처리 결과를 원장에", + "in": [ + "T_ID1", + "T_STR1", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_PURCHASE_ID" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CLAIMQRY", + "desc": "클레임 목록조회: 영업일의 미종결 클레임을 커서로 순회, 총건수와 함께", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CLAIMREG", + "desc": "클레임 등록: 가맹점 이의신청을 미매입 원장(ac_unmatch)에 CLAIM 사유로", + "in": [ + "T_PURCHASE_ID", + "T_REASON", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID1" + ] + }, { "mod": "ac", "modnm": "매입", @@ -83,6 +262,35 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CORRHIST", + "desc": "정정 이력조회: 매입 건에 붙은 수수료 조정 이력(최근 10건)을 역순 커서로", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_COUNT", + "T_ARG1" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_CTRYSUM", + "desc": "국가별 매입 집계: 통화코드를 국가권역(미주/일본/유럽/기타)으로 매핑하는", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_ARG4", + "T_GROSS" + ] + }, { "mod": "ac", "modnm": "매입", @@ -94,6 +302,25 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_DAYSTAT", + "desc": "매입 일별 상태 통계: 영업일 전 건을 상태별(접수/대사/정산/취소/보류/무효)", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_ARG4", + "T_ARG5", + "T_ARG6", + "T_COUNT", + "T_GROSS" + ] + }, { "mod": "ac", "modnm": "매입", @@ -123,6 +350,21 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_DUPSCAN", + "desc": "중복매입 스캔: 영업일 내 (가맹점, 금액) 조합이 2건 이상인 그룹을 부질의로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_AMT1", + "T_RC" + ] + }, { "mod": "ac", "modnm": "매입", @@ -152,6 +394,20 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_EDIRESEND", + "desc": "EDI 재전송: 문서번호의 최신 EDI 원장을 찾아 재전송 사본(-R 접미 문서번호)", + "in": [ + "T_DOCNO" + ], + "out": [ + "T_RC", + "T_ID1", + "T_AMOUNT" + ] + }, { "mod": "ac", "modnm": "매입", @@ -167,6 +423,54 @@ const SVC_SCREENS = [ "T_ADJ" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_FEEHIST", + "desc": "수수료 조정 이력 집계: 매입 건의 조정 건수, 순증감 합계, 증액분/감액분을", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_COUNT", + "T_DELTA", + "T_AMT1", + "T_AMT2", + "T_RC" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_FEERECALC", + "desc": "수수료 재산정: 가맹점의 현행 MDR 로 수수료를 다시 계산해 기존 수수료와의", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_ADJ", + "T_FEE", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_FEEWAIVE", + "desc": "수수료 감면: 요청 감면율(bps)만큼 수수료를 깎아 가맹점 정산금에 돌려준다.", + "in": [ + "T_PURCHASE_ID", + "T_ARG1", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_AMT1", + "T_ADJ" + ] + }, { "mod": "ac", "modnm": "매입", @@ -183,6 +487,41 @@ const SVC_SCREENS = [ "T_FEE" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_FXCONVERT", + "desc": "통화 환산 등록: 해외매입의 원통화 금액에 적용환율(bps)을 곱해 원화를", + "in": [ + "T_PURCHASE_ID", + "T_FX_AMT", + "T_ARG1", + "T_CCY", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID1", + "T_AMOUNT" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_FXRATE", + "desc": "통화 적용환율 조회: 해외매입 원장(ac_fx)에서 특정 통화의 최근 영업일", + "in": [ + "T_CCY", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_COUNT" + ] + }, { "mod": "ac", "modnm": "매입", @@ -195,6 +534,20 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_HOLDQRY", + "desc": "보류 매입 조회: 가맹점의 보류(H) 건을 최근 갱신순 커서로 훑어 건수·총액을", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, { "mod": "ac", "modnm": "매입", @@ -210,6 +563,22 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_ISSSTAT", + "desc": "매입 발급사별 통계: 영업일 발급사 그룹 집계에서 1위 발급사(건수·총액)와", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_GROSS", + "T_ARG1", + "T_ARG2" + ] + }, { "mod": "ac", "modnm": "매입", @@ -235,6 +604,23 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_LIMITUSAGE", + "desc": "가맹점 한도소진 조회: 영업일 유효 매입(취소/무효 제외) 합계를 일한도와", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_AMT1", + "T_AMT2", + "T_AMT3", + "T_ARG1" + ] + }, { "mod": "ac", "modnm": "매입", @@ -249,6 +635,70 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_MONSTAT", + "desc": "매입 월간 통계: 기준일이 속한 달 전체의 건수/총액/평균 거래액과 영업일수", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_AMT1", + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_NETCALC", + "desc": "정산금 재계산: 매입금액에서 수수료와 원천세 누계를 차감한 실정산금을", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_NET", + "T_TAX", + "T_DELTA" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_NOINTSHARE", + "desc": "무이자 할부 분담금 산정: 할부 매입의 개월수에 월당 15bps 를 곱한 발급사", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_AMT1", + "T_INSTALL_N", + "T_ADJ" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_PARTCXL", + "desc": "부분취소: 취소액이 잔여 매입금액 미만인 경우에만 허용. 수수료를 비례", + "in": [ + "T_PURCHASE_ID", + "T_AMOUNT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_CANCEL_ID", + "T_AMT1", + "T_FEE" + ] + }, { "mod": "ac", "modnm": "매입", @@ -263,6 +713,55 @@ const SVC_SCREENS = [ "T_CANCEL_ID" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_PARTRFND", + "desc": "부분환불: 기존 환불 누계 + 요청액이 매입금액을 넘지 않도록 검증하고", + "in": [ + "T_PURCHASE_ID", + "T_AMOUNT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_CANCEL_ID", + "T_AMT2" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_PROMO", + "desc": "프로모션 매입 등록: 행사 코드가 유효하면 수수료 0 의 프로모션 매입을", + "in": [ + "T_AMOUNT", + "T_MERCHANT", + "T_STR1", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_PURCHASE_ID", + "T_NET" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_REBUY", + "desc": "재매입: 취소(C)된 원매입의 가맹점/금액/채널을 승계해 신규 매입을 생성하고", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID1", + "T_FEE", + "T_NET" + ] + }, { "mod": "ac", "modnm": "매입", @@ -303,6 +802,20 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_REMATCH", + "desc": "승인미매칭 재처리: 미매입 원장에 남은 건에 유효 승인이 도착했는지 다시", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_ARG2" + ] + }, { "mod": "ac", "modnm": "매입", @@ -313,6 +826,80 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_RESERVE", + "desc": "매입 예약: 활성(ACTIVE) 가맹점에 대해 미래 영업일 예약 매입(status P)을", + "in": [ + "T_AMOUNT", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_PURCHASE_ID", + "T_FEE" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_RESERVECXL", + "desc": "매입 예약취소: 아직 접수 전환되지 않은 예약(P) 건만 물리 삭제한다.", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_AMOUNT" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_RESERVEQRY", + "desc": "매입 예약 조회: 가맹점의 대기 중 예약(P)을 예약일 순 커서로 훑어 건수와", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_RETURN", + "desc": "매입 반송: 접수(A) 상태의 매입을 전액 반송 처리한다. 상태를 취소(C)로", + "in": [ + "T_PURCHASE_ID", + "T_REASON", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_CANCEL_ID", + "T_AMOUNT" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_SETTLEDUE", + "desc": "정산예정 조회: 대사완료(M) 상태이면서 아직 정산행이 만들어지지 않은", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_NET", + "T_RC" + ] + }, { "mod": "ac", "modnm": "매입", @@ -328,6 +915,39 @@ const SVC_SCREENS = [ "T_SETTLE_ID" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_SPLITPAY", + "desc": "매입 분할납부 등록: 매입금액을 요청 회차수로 균등 분할(잔액은 마지막", + "in": [ + "T_PURCHASE_ID", + "T_INSTALL_N", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_AMT1", + "T_AMT2" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_SPLITQRY", + "desc": "분할납부 회차 조회: 매입 건의 회차 스케줄을 커서로 순회하며 회차수·합계와", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_AMT1", + "T_AMT2", + "T_RC" + ] + }, { "mod": "ac", "modnm": "매입", @@ -338,6 +958,20 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_TAXCXL", + "desc": "세금계산서 취소: 원본 계산서를 상쇄하는 음수(공급가/부가세 부호 반전)", + "in": [ + "T_ID1" + ], + "out": [ + "T_RC", + "T_ID2", + "T_TAX" + ] + }, { "mod": "ac", "modnm": "매입", @@ -353,6 +987,36 @@ const SVC_SCREENS = [ "T_TAX" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_TAXREISSUE", + "desc": "세금계산서 재발행: 원본 계산서의 공급가/부가세를 승계한 신규 계산서를", + "in": [ + "T_ID1", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID2", + "T_TAX" + ] + }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_TOPMERCH", + "desc": "상위 가맹점 조회: 영업일 매출 상위 N개 가맹점을 커서로 뽑아 가맹점 ID와", + "in": [ + "T_ARG1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, { "mod": "ac", "modnm": "매입", @@ -365,6 +1029,21 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_VELOCITY", + "desc": "매입 유입속도 점검: 가맹점의 최근 10분간 접수 건수를 재어 허용치 초과", + "in": [ + "T_ARG2", + "T_MERCHANT" + ], + "out": [ + "T_ARG1", + "T_ARG3", + "T_RC" + ] + }, { "mod": "ac", "modnm": "매입", @@ -379,6 +1058,21 @@ const SVC_SCREENS = [ "T_CANCEL_ID" ] }, + { + "mod": "ac", + "modnm": "매입", + "svc": "ACQ_WEEKSTAT", + "desc": "매입 주간 통계: 기준일 포함 직전 7일 구간의 건수/총액/수수료 합계와", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_FEE", + "T_AMT1" + ] + }, { "mod": "ac", "modnm": "매입", @@ -404,6 +1098,22 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "APPROVAL_RATE", + "desc": "가맹점 승인율 조회: 영업일 접수 대비 승인 성공(A/M/S) 비율을 bps 로", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_COUNT", + "T_ARG2", + "T_RC" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -420,6 +1130,20 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "AUTH_AMT_ADJUST", + "desc": "승인금액 조정(incremental/decremental auth): 호텔·주유소처럼 최종 금액이", + "in": [ + "T_ID1", + "T_DELTA" + ], + "out": [ + "T_AMOUNT", + "T_RC" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -469,6 +1193,53 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "AUTH_DELAY_MON", + "desc": "승인 지연 모니터: 승인(A) 상태로 기준 시간(분)을 넘겨 매입확정이 안 된", + "in": [ + "T_BIZDATE", + "T_ARG1" + ], + "out": [ + "T_COUNT", + "T_ARG2", + "T_ARG3", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "AUTH_DUP_CHK", + "desc": "중복승인 검출: 단말 재시도 등으로 동일 카드+가맹점+금액 조합이 직전 5분내", + "in": [ + "T_KEY1", + "T_MERCHANT", + "T_AMOUNT" + ], + "out": [ + "T_ARG1", + "T_ID1", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "AUTH_EXPIRE_CHK", + "desc": "미매입 승인 만기 점검: 기준일수를 넘겨도 매입확정되지 않은 승인(A) 건의", + "in": [ + "T_ARG1" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC", + "T_ID1" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -509,6 +1280,23 @@ const SVC_SCREENS = [ "T_AMOUNT" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "AUTH_PARTIAL", + "desc": "부분승인: 잔여한도가 요청금액에 못 미치면 잔여한도만큼만 승인(RC=2)하고,", + "in": [ + "T_KEY1", + "T_MERCHANT", + "T_BIZDATE", + "T_AMOUNT" + ], + "out": [ + "T_ID1", + "T_AMT1", + "T_RC" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -521,6 +1309,19 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "AUTH_RESEND", + "desc": "승인 재전송: 망 단절 등으로 응답을 못 받은 단말이 원승인의 승인코드를", + "in": [ + "T_ID1" + ], + "out": [ + "T_AMOUNT", + "T_RC" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -598,6 +1399,33 @@ const SVC_SCREENS = [ "T_CANCEL_ID" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "BIN_ROUTE_INQ", + "desc": "BIN별 라우팅 조회: 카드번호 선두 6자리(BIN)의 첫 자리로 매입 게이트웨이를", + "in": [ + "T_KEY1" + ], + "out": [ + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "BLACKLIST_ADD", + "desc": "블랙리스트 등재: 카드를 블랙리스트에 올리고(기등재 건은 사유만 갱신),", + "in": [ + "T_KEY1", + "T_REASON" + ], + "out": [ + "T_COUNT", + "T_RC" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -610,6 +1438,136 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "BLACKLIST_DEL", + "desc": "블랙리스트 해제: 등재 행을 삭제하고, 옵션(T_ARG1=1)에 따라 함께 정지됐던", + "in": [ + "T_KEY1", + "T_ARG1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "BLACKLIST_INQ", + "desc": "블랙리스트 조회: 특정 카드의 등재 여부/사유/등재일시(문자열 포맷)를", + "in": [ + "T_KEY1" + ], + "out": [ + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "CANCEL_REPROC", + "desc": "승인취소 재처리: au_cancel 에 접수됐지만 원승인 원장에 반영되지 못한 건을", + "in": [ + "T_CANCEL_ID" + ], + "out": [ + "T_ID1", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "CANCEL_STATUS", + "desc": "취소 처리현황 조회: 영업일 기준 취소 접수를 유형(FULL/PARTIAL/VOID)별로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_GROSS" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "CARD_DAILY_RPT", + "desc": "카드 일일 승인 리포트: 카드+영업일 기준 상태별(승인/거절/취소류/매입) 건수,", + "in": [ + "T_KEY1", + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_ARG4", + "T_GROSS", + "T_AMT1", + "T_ARG5" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "CARD_REISSUE", + "desc": "카드 재발급 연계: 구 카드의 한도원장을 신 카드번호로 이관 생성하고", + "in": [ + "T_KEY1", + "T_KEY2" + ], + "out": [ + "T_RC", + "T_AMT1" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "CARD_RESUME", + "desc": "카드 정지 해제: 블랙리스트에 남아 있으면 해제를 거부하고,", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "CARD_STATE_INQ", + "desc": "카드 상태 종합조회: 한도원장(상태/기본·임시한도/사용액)에", + "in": [ + "T_KEY1" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_AMT3", + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "CARD_SUSPEND", + "desc": "카드 정지: ACTIVE 상태의 카드만 SUSPEND 로 전환하고, 참고용으로", + "in": [ + "T_KEY1", + "T_REASON" + ], + "out": [ + "T_COUNT", + "T_RC" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -636,6 +1594,33 @@ const SVC_SCREENS = [ "T_ARG1" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "DECLINE_ANALYZE", + "desc": "승인 거절사유 분석: 영업일 거절(D) 건을 사유별로 그룹핑해 상위 3개 사유를", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "FRAUD_CASE_INQ", + "desc": "이상거래 케이스 조회: 카드의 부정거래 적발 이력을 집계(건수/최고/평균점수)", + "in": [ + "T_KEY1" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -653,6 +1638,54 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "FRAUD_RULE_AMT", + "desc": "이상거래 고액 룰: 거래금액이 카드의 30일 평균 사용액의 3배를 넘고", + "in": [ + "T_KEY1", + "T_BIZDATE", + "T_AMOUNT" + ], + "out": [ + "T_AMT1", + "T_RC", + "T_ARG1", + "T_ID1" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "FRAUD_RULE_DUP", + "desc": "이상거래 동일금액 반복 룰: 당일 카드 승인 가운데 같은 금액이 3회 이상", + "in": [ + "T_KEY1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_AMT1", + "T_RC", + "T_ID1" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "FRAUD_RULE_FREQ", + "desc": "이상거래 다빈도 룰: 직전 10분간 승인 시도(거절 포함)가 5건 이상이면", + "in": [ + "T_KEY1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_RC", + "T_ID1" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -695,6 +1728,21 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "LIMIT_HIST", + "desc": "한도 사용 이력조회: 최근 N일 일자별 승인합계를 커서로 훑어", + "in": [ + "T_KEY1", + "T_ARG1" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_AMT1" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -709,6 +1757,48 @@ const SVC_SCREENS = [ "T_AMT3" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "LIMIT_RAISE_REQ", + "desc": "한도 영구상향 심사: 최근 30일 승인실적 합계와 거절 건수를 심사해", + "in": [ + "T_KEY1", + "T_AMT1" + ], + "out": [ + "T_GROSS", + "T_RC", + "T_AMT2" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "LIMIT_REMAIN", + "desc": "잔여한도 조회: (기본한도 + 임시한도 - 사용액) 을 계산해 내려준다.", + "in": [ + "T_KEY1" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "LIMIT_RESTORE", + "desc": "임시한도 자동복원: LIMIT_TEMP 로 상향됐던 temp_limit 를 걷어내고", + "in": [ + "T_KEY1" + ], + "out": [ + "T_AMT1", + "T_RC" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -741,6 +1831,83 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "LIMIT_USAGE", + "desc": "한도 사용률 조회: 사용액/기본한도를 bps(만분율)로 환산하고", + "in": [ + "T_KEY1" + ], + "out": [ + "T_ARG1", + "T_AMT1", + "T_AMT2" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "MERCH_AUTH_RANK", + "desc": "가맹점 승인 순위: 영업일의 가맹점별 승인금액을 rank() 윈도우로 서열화한", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "NIGHT_POLICY", + "desc": "심야 승인정책 점검: 최근 7일간 심야시간대(23시~06시) 승인 이력을 집계해", + "in": [ + "T_KEY1", + "T_AMOUNT" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "NOAUTH_CAPTURE", + "desc": "무승인 매입 검출: 매입원장(purchase)에는 있는데 같은 가맹점/영업일/금액의", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "OVERSEAS_POLICY", + "desc": "해외 승인정책: 제재국(간이 목록) 거래는 즉시 차단(RC=9)하고,", + "in": [ + "T_KEY1", + "T_MERCHANT", + "T_STR1", + "T_BIZDATE", + "T_AMOUNT" + ], + "out": [ + "T_RC", + "T_ID1" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -756,6 +1923,19 @@ const SVC_SCREENS = [ "T_ID2" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "PREAUTH_CANCEL", + "desc": "선승인 취소: 미확정(P) 선승인을 X 로 전환해 홀드 금액을 해제한다.", + "in": [ + "T_ID2" + ], + "out": [ + "T_RC", + "T_AMT1" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -769,6 +1949,49 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "PREAUTH_EXTEND", + "desc": "선승인 연장: 아직 확정되지 않은(P) 선승인의 기준일(biz_date)을 지정 일수만큼", + "in": [ + "T_ID2", + "T_ARG1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "PREAUTH_INQ", + "desc": "선승인 조회: 카드의 선승인 현황을 상태(P/C/X)별 FILTER 집계로 나눠 주고", + "in": [ + "T_KEY1" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_AMT1", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "RISK_SCORE_INQ", + "desc": "카드 리스크 점수 산출: 부정거래 적발 이력(평균점수/건수), 30일 거절률,", + "in": [ + "T_KEY1" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -785,6 +2008,93 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "STANDIN_INQ", + "desc": "대행승인 현황 조회: 영업일의 STANDIN 승인 건을 미정산(A)/정산완료(S)로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_AMT1", + "T_AMT2", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "STANDIN_SETTLE", + "desc": "대행승인(stand-in) 정산: 이슈어 복구 후, 대행으로 승인(A)해 둔 STANDIN", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "STAT_BYTYPE", + "desc": "승인유형(채널)별 통계: 영업일 승인 접수를 auth_type", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_ARG4", + "T_ARG5", + "T_ARG6", + "T_COUNT" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "STAT_HOURLY", + "desc": "시간대별 승인 통계: 영업일의 승인 접수를 시(HH24) 단위로 그룹핑한 커서를", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_COUNT", + "T_ARG2" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "STAT_ISSUER", + "desc": "발급사(카드망)별 승인 통계: 카드번호 선두 자리로 발급사망을 판별", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_COUNT" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "TOKEN_VERIFY", + "desc": "토큰 검증: 결제 토큰(PAN)을 4단계로 검증해 비트마스크 RC 로 응답한다.", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC" + ] + }, { "mod": "au", "modnm": "승인/한도", @@ -800,6 +2110,49 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "VELOCITY_HOUR", + "desc": "시간 단위 velocity 점검: 직전 60분 슬라이딩 윈도우의 카드 승인", + "in": [ + "T_KEY1" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "VELOCITY_MERCH", + "desc": "가맹점 velocity 점검: 직전 10분간 가맹점 승인 요청 폭주를 감시한다.", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "au", + "modnm": "승인/한도", + "svc": "VELOCITY_WEEK", + "desc": "주간 velocity 점검: 최근 7일 카드 승인 건수/금액에 더해 방문 가맹점의", + "in": [ + "T_KEY1" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_ARG1", + "T_RC" + ] + }, { "mod": "cl", "modnm": "마감", @@ -827,6 +2180,37 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_AMTXCHECK", + "desc": "마감 금액 크로스체크: 마감이력 total_amount vs 매입 amount 합계 vs 스냅샷", + "in": [ + "T_ID1" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_AMT3", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_APPRHIST", + "desc": "마감 승인 이력: 기간 프리픽스(YYYY-MM 등)의 마감을 승인(A/L)/완료(C)/취소(X)", + "in": [ + "T_STR1" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_COUNT" + ] + }, { "mod": "cl", "modnm": "마감", @@ -837,6 +2221,32 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_APPRREJECT", + "desc": "마감 승인 반려: 승인(A) 상태 마감을 완료(C)로 되돌리며 반려 사유를 기록.", + "in": [ + "T_ID1", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_APPRREQ", + "desc": "마감 승인 요청: C 상태이며 집계건수>0 인 마감에 승인요청(APPR_REQ) 표시를", + "in": [ + "T_ID1" + ], + "out": [ + "T_ARG1", + "T_RC" + ] + }, { "mod": "cl", "modnm": "마감", @@ -849,6 +2259,19 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_AUDITTRAIL", + "desc": "마감 이력 감사: 마감건의 생성-수정 시각 간격(초)을 산출하고, 잠금(L)", + "in": [ + "T_ID1" + ], + "out": [ + "T_ARG1", + "T_RC" + ] + }, { "mod": "cl", "modnm": "마감", @@ -863,6 +2286,18 @@ const SVC_SCREENS = [ "T_DELTA" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_CALENDAR", + "desc": "마감 캘린더: 월(YYYY-MM)의 1~31일 각 일자 마감 상태를 한 글자씩 표현한", + "in": [ + "T_STR1" + ], + "out": [ + "T_COUNT" + ] + }, { "mod": "cl", "modnm": "마감", @@ -873,6 +2308,67 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_CHNCLOSE", + "desc": "채널별 마감: 채널(POS/EDC/EDI/FOREIGN) 단위로 당일 매입을 집계하고", + "in": [ + "T_BIZDATE", + "T_CHANNEL" + ], + "out": [ + "T_ID1", + "T_COUNT", + "T_GROSS" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_CHNVERIFY", + "desc": "채널별 마감 검증: 영업일+채널의 매입건수와 해당 매입에 연결된 ledger", + "in": [ + "T_BIZDATE", + "T_CHANNEL" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_CLOSERATE", + "desc": "마감율 산출: 월 프리픽스(YYYY-MM)에 대해 매입이 발생한 영업일수 대비", + "in": [ + "T_STR1" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_CNTXCHECK", + "desc": "마감 건수 크로스체크: 마감이력 txn_count vs 실매입 count vs 스냅샷", + "in": [ + "T_ID1" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_RC" + ] + }, { "mod": "cl", "modnm": "마감", @@ -883,6 +2379,31 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_CORPCLOSE", + "desc": "법인별 마감: 당일 매입을 발급사(issuer) 법인 단위로 그룹 커서 순회하며", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ID1" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_CXLREASON", + "desc": "마감 취소 사유별 집계: 기간 프리픽스의 취소(X) 마감을 memo(사유)별로 그룹", + "in": [ + "T_STR1" + ], + "out": [ + "T_COUNT" + ] + }, { "mod": "cl", "modnm": "마감", @@ -895,6 +2416,30 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_DELAYLIST", + "desc": "지연 마감 목록: 생성 후 24시간이 지나도록 개시(O) 상태로 남은 마감을", + "in": [], + "out": [ + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_DELAYREG", + "desc": "마감 지연 사유 등록: 개시(O) 상태의 마감건에만 지연 사유를 memo 로", + "in": [ + "T_ID1", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, { "mod": "cl", "modnm": "마감", @@ -908,6 +2453,132 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_DIVCLOSE", + "desc": "부문별 마감: 업종(category) 부문 단위로 당일 매입 건수/정산액(net)을 집계하고", + "in": [ + "T_BIZDATE", + "T_CATEGORY" + ], + "out": [ + "T_ID1", + "T_COUNT", + "T_NET" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_DUPCHK", + "desc": "중복 마감 탐지: 취소(X) 제외 동일 (close_type, period_key) 에 마감이 2건", + "in": [], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_ESCALATE", + "desc": "승인 지연 에스컬레이션: 기준일수(T_ARG1, 기본 3일) 초과 승인 대기(C)", + "in": [ + "T_ARG1" + ], + "out": [ + "T_COUNT", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_FEEVERIFY", + "desc": "수수료 검증: 마감 스냅샷의 fee 합계와 해당 영업일 매입 fee 합계에", + "in": [ + "T_ID1" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_AMT3", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_FINALIZE", + "desc": "본마감: 해당 영업일의 가마감(PROV, status O)을 찾아 매입을 재집계한 뒤", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ID1", + "T_COUNT", + "T_DELTA" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_FORCECLOSE", + "desc": "강제 마감: 체크리스트를 우회해 해당 영업일의 미확정 개시(O) 마감을 제거한", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ID1", + "T_ARG1", + "T_COUNT" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_GAPDAYS", + "desc": "마감 누락일 탐지: 월 프리픽스 내 매입은 존재하나 일마감이 없는 일자를", + "in": [ + "T_STR1" + ], + "out": [ + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_HALFCLOSE", + "desc": "반기 결산 마감: 연도(T_STR1=YYYY)와 반기(T_ARG1=1|2)를 받아 상/하반기", + "in": [ + "T_STR1", + "T_ARG1" + ], + "out": [ + "T_ID1", + "T_COUNT", + "T_GROSS" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_HOLIDAY", + "desc": "휴일 마감 정책: 영업일의 요일을 판정해 주말이면 마감 SKIP(RC=1) 정책을,", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, { "mod": "cl", "modnm": "마감", @@ -933,6 +2604,62 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_LOCKAUDIT", + "desc": "잠금 감사: 잠금(locked=true) 기간 중 승인(A) 또는 잠금(L) 마감이 하나도", + "in": [], + "out": [ + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_LOCKSWEEP", + "desc": "기간 잠금 일괄: 프리픽스 구간의 기존 잠금 레코드를 locked=true 로 재잠금하고", + "in": [ + "T_STR1" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_COUNT" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_MEMOSEARCH", + "desc": "마감 사유 검색: memo 에 키워드(T_STR1)가 포함된 마감을 검색해 총 건수와", + "in": [ + "T_STR1" + ], + "out": [ + "T_COUNT", + "T_RC", + "T_ID1" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_MERCLOSE", + "desc": "가맹점별 마감: 단일 가맹점의 당일 매입(gross/fee/net/건수)을 집계해 해당", + "in": [ + "T_BIZDATE", + "T_MERCHANT" + ], + "out": [ + "T_ID1", + "T_ID2", + "T_GROSS", + "T_NET", + "T_COUNT" + ] + }, { "mod": "cl", "modnm": "마감", @@ -959,6 +2686,33 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_NETVERIFY", + "desc": "정산액 검증: 마감 스냅샷에서 gross - fee <> net 인 내부 모순 행을 찾아", + "in": [ + "T_ID1" + ], + "out": [ + "T_COUNT", + "T_RC", + "T_DELTA" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_PENDLIST", + "desc": "승인 대기 목록: 승인요청(memo APPR_REQ) 상태로 대기 중인 완료(C) 마감의", + "in": [], + "out": [ + "T_COUNT", + "T_RC", + "T_ID1", + "T_ARG1" + ] + }, { "mod": "cl", "modnm": "마감", @@ -973,6 +2727,46 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_POSTTRIG", + "desc": "마감 후속작업 트리거: 마감(C/A) 확인 후 해당 영업일의 정산건을 ledger 에", + "in": [ + "T_ID1" + ], + "out": [ + "T_COUNT" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_PRECHECK", + "desc": "마감 전 체크리스트 검증: (1)미마감 M 매입 (2)미매칭 ac_unmatch (3)기간 기잠금", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_PROVCLOSE", + "desc": "가마감: 당일 매입 집계 후 status O + memo 'PROV' 가마감을 생성한다.", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID1", + "T_COUNT", + "T_GROSS" + ] + }, { "mod": "cl", "modnm": "마감", @@ -1026,6 +2820,19 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_REOPENREQ", + "desc": "마감 재개시: 완료(C) 상태이고 기간이 잠금되지 않은 마감을 개시(O)로", + "in": [ + "T_ID1", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, { "mod": "cl", "modnm": "마감", @@ -1061,6 +2868,69 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_RPTCAT", + "desc": "업종별 마감 리포트: 영업일 매입을 업종(category) 그룹으로 집계하며 업종별", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG4" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_RPTCHN", + "desc": "채널별 마감 리포트: 영업일의 매입을 채널 그룹으로 집계해 occurrence", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_RPTISSUER", + "desc": "발급사별 마감 리포트: 영업일 매입을 발급사 그룹으로 집계하되 임계금액", + "in": [ + "T_BIZDATE", + "T_AMT1" + ], + "out": [ + "T_COUNT" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_RPTTOPMER", + "desc": "상위 가맹점 마감 리포트: 마감 스냅샷에서 net 상위 N(T_COUNT 입력, 기본 5)", + "in": [ + "T_ID1", + "T_COUNT" + ], + "out": [ + "T_COUNT" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_SCHEDULE", + "desc": "마감 일정 관리: 최종 일마감(biz_date 최대) 기준으로 차기 마감 예정일과", + "in": [], + "out": [ + "T_RC", + "T_ARG1" + ] + }, { "mod": "cl", "modnm": "마감", @@ -1073,6 +2943,49 @@ const SVC_SCREENS = [ "T_ID2" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_SEQAUDIT", + "desc": "마감 순번 감사: cl_seq_reg 의 유형별 last_seq 와 실제 cl_close_log 의", + "in": [], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_SLAMON", + "desc": "마감 SLA 모니터: 기간 프리픽스의 일마감 중 기준시각(T_STR2=HHMI, 기본 1000)", + "in": [ + "T_STR1", + "T_STR2" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_SNAPDIFF", + "desc": "스냅샷 비교: 두 마감(T_ID1, T_ID2)의 스냅샷 gross/net 합계와 가맹점 수를", + "in": [ + "T_ID1", + "T_ID2" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_ARG1", + "T_RC" + ] + }, { "mod": "cl", "modnm": "마감", @@ -1087,6 +3000,22 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_SNAPXCHECK", + "desc": "스냅샷-원장 크로스체크: 마감 스냅샷 net 합계 vs 같은 영업일 ledger SETTLE", + "in": [ + "T_ID1" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_DELTA", + "T_ARG1", + "T_RC" + ] + }, { "mod": "cl", "modnm": "마감", @@ -1124,6 +3053,38 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_TREND", + "desc": "마감 금액 추이: 최근 N회(T_ARG1, 기본 7) 일마감의 총액 평균/최소/최대를", + "in": [ + "T_ARG1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_AMT1", + "T_AMT2", + "T_AMT3" + ] + }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_TRIGSTAT", + "desc": "후속작업 상태: 영업일 정산 건수 대비 ledger XFER 전기 건수를 비교해", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_DELTA", + "T_RC" + ] + }, { "mod": "cl", "modnm": "마감", @@ -1173,6 +3134,21 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "cl", + "modnm": "마감", + "svc": "CL_YEARPREP", + "desc": "연말 결산 사전점검: 연도 내 완료된 월마감(MONTHLY, C/A/L)의 distinct", + "in": [ + "T_STR1" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_RC" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1186,6 +3162,19 @@ const SVC_SCREENS = [ "T_STR3" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_AMT_HANGUL", + "desc": "금액 한글변환(순수 계산): 정수 금액을 4자리(만 단위) 그룹으로 쪼개", + "in": [ + "T_AMOUNT" + ], + "out": [ + "T_RC", + "T_STR4" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1214,6 +3203,114 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_AUDIT_BYLVL", + "desc": "감사로그 레벨별 조회(조회 세분): 레벨(I/W/E)을 검증한 뒤 기준일 해당 레벨", + "in": [ + "T_STR1", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_MSG" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_AUDIT_BYSVC", + "desc": "감사로그 서비스별 조회(조회 세분): 서비스명 기준으로 기준일 로그 건수와", + "in": [ + "T_SVCNAME", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_ID2" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_AUDIT_RANGE", + "desc": "감사로그 ID 구간 조회(조회 세분): log_id 구간(id1~id2, 최대 폭 10000)의", + "in": [ + "T_ID1", + "T_ID2" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG1", + "T_ARG2" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_BATHIST_CNT", + "desc": "배치 실행 이력 집계: 기준일 해당 잡(svc_name)의 로그를 레벨별로 집계해", + "in": [ + "T_KEY1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_ID1" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_BATRERUN_REQ", + "desc": "배치 재실행 요청: 기준일에 실행 이력이 실제로 있는 잡만 재실행 요청을 받아", + "in": [ + "T_KEY1", + "T_BIZDATE", + "T_REASON" + ], + "out": [ + "T_RC", + "T_ID1", + "T_COUNT" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_BATSCHD_GET", + "desc": "배치 스케줄 조회: SCHD: 파라미터를 읽어 주기와 실행시각을 분리해", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC", + "T_STR4", + "T_ARG1" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_BATSCHD_REG", + "desc": "배치 스케줄 등록: 잡 이름과 주기(DAILY/WEEKLY/MONTHLY) + 실행시각(HHMM)을", + "in": [ + "T_KEY1", + "T_STR1", + "T_ARG1", + "T_BIZDATE" + ], + "out": [ + "T_RC" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1224,6 +3321,22 @@ const SVC_SCREENS = [ "T_BIZDATE" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_BIZDAY_CNT", + "desc": "영업일수 계산: from~to 사이를 하루씩 진행하며 주말은 순수 요일 공식", + "in": [ + "T_STR1", + "T_STR2" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG1", + "T_ARG2" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1275,6 +3388,83 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_CODEGRP_CLOSE", + "desc": "코드그룹 코드 폐기: 사용중(use_yn='Y')인 코드만 use_yn='N' 으로 논리 삭제하고,", + "in": [ + "T_KEY1", + "T_KEY2", + "T_REASON", + "T_BIZDATE" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_CODEGRP_CNT", + "desc": "코드그룹 현황: 그룹의 전체 코드 수와 사용중(use_yn='Y') 코드 수를 한 번의", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG1", + "T_ARG2" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_CODEGRP_HIST", + "desc": "코드그룹 변경 이력 조회: CODEGRP 이력 로그에서 그룹 접두 패턴으로 건수와", + "in": [ + "T_KEY1" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_MSG" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_CODEGRP_REG", + "desc": "코드그룹 코드 등록: 코드 중복을 검사한 뒤 cm_code 에 신규 코드를 등록하고,", + "in": [ + "T_KEY1", + "T_KEY2", + "T_STR1", + "T_ARG1", + "T_BIZDATE" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_CODEGRP_UPD", + "desc": "코드그룹 코드 변경: 변경 전 코드명을 먼저 조회해 이력 메시지에 담고,", + "in": [ + "T_KEY1", + "T_KEY2", + "T_STR1", + "T_ARG1", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_STR4" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1301,6 +3491,36 @@ const SVC_SCREENS = [ "T_ARG1" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_DATEDIFF", + "desc": "기간 계산 유틸(순수 계산): 두 날짜를 역법 공식으로 절대 일련일수로 바꿔", + "in": [ + "T_STR1", + "T_STR2" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_ARG2" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_ENC_HEX", + "desc": "간이 암복호화(암호화 유틸 세분, 순수 계산): 키 문자열을 순환시키며 XOR 한", + "in": [ + "T_STR1", + "T_KEY1", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_STR4" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1314,6 +3534,21 @@ const SVC_SCREENS = [ "T_MSG" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_FILE_CKSUM", + "desc": "파일 체크섬(순수 계산): 파일 내용 문자열에 대해 Adler-32 체크섬(mod 65521", + "in": [ + "T_STR1" + ], + "out": [ + "T_STR4", + "T_ARG1", + "T_ARG2", + "T_COUNT" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1351,6 +3586,21 @@ const SVC_SCREENS = [ "T_ARG1" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_FX_AGE", + "desc": "환율 최신성 조회: 통화의 마지막 고시일과 오늘 사이 경과일수를 DB 날짜", + "in": [ + "T_CCY", + "T_ARG2" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_STR4" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1366,6 +3616,114 @@ const SVC_SCREENS = [ "T_ARG1" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_FX_CROSS", + "desc": "크로스 환율 계산: 두 통화의 같은 기준일 원화 환율을 조회해 ccy1 1단위당", + "in": [ + "T_CCY", + "T_STR1", + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_RC", + "T_AMT1", + "T_AMT2" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_FX_HIST", + "desc": "환율 이력 조회: 통화의 기간(from~to) 내 고시 건수, 최소/최대 환율, 마지막", + "in": [ + "T_CCY", + "T_STR1", + "T_STR2" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_AMT1", + "T_AMT2", + "T_STR4" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_FX_PUBLISH", + "desc": "환율 고시: 기준일/통화의 환율(rate_bps>0)을 고시한다. 이미 고시된 날이면", + "in": [ + "T_CCY", + "T_BIZDATE", + "T_ARG1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_FX_VALID", + "desc": "환율 유효성 검증: 입력 환율을 해당 통화의 직전 고시가와 비교해 변동폭(bps)", + "in": [ + "T_CCY", + "T_BIZDATE", + "T_ARG1" + ], + "out": [ + "T_ARG2", + "T_ARG3", + "T_RC" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_HEALTH_DB", + "desc": "시스템 헬스(DB 세분): DB 왕복(SELECT)으로 연결을 확인하고 DB 서버 일자와", + "in": [], + "out": [ + "T_STATUS", + "T_STR4", + "T_STR5", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_HEALTH_LOG", + "desc": "시스템 헬스(로그 세분): 금일 로그 총량과 오류(E) 비율(bps)을 계산해 등급을", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_STATUS", + "T_COUNT", + "T_ARG1", + "T_ARG2" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_HEALTH_SEQ", + "desc": "시스템 헬스(채번 세분): 카운터 개수/최대값과 음수로 굴러간 비정상 카운터", + "in": [], + "out": [ + "T_STATUS", + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_RC" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1378,6 +3736,49 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_HOLIDAY_CNT", + "desc": "기간 휴일 조회(캘린더 조회 세분): 국가별로 기간 내 휴일 수와 첫/마지막", + "in": [ + "T_STR1", + "T_STR2", + "T_STR3" + ], + "out": [ + "T_COUNT", + "T_STR4", + "T_STR5" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_HOLIDAY_DEL", + "desc": "휴일 삭제(캘린더 관리): 삭제 전에 휴일명을 읽어 이력에 남기고 cm_holiday", + "in": [ + "T_STR1" + ], + "out": [ + "T_RC", + "T_STR4" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_HOLIDAY_REG", + "desc": "휴일 등록(캘린더 관리): 날짜 형식을 검증하고 중복 등록을 거른 뒤 cm_holiday", + "in": [ + "T_STR1", + "T_STR2", + "T_STR3" + ], + "out": [ + "T_RC" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1430,6 +3831,48 @@ const SVC_SCREENS = [ "T_STR4" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_MASK_CARD", + "desc": "카드번호 마스킹(마스킹 세분, 순수 계산): 전체가 숫자(13~19자리)인지 먼저", + "in": [ + "T_STR1" + ], + "out": [ + "T_RC", + "T_STR4", + "T_ARG1" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_MASK_NAME", + "desc": "성명 마스킹(마스킹 세분, 순수 계산): UTF-8 문자 단위로 첫 글자만 남기고", + "in": [ + "T_STR1" + ], + "out": [ + "T_RC", + "T_STR4", + "T_ARG1" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_MASK_PHONE", + "desc": "전화번호 마스킹(마스킹 세분, 순수 계산): 하이픈 등 구분자는 위치를 그대로", + "in": [ + "T_STR1" + ], + "out": [ + "T_RC", + "T_STR4", + "T_ARG1" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1443,6 +3886,49 @@ const SVC_SCREENS = [ "T_ARG1" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_MSG_FALLBACK", + "desc": "다국어 메시지 폴백 조회: 요청 언어 그룹에서 먼저 찾고, 없으면 기본 언어", + "in": [ + "T_STR1", + "T_KEY1" + ], + "out": [ + "T_MSG", + "T_RC" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_MSG_GET", + "desc": "다국어 메시지 조회: 언어코드(KO/EN/JA...)로 메시지 그룹 MSG_ 을 정해", + "in": [ + "T_STR1", + "T_KEY1" + ], + "out": [ + "T_RC", + "T_MSG", + "T_STR4" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_MSG_SET", + "desc": "다국어 메시지 등록/수정(upsert): MSG_ 그룹에 UPDATE 를 먼저 시도하고", + "in": [ + "T_STR1", + "T_KEY1", + "T_MSG" + ], + "out": [ + "T_RC" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1469,6 +3955,37 @@ const SVC_SCREENS = [ "T_STR3" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_NOTI_HIST", + "desc": "알림 발송 이력: 기준일 NOTI 로그에서 채널 접두 패턴([SMS>...] 등)으로 발송", + "in": [ + "T_CHANNEL", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_MSG" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_NOTI_SEND", + "desc": "알림 발송: 채널(SMS/EMAIL/PUSH 검증) + 수신자 + 본문을 발송 큐 성격의", + "in": [ + "T_CHANNEL", + "T_STR1", + "T_MSG", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID1" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1481,6 +3998,64 @@ const SVC_SCREENS = [ "T_STR3" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_PARAM_APPROVE", + "desc": "파라미터 변경 승인(승인 워크플로 2단계): 대기 키(REQ:)의 요청 값을 읽어", + "in": [ + "T_STR1", + "T_STR3", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_STR4" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_PARAM_PENDING", + "desc": "파라미터 승인 대기 현황: 대기 키(REQ:%) 건수, 가장 오래된 요청 키, 최장", + "in": [], + "out": [ + "T_COUNT", + "T_STR4", + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_PARAM_REJECT", + "desc": "파라미터 변경 반려(승인 워크플로 종결): 대기 키(REQ:)를 삭제만 하고 본", + "in": [ + "T_STR1", + "T_REASON", + "T_BIZDATE" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_PARAM_REQ", + "desc": "파라미터 변경 요청(승인 워크플로 1단계): 본 키는 건드리지 않고 대기 키", + "in": [ + "T_STR1", + "T_STR2", + "T_STR3", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_STR4" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1503,6 +4078,19 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_PREVBIZ", + "desc": "직전 영업일 조회: 기준일에서 하루씩 거슬러 내려가며 토/일(순수 요일 공식)과", + "in": [ + "T_STR1" + ], + "out": [ + "T_STR3", + "T_ARG1" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1529,6 +4117,63 @@ const SVC_SCREENS = [ "T_ARG1" ] }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_SEQ_DAILY", + "desc": "일자별 채번(채번 세분): 이름+영업일을 합친 키(:)로 매일 1부터", + "in": [ + "T_KEY1", + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_STR4" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_SEQ_PEEK", + "desc": "채번 현재값 조회(채번 세분): 카운터를 증가시키지 않고 cm_seq 의 현재값과", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_STR4" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_SEQ_RESET", + "desc": "채번 초기화(채번 세분): 운영 파라미터 SEQ_RESET_YN 이 'Y' 일 때만 카운터를", + "in": [ + "T_KEY1", + "T_ARG1", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ARG2" + ] + }, + { + "mod": "cm", + "modnm": "공통", + "svc": "CM_SEQ_TYPED", + "desc": "유형별 채번(채번 세분): 허용된 채번 유형(SLIP/TICKET/BATCH/NOTI)만 유형별", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_STR4" + ] + }, { "mod": "cm", "modnm": "공통", @@ -1555,6 +4200,71 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_ACCRUEREV", + "desc": "계상 환입 분개: 기준일(T_BIZDATE) 이전의 미결 '계상' 전표를 커서로 찾아", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ID2" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_ACCTHIST", + "desc": "계정 잔액 이력 조회: [T_BIZDATE ~ T_STR1] 구간의 계정잔액(lg_account_bal)", + "in": [ + "T_KEY1", + "T_BIZDATE", + "T_STR1" + ], + "out": [ + "T_COUNT", + "T_AMT2", + "T_AMT3", + "T_AMT4" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_ACCTLEDGER", + "desc": "계정과목별 원장조회: 지정 계정/영업일의 라인을 커서로 훑어 차변/대변", + "in": [ + "T_KEY1", + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_AMT2", + "T_AMT3", + "T_DELTA", + "T_ID1" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_ACCTRANGE", + "desc": "계정과목 기간별 원장집계: [T_BIZDATE ~ T_STR1] 구간의 차변/대변 합계와", + "in": [ + "T_KEY1", + "T_BIZDATE", + "T_STR1" + ], + "out": [ + "T_AMT2", + "T_AMT3", + "T_COUNT", + "T_DELTA" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1612,6 +4322,63 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_APPROVE", + "desc": "전표 승인 워크플로: 작성(D) 전표만 대상으로 차변합=대변합 균형을 검증한 뒤", + "in": [ + "T_ID1" + ], + "out": [ + "T_DELTA", + "T_AMT2" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_APPRQUEUE", + "desc": "전표 승인 대기 큐 조회: 미결(D) 전표의 대기 건수와 전표 ID 범위(최소/최대),", + "in": [], + "out": [ + "T_COUNT", + "T_ID1", + "T_ID2", + "T_ARG1", + "T_ARG2" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_BADDEBT", + "desc": "대손 처리 분개: 가맹점(T_MERCHANT) 채권 T_AMOUNT 를 상각 —", + "in": [ + "T_AMOUNT", + "T_MERCHANT", + "T_BIZDATE", + "T_REASON" + ], + "out": [ + "T_ID1" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_BADDEBTRCV", + "desc": "대손 회수(환입) 분개: 상각했던 채권이 회수되면 D:CASH / C:BADDEBT_RCV 로", + "in": [ + "T_AMOUNT", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_AMT2", + "T_ID1" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1626,6 +4393,54 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_BALPROOF", + "desc": "잔액 증명: 계정잔액 테이블(lg_account_bal)의 기록 잔액과 원장 라인에서", + "in": [ + "T_KEY1", + "T_BIZDATE" + ], + "out": [ + "T_AMT2", + "T_AMT3", + "T_DELTA" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_BIGLINE", + "desc": "고액 분개 조회: 임계금액(T_AMOUNT, 기본 1000만) 이상 라인의 건수·합계와", + "in": [ + "T_AMOUNT", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_ID1", + "T_ID2", + "T_AMT2" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_BLACKENTRY", + "desc": "흑색 정정 분개: 원전표(T_ID1)를 반전 상쇄한 뒤 정정 금액(T_AMOUNT)으로", + "in": [ + "T_ID1", + "T_AMOUNT", + "T_BIZDATE", + "T_MERCHANT" + ], + "out": [ + "T_ID2", + "T_ARG1" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1638,6 +4453,22 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_CLOSECHK", + "desc": "결산 체크리스트: 당일 (1)미결 전표 (2)불균형 전표 (3)0원 라인", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_ARG4", + "T_RC" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1699,6 +4530,19 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_DAYCLOSE", + "desc": "일마감: 당일 균형(차변합=대변합) 미결 전표를 일괄 P 승격한 뒤, 당일 사용된", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_COUNT" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1713,6 +4557,20 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_DEPTPL", + "desc": "부문별 손익 조회: 당일 매입을 업종/부문(category)별로 묶어 수수료 손익을", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_AMT2" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1745,6 +4603,34 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_DUPSCAN", + "desc": "중복 분개 스캔: 당일 동일 (전표, 계정, 차대, 금액) 조합이 2건 이상인 중복", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ID1", + "T_ARG2" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_FEEACCRUE", + "desc": "수수료 미수 계상: 대사완료(M)했지만 아직 미정산인 매입의 수수료를", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_FEE", + "T_ID1" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1760,6 +4646,36 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_FEERECOG", + "desc": "수수료 수익 인식: 당일 정산완료(S) 매입 수수료 합계를", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_FEE", + "T_ID1", + "T_COUNT" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_FEESPLIT", + "desc": "수수료 부문 배부: 당일 FEE_INC 대변 합계를 배부율(T_ARG1 bps)로 부문 A/B 에", + "in": [ + "T_ARG1", + "T_BIZDATE" + ], + "out": [ + "T_GROSS", + "T_ID1", + "T_AMT2", + "T_AMT3" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1782,6 +4698,49 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_LINECOUNT", + "desc": "원장 라인 집계: 당일 라인의 posted/closed 상태별 건수, 관련 전표 수,", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_COUNT", + "T_ARG3" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_MASKINQ", + "desc": "원장 마스킹 조회: 라인 ID(T_ID1)의 원장 라인을 가맹점 ID 마스킹(앞 2자리", + "in": [ + "T_ID1" + ], + "out": [ + "T_ID2", + "T_AMOUNT" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_MEMOAUDIT", + "desc": "전표 메모 감사: 당일 전기(P) 전표 중 메모가 비어 있는 건수와 표본 전표 ID,", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ID1", + "T_ARG2" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1793,6 +4752,36 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_MERCHPROOF", + "desc": "가맹점 잔액 증명: 가맹점 잔액 테이블(lg_merch_bal)의 차변/대변 누계와", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_AMT2", + "T_AMT3", + "T_AMT4", + "T_ARG1" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_MONTHCLOSE", + "desc": "월차 결산 분개: [T_BIZDATE=월초 ~ T_STR1=월말] 구간 FEE_INC 순액(대변-차변)을", + "in": [ + "T_BIZDATE", + "T_STR1" + ], + "out": [ + "T_AMT2", + "T_ID1" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1807,6 +4796,104 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_NETPOS", + "desc": "순포지션 조회: 당일 채권(RECV) 잔액과 가맹점미지급(MERCH_PAY) 잔액을", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_AMT2", + "T_AMT3", + "T_NET" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_PAYACCRUE", + "desc": "미지급 계상 분개: 당일 정산 순액 합계와 원장 MERCH_PAY 대변 합계의 갭을", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_NET", + "T_AMT3", + "T_DELTA", + "T_ID1" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_PAYRECON", + "desc": "원장-지급 대사: 가맹점(T_MERCHANT)의 원장 라인 순액(대변-차변)과 정산", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_AMT2", + "T_NET", + "T_DELTA", + "T_COUNT" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_PENDAGE", + "desc": "미결전표 경과일 집계: 작성(D) 전표를 경과일 구간(0-1일 / 2-7일 / 8일+)별로", + "in": [], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_ARG4", + "T_COUNT" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_PENDLIST", + "desc": "미결전표 관리(목록 집계): 작성(D) 상태 전표의 건수, 연계 라인 총차변액,", + "in": [], + "out": [ + "T_COUNT", + "T_ID1", + "T_AMT2" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_PERIODTB", + "desc": "기간별 시산표: [T_BIZDATE ~ T_STR1] 구간을 계정별 GROUP BY 커서로 집계해", + "in": [ + "T_BIZDATE", + "T_STR1" + ], + "out": [ + "T_COUNT", + "T_DELTA" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_PLCLOSE", + "desc": "손익 대체 분개(일 단위): 당일 손익계정(FEE_INC, BADDEBT_EXP)의 순액을 각각", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_NET" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1824,6 +4911,20 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_QTRCLOSE", + "desc": "분기 결산 분개: 연도(T_ARG1)/분기(T_ARG2)로 분기 구간을 산출해 그 구간의", + "in": [ + "T_ARG1", + "T_ARG2" + ], + "out": [ + "T_ID1", + "T_AMT2" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1835,6 +4936,57 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_RECVACCRUE", + "desc": "미수 계상 분개: 접수(A) 상태 매입 총액을 D:RECV_ACCR / C:SUSP 로 미결 계상", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_AMT2" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_REDENTRY", + "desc": "적색 정정 분개: 원전표(T_ID1)의 모든 라인을 차/대변 반전(INSERT..SELECT CASE)", + "in": [ + "T_ID1", + "T_BIZDATE" + ], + "out": [ + "T_ID2", + "T_COUNT" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_REJECT", + "desc": "전표 반려 워크플로: 작성(D) 전표를 X(취소) 처리하고 반려 사유(T_REASON)를", + "in": [ + "T_ID1", + "T_REASON" + ], + "out": [] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_REOPEN", + "desc": "취소 전표 재개: X(취소) 전표를 D(작성)로 되돌리고 closed 라인을 posted 로", + "in": [ + "T_ID1" + ], + "out": [ + "T_COUNT" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1876,6 +5028,67 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_SETTLERECON", + "desc": "원장-정산 대사: 당일 정산(settlement) 순액 합계와 원장 MERCH_PAY 대변", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_NET", + "T_AMT3", + "T_DELTA", + "T_COUNT" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_STATACCT", + "desc": "원장 통계 세분(계정): 당일 사용된 계정과목 수와 잔액 절대값이 가장 큰", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_AMT2" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_STATDC", + "desc": "원장 통계 세분(차대변): 당일 차변/대변 각각의 건수·합계·최대금액을 구하고", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_AMT2", + "T_AMT3", + "T_ARG3", + "T_ARG4", + "T_AMT4" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_STATMERCH", + "desc": "원장 통계 세분(가맹점): 특정 가맹점의 원장 라인 건수, 최초/최종 영업일,", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_COUNT", + "T_AMT2", + "T_AMT3", + "T_DELTA" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1889,6 +5102,20 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_STATTYPE", + "desc": "원장 통계 세분(전표유형): 당일 전표를 유형(voucher_type)별로 집계해", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_COUNT", + "T_ARG2" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1900,6 +5127,34 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_SUSPENSE", + "desc": "가지급(SUSP) 정리 분개: 당일 SUSP 계정 순액(차-대)을 0 으로 만드는 반대", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_DELTA", + "T_ID1", + "T_AMT2" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_TBDIFF", + "desc": "시산표 차액 조회: 당일 시산표(lg_trial_bal)에서 불균형(diff<>0) 계정 수,", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_DELTA" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1943,6 +5198,18 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_VOIDVOUCHER", + "desc": "전표 취소: 마감연계(S) 상태가 아닌 전표를 X 처리하고 소속 라인 전부를", + "in": [ + "T_ID1" + ], + "out": [ + "T_COUNT" + ] + }, { "mod": "lg", "modnm": "원장", @@ -1958,6 +5225,35 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_VOUCHTRACE", + "desc": "전표 추적: 전표(T_ID1)의 헤더(유형/상태/연계 ID)와 라인 통계(건수, 차변합,", + "in": [ + "T_ID1" + ], + "out": [ + "T_ID2", + "T_COUNT", + "T_AMT2", + "T_AMT3", + "T_RC" + ] + }, + { + "mod": "lg", + "modnm": "원장", + "svc": "LG_ZEROLINE", + "desc": "0원 분개 검출/정리: 당일 금액 0 인 posted 라인을 검출해 closed 처리하고", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -1970,6 +5266,73 @@ const SVC_SCREENS = [ "T_STATUS" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_ADVICE", + "desc": "0220 매입 어드바이스 수신: 응답이 필요 없는 통지 전문이라", + "in": [ + "T_CHANNEL", + "T_STR3", + "T_BIZDATE", + "T_AMOUNT" + ], + "out": [ + "T_ID1", + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_AMTCHECK", + "desc": "전문 금액/카드 사전검증: 금액 범위(0 < 금액 <= 한도)와", + "in": [ + "T_AMOUNT", + "T_AMT4", + "T_STR5" + ], + "out": [ + "T_AMT4", + "T_RC" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_AUTHREQ", + "desc": "PAN 앞6/뒤4 만 남기고 '*' 마스킹 (승인요청 로그 저장용). */", + "in": [ + "T_CHANNEL", + "T_STR3", + "T_BIZDATE", + "T_AMOUNT" + ], + "out": [ + "T_STR2", + "T_STR5", + "T_ID1", + "T_ARG1" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_AUTHRSP", + "desc": "0110 승인응답 생성: 승인한도(5천만원) 초과 시 rc=61 거절,", + "in": [ + "T_CHANNEL", + "T_BIZDATE", + "T_ID1", + "T_AMOUNT" + ], + "out": [ + "T_STR2", + "T_ID2", + "T_RC" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -1983,6 +5346,41 @@ const SVC_SCREENS = [ "T_STR6" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_CAPRSP", + "desc": "0210 매입응답 처리: STAN 으로 원 0200 전문을 찾아", + "in": [ + "T_CHANNEL", + "T_BIZDATE", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_ID1", + "T_ID2" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_CAPTURE", + "desc": "0200 매입요청 전문 처리: MDR(bps) 수수료/정산액을 산출해", + "in": [ + "T_CHANNEL", + "T_STR3", + "T_BIZDATE", + "T_AMOUNT", + "T_ARG2" + ], + "out": [ + "T_RC", + "T_FEE", + "T_NET", + "T_ID1" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -1996,6 +5394,51 @@ const SVC_SCREENS = [ "T_STATUS" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_CHANCLOSE", + "desc": "채널 폐쇄: 채널을 DOWN 으로 내리고 세션키를 폐기하되,", + "in": [ + "T_CHANNEL" + ], + "out": [ + "T_RC", + "T_STATUS", + "T_COUNT" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_CHANHEALTH", + "desc": "채널 종합 건강점검: 상태(UP 여부) + 당일 오류율(bps) +", + "in": [ + "T_CHANNEL", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_STATUS", + "T_ARG3", + "T_ARG7", + "T_ARG8" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_CHANOPEN", + "desc": "채널 개통: DOWN 채널만 UP 으로 올리고 sign-on 시각을 기록.", + "in": [ + "T_CHANNEL" + ], + "out": [ + "T_RC", + "T_STATUS", + "T_STR5" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2008,6 +5451,22 @@ const SVC_SCREENS = [ "T_STATUS" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_CHANSWITCH", + "desc": "채널 절체: 주채널을 DOWN, 예비채널을 UP 으로 한 트랜잭션", + "in": [ + "T_CHANNEL", + "T_STR2" + ], + "out": [ + "T_RC", + "T_STR5", + "T_STR2", + "T_COUNT" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2051,6 +5510,37 @@ const SVC_SCREENS = [ "T_STR3" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_DIRECTADPT", + "desc": "구간 [s, s+n) 이 전부 십진 숫자인지 검사 (직연동 전문 필드 검증). */", + "in": [ + "T_STR3" + ], + "out": [ + "T_RC", + "T_STR1", + "T_AMOUNT", + "T_CHANNEL", + "T_COUNT" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_DUPCHK", + "desc": "중복전문 검출: 동일 STAN 이 당일 원장에 이미 몇 건인지 세어", + "in": [ + "T_BIZDATE", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG2" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2065,6 +5555,22 @@ const SVC_SCREENS = [ "T_STATUS" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_ERRLIST", + "desc": "오류전문 대기목록 요약: 당일 NACK(N) 전문의 건수, 금액합,", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_ID1", + "T_ID2", + "T_RC" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2081,6 +5587,66 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_ERRPURGE", + "desc": "오류전문 폐기: 재처리를 포기한 당일 NACK(N) 전문 중", + "in": [ + "T_BIZDATE", + "T_STR2" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_ERRRETRY", + "desc": "오류전문 단건 재처리: NACK(N) 된 전문을 rc=00/R 로 리셋하고", + "in": [ + "T_ID1" + ], + "out": [ + "T_RC", + "T_ID2" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_FROMISO", + "desc": "ISO -> 내부전문 변환: 고정길이 ISO 전문을 잘라", + "in": [ + "T_STR3" + ], + "out": [ + "T_RC", + "T_STR3", + "T_STR1", + "T_STR4", + "T_AMOUNT", + "T_COUNT" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_GAPSCAN", + "desc": "STAN 순번 갭 검출: 당일 최소~최대 순번 구간 폭에서 실제", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_COUNT", + "T_ARG6", + "T_RC" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2123,6 +5689,35 @@ const SVC_SCREENS = [ "T_ARG2" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_KEYROTATE", + "desc": "키 전환(교체): KEYVER 카운터를 올려 새 세대키 번호를 따고,", + "in": [ + "T_CHANNEL" + ], + "out": [ + "T_KEY1", + "T_ARG3", + "T_ARG2" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_KEYSTATUS", + "desc": "키 상태 조회: 현재 키버전(KEYVER)과 채널의 세션키/사인온", + "in": [ + "T_CHANNEL" + ], + "out": [ + "T_RC", + "T_KEY1", + "T_ARG3", + "T_ARG4" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2149,6 +5744,33 @@ const SVC_SCREENS = [ "T_KEY1" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_MACGEN", + "desc": "XOR-회전 8-hex MAC: 키버전을 시드로 섞어 세대별로 값이 달라진다. */", + "in": [ + "T_STR3" + ], + "out": [ + "T_RC", + "T_KEY1", + "T_ARG3" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_MACVERIFY", + "desc": "MG_MAC 과 동일 계열의 가산 체크섬 (검증측 독립 구현, 4-hex). */", + "in": [ + "T_ID1" + ], + "out": [ + "T_RC", + "T_KEY2" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2162,6 +5784,22 @@ const SVC_SCREENS = [ "T_STATUS" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_NETMGMT", + "desc": "0800 망관리 전문 라우터: 처리코드 001=사인온(UP),", + "in": [ + "T_CHANNEL", + "T_STR4", + "T_BIZDATE" + ], + "out": [ + "T_STR1", + "T_STR2", + "T_ID1" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2177,6 +5815,20 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_PANMASK", + "desc": "BIN(앞6)/뒤4 보존 마스킹. 12자리 미만 짧은 PAN 은 전체 마스킹. */", + "in": [ + "T_STR5", + "T_ID1" + ], + "out": [ + "T_RC", + "T_STR5" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2191,6 +5843,23 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_QDEPTH", + "desc": "송수신 큐 깊이 조회: 대기(Q)/송신(S)/완료(D)/실패(F) 상태별", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_ARG4", + "T_ARG7", + "T_RC" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2208,6 +5877,21 @@ const SVC_SCREENS = [ "T_ARG1" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_REASM", + "desc": "분할전문 재조립: 세그먼트 T_STR5~T_STR8 (최대 4편) 을 순서대로", + "in": [ + "T_ARG1" + ], + "out": [ + "T_RC", + "T_STR3", + "T_COUNT", + "T_ARG2" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2254,6 +5938,40 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_REVADVICE", + "desc": "0420 취소 어드바이스 수신: 통지성 전문이므로 무조건 수용(A),", + "in": [ + "T_CHANNEL", + "T_BIZDATE", + "T_STR3", + "T_AMOUNT", + "T_ARG1" + ], + "out": [ + "T_ID1", + "T_ARG1" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_REVERSAL", + "desc": "0400 취소요청 처리: 원전문을 STAN 으로 찾아 rc=17(고객취소)", + "in": [ + "T_CHANNEL", + "T_BIZDATE", + "T_ARG1", + "T_AMOUNT" + ], + "out": [ + "T_RC", + "T_ID1", + "T_ID2" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2268,6 +5986,69 @@ const SVC_SCREENS = [ "T_STR3" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_RTADD", + "desc": "라우팅 룰 등록: 채널+MTI -> 목적지 룰을 신규 채번해 추가.", + "in": [ + "T_CHANNEL", + "T_STR1", + "T_STR3", + "T_BIZDATE", + "T_ARG4" + ], + "out": [ + "T_RC", + "T_ID1" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_RTDROP", + "desc": "라우팅 룰 비활성: 채널+MTI 의 활성 룰을 전부 내려서", + "in": [ + "T_CHANNEL", + "T_STR1" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_RTLIST", + "desc": "라우팅 룰 열람 요약: 채널의 활성/비활성 룰 수와 최우선(최소)", + "in": [ + "T_CHANNEL" + ], + "out": [ + "T_STR3", + "T_ARG4", + "T_COUNT", + "T_ARG7", + "T_RC" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_RTSWITCH", + "desc": "라우팅 목적지 절체: 채널+MTI 활성 룰의 dest 를 새 목적지로", + "in": [ + "T_CHANNEL", + "T_STR1", + "T_STR3" + ], + "out": [ + "T_RC", + "T_STR5", + "T_COUNT" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2285,6 +6066,22 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_SEQAUDIT", + "desc": "채널 순번 감사: 채널이 기억하는 last_stan 과 원장의 실제", + "in": [ + "T_CHANNEL", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_ARG2", + "T_ARG6" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2295,6 +6092,41 @@ const SVC_SCREENS = [ "T_ARG2" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_SETTLEREQ", + "desc": "0500 정산집계 요청 생성: 당일 채널의 수신 0200 건수/금액을", + "in": [ + "T_CHANNEL", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_ID1", + "T_STR3" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_SETTLERSP", + "desc": "0510 정산집계 응답 대사: 상대기관이 보낸 건수/합계를 당일", + "in": [ + "T_CHANNEL", + "T_BIZDATE", + "T_COUNT", + "T_GROSS" + ], + "out": [ + "T_STR2", + "T_ARG1", + "T_AMT1", + "T_ID1", + "T_RC" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2330,6 +6162,71 @@ const SVC_SCREENS = [ "T_ARG1" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_STATDIR", + "desc": "송수신 방향별 통계: 당일 IN/OUT 각각의 건수와 금액합,", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_AMT1", + "T_ARG2", + "T_AMT2", + "T_ARG8", + "T_RC" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_STATHOUR", + "desc": "시간대별 통계: 요청 시(0~23)의 당일 건수/금액합과 함께", + "in": [ + "T_BIZDATE", + "T_ARG5" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_ARG5", + "T_ARG6" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_STATMTI", + "desc": "전문유형(MTI)별 당일 통계: 지정 MTI 의 건수/금액합/최대금액과", + "in": [ + "T_STR1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_AMT1", + "T_ARG8" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_STATRC", + "desc": "응답코드 분포 조회: 당일 정상(00)/거절 건수, 고유 거절코드", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG7", + "T_STR2", + "T_ARG6" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2358,6 +6255,79 @@ const SVC_SCREENS = [ "T_STATUS" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_THROTTLE", + "desc": "채널 트래픽 제어: 당일 수신량이 채널 한도(기본 10000건)의", + "in": [ + "T_CHANNEL", + "T_BIZDATE", + "T_ARG4" + ], + "out": [ + "T_COUNT", + "T_ARG4", + "T_ARG8", + "T_RC" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_TIMEOUT", + "desc": "전문 타임아웃 스윕: 임계초(TMOUTSEC, 기본 60초)를 넘겨도", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG5" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_TIMEOUTSET", + "desc": "타임아웃 임계초 설정: 운영자가 지정한 초(5~600 범위 강제)를", + "in": [ + "T_ARG5" + ], + "out": [ + "T_ARG5", + "T_RC" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_TOISO", + "desc": "내부전문 -> ISO 변환: 콜론 구분 내부포맷 \"MTI:처리코드:금액\" 을", + "in": [ + "T_STR3" + ], + "out": [ + "T_RC", + "T_STR3", + "T_STR6", + "T_STR1", + "T_AMOUNT" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_TPSCHECK", + "desc": "순간 처리율 점검: 최근 60초 유입 전문 수로 근사 TPS(x100)를", + "in": [], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG8", + "T_ARG6", + "T_RC" + ] + }, { "mod": "mg", "modnm": "전문게이트웨이", @@ -2372,6 +6342,184 @@ const SVC_SCREENS = [ "T_AMOUNT" ] }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_VAN1ADPT", + "desc": "VAN1 채널 어댑터: 파이프 구분 전문 \"V1|MTI|금액|처리코드\" 를", + "in": [ + "T_STR3" + ], + "out": [ + "T_RC", + "T_STR3", + "T_STR1", + "T_AMOUNT", + "T_CHANNEL" + ] + }, + { + "mod": "mg", + "modnm": "전문게이트웨이", + "svc": "MG_VAN2ADPT", + "desc": "VAN2 채널 어댑터: 키=값 세미콜론 전문 \"M=0200;A=1000;P=000000\"", + "in": [ + "T_STR3" + ], + "out": [ + "T_RC", + "T_STR3", + "T_STR1", + "T_AMOUNT", + "T_CHANNEL" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_ACCT_VERIFY", + "desc": "기능: 정산계좌 검증 실행 (형식 검사 + ACCT_VRF 검증 이력 적재).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_ACCT_VRF_HIST", + "desc": "기능: 정산계좌 검증 이력 조회 (누적 횟수 + 최근 결과/일시).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_ACCT_VRF_STAT", + "desc": "기능: 정산계좌 검증 전사 통계 (총 검증 수, 검증 대상 가맹점 수, 실패 건수).", + "in": [], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_APPLY_APPROVE", + "desc": "기능: 가입 심사 승인 (REVIEW -> ACTIVE, 기본 수수료율/한도 자동 개설).", + "in": [ + "T_MERCHANT", + "T_ARG1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_APPLY_REG", + "desc": "기능: 가맹점 가입 신청 접수 (심사 워크플로 시작, 상태 APPLIED).", + "in": [ + "T_MERCHANT", + "T_STR1", + "T_STR2", + "T_STR3" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_APPLY_REJECT", + "desc": "기능: 가입 심사 반려 (APPLIED/REVIEW -> REJECTED, 반려 사유 필수).", + "in": [ + "T_MERCHANT", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_APPLY_REVIEW", + "desc": "기능: 가입 신청 심사 개시 (APPLIED -> REVIEW, MCC 유효성 사전점검).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_ARG1" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_APPLY_STATUS", + "desc": "기능: 가입 심사 진행 상태 조회 (단계 코드 + 워크플로 이력 건수).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_COUNT" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_AUDIT_PURGE", + "desc": "기능: 감사 이력 보존기한 경과분 삭제 (삭제 내역 자체도 감사 기록으로 남김).", + "in": [ + "T_ARG1" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_AUDIT_RECENT", + "desc": "기능: 가맹점 최근 변경 감사 조회 (최신 이력 1건: 액션/변경값/메모/일자).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_AUDIT_TRAIL", + "desc": "기능: 가맹점 마스터 변경 감사 추적 (액션 필터별 이력 건수 + 발생 액션 종류 수).", + "in": [ + "T_MERCHANT", + "T_STR1" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2394,6 +6542,110 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_BIZNO_CHECK", + "desc": "기능: 사업자등록번호 검증 (10자리 형식 + 국세청 검증공식 checksum + 마스터 중복 사용 확인).", + "in": [ + "T_STR2" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_BIZNO_UPD", + "desc": "기능: 사업자등록번호 변경 (형식 검사 + 타 가맹점 중복 차단 + 변경 이력).", + "in": [ + "T_MERCHANT", + "T_STR2" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CHK_FEERATE", + "desc": "기능: 수수료율 마스터 정합성 점검 (활성 가맹점 요율 누락 / bps 범위 이탈 / 고아 요율).", + "in": [], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CHK_LIMIT", + "desc": "기능: 한도 마스터 정합성 점검 (활성 가맹점 한도 누락 + 한도 체계 역전 검출).", + "in": [], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CHK_TERM", + "desc": "기능: 단말 마스터 정합성 점검 (소속 가맹점 없는 고아 단말 + 폐업 가맹점의 가동 단말).", + "in": [], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CODEMAP_DEL", + "desc": "기능: 코드 매핑 삭제 (단건 또는 네임스페이스 전체 일괄 삭제).", + "in": [ + "T_KEY1", + "T_STR1" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CODEMAP_GET", + "desc": "기능: 코드 매핑 변환 조회 (네임스페이스의 원본 코드를 대상 코드로 변환).", + "in": [ + "T_KEY1", + "T_STR1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CODEMAP_SET", + "desc": "기능: 코드 매핑 등록/갱신 (외부코드->내부코드 변환표, MAP_<네임스페이스> 코드그룹 upsert).", + "in": [ + "T_KEY1", + "T_STR1", + "T_STR2" + ], + "out": [ + "T_RC" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2439,6 +6691,59 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CONTRACT_END", + "desc": "기능: 가맹 계약 해지 (CONT_END 이력 + 가맹점 상태 HOLD 전환).", + "in": [ + "T_MERCHANT", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CONTRACT_INQ", + "desc": "기능: 가맹 계약 현황 조회 (체결/갱신 횟수, 최종 만기일, 유효 여부).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG1" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CONTRACT_NEW", + "desc": "기능: 가맹 계약 체결 (기간 개월수로 만기일 산출, CONTRACT 이력 기록).", + "in": [ + "T_MERCHANT", + "T_ARG1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_CONTRACT_RENEW", + "desc": "기능: 가맹 계약 갱신 (직전 만기일 기준 개월수 연장).", + "in": [ + "T_MERCHANT", + "T_ARG1" + ], + "out": [ + "T_RC" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2501,6 +6806,49 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_GRADE_ADJUST", + "desc": "기능: 가맹점 등급 수동 보정 (산정 결과 예외 조정, 조정 사유 필수 감사 기록).", + "in": [ + "T_MERCHANT", + "T_STR2", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_GRADE_DIST", + "desc": "기능: 가맹점 등급 분포 조회 (최근 등급 통계일 기준 S/A/B/C 세분 현황).", + "in": [], + "out": [ + "T_RC", + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_ARG4", + "T_COUNT" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_GRADE_PREVIEW", + "desc": "기능: 가맹점 등급 산정 시뮬레이션 (누적 매출로 등급 산출, 마스터는 불변).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_GROSS", + "T_ARG1" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2561,6 +6909,20 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_MCC_INQ", + "desc": "기능: MCC 업종 조회 (명칭/위험도 + 해당 업종 활성 가맹점 수).", + "in": [ + "T_CATEGORY" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_COUNT" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2573,6 +6935,35 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_MCC_RISKLIST", + "desc": "기능: 고위험 MCC 현황 (기준 위험도 이상 MCC 수, 최고 위험도, 소속 활성 가맹점 수).", + "in": [ + "T_ARG1" + ], + "out": [ + "T_COUNT", + "T_ARG2", + "T_ARG3", + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_MCC_UPD", + "desc": "기능: MCC 업종 마스터 변경 (명칭 선택 변경 + 위험도 1~5 재설정).", + "in": [ + "T_CATEGORY", + "T_STR1", + "T_ARG1" + ], + "out": [ + "T_RC" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2598,6 +6989,20 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_MERCH_CLOSE", + "desc": "기능: 가맹점 폐업 처리 (DELETED 전환 + 전체 단말 폐기 + 하위 관계 해제).", + "in": [ + "T_MERCHANT", + "T_REASON" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2629,6 +7034,20 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_MERCH_PAUSE", + "desc": "기능: 가맹점 휴업 처리 (ACTIVE -> HOLD, 가동 단말 일괄 SUSPENDED).", + "in": [ + "T_MERCHANT", + "T_REASON" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2642,6 +7061,19 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_MERCH_RESUME", + "desc": "기능: 가맹점 영업 재개 (HOLD -> ACTIVE, 정지 단말 재가동).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2677,6 +7109,126 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_PARTNER_INQ", + "desc": "기능: 제휴사 마스터 단건 조회 (명칭/상태/표시순서).", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC", + "T_ARG1" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_PARTNER_REG", + "desc": "기능: 제휴사 마스터 등록/갱신 (공통코드 PARTNER 그룹 upsert).", + "in": [ + "T_KEY1", + "T_STR1", + "T_ARG1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_PARTNER_STATUS", + "desc": "기능: 제휴사 사용 상태 변경 (ACTIVE/INACTIVE 만 허용).", + "in": [ + "T_KEY1", + "T_STATUS" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_RATE_APPROVE", + "desc": "기능: 수수료율 변경 승인 (대기 요청을 실제 요율 마스터에 반영, RATE_APV 이력).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_AMT1" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_RATE_REJECT", + "desc": "기능: 수수료율 변경 반려 (대기 요청을 반려 처리, 요율 마스터는 불변).", + "in": [ + "T_MERCHANT", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_RATE_REQ", + "desc": "기능: 수수료율 변경 요청 접수 (요율 승인 워크플로 1단계, RATE_REQ 이력 대기건 생성).", + "in": [ + "T_MERCHANT", + "T_STR2", + "T_REASON", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_AMT1" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_REL_LINK", + "desc": "기능: 대표가맹점-하위가맹점 관계 연결 (MERCH_REL 코드그룹, 하위=code, 대표=code_name).", + "in": [ + "T_MERCHANT", + "T_KEY1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_REL_LIST", + "desc": "기능: 대표가맹점의 하위가맹점 현황 (활성 연결 수 + 최초 하위 ID).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_REL_UNLINK", + "desc": "기능: 대표가맹점-하위가맹점 관계 해제 (관계 행 INACTIVE 전환 + 이력).", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2710,6 +7262,59 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_TERM_CNT", + "desc": "기능: 가맹점별 단말기 보유 현황 집계 (전체/가동/폐기 건수).", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_TERM_MOVE", + "desc": "기능: 단말기 가맹점간 이동 (소속 변경 + 반입 이력).", + "in": [ + "T_KEY1", + "T_MERCHANT" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_TERM_SCRAP", + "desc": "기능: 단말기 폐기 (SCRAPPED 전환 + 폐기 사유 이력).", + "in": [ + "T_KEY1", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "mm", + "modnm": "마스터", + "svc": "MM_TERM_SUSPEND", + "desc": "기능: 단말기 사용정지/재개 토글 (ACTIVE<->SUSPENDED).", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC" + ] + }, { "mod": "mm", "modnm": "마스터", @@ -2722,6 +7327,19 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_ACCTCHANGE", + "desc": "지급계좌 변경: 미지급(R/H/J) 건만 대상. 새 계좌가 동일 가맹점의", + "in": [ + "T_ID1", + "T_KEY1" + ], + "out": [ + "T_RC" + ] + }, { "mod": "py", "modnm": "지급", @@ -2735,6 +7353,21 @@ const SVC_SCREENS = [ "T_AMT1" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_ACCTLIST", + "desc": "가맹점 지급계좌 현황: 가맹점의 모든 지급계좌를 커서로 훑어 계좌/은행/", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_GROSS", + "T_RC" + ] + }, { "mod": "py", "modnm": "지급", @@ -2767,6 +7400,70 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_ADVCALC", + "desc": "선지급 가능액 산정: 가맹점 미지급(R/A) 잔액에 지급률(bps)을 곱한 한도에서", + "in": [ + "T_MERCHANT", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_GROSS", + "T_AMT1", + "T_AMT2", + "T_ARG1" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_ADVREPAY", + "desc": "선지급 상환: ADVANCE 채널 지급건의 잔여 net 에서 상환액을 차감하고", + "in": [ + "T_ID1", + "T_AMOUNT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_AMOUNT", + "T_NET" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_APPRBULK", + "desc": "소액 일괄 승인: 당일 요청(R)건 중 net 이 승인한도 이하인 건을 한 번의", + "in": [ + "T_BIZDATE", + "T_AMT1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG1", + "T_AMT1" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_APPRLIMIT", + "desc": "고액 승인 통제: 한도 초과 건은 승인자 지정 없이는 거절(2). 승인 시", + "in": [ + "T_ID1", + "T_KEY2", + "T_AMT1" + ], + "out": [ + "T_RC", + "T_NET" + ] + }, { "mod": "py", "modnm": "지급", @@ -2780,6 +7477,36 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_APPRREQ", + "desc": "승인 대기 큐 조회: 당일 요청(R)건을 금액 내림차순 상위 N건 커서로 훑어", + "in": [ + "T_BIZDATE", + "T_ARG1" + ], + "out": [ + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_BANKFILE", + "desc": "은행별 지급파일 생성: 지정 은행코드의 승인(A) 지급건만 py_account 조인으로", + "in": [ + "T_KEY1", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ID2", + "T_GROSS" + ] + }, { "mod": "py", "modnm": "지급", @@ -2811,6 +7538,24 @@ const SVC_SCREENS = [ "T_ADJ" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_CANCELHIST", + "desc": "취소/환입 이력 집계: 가맹점 당일 취소(C)와 역분개/환입(V)을 각각", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_AMT1", + "T_ARG2", + "T_AMT2", + "T_COUNT", + "T_RC" + ] + }, { "mod": "py", "modnm": "지급", @@ -2833,6 +7578,79 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_DORMANT", + "desc": "휴면 가맹점 검출: 최근 N일간 지급 이력이 전혀 없는 정상(ACTIVE) 가맹점", + "in": [ + "T_ARG1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG1" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_DORMSTOP", + "desc": "휴면 가맹점 지급정지: 대상 가맹점의 정상 계좌를 DORMANT 로 내리고", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_COUNT" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_FAILRATE", + "desc": "지급 실패율 산출: 가맹점 당일 전체 지급건 대비 반려(J) 비중을 bps 로", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG1", + "T_ARG2" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_FAILSTAT", + "desc": "실패사유별 통계: 당일 반려(J)건을 사유별 커서로 집계해 상위 8개 사유를", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_FAILTOP", + "desc": "최다 실패 가맹점: 당일 반려(J)건이 가장 많은 가맹점 1곳을 ORDER BY +", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_GROSS" + ] + }, { "mod": "py", "modnm": "지급", @@ -2848,6 +7666,52 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_FEEMIN", + "desc": "최소수수료 보정: 당일 요청(R) 건 중 수수료가 최소수수료 미만인 건을", + "in": [ + "T_BIZDATE", + "T_AMT1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_AMT1" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_FEERATE", + "desc": "수수료율 재적용: 가맹점 계약 요율(merchant.mdr_bps)을 조인으로 가져와", + "in": [ + "T_ID1" + ], + "out": [ + "T_RC", + "T_FEE", + "T_NET", + "T_ARG1" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_FEEWAIVE", + "desc": "수수료 면제: 미지급(R/A) 건의 수수료를 0 으로 하고 net 을 amount 로 복원.", + "in": [ + "T_ID1", + "T_REASON", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_FEE", + "T_NET" + ] + }, { "mod": "py", "modnm": "지급", @@ -2862,6 +7726,52 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_FILEREBUILD", + "desc": "지급파일 재생성: 아직 미확정(G)인 파일 헤더를 실제 편성 상세 기준으로", + "in": [ + "T_ID2" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_GROSS" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_FILESTAT", + "desc": "지급파일 현황: 당일 파일을 생성(G)/확정(C) 상태별로 세고 총 레코드수와", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_FILEVERIFY", + "desc": "지급파일 검증: py_file 헤더의 건수/총액과 실제 편성된 py_payout 상세 집계를", + "in": [ + "T_ID2" + ], + "out": [ + "T_ARG1", + "T_AMT1", + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, { "mod": "py", "modnm": "지급", @@ -2886,6 +7796,48 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_HOLDAGING", + "desc": "장기 보류 점검: 보류(H) 상태로 N일 이상 경과한 건수/금액과 가장 오래된", + "in": [ + "T_ARG1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_GROSS", + "T_ARG1" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_HOLDLIST", + "desc": "보류 사유별 집계: 당일 보류(H)건을 사유별로 커서 집계해 상위 10개 사유를", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_HOLDREASON", + "desc": "사유부 지급보류: 요청(R)/승인(A) 건을 보류(H)로 전이하면서 보류 사유를", + "in": [ + "T_ID1", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, { "mod": "py", "modnm": "지급", @@ -2915,6 +7867,50 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_LEDGERSUM", + "desc": "지급원장 유형별 합계: 당일 py_ledger 를 기표유형별 커서 집계해", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_LIMITCHK", + "desc": "지급한도 점검: 가맹점 일한도(merchant.daily_limit) 대비 당일 확정성", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_AMT3", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_LIMITHOLD", + "desc": "한도 초과 자동 보류: 당일 사용액이 일한도를 넘은 가맹점을 HAVING 커서로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ARG1", + "T_COUNT" + ] + }, { "mod": "py", "modnm": "지급", @@ -2929,6 +7925,22 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_MERGE", + "desc": "지급 합산: 가맹점 당일 요청(R) STD 건이 2건 이상이면 전부 취소(C) 표시 후", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ID2", + "T_NET" + ] + }, { "mod": "py", "modnm": "지급", @@ -2942,6 +7954,64 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_NETRECALC", + "desc": "순지급액 정합 검증: 당일 건 중 amount - fee != net 인 불일치 건수와", + "in": [ + "T_BIZDATE", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_DELTA", + "T_ARG2" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_NOTIFYEMAIL", + "desc": "지급내역 이메일 통지(시뮬): 가맹점의 당일 지급완료(P) 건수/합계를 집계해", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_GROSS" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_NOTIFYPLAN", + "desc": "지급 예정 통보: 지정 실행일의 예약(S) 건을 py_payout 과 조인 집계해", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_GROSS" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_NOTIFYSMS", + "desc": "지급완료 SMS 통지(시뮬): 지급완료(P)건만 대상. 가맹점/금액을 조회해", + "in": [ + "T_ID1" + ], + "out": [ + "T_RC", + "T_NET" + ] + }, { "mod": "py", "modnm": "지급", @@ -2955,6 +8025,20 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_REFUND", + "desc": "지급 환입: 지급완료(P) 건을 역분개(V)하며 지급계좌에 net 을 복원하고", + "in": [ + "T_ID1", + "T_REASON" + ], + "out": [ + "T_RC", + "T_NET" + ] + }, { "mod": "py", "modnm": "지급", @@ -2992,6 +8076,19 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_REPAYNEW", + "desc": "계좌 변경 재지급: 반려(J)된 지급건을 가맹점의 최신 정상(ACTIVE) 계좌로", + "in": [ + "T_ID1" + ], + "out": [ + "T_RC", + "T_ID2" + ] + }, { "mod": "py", "modnm": "지급", @@ -3040,6 +8137,19 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_SCHEDCANCEL", + "desc": "지급 예약 취소: 아직 미실행(S)인 예약만 DELETE 로 제거한다. 이미", + "in": [ + "T_ID1" + ], + "out": [ + "T_RC", + "T_ID2" + ] + }, { "mod": "py", "modnm": "지급", @@ -3053,6 +8163,47 @@ const SVC_SCREENS = [ "T_ID2" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_SEIZE", + "desc": "압류/가압류 등록: 대상 계좌를 SEIZED 로 전환하고 그 계좌의 미지급(R/A)", + "in": [ + "T_KEY1", + "T_REASON" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_SEIZECHK", + "desc": "압류계좌 지급 차단 점검: 당일 미지급(R/A)건 중 압류(SEIZED) 계좌로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_SEIZEREL", + "desc": "압류 해제: SEIZED 계좌를 ACTIVE 로 되돌리고, 압류 사유로 보류됐던", + "in": [ + "T_KEY1" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, { "mod": "py", "modnm": "지급", @@ -3069,6 +8220,67 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_SPLITRATIO", + "desc": "비율 분할: 요청(R) 건을 bps 비율로 두 건의 SPLIT 지급건으로 쪼개고", + "in": [ + "T_ID1", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_ID2", + "T_ARG2", + "T_AMT1", + "T_AMT2" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_STATBANK", + "desc": "은행별 지급 통계: 당일 지급건을 py_account 조인으로 은행코드별 커서", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_STATCHAN", + "desc": "채널별 지급 통계: 당일 지급건을 채널(STD/SPLIT/BULK/ADVANCE/URGENT)별", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_STATDAILY", + "desc": "일자별 지급 추이: 시작일부터 N일 구간을 일자별 커서 집계해 occurrence 로", + "in": [ + "T_BIZDATE", + "T_ARG1" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_AMT2", + "T_RC" + ] + }, { "mod": "py", "modnm": "지급", @@ -3085,6 +8297,22 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_STATRANGE", + "desc": "금액 구간별 분포: 당일 지급건을 소액(<10만)/중액(10만~100만)/고액(100만+)", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_COUNT", + "T_RC" + ] + }, { "mod": "py", "modnm": "지급", @@ -3109,6 +8337,24 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_URGENT", + "desc": "긴급지급: 긴급 수수료(0.5%) 공제 후 잔액 확인 -> 즉시 지급완료(P) 건을", + "in": [ + "T_MERCHANT", + "T_KEY1", + "T_AMOUNT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID1", + "T_FEE", + "T_NET" + ] + }, { "mod": "py", "modnm": "지급", @@ -3121,6 +8367,35 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_XFERMATCH", + "desc": "이체결과 대사(단건): 지급건 net 과 py_ledger 의 XFER 기표 합계를 비교해", + "in": [ + "T_ID1" + ], + "out": [ + "T_NET", + "T_AMT1", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "py", + "modnm": "지급", + "svc": "PAY_XFERMISS", + "desc": "이체 기표 누락 검출: 당일 지급완료(P)인데 py_ledger 에 XFER 기표가 없는", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3135,6 +8410,21 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_4WAY", + "desc": "승인-매입-입금-지급 4-way 대사: 승인(approval), 입금(settlement), 지급", + "in": [ + "T_PURCHASE_ID", + "T_NET", + "T_BIZDATE" + ], + "out": [ + "T_ID1", + "T_RC" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3150,6 +8440,21 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_AGEING", + "desc": "미대사 에이징: 미해결 예외를 당일 / 최근 7일 / 7일 초과 세 버킷으로 나눠", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_COUNT" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3194,6 +8499,34 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_AUDITLOG", + "desc": "대사 감사 로그 기록: 호출 서비스명과 사유를 받아 원장에 RC_AUDIT 증적을", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE", + "T_SVCNAME", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_AUDITTRAIL", + "desc": "대사 감사추적 조회: 특정 매입의 대사 이력 건수, 대사 관련 원장 증적 건수,", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_ARG1", + "T_ARG2" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3206,6 +8539,36 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_BYCATEGORY", + "desc": "업종(카테고리)별 대사 세분: 요청 업종의 당일 MATCHED 건수와 EXCEPTION", + "in": [ + "T_BIZDATE", + "T_CATEGORY" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_BYCHANNEL", + "desc": "채널별 대사 세분 집계: 요청 채널(POS/EDC/EDI/FOREIGN)의 당일 MATCHED 대사", + "in": [ + "T_BIZDATE", + "T_CHANNEL" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_FEE" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3245,6 +8608,64 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_BYMGROUP", + "desc": "가맹점그룹별 대사 세분: 가맹점 ID prefix (그룹 키) 로 묶어 당일 MATCHED", + "in": [ + "T_BIZDATE", + "T_KEY1" + ], + "out": [ + "T_COUNT", + "T_GROSS" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_CANCELREC", + "desc": "취소분 대사: 매입 금액과 취소(ac_cancel) 누적 금액을 비교하여 전액 취소면", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_ID1" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_CARRYOVER", + "desc": "이월 대사: 이전 영업일의 EXCEPTION 대사 건들을 다음 영업일 HOLD 건으로", + "in": [ + "T_STR1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_CLOSEDAY", + "desc": "일마감 대사 확정: 미해결 예외나 보류가 남아 있으면 마감을 거부하고, 모두", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_RC", + "T_COUNT", + "T_GROSS" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3281,6 +8702,36 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_DEPMATCH", + "desc": "입금(정산) 대사: 매입 net 금액과 settlement 입금 합계를 비교하여 일치 시", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_ID1" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_DIFFCAUSE", + "desc": "대사 차이 원인분석: 당일 차이를 (1) 수수료 계산 불일치, (2) 취소 존재,", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_COUNT" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3294,6 +8745,60 @@ const SVC_SCREENS = [ "T_DELTA" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_DIFFTREND", + "desc": "대사 차이 추이 분석: 당일 평균 |차이금액| 과 직전 7영업일 평균을 비교하여", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_RC" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_DUPCHK", + "desc": "중복 대사 검출: 당일 대사 로그에서 같은 매입이 두 번 이상 대사된 매입", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_EDIRECON", + "desc": "EDI 대사: 가맹점의 당일 EDI 전문 금액 합계와 매입(취소 제외) 금액 합계를", + "in": [ + "T_BIZDATE", + "T_MERCHANT" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_DELTA", + "T_COUNT" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_ESCALATE", + "desc": "예외 에스컬레이션: 미해결 예외가 걸린 매입의 당일 WARN 경보들을 IN-서브", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3308,6 +8813,77 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_FEEDIFF", + "desc": "수수료 차이 일괄 집계: 당일 매입 전체에서 MDR 기대치와 다른 수수료 건수와", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_DELTA" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_FEERECON", + "desc": "수수료 대사: 매입 수수료를 가맹점 MDR(bp) 기준 기대 수수료와 비교한다.", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_FEE", + "T_DELTA", + "T_ID1" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_FXRATECHK", + "desc": "외화 환율 검증: 최신 외화 거래의 원통화 금액 x 환율(bp) 재계산 값이 저장된", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_FX_AMT", + "T_AMT1", + "T_AMT2", + "T_RC" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_FXRECON", + "desc": "외화 대사: 외화 거래(ac_fx)의 원화 환산 금액 합계와 매입 금액을 비교하여", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_ID1" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_GAPSCAN", + "desc": "대사 누락 스캔: 대사완료(M) 상태인데 대사 로그가 하나도 없는 당일 매입", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ID1" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3320,6 +8896,49 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_HOLDAGE", + "desc": "보류 장기화 조회: 기준일 이전부터 남아 있는 HOLD 대사 건수와 가장 오래된", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_HOLDRSN", + "desc": "대사 보류 사유별 현황: 특정 사유의 당일 예외 건수를 미해결/해결로 나누어", + "in": [ + "T_BIZDATE", + "T_REASON" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_COUNT" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_INSTRECON", + "desc": "할부 대사: 할부 개월수(months) 대비 등록된 회차(seq_no) 수를 비교하여", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_INSTALL_N", + "T_COUNT", + "T_GROSS", + "T_ID1" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3332,6 +8951,65 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_LIMITCHK", + "desc": "가맹점 한도 대비 대사: 가맹점 일한도(daily_limit)와 당일 매입(취소 제외)", + "in": [ + "T_BIZDATE", + "T_MERCHANT" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_RC" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_MANAPPRV", + "desc": "수작업 대사 승인: HOLD 상태의 MANREG 수작업 대사 1건을 MATCHED 로 확정하고", + "in": [ + "T_ID1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_PURCHASE_ID" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_MANREG", + "desc": "수작업 대사 등록: 담당자가 입력한 수작업 대사를 HOLD 상태(승인 대기)로", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE", + "T_REASON", + "T_NET" + ], + "out": [ + "T_ID1" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_MANREJECT", + "desc": "수작업 대사 반려: 승인 대기(HOLD) 중인 수작업 대사를 ROLLBACK 처리하고,", + "in": [ + "T_ID1", + "T_BIZDATE", + "T_REASON" + ], + "out": [ + "T_ID2", + "T_PURCHASE_ID" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3346,6 +9024,47 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_NOTIFY", + "desc": "대사 결과 통보: 당일 대사 요약(rc_summary)을 읽어 통보 메시지를 구성하고,", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_GROSS" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_ORPHAN", + "desc": "고아 매입 검출: 승인(approval) 이 전혀 없는 당일 매입을 NOT EXISTS 로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_PARTACQ", + "desc": "부분매입 대사: 분할 매입(ac_install 회차) 합계가 원매입 금액과 일치하는지", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_AMT2", + "T_ID1" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3360,6 +9079,20 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_PAYMATCH", + "desc": "지급 대사: 매입 net 대비 원장(ledger) SETTLE/XFER 지급 합계를 비교하고,", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_DELTA", + "T_ID1" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3372,6 +9105,35 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_RATEBYISS", + "desc": "발급사별 대사율: 요청 발급사의 당일 대사 로그를 매입과 조인하여 총건수와", + "in": [ + "T_BIZDATE", + "T_ISSUER" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_RATESTAT", + "desc": "대사율 통계: 당일 전체 대사 로그 대비 MATCHED 비율을 bp(1/10000) 단위로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3394,6 +9156,18 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_REOPEN", + "desc": "마감 재개: 확정(CONFIRMED)된 당일 대사 건을 다시 MATCHED 로 되돌려 마감을", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3404,6 +9178,34 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_RERUN", + "desc": "임계초과 재대사 실행: 임계값을 넘는 당일 차이 건들을 커서로 순회하며", + "in": [ + "T_AMT1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_RETRO", + "desc": "소급 대사: 과거 영업일에 보류/예외로 남은 특정 매입의 대사를 새 영업일로", + "in": [ + "T_PURCHASE_ID", + "T_NET", + "T_STR1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3428,6 +9230,21 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_SNAPSHOT", + "desc": "대사 스냅샷: 당일 대사 로그의 총건수/MATCHED 건수/MATCHED 금액을 CASE", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_GROSS" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3451,6 +9268,20 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_SUMCHK", + "desc": "요약-상세 정합성 검증: rc_summary 의 확정 matched 건수와 대사 로그 실측", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_RC" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3464,6 +9295,33 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_TAXRECON", + "desc": "세금계산서 대사: 발행된 세금계산서(공급가+부가세) 합계와 매입 금액을", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_DELTA", + "T_ID1" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_THRESHOLD", + "desc": "임계초과 검출: 당일 차이 중 |차이금액| 이 임계값을 초과하는 건수를 세고,", + "in": [ + "T_AMT1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3479,6 +9337,20 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_TOPDIFF", + "desc": "차이 상위 조회: 당일 |차이금액| 의 최대값과 평균값을 구한 뒤 평균을 넘는", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_COUNT" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3489,6 +9361,48 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_VOIDREC", + "desc": "VOID 매입 대사: 매입 상태가 V(void) 인지와 VOID 유형 취소 증빙 존재를", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_ID1", + "T_RC" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_WHTRECON", + "desc": "원천세 대사: 매입의 원천징수(ac_wht) 세액 합계가 정산 net 금액을 넘지", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_TAX", + "T_NET", + "T_RC", + "T_ID1" + ] + }, + { + "mod": "rc", + "modnm": "대사", + "svc": "RC_ZEROAMT", + "desc": "이상금액 검출: 당일 매입 중 금액 0원 또는 음수 net 인 이상 거래를 찾아", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT" + ] + }, { "mod": "rc", "modnm": "대사", @@ -3516,6 +9430,20 @@ const SVC_SCREENS = [ "T_SETTLE_ID" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_ADJHIST", + "desc": "조정 이력 조회: 매입에 걸린 정산 조정을 최신순 커서로 순회해 건수·증감", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_DELTA" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3547,6 +9475,18 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_APPROVE", + "desc": "정산 승인 워크플로: 최신 정산 상태를 검증해 CONFIRMED 인 건만 APPROVED 로", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3602,6 +9542,52 @@ const SVC_SCREENS = [ "T_DELTA" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_CAPCHECK", + "desc": "수수료 상한 도달 점검: 수수료가 상한을 초과하는지 판정만 하고 초과분을", + "in": [ + "T_FEE", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_DELTA", + "T_AMT1" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_CARRYOVER", + "desc": "이월 정산: 대사완료(M) 상태이지만 정산 레코드가 없는 매입(NOT EXISTS)을", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ID1", + "T_NET" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_CHNLSTAT", + "desc": "채널별 정산 통계 세분: 정산-매입 조인 커서로 채널(POS/EDC/EDI/FOREIGN)별", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_GROSS", + "T_AMT1" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3612,6 +9598,113 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_CXLRECALC", + "desc": "정산 취소분 재계산: ac_cancel 누계를 매입 net 에서 차감해 정산 net 을", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_NET", + "T_DELTA" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_CYCLED1", + "desc": "D+1 정산주기: 당일 SETTLED 정산 net 합계를 산출해 익영업일(pay_date=D+1)", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID1", + "T_NET" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_CYCLED2", + "desc": "D+2 정산주기: 당일 CONFIRMED 확정 정산만 집계해 2영업일 뒤(D+2, 주말 스킵)", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ID1", + "T_NET" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_CYCLEMON", + "desc": "월간 정산주기: 대상 월(YYYY-MM)의 가맹점 정산 net 을 합산해 지급일 기준으로", + "in": [ + "T_MERCHANT", + "T_BIZDATE", + "T_KEY1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ID1", + "T_GROSS" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_CYCLEWEEK", + "desc": "주간 정산주기: 주 종료일 기준 직전 7일(취소 제외) 정산 net 을 합산해", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID1", + "T_COUNT", + "T_GROSS" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_DAILYCLOSE", + "desc": "정산 일마감: 당일 정산·지급·조정 건수를 각각 검증 집계하고 정산 net 총액으로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_GROSS" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_DEBTCOLLECT", + "desc": "채권 회수 정산: 회수 입금액을 양수 조정(COLLECT)으로 기록하고 원장에 COLLECT", + "in": [ + "T_AMOUNT", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_ADJ", + "T_DELTA" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3630,6 +9723,22 @@ const SVC_SCREENS = [ "T_AMOUNT" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_DEPOSITCUT", + "desc": "보증금 차감: 보증금 납부 예정액과 SCHEDULED 지급 가용액 중 작은 금액을", + "in": [ + "T_AMT1", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_DELTA", + "T_ADJ" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3643,6 +9752,88 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_DISPUTE", + "desc": "정산 이의신청 처리: 대상 매입의 가맹점을 역추적해 예정 지급을 보류(HELD)로", + "in": [ + "T_DELTA", + "T_PURCHASE_ID", + "T_BIZDATE", + "T_REASON" + ], + "out": [ + "T_ADJ" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_DTLSUM", + "desc": "정산 상세 합계: 가맹점+수수료종류+영업일 단위로 상세 라인의 건수·합계·", + "in": [ + "T_MERCHANT", + "T_STR1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_AMT1", + "T_AMT2", + "T_AMT3" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_EDIRECON", + "desc": "EDI 정산 대사: 가맹점 당일 EDI 문서(ac_edi) 청구 합계와 정산 집계", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_NET", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_FEEPREVIEW", + "desc": "수수료 사전 미리보기: 가맹점 등록 요율(mdr_bps)로 수수료·부가세·원천세와", + "in": [ + "T_AMOUNT", + "T_MERCHANT" + ], + "out": [ + "T_FEE", + "T_TAX", + "T_AMT1", + "T_NET", + "T_RC" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_FEERATIO", + "desc": "수수료율 실효 검증: 매입-가맹점 조인 한 번으로 실제 부과 수수료의 실효", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_DELTA", + "T_RC" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3656,6 +9847,39 @@ const SVC_SCREENS = [ "T_AMOUNT" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_FXSETTLE", + "desc": "해외 매입 정산: 매입에 걸린 외화 내역(ac_fx)의 원화 환산액을 합산해 FX", + "in": [ + "T_SETTLE_ID", + "T_PURCHASE_ID", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_AMOUNT", + "T_COUNT" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_GRPSETTLE", + "desc": "가맹점그룹 통합정산: 그룹 prefix(LIKE) 소속 가맹점별 정산 net 을 커서로", + "in": [ + "T_KEY1", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ID1", + "T_COUNT", + "T_GROSS" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3667,6 +9891,34 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_HOLDAMT", + "desc": "금액 조건부 보류: 임계액(threshold) 이상의 SCHEDULED 지급을 집합 UPDATE 로", + "in": [ + "T_AMT1", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_ISSUERSTAT", + "desc": "발급사별 수수료 통계 세분: 당일 정산완료(S) 매입을 발급사(issuer) 단위로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_FEE" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3679,6 +9931,36 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_LEDGERSNAP", + "desc": "정산 스냅샷: 당일 정산 건수·net 총액을 집계해 원장에 STSNAP 스냅샷 엔트리로", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ID1", + "T_COUNT", + "T_GROSS" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_LIMITCHK", + "desc": "가맹점 한도 점검: 가맹점 일 한도(daily_limit)와 당일 매입 누계(취소 제외)를", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_GROSS", + "T_DELTA", + "T_RC" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3691,6 +9973,41 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_MERCHRATE", + "desc": "가맹점 개별 요율 적용: 구간요율표 대신 가맹점 마스터의 계약 요율(mdr_bps)로", + "in": [ + "T_SETTLE_ID", + "T_PURCHASE_ID", + "T_AMOUNT", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_FEE", + "T_RC" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_MINFEE", + "desc": "최저 수수료 보정: 산출 수수료가 최저 수수료에 미달하면 부족분을 ADJ 정산", + "in": [ + "T_SETTLE_ID", + "T_PURCHASE_ID", + "T_FEE", + "T_ARG1", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_FEE", + "T_DELTA" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3704,6 +10021,20 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_NEGSWEEP", + "desc": "음수 정산 정리: 당일 net 이 음수인 SETTLED 정산의 합계를 먼저 파악한 뒤", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3732,6 +10063,23 @@ const SVC_SCREENS = [ "T_ADJ" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_PARTIAL", + "desc": "부분정산: 매입 net 에서 기정산 합계를 뺀 잔여 한도 내에서 요청 금액만큼", + "in": [ + "T_PURCHASE_ID", + "T_AMOUNT", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_NET", + "T_SETTLE_ID" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3743,6 +10091,53 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_PAYRETRY", + "desc": "지급 재시도: 이체 실패 등으로 CANCELLED 된 지급을 익영업일 지급일로 갱신해", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_PAYSTATUS", + "desc": "지급 상태 조회: 가맹점의 가장 최근 지급 레코드에서 상태·금액·지급 예정일을", + "in": [ + "T_MERCHANT" + ], + "out": [ + "T_RC", + "T_NET" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_PROMOCALC", + "desc": "프로모션/이벤트 정산: 이벤트 코드와 할인율(%)을 받아 수수료 할인액을 계산하고", + "in": [ + "T_SETTLE_ID", + "T_PURCHASE_ID", + "T_FEE", + "T_ARG1", + "T_BIZDATE", + "T_STR1" + ], + "out": [ + "T_RC", + "T_DELTA", + "T_ADJ", + "T_FEE" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3761,6 +10156,21 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_RATESIM", + "desc": "요율 시뮬레이션: 요율표(st_fee_rate)의 전 구간을 커서로 순회하며 입력 금액에", + "in": [ + "T_AMOUNT", + "T_STR1" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_FEE" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3773,6 +10183,21 @@ const SVC_SCREENS = [ "T_NET" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_RECVOFFSET", + "desc": "미수금 상계: 매입 조인으로 가맹점의 누적 미수금(음수 조정 절대값)을 산출하고", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_AMOUNT", + "T_AMT1" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3800,6 +10225,19 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_REOPEN", + "desc": "정산 마감 해제: 당일 마감 원장(STCLOSE)을 삭제해 마감을 철회한다.", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_RC" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3813,6 +10251,54 @@ const SVC_SCREENS = [ "T_GROSS" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_RERUN", + "desc": "정산 재실행: 해당 매입의 정산 상세를 전량 삭제하고 매입 원본(fee)을 기준으로", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_COUNT", + "T_FEE" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_RETRORATE", + "desc": "요율 소급적용: 신규 요율(bps)로 매입 금액의 수수료를 재계산하고, 기계상된", + "in": [ + "T_SETTLE_ID", + "T_ARG1", + "T_PURCHASE_ID", + "T_BIZDATE", + "T_STR1" + ], + "out": [ + "T_RC", + "T_DELTA", + "T_ADJ", + "T_FEE" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_ROUNDING", + "desc": "원단위 절사: 최신 정산 net 의 10원 미만 끝자리를 절사해 갱신하고 절사분을", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_DELTA", + "T_ADJ", + "T_NET" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3828,6 +10314,38 @@ const SVC_SCREENS = [ "T_COUNT" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_SPLITEVEN", + "desc": "분할 지급 잔여 보정: net 을 n 회로 균등 분할하되 나눗셈 끝수를 마지막", + "in": [ + "T_NET", + "T_INSTALL_N", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_AMT1", + "T_AMT2" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_STATDETAIL", + "desc": "정산 통계 세분: 당일 정산 상세를 수수료 종류(fee_type)별로 분해 집계하는", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_GROSS", + "T_AMT1" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3852,6 +10370,66 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_TAXINVCHK", + "desc": "세금계산서 대사: 가맹점 당일 세금계산서(ac_tax_invoice) 공급가·세액 합계와", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_GROSS", + "T_AMT1", + "T_TAX", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_TIERINCENT", + "desc": "구간별 인센티브: 가맹점 일 거래대금(gross) 구간에 따라 인센티브 요율을", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_AMOUNT", + "T_ADJ" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_TOPMERCH", + "desc": "상위 가맹점 조회: 당일 정산 net 합계 1위 가맹점을 GROUP BY + ORDER BY", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_GROSS" + ] + }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_UNPAIDSUM", + "desc": "미지급 집계: 기준일 이전이 지급 예정일인데 아직 완료(PAID)되지 않은", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_GROSS" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3882,6 +10460,22 @@ const SVC_SCREENS = [ "T_TAX" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_VATREPORT", + "desc": "부가세 신고자료: 대상 월의 정산 상세 VAT 합계와 세금계산서(ac_tax_invoice)", + "in": [ + "T_KEY1" + ], + "out": [ + "T_TAX", + "T_COUNT", + "T_GROSS", + "T_AMT1", + "T_DELTA" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3898,6 +10492,22 @@ const SVC_SCREENS = [ "T_TAX" ] }, + { + "mod": "st", + "modnm": "정산/수수료", + "svc": "ST_WHTREPORT", + "desc": "원천세 명세: 가맹점의 대상 월 정산 상세(WHT) 합계와 원천 대장(ac_wht) 합계를", + "in": [ + "T_MERCHANT", + "T_KEY1" + ], + "out": [ + "T_TAX", + "T_AMT1", + "T_DELTA", + "T_RC" + ] + }, { "mod": "st", "modnm": "정산/수수료", @@ -3910,6 +10520,35 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_AGEDCONF", + "desc": "기능: 장기 미종결 확인건 검출 (CONFIRMED N일 초과, RC 1=존재; T_ARG2=1이면 severity CRIT 승격).", + "in": [ + "T_ARG1", + "T_ARG2" + ], + "out": [ + "T_COUNT", + "T_ARG3", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_AGEDOPEN", + "desc": "기능: 장기미결 의심거래 검출 (OPEN 상태로 N일 초과 경과, RC 1=존재).", + "in": [ + "T_ARG1" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_RC" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -3937,6 +10576,36 @@ const SVC_SCREENS = [ "T_ID1" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_APPRAGG", + "desc": "기능: 일자별 매입-승인 금액 대사 (승인보유 매입금액 vs 전체 매입금액, RC 1=미승인 금액 존재).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_GROSS", + "T_AMT1", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_APPRAMT", + "desc": "기능: 건별 매입-승인 금액/존재 정합 검증 (RC 1=승인없음, 2=거절승인만 존재, 3=매입없음).", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_AMOUNT", + "T_COUNT", + "T_ARG1" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -3992,6 +10661,21 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_CANCELMIS", + "desc": "기능: 취소 정합 검증 (RC 비트합 1=취소합>매입금액, 2=취소 존재하나 매입상태 미반영, 3=매입없음).", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_AMOUNT", + "T_AMT1", + "T_COUNT" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4053,6 +10737,35 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_DATEREV", + "desc": "기능: 건별 정산 일자 역전 검출 (settlement.biz_date < purchase.biz_date → RC 1, 3=매입없음).", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_COUNT" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_DAYLIMIT", + "desc": "기능: 가맹점 일한도 위반 검증 (일자 매입합 > daily_limit → RC 1, 3=가맹점 없음).", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_AMT1", + "T_GROSS", + "T_DELTA" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4081,6 +10794,51 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_DUPPAY", + "desc": "기능: 건별 중복지급 검출 (ledger XFER 엔트리 2건 이상 → RC 1, 검증로그 기록).", + "in": [ + "T_PURCHASE_ID", + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_GROSS", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_DUPPAYDAY", + "desc": "기능: 일자별 중복지급 그룹 집계 (XFER 2건 이상 매입 그룹수/초과건수, RC 1=중복지급 존재).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_DUPSETTLE", + "desc": "기능: 건별 중복 정산 검출 (동일 매입에 정산 2건 이상 → RC 1, 최초/최종 정산 ID 반환).", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_COUNT", + "T_NET", + "T_ID1", + "T_ID2", + "T_RC" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4108,6 +10866,37 @@ const SVC_SCREENS = [ "T_DELTA" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_FEEADJNET", + "desc": "기능: 수수료 조정 반영 검증 (fee+sum(ac_fee_adj.delta) 유효범위 점검, RC 1=음수 수수료 2=금액 초과 3=매입없음)", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_FEE", + "T_DELTA", + "T_COUNT" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_FEERECALC", + "desc": "기능: 수수료 재계산 검증 (가맹점 요율 mdr_bps로 기대수수료 재산출, |fee-기대|>허용오차 → RC 1, 3=매입/가맹점 없음).", + "in": [ + "T_PURCHASE_ID", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_FEE", + "T_AMT1", + "T_DELTA" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4121,6 +10910,84 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_FXCHK", + "desc": "기능: 해외 매입 환산 정합 검증 (krw vs fx*rate_bps/10000, 오차>T_ARG1 → RC 1, 2=해외 내역 없음).", + "in": [ + "T_PURCHASE_ID", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_FX_AMT", + "T_AMT1", + "T_DELTA" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_INSTCHK", + "desc": "기능: 할부 정합 검증 (sum(월부금) vs 매입금액, 오차>T_ARG1 → RC 1, 2=할부 없음 3=매입없음).", + "in": [ + "T_PURCHASE_ID", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_AMT1", + "T_INSTALL_N", + "T_COUNT", + "T_DELTA" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_LEDGBAL", + "desc": "기능: 일자별 원장 대차 정합 검증 (차변 RECON 합 vs 대변 SETTLE+XFER 합, RC 1=대차 불일치).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_COUNT", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_LEDGDATE", + "desc": "기능: 건별 원장 일자 역전 검출 (ledger.biz_date < purchase.biz_date 엔트리 수, RC 1=역전 존재).", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_LEDGENTRY", + "desc": "기능: 건별 원장 대사 엔트리 검증 (purchase.amount vs ledger RECON 합, RC 1=금액불일치 2=원장없음 3=매입없음", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_AMOUNT", + "T_AMT1", + "T_COUNT" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4148,6 +11015,35 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_LIMITNEAR", + "desc": "기능: 가맹점 일한도 임박 검출 (사용률>=T_ARG1% → RC 1, 초과 → RC 2, 3=가맹점 없음).", + "in": [ + "T_ARG1", + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_AMT1", + "T_GROSS", + "T_ARG2" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_MDRRATE", + "desc": "기능: 가맹점 요율 위반 검출 (mdr_bps<0 또는 규칙 MDR_MAX 초과, RC 1=위반 존재).", + "in": [], + "out": [ + "T_COUNT", + "T_ARG1", + "T_RC" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4160,6 +11056,51 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_NEGBAL", + "desc": "기능: 마이너스 잔액 가맹점 검출 (merchant_summary.net_amount<0, RC 1=존재; 최악치 반환).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_AMT1", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_NEGNET", + "desc": "기능: 일자별 음수 순액/역전 수수료 매입 검출 (net<0, fee<0, fee>amount, RC 1=존재).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RATEDEV", + "desc": "기능: 건별 실적용 요율 편차 검출 (fee*10000/amount vs mdr_bps, 편차>T_ARG1 → RC 1, 2=금액 0 이하, 3", + "in": [ + "T_PURCHASE_ID", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_ARG2", + "T_ARG3", + "T_DELTA" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4198,6 +11139,65 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RILEDGER", + "desc": "기능: 참조무결성 세분 검증 - 원장→매입 고아 엔트리 (RC 비트합 1=RECON 2=SETTLE 4=XFER 고아).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_COUNT", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RIMERCH", + "desc": "기능: 참조무결성 세분 검증 - 매입→가맹점 (일자별 고아 매입 수, RC 1=고아 존재).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RIPURCH", + "desc": "기능: 참조무결성 세분 검증 - 정산→매입 (일자별 고아 정산 수/금액, RC 1=고아 존재).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_NET", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RISETTLE", + "desc": "기능: 참조무결성 세분 검증 - 정산 가맹점 불일치 (settlement.merchant_id <> purchase.merchant_id, RC", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_PURCHASE_ID", + "T_RC" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4211,6 +11211,77 @@ const SVC_SCREENS = [ ], "out": [] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RULEHIST", + "desc": "기능: 검증 규칙 변경이력 조회 (vl_check_log의 VL_RULEMOD 이력 건수/최종 로그 ID, RC 1=이력 없음).", + "in": [ + "T_STR3" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RULEMOD", + "desc": "기능: 검증 규칙 변경 + 변경이력 기록 (임계값/심각도 UPDATE 후 vl_check_log에 이력 적재, RC 3=규칙 없음).", + "in": [ + "T_STR3", + "T_ARG1", + "T_STR4", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_ARG2" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RULEOFF", + "desc": "기능: 검증 규칙 활성/비활성 전환 (T_STR5 = ACTIVE|INACTIVE, RC 1=규칙 없음 2=상태값 오류).", + "in": [ + "T_STR3", + "T_STR5" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RULEREG", + "desc": "기능: 검증 규칙 신규 등록 (이미 존재하면 RC 1로 거부, 신규만 INSERT).", + "in": [ + "T_STR3", + "T_STR1", + "T_STR4", + "T_ARG1" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_RULESTAT", + "desc": "기능: 검증 규칙 현황 집계 (전체/ACTIVE/CRIT 건수, RC 1=활성 규칙 전무).", + "in": [], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_RC" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4260,6 +11331,24 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_SUMFEE", + "desc": "기능: 통계-원천 수수료/순액 대사 (merchant_summary fee/net vs purchase 재집계, RC 비트합 1=수수료 2=순액", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_FEE", + "T_AMT1", + "T_NET", + "T_AMT2", + "T_DELTA" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4273,6 +11362,121 @@ const SVC_SCREENS = [ "T_RC" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_SUMRECON", + "desc": "기능: 통계-원천 대사 검증 (merchant_summary 건수/총액 vs purchase 재집계, RC 비트합 1=건수 2=총액, 8=통계 ", + "in": [ + "T_MERCHANT", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_COUNT", + "T_ARG1", + "T_GROSS", + "T_AMT1" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_SUSPACK", + "desc": "기능: 의심거래 워크플로 1단계 확인 (OPEN→CONFIRMED, 확인자 기록, RC 1=OPEN 상태 아님/없음).", + "in": [ + "T_ID1", + "T_STR1" + ], + "out": [ + "T_ARG1", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_SUSPACT", + "desc": "기능: 의심거래 워크플로 2단계 조치 등록 (조치내용 detail 부기 + 검증로그 적재, RC 2=이미 종결 3=없음 4=조치내용 누락).", + "in": [ + "T_ID1", + "T_REASON", + "T_BIZDATE" + ], + "out": [ + "T_RC", + "T_PURCHASE_ID" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_SUSPFINAL", + "desc": "기능: 의심거래 워크플로 3단계 종결 (CONFIRMED→CLOSED만 허용, RC 1=이미 종결 2=확인 전 종결 불가 3=없음).", + "in": [ + "T_ID1" + ], + "out": [ + "T_RC", + "T_ARG1" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_SUSPREOPEN", + "desc": "기능: 종결 의심거래 재오픈 (CLOSED→OPEN, 재오픈 사유 필수, RC 1=종결건 아님/없음 2=사유 누락).", + "in": [ + "T_ID1", + "T_REASON" + ], + "out": [ + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_SUSPSTAT", + "desc": "기능: 의심거래 워크플로 상태별 현황 (일자별 OPEN/CONFIRMED/CLOSED 건수, RC 1=미결 존재).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_COUNT", + "T_ARG1", + "T_ARG2", + "T_ARG3", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_THRAUDIT", + "desc": "기능: 임계값 설정 감사 (RC 비트합 1=ACTIVE ERROR/CRIT 규칙의 임계값 미설정, 2=CRIT 규칙 비활성 방치).", + "in": [], + "out": [ + "T_ARG1", + "T_ARG2", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_THRBUMP", + "desc": "기능: 규칙 임계값 증감 조정 (T_DELTA 만큼 가감, 음수는 0으로 클램프 RC 2, 규칙 없으면 RC 3).", + "in": [ + "T_STR3", + "T_DELTA" + ], + "out": [ + "T_RC", + "T_ARG2", + "T_ARG3" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4287,6 +11491,20 @@ const SVC_SCREENS = [ "T_ARG1" ] }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_THRSET", + "desc": "기능: 규칙 임계값 직접 설정 (음수 거부 RC 2, 대상 규칙 없으면 RC 3).", + "in": [ + "T_STR3", + "T_ARG1" + ], + "out": [ + "T_RC", + "T_ARG2" + ] + }, { "mod": "vl", "modnm": "정합성검증", @@ -4300,5 +11518,63 @@ const SVC_SCREENS = [ "T_RC", "T_DELTA" ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_TRIAGG", + "desc": "기능: 일자별 매입-정산-지급 3단 총액 대사 (RC 비트합 1=매입vs정산 불일치 2=정산vs지급 불일치).", + "in": [ + "T_BIZDATE" + ], + "out": [ + "T_AMT1", + "T_AMT2", + "T_AMT3", + "T_DELTA", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_TRISTAGE", + "desc": "기능: 건별 매입-정산-지급 3단 정합 검증 (RC 비트합 1=정산누락 2=지급누락 4=정산액불일치 8=지급액불일치, 32=매입없음).", + "in": [ + "T_PURCHASE_ID" + ], + "out": [ + "T_RC", + "T_NET", + "T_AMT1", + "T_AMT2" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_UNACQ", + "desc": "기능: 미매입 승인 검출 (매입 원장에 없는 승인 레코드 수, RC 1=고아 승인 존재).", + "in": [], + "out": [ + "T_COUNT", + "T_ID1", + "T_RC" + ] + }, + { + "mod": "vl", + "modnm": "정합성검증", + "svc": "VL_UNACQAGE", + "desc": "기능: 장기 미확정 매입 검출 (접수 A/보류 H 상태로 N일 초과 경과, RC 1=존재).", + "in": [ + "T_ARG1" + ], + "out": [ + "T_COUNT", + "T_ID1", + "T_ARG2", + "T_RC" + ] } ]; diff --git a/db/schema.d/95-module-seed.sql b/db/schema.d/95-module-seed.sql new file mode 100644 index 0000000..b2fbaec --- /dev/null +++ b/db/schema.d/95-module-seed.sql @@ -0,0 +1,912 @@ +-- =========================================================================== +-- 95-module-seed.sql : 전 모듈 운영 이력 시드 (90-seed 기반 파생) +-- 90-seed 의 purchase/settlement/merchant 이력에서 au/py/rc/st/lg/cl/mm/vl/ +-- mg/cm (+ac 상세) 모듈 테이블을 실운영계처럼 파생 적재한다. +-- - 결정적(setseed) / 재실행 안전(ON CONFLICT DO NOTHING) +-- - docker-entrypoint-initdb.d 에서 90-seed 직후 1회 실행 +-- =========================================================================== +SELECT setseed(0.77); + +-- --------------------------------------------------------------------------- +-- 0. 공통 파생 베이스: 매입 + 카드번호/채널 매핑 +-- --------------------------------------------------------------------------- +CREATE TEMP TABLE ms_px AS +SELECT p.purchase_id, p.merchant_id, p.amount, p.fee, p.net, p.status, + p.biz_date, p.channel, p.issuer, p.created_at, + m.mdr_bps, + (CASE p.issuer + WHEN 'BC' THEN '457301' WHEN 'KB' THEN '945712' + WHEN 'SHINHAN' THEN '451845' WHEN 'HYUNDAI' THEN '433428' + WHEN 'SAMSUNG' THEN '537740' WHEN 'LOTTE' THEN '438676' + WHEN 'NH' THEN '546551' ELSE '523430' + END) || lpad(((p.purchase_id * 7919) % 2400)::text, 10, '0') AS card_no, + CASE p.channel WHEN 'POS' THEN 'CH01' WHEN 'EDC' THEN 'CH02' + WHEN 'EDI' THEN 'CH02' ELSE 'CH03' END AS mg_chan +FROM purchase p JOIN merchant m ON m.merchant_id = p.merchant_id; + +-- 가맹점-정산일 단위 net 집계 (지급/전표/정산 파생의 공통 소스) +CREATE TEMP TABLE ms_ms AS +SELECT row_number() OVER (ORDER BY s.biz_date, s.merchant_id) AS rn, + dense_rank() OVER (ORDER BY s.biz_date) AS day_rn, + s.merchant_id, s.biz_date AS settle_date, + (s.biz_date + 1)::date AS pay_date, + min(s.settlement_id) AS ref_sid, + count(*) AS cnt, + sum(p.amount) AS gross_sum, + sum(p.fee) AS fee_sum, + sum(s.net) AS net_sum +FROM settlement s JOIN purchase p ON p.purchase_id = s.purchase_id +GROUP BY s.merchant_id, s.biz_date; + +-- =========================================================================== +-- 1. au 모듈 (승인/한도/부정거래) +-- =========================================================================== +-- 승인 원장: 매입 1:1 미러 (같은 가맹점/금액/일자, 상태 도메인 동일) +INSERT INTO au_authorization (auth_id, card_no, merchant_id, amount, approval_code, + auth_type, status, decline_reason, biz_date, + captured_at, created_at, updated_at) +SELECT purchase_id, card_no, merchant_id, amount, + lpad(((purchase_id * 37) % 100000000)::text, 8, '0'), + CASE WHEN purchase_id % 53 = 0 THEN 'PREAUTH' + WHEN purchase_id % 11 = 0 AND amount >= 500000 THEN 'INSTALL' + WHEN channel = 'FOREIGN' THEN 'STANDIN' + ELSE 'AUTH' END, + status, + '', + biz_date, + CASE WHEN status IN ('M','S') THEN created_at + interval '70 minutes' END, + created_at, created_at + interval '2 minutes' +FROM ms_px +ON CONFLICT (auth_id) DO NOTHING; + +-- 거절 건 (~2%): 별도 auth_id 대역, 사유 코드 부여 → 승인율 98% +INSERT INTO au_authorization (auth_id, card_no, merchant_id, amount, approval_code, + auth_type, status, decline_reason, biz_date, created_at, updated_at) +SELECT 500000 + row_number() OVER (ORDER BY purchase_id), + card_no, merchant_id, (amount * 2) / 100 * 100 + 900, '', + 'AUTH', 'D', + (ARRAY['LIMIT_EXCEEDED','INSUFFICIENT_FUNDS','INVALID_CARD','SUSPECT_FRAUD'])[purchase_id % 4 + 1], + biz_date, created_at + interval '31 minutes', created_at + interval '31 minutes' +FROM ms_px WHERE purchase_id % 48 = 0 +ON CONFLICT (auth_id) DO NOTHING; + +-- 카드 한도: 시드 카드 전체 등록 (금일 사용액 = 최근 영업일 승인합) +INSERT INTO card_limit (card_no, daily_limit, used_amount, temp_limit, status, updated_at) +SELECT card_no, + CASE (('x' || substr(md5(card_no), 1, 6))::bit(24)::int) % 4 + WHEN 0 THEN 1000000 WHEN 1 THEN 3000000 WHEN 2 THEN 5000000 ELSE 10000000 END, + coalesce(sum(amount) FILTER (WHERE biz_date >= '2026-07-19'), 0), + 0, 'ACTIVE', now() +FROM ms_px GROUP BY card_no +ON CONFLICT (card_no) DO NOTHING; + +-- 부정거래 로그: 고액(>=300만) + 다건(velocity) 이상거래 +INSERT INTO au_fraud_log (fraud_id, auth_id, card_no, score, reason, biz_date, created_at) +SELECT row_number() OVER (ORDER BY purchase_id), + purchase_id, card_no, + 700 + (purchase_id % 280), + CASE WHEN amount >= 3000000 THEN 'HIGH_AMOUNT' ELSE 'VELOCITY' END, + biz_date, created_at + interval '1 minute' +FROM ms_px WHERE amount >= 3000000 OR purchase_id % 197 = 0 +ON CONFLICT (fraud_id) DO NOTHING; + +-- 예비승인: 일부 거래는 PREAUTH 선행 (과거=확정 C, 최근=P, 극소수 취소 X) +INSERT INTO au_preauth (preauth_id, card_no, merchant_id, amount, status, biz_date, created_at) +SELECT row_number() OVER (ORDER BY purchase_id), + card_no, merchant_id, amount, + CASE WHEN purchase_id % 371 = 0 THEN 'X' + WHEN biz_date < '2026-07-15' THEN 'C' ELSE 'P' END, + biz_date, created_at - interval '2 hours' +FROM ms_px WHERE purchase_id % 53 = 0 +ON CONFLICT (preauth_id) DO NOTHING; + +-- 승인 취소: 매입 취소(C)/무효(V) 건 연동 +INSERT INTO au_cancel (cancel_id, auth_id, cancel_type, amount, reason, biz_date, created_at) +SELECT row_number() OVER (ORDER BY purchase_id), + purchase_id, + CASE status WHEN 'V' THEN 'VOID' + ELSE CASE WHEN purchase_id % 5 = 0 THEN 'PARTIAL' ELSE 'FULL' END END, + CASE WHEN status <> 'V' AND purchase_id % 5 = 0 THEN amount / 2 / 100 * 100 ELSE amount END, + (ARRAY['고객 요청 취소','상품 반품','중복 승인 취소','가맹점 요청'])[purchase_id % 4 + 1], + biz_date, created_at + interval '3 hours' +FROM ms_px WHERE status IN ('C','V') +ON CONFLICT (cancel_id) DO NOTHING; + +-- 추가 블랙리스트 +INSERT INTO au_blacklist (card_no, reason) VALUES + ('4573010000000777', 'CHARGEBACK_ABUSE'), + ('5377400000001313', 'CONFIRMED_FRAUD'), + ('4518450000002099', 'CARD_LOST') +ON CONFLICT (card_no) DO NOTHING; + +-- 가맹점 일별 승인 집계 +INSERT INTO au_summary (merchant_id, biz_date, auth_count, approve_count, decline_count, auth_amount) +SELECT merchant_id, biz_date, count(*), + count(*) FILTER (WHERE status <> 'D'), + count(*) FILTER (WHERE status = 'D'), + coalesce(sum(amount) FILTER (WHERE status <> 'D'), 0) +FROM au_authorization GROUP BY merchant_id, biz_date +ON CONFLICT (merchant_id, biz_date) DO NOTHING; + +-- =========================================================================== +-- 2. py 모듈 (지급) - 정산 T+1 지급 +-- =========================================================================== +-- 지급계좌: 전 가맹점 (기존 M0001~3 시드 유지) +INSERT INTO py_account (account_no, merchant_id, bank_code, holder, balance, status, updated_at) +SELECT lpad((100 + n)::text, 3, '0') || '-' || + lpad(((n * 1237) % 10000)::text, 4, '0') || '-' || + lpad(((n * 98717) % 1000000)::text, 6, '0'), + m.merchant_id, + (ARRAY['004','011','020','081','088','090'])[n % 6 + 1], + m.name, + ((n * 7654321) % 400000000) + 5000000, + CASE m.status WHEN 'ACTIVE' THEN 'ACTIVE' ELSE 'HOLD' END, + now() - interval '30 days' +FROM merchant m CROSS JOIN LATERAL (SELECT substr(m.merchant_id, 2)::int AS n) v +ON CONFLICT (account_no) DO NOTHING; + +-- 지급 파일: 지급일별 1파일 (과거=확정 C, 최근=생성 G) +INSERT INTO py_file (file_id, biz_date, file_name, record_count, total_amount, status, created_at, confirmed_at) +SELECT day_rn, pay_date, + 'PAYOUT_' || to_char(pay_date, 'YYYYMMDD') || '_01.dat', + count(*), sum(net_sum), + CASE WHEN pay_date < '2026-07-19' THEN 'C' ELSE 'G' END, + pay_date::timestamptz + interval '7 hours', + CASE WHEN pay_date < '2026-07-19' THEN pay_date::timestamptz + interval '9 hours' END +FROM ms_ms GROUP BY day_rn, pay_date +ON CONFLICT (file_id) DO NOTHING; + +-- 지급 원장: 가맹점-정산일 합산 → T+1 지급, 이체수수료 500원, ~1.6% 반려/재처리 +INSERT INTO py_payout (payout_id, merchant_id, account_no, amount, fee, net, status, + channel, file_id, retry_count, reject_reason, biz_date, created_at, updated_at) +SELECT 10000 + ms.rn, ms.merchant_id, + coalesce(a.account_no, ''), + ms.net_sum, 500, ms.net_sum - 500, + CASE WHEN ms.rn % 61 = 0 THEN 'J' + WHEN ms.pay_date < '2026-07-18' THEN 'P' + WHEN ms.pay_date = '2026-07-18' THEN 'F' + WHEN ms.pay_date = '2026-07-19' THEN 'A' + ELSE 'R' END, + CASE WHEN ms.rn % 29 = 0 THEN 'BULK' + WHEN ms.rn % 13 = 0 THEN 'ADVANCE' ELSE 'STD' END, + ms.day_rn, + CASE WHEN ms.rn % 61 = 0 THEN 1 + ms.rn % 2 ELSE 0 END, + CASE WHEN ms.rn % 61 = 0 + THEN (ARRAY['계좌번호 오류','예금주 상이','지급정지 계좌'])[ms.rn % 3 + 1] + ELSE '' END, + ms.pay_date, + ms.pay_date::timestamptz + interval '7 hours', + ms.pay_date::timestamptz + interval '10 hours' +FROM ms_ms ms +LEFT JOIN LATERAL (SELECT account_no FROM py_account a + WHERE a.merchant_id = ms.merchant_id + ORDER BY account_no LIMIT 1) a ON true +ON CONFLICT (payout_id) DO NOTHING; + +-- 지급 상세원장: 완료건 REQ+PAID 복식 기록 +INSERT INTO py_ledger (ledger_id, payout_id, entry_type, amount, biz_date, created_at) +SELECT row_number() OVER (ORDER BY payout_id, et.ord), + payout_id, et.entry_type, + CASE et.entry_type WHEN 'REQ' THEN amount ELSE net END, + biz_date, updated_at +FROM py_payout +CROSS JOIN (VALUES (1, 'REQ'), (2, 'PAID')) AS et(ord, entry_type) +WHERE status = 'P' +ON CONFLICT (ledger_id) DO NOTHING; + +-- 지급 예약: 미완료(요청/승인/파일편성)건 스케줄 +INSERT INTO py_schedule (schedule_id, payout_id, run_date, status, created_at) +SELECT row_number() OVER (ORDER BY payout_id), payout_id, biz_date, + CASE WHEN status = 'R' THEN 'S' ELSE 'D' END, created_at +FROM py_payout WHERE status IN ('R','A','F') +ON CONFLICT (schedule_id) DO NOTHING; + +-- 지급 조정: 소수 가감 조정 +INSERT INTO py_adjust (adj_id, payout_id, delta, reason, biz_date, created_at) +SELECT row_number() OVER (ORDER BY payout_id), payout_id, + CASE WHEN payout_id % 2 = 0 THEN 1 ELSE -1 END * ((payout_id % 7 + 1) * 1000), + (ARRAY['수수료 정정 가감','반품 상계','민원 보상 지급'])[payout_id % 3 + 1], + biz_date, updated_at +FROM py_payout WHERE payout_id % 199 = 0 +ON CONFLICT (adj_id) DO NOTHING; + +-- 가맹점 일별 지급 집계 +INSERT INTO py_summary (merchant_id, biz_date, payout_count, payout_amount) +SELECT merchant_id, biz_date, count(*), sum(net) +FROM py_payout GROUP BY merchant_id, biz_date +ON CONFLICT (merchant_id, biz_date) DO NOTHING; + +-- =========================================================================== +-- 3. rc 모듈 (대사) +-- =========================================================================== +-- 대사 로그: 대사완료(M)/정산완료(S) = MATCHED, 보류(H) = HOLD, ~1% 예외 +INSERT INTO rc_match_log (match_id, purchase_id, match_type, amount, status, biz_date, created_at, updated_at) +SELECT purchase_id, purchase_id, + CASE WHEN status = 'H' THEN 'DELAY' + WHEN channel = 'EDI' THEN '3WAY' + WHEN purchase_id % 5 = 0 THEN 'ACQ_DEP' + ELSE 'APPR_ACQ' END, + amount, + CASE WHEN status = 'H' THEN 'HOLD' + WHEN purchase_id % 97 = 0 THEN 'EXCEPTION' + WHEN status = 'S' AND purchase_id % 3 = 0 THEN 'CONFIRMED' + ELSE 'MATCHED' END, + biz_date, + biz_date::timestamptz + interval '19 hours', + biz_date::timestamptz + interval '19 hours 30 minutes' +FROM ms_px WHERE status IN ('M','S','H') +ON CONFLICT (match_id) DO NOTHING; + +-- 대사 예외: EXCEPTION 건 사유 적재 (과거 건은 해소 처리) +INSERT INTO rc_exception (exc_id, purchase_id, reason, biz_date, resolved, created_at) +SELECT row_number() OVER (ORDER BY purchase_id), purchase_id, + (ARRAY['매입-승인 금액 불일치','입금액 부족','승인건 누락','중복 매입 접수'])[purchase_id % 4 + 1], + biz_date, biz_date < '2026-07-01', + biz_date::timestamptz + interval '20 hours' +FROM ms_px WHERE status IN ('M','S') AND purchase_id % 97 = 0 +ON CONFLICT (exc_id) DO NOTHING; + +-- 대사 차이: 예외건 기대/실제 금액 차이 +INSERT INTO rc_diff (diff_id, purchase_id, expected_amt, actual_amt, diff_amt, diff_kind, biz_date, created_at) +SELECT row_number() OVER (ORDER BY purchase_id), purchase_id, + amount, amount - (purchase_id % 5 + 1) * 100, (purchase_id % 5 + 1) * 100, + CASE WHEN purchase_id % 3 = 0 THEN 'CNT' ELSE 'AMT' END, + biz_date, biz_date::timestamptz + interval '20 hours' +FROM ms_px WHERE status IN ('M','S') AND purchase_id % 97 = 0 +ON CONFLICT (diff_id) DO NOTHING; + +-- 대사 경보: 예외건 등급별 경보 +INSERT INTO rc_alert (alert_id, purchase_id, lvl, message, biz_date, created_at) +SELECT row_number() OVER (ORDER BY purchase_id), purchase_id, + CASE WHEN amount >= 1000000 THEN 'CRIT' ELSE 'WARN' END, + '대사 예외 발생: ' || merchant_id || ' 금액 ' || amount || '원', + biz_date, biz_date::timestamptz + interval '20 hours 5 minutes' +FROM ms_px WHERE status IN ('M','S') AND purchase_id % 97 = 0 +ON CONFLICT (alert_id) DO NOTHING; + +-- 대사 일별 요약 +INSERT INTO rc_summary (biz_date, matched_cnt, unmatched_cnt, gross_amount) +SELECT biz_date, + count(*) FILTER (WHERE status IN ('MATCHED','CONFIRMED')), + count(*) FILTER (WHERE status IN ('EXCEPTION','HOLD')), + sum(amount) +FROM rc_match_log GROUP BY biz_date +ON CONFLICT (biz_date) DO NOTHING; + +-- =========================================================================== +-- 4. st 모듈 (정산/수수료) +-- =========================================================================== +-- 정산 상세: 정산건별 수수료 분해 (MDR + VAN, EDI 는 중계 RELAY 추가) +INSERT INTO st_settle_dtl (dtl_id, settlement_id, purchase_id, merchant_id, fee_type, amount, biz_date, created_at) +SELECT row_number() OVER (ORDER BY s.settlement_id, ft.ord), + s.settlement_id, s.purchase_id, s.merchant_id, ft.fee_type, + CASE ft.fee_type WHEN 'MDR' THEN p.fee + WHEN 'VAN' THEN p.amount * 10 / 10000 + ELSE p.amount * 5 / 10000 END, + s.biz_date, s.settled_at +FROM settlement s +JOIN purchase p ON p.purchase_id = s.purchase_id +CROSS JOIN (VALUES (1, 'MDR'), (2, 'VAN'), (3, 'RELAY')) AS ft(ord, fee_type) +WHERE ft.fee_type <> 'RELAY' OR p.channel = 'EDI' +ON CONFLICT (dtl_id) DO NOTHING; + +-- 수수료 구간 요율 보강 (기존 시드 유지) +INSERT INTO st_fee_rate (fee_type, band_min, rate_bps) VALUES + ('MDR', 50000000, 18), + ('VAN', 10000000, 8), + ('WHT', 0, 220), + ('DELAYINT', 0, 30) +ON CONFLICT (fee_type, band_min) DO NOTHING; + +-- 정산 조정: 소수 가감/상계 +INSERT INTO st_adjust (adj_id, settlement_id, purchase_id, delta, reason, biz_date, created_at) +SELECT row_number() OVER (ORDER BY settlement_id), settlement_id, purchase_id, + CASE WHEN settlement_id % 2 = 0 THEN 1 ELSE -1 END * ((settlement_id % 9 + 1) * 500), + (ARRAY['요율 소급 정정','부분취소 상계','반올림 차액 조정','민원 보상'])[settlement_id % 4 + 1], + biz_date, settled_at + interval '2 hours' +FROM settlement WHERE settlement_id % 173 = 0 +ON CONFLICT (adj_id) DO NOTHING; + +-- 가맹점 지급예정: 정산일 기준 T+1 지급 스케줄 +INSERT INTO st_payment (pay_id, merchant_id, net_amount, pay_date, status, biz_date, created_at) +SELECT rn, merchant_id, net_sum, pay_date, + CASE WHEN rn % 61 = 0 THEN 'HELD' + WHEN pay_date < '2026-07-18' THEN 'PAID' + ELSE 'SCHEDULED' END, + settle_date, settle_date::timestamptz + interval '22 hours' +FROM ms_ms +ON CONFLICT (pay_id) DO NOTHING; + +-- 가맹점 정산 집계 (월집계는 ST_BYMERCH/배치 형식과 동일한 일집계 소스) +INSERT INTO st_merch_settle (merchant_id, biz_date, txn_count, gross_amount, fee_amount, net_amount) +SELECT merchant_id, settle_date, cnt, gross_sum, fee_sum, net_sum +FROM ms_ms +ON CONFLICT (merchant_id, biz_date) DO NOTHING; + +-- =========================================================================== +-- 5. lg 모듈 (원장 - 복식부기) +-- =========================================================================== +-- 전표 1: 정산 전기 (SETTLE) - 차변 RECV(총액) = 대변 MERCH_PAY(순액)+FEE_INC(수수료) +INSERT INTO lg_voucher (voucher_id, voucher_type, ref_id, biz_date, status, memo, created_at, updated_at) +SELECT 1000 + rn * 2 - 1, 'SETTLE', ref_sid, settle_date, 'P', + merchant_id || ' 일정산 전기 (' || cnt || '건)', + settle_date::timestamptz + interval '21 hours', + settle_date::timestamptz + interval '21 hours' +FROM ms_ms +ON CONFLICT (voucher_id) DO NOTHING; + +-- 전표 2: 지급 이체 (TRANSFER) - 차변 MERCH_PAY = 대변 CASH +INSERT INTO lg_voucher (voucher_id, voucher_type, ref_id, biz_date, status, memo, created_at, updated_at) +SELECT 1000 + rn * 2, 'TRANSFER', ref_sid, pay_date, + CASE WHEN pay_date < '2026-07-19' THEN 'P' ELSE 'D' END, + merchant_id || ' 지급 이체 전기', + pay_date::timestamptz + interval '10 hours', + pay_date::timestamptz + interval '10 hours' +FROM ms_ms +ON CONFLICT (voucher_id) DO NOTHING; + +-- 원장 라인: SETTLE 전표 3분개 + TRANSFER 전표 2분개 (차대변 균형) +INSERT INTO lg_ledger_line (line_id, voucher_id, acct, dc, amount, merchant_id, biz_date, status, created_at) +SELECT row_number() OVER (ORDER BY rn, ln.ord), + CASE WHEN ln.ord <= 3 THEN 1000 + rn * 2 - 1 ELSE 1000 + rn * 2 END, + ln.acct, ln.dc, + CASE ln.ord WHEN 1 THEN gross_sum WHEN 2 THEN net_sum WHEN 3 THEN fee_sum + WHEN 4 THEN net_sum ELSE net_sum END, + merchant_id, + CASE WHEN ln.ord <= 3 THEN settle_date ELSE pay_date END, + CASE WHEN settle_date < '2026-07-01' THEN 'closed' ELSE 'posted' END, + CASE WHEN ln.ord <= 3 THEN settle_date::timestamptz + interval '21 hours' + ELSE pay_date::timestamptz + interval '10 hours' END +FROM ms_ms +CROSS JOIN (VALUES (1, 'RECV', 'D'), (2, 'MERCH_PAY', 'C'), (3, 'FEE_INC', 'C'), + (4, 'MERCH_PAY', 'D'), (5, 'CASH', 'C')) AS ln(ord, acct, dc) +WHERE fee_sum > 0 OR ln.ord <> 3 +ON CONFLICT (line_id) DO NOTHING; + +-- 계정 잔액 (계정 x 영업일) +INSERT INTO lg_account_bal (acct, biz_date, debit_total, credit_total, balance) +SELECT acct, biz_date, + coalesce(sum(amount) FILTER (WHERE dc = 'D'), 0), + coalesce(sum(amount) FILTER (WHERE dc = 'C'), 0), + coalesce(sum(amount) FILTER (WHERE dc = 'D'), 0) - coalesce(sum(amount) FILTER (WHERE dc = 'C'), 0) +FROM lg_ledger_line GROUP BY acct, biz_date +ON CONFLICT (acct, biz_date) DO NOTHING; + +-- 가맹점 잔액 (가맹점 x 영업일) +INSERT INTO lg_merch_bal (merchant_id, biz_date, debit_total, credit_total, balance) +SELECT merchant_id, biz_date, + coalesce(sum(amount) FILTER (WHERE dc = 'D'), 0), + coalesce(sum(amount) FILTER (WHERE dc = 'C'), 0), + coalesce(sum(amount) FILTER (WHERE dc = 'D'), 0) - coalesce(sum(amount) FILTER (WHERE dc = 'C'), 0) +FROM lg_ledger_line GROUP BY merchant_id, biz_date +ON CONFLICT (merchant_id, biz_date) DO NOTHING; + +-- 시산표 (계정 x 영업일; 전 계정 합산 시 차대변 균형) +INSERT INTO lg_trial_bal (acct, biz_date, debit_total, credit_total, diff) +SELECT acct, biz_date, debit_total, credit_total, balance +FROM lg_account_bal +ON CONFLICT (acct, biz_date) DO NOTHING; + +-- =========================================================================== +-- 6. cl 모듈 (마감) +-- =========================================================================== +-- 일마감: 매 영업일 DAILY (최종 영업일만 개시 상태) +INSERT INTO cl_close_log (close_id, close_type, period_key, biz_date, status, + txn_count, total_amount, memo, created_at, updated_at) +SELECT row_number() OVER (ORDER BY biz_date), 'DAILY', + to_char(biz_date, 'YYYY-MM-DD'), biz_date, + CASE WHEN biz_date >= '2026-07-19' THEN 'O' ELSE 'C' END, + count(*), sum(amount), + CASE WHEN biz_date >= '2026-07-19' THEN '일마감 개시' ELSE '일마감 완료' END, + biz_date::timestamptz + interval '23 hours', + biz_date::timestamptz + interval '23 hours 30 minutes' +FROM ms_px GROUP BY biz_date +ON CONFLICT (close_id) DO NOTHING; + +-- 월마감: 4~6월 잠금(L), 7월 진행중(O) + 2분기 마감 +INSERT INTO cl_close_log (close_id, close_type, period_key, biz_date, status, + txn_count, total_amount, memo, created_at, updated_at) +SELECT 200 + row_number() OVER (ORDER BY pk), ct, pk, bd::date, st, cnt, amt, memo, + bd::timestamptz + interval '5 hours', bd::timestamptz + interval '6 hours' +FROM ( + SELECT 'MONTHLY' AS ct, to_char(date_trunc('month', biz_date), 'YYYY-MM') AS pk, + (date_trunc('month', biz_date) + interval '1 month')::date AS bd, + CASE WHEN date_trunc('month', biz_date) < '2026-06-01' THEN 'L' + WHEN date_trunc('month', biz_date) = '2026-06-01' THEN 'A' + ELSE 'O' END AS st, + count(*) AS cnt, sum(amount) AS amt, + CASE WHEN date_trunc('month', biz_date) < '2026-07-01' + THEN '월마감 완료·승인' ELSE '월마감 진행중' END AS memo + FROM ms_px GROUP BY date_trunc('month', biz_date) + UNION ALL + SELECT 'QUARTER', '2026-Q2', '2026-07-01', 'C', count(*), sum(amount), '2분기 마감 완료' + FROM ms_px WHERE biz_date < '2026-07-01' +) q +ON CONFLICT (close_id) DO NOTHING; + +-- 마감 스냅샷: 일마감 x 가맹점 집계 고정 (merchant_summary 파생) +INSERT INTO cl_snapshot (snapshot_id, close_id, period_key, biz_date, merchant_id, + gross_amount, fee_amount, net_amount, txn_count, created_at) +SELECT row_number() OVER (ORDER BY s.biz_date, s.merchant_id), + c.close_id, c.period_key, s.biz_date, s.merchant_id, + s.gross_amount, s.fee_amount, s.net_amount, s.txn_count, + s.biz_date::timestamptz + interval '23 hours' +FROM merchant_summary s +JOIN cl_close_log c ON c.close_type = 'DAILY' AND c.biz_date = s.biz_date +ON CONFLICT (snapshot_id) DO NOTHING; + +-- 기간 잠금 + 마감 채번 레지스트리 +INSERT INTO cl_period_lock (period_key, locked) VALUES + ('2026-04', true), ('2026-05', true), ('2026-06', true), ('2026-Q2', true) +ON CONFLICT (period_key) DO NOTHING; + +INSERT INTO cl_seq_reg (close_type, last_seq) +SELECT close_type, count(*) FROM cl_close_log GROUP BY close_type +ON CONFLICT (close_type) DO NOTHING; + +-- =========================================================================== +-- 7. mm 모듈 (마스터) +-- =========================================================================== +-- 가맹점 확장 마스터: 전 40개 (사업자번호/등급/정산계좌/업종) +INSERT INTO mm_merchant_ext (merchant_id, name, biz_reg_no, grade, settle_acct, mcc, status, created_at, updated_at) +SELECT m.merchant_id, m.name, + ((v.n * 7) % 600 + 101)::text || '-' || lpad(((v.n * 13) % 88 + 10)::text, 2, '0') + || '-' || lpad(((v.n * 54321) % 100000)::text, 5, '0'), + CASE WHEN m.daily_limit >= 400000000 THEN 'S' + WHEN m.daily_limit >= 150000000 THEN 'A' + WHEN m.daily_limit >= 50000000 THEN 'B' ELSE 'C' END, + coalesce(a.account_no, ''), + (ARRAY['5814','5411','5912','5651','7230','5812','5541','5942','7997','8062', + '5732','5992','4722','7011','8299'])[v.n % 15 + 1], + CASE m.status WHEN 'ACTIVE' THEN 'ACTIVE' ELSE 'HOLD' END, + m.created_at, m.created_at + interval '1 day' +FROM merchant m +CROSS JOIN LATERAL (SELECT substr(m.merchant_id, 2)::int AS n) v +LEFT JOIN LATERAL (SELECT account_no FROM py_account a + WHERE a.merchant_id = m.merchant_id ORDER BY account_no LIMIT 1) a ON true +ON CONFLICT (merchant_id) DO NOTHING; + +-- 가맹점 변경 이력: 전건 등록 + 일부 변경 +INSERT INTO mm_merch_hist (hist_id, merchant_id, action, before_val, after_val, memo, created_at) +SELECT row_number() OVER (ORDER BY merchant_id, ord), merchant_id, action, before_val, after_val, memo, ts +FROM ( + SELECT m.merchant_id, 1 AS ord, 'REG' AS action, '' AS before_val, 'ACTIVE' AS after_val, + '신규 가맹점 등록' AS memo, m.created_at AS ts + FROM merchant m + UNION ALL + SELECT m.merchant_id, 2, 'GRADE', 'B', 'A', '분기 실적 평가 승급', m.created_at + interval '90 days' + FROM merchant m WHERE substr(m.merchant_id, 2)::int % 7 = 0 + UNION ALL + SELECT 'M0039', 3, 'STATUS', 'ACTIVE', 'HOLD', '정산계좌 오류로 거래 보류', now() - interval '12 days' + UNION ALL + SELECT 'M0012', 3, 'ACCT', '(구)계좌', '(신)계좌', '지급계좌 변경 요청 처리', now() - interval '40 days' +) h +ON CONFLICT (hist_id) DO NOTHING; + +-- 수수료율 마스터: 가맹점 x 신용/체크 +INSERT INTO mm_fee_rate (merchant_id, card_type, rate_bps, eff_date) +SELECT m.merchant_id, ct.card_type, + CASE ct.card_type WHEN 'CREDIT' THEN m.mdr_bps ELSE greatest(m.mdr_bps - 7, 10) END, + '2026-01-02' +FROM merchant m CROSS JOIN (VALUES ('CREDIT'), ('CHECK')) AS ct(card_type) +ON CONFLICT (merchant_id, card_type) DO NOTHING; + +-- 수수료율 변경 이력 (직전 요율 + 현행) +INSERT INTO mm_fee_rate_hist (hist_id, merchant_id, card_type, rate_bps, eff_date, created_at) +SELECT row_number() OVER (ORDER BY merchant_id, eff), merchant_id, 'CREDIT', bps, eff, eff::timestamptz +FROM ( + SELECT m.merchant_id, m.mdr_bps + 3 AS bps, '2025-07-01'::date AS eff FROM merchant m + UNION ALL + SELECT m.merchant_id, m.mdr_bps, '2026-01-02' FROM merchant m +) h +ON CONFLICT (hist_id) DO NOTHING; + +-- BIN 마스터 (승인 카드번호 채번과 일치) +INSERT INTO mm_bin (bin, issuer_cd, card_brand, card_type, status) VALUES + ('457301', 'BC', 'VISA', 'CREDIT', 'ACTIVE'), + ('945712', 'KB', 'LOCAL', 'CREDIT', 'ACTIVE'), + ('451845', 'SHINHAN', 'VISA', 'CREDIT', 'ACTIVE'), + ('433428', 'HYUNDAI', 'VISA', 'CREDIT', 'ACTIVE'), + ('537740', 'SAMSUNG', 'MASTER', 'CREDIT', 'ACTIVE'), + ('438676', 'LOTTE', 'VISA', 'CREDIT', 'ACTIVE'), + ('546551', 'NH', 'MASTER', 'CHECK', 'ACTIVE'), + ('523430', 'HANA', 'MASTER', 'CREDIT', 'ACTIVE'), + ('940000', 'BC', 'LOCAL', 'CHECK', 'ACTIVE'), + ('356789', 'JCB', 'LOCAL', 'CREDIT', 'INACTIVE') +ON CONFLICT (bin) DO NOTHING; + +-- 공통코드 보강 +INSERT INTO mm_code (code_group, code, code_name, sort_order) VALUES + ('GRADE', 'B', '일반', 3), + ('GRADE', 'C', '신규', 4), + ('CARD_TYPE', 'PREPAID','선불카드', 3), + ('MERCH_STATUS', 'ACTIVE', '정상영업', 1), + ('MERCH_STATUS', 'HOLD', '거래보류', 2), + ('MERCH_STATUS', 'DELETED','해지', 3), + ('PAYOUT_STATUS','P', '지급완료', 1), + ('PAYOUT_STATUS','R', '지급요청', 2), + ('PAYOUT_STATUS','J', '지급반려', 3) +ON CONFLICT (code_group, code) DO NOTHING; + +-- 가맹점 한도 마스터 +INSERT INTO mm_limit (merchant_id, daily_limit, monthly_limit, per_txn_limit) +SELECT merchant_id, daily_limit, daily_limit * 25, greatest(daily_limit / 10, 5000000) +FROM merchant +ON CONFLICT (merchant_id) DO NOTHING; + +-- 발급사 마스터 (매입 이력의 8개 발급사) +INSERT INTO mm_issuer (issuer_cd, issuer_name, country) VALUES + ('KB', 'KB국민카드', 'KR'), + ('SHINHAN', '신한카드', 'KR'), + ('HYUNDAI', '현대카드', 'KR'), + ('SAMSUNG', '삼성카드', 'KR'), + ('LOTTE', '롯데카드', 'KR'), + ('NH', 'NH농협카드', 'KR'), + ('HANA', '하나카드', 'KR'), + ('JCB', 'JCB', 'JP') +ON CONFLICT (issuer_cd) DO NOTHING; + +-- 단말 마스터: 가맹점당 2대 +INSERT INTO mm_terminal (terminal_id, merchant_id, model, status) +SELECT 'TM' || substr(m.merchant_id, 2) || '-0' || t.i, m.merchant_id, + (ARRAY['IC-700','IC-700','CAT-9200','MPOS-X1'])[(substr(m.merchant_id, 2)::int + t.i) % 4 + 1], + CASE WHEN m.status = 'ACTIVE' THEN 'ACTIVE' ELSE 'SUSPEND' END +FROM merchant m CROSS JOIN (VALUES (1), (2)) AS t(i) +ON CONFLICT (terminal_id) DO NOTHING; + +-- MCC 업종 마스터 +INSERT INTO mm_mcc (mcc, mcc_name, risk_level) VALUES + ('5814', '카페/제과', 1), ('5411', '슈퍼마켓/마트', 1), + ('5912', '약국', 1), ('5651', '의류', 1), + ('7230', '미용실', 1), ('5812', '일반음식점', 1), + ('5541', '주유소', 1), ('5942', '서적/문구', 1), + ('7997', '스포츠/레저시설', 2), ('8062', '병원/의원', 1), + ('5732', '가전/통신기기', 2), ('5992', '화훼', 1), + ('4722', '여행사', 3), ('7011', '숙박', 2), + ('8299', '교육/학원', 2), ('7995', '사행성(차단)', 5) +ON CONFLICT (mcc) DO NOTHING; + +-- 공휴일 마스터 (2026 KR) +INSERT INTO mm_holiday (holiday_date, holiday_name, nation) VALUES + ('2026-01-01', '신정', 'KR'), ('2026-02-16', '설날 연휴', 'KR'), + ('2026-02-17', '설날', 'KR'), ('2026-02-18', '설날 연휴', 'KR'), + ('2026-03-01', '삼일절', 'KR'), ('2026-05-05', '어린이날', 'KR'), + ('2026-05-24', '부처님오신날', 'KR'), ('2026-06-06', '현충일', 'KR'), + ('2026-08-15', '광복절', 'KR'), ('2026-09-24', '추석 연휴', 'KR'), + ('2026-09-25', '추석', 'KR'), ('2026-09-26', '추석 연휴', 'KR'), + ('2026-10-03', '개천절', 'KR'), ('2026-10-09', '한글날', 'KR'), + ('2026-12-25', '성탄절', 'KR') +ON CONFLICT (holiday_date) DO NOTHING; + +-- 환율 마스터 +INSERT INTO mm_fx_rate (ccy, rate_bps, base_date) VALUES + ('USD', 135000, '2026-07-19'), ('JPY', 900, '2026-07-19'), + ('EUR', 147000, '2026-07-19'), ('CNY', 19000, '2026-07-19'), + ('GBP', 171000, '2026-07-19') +ON CONFLICT (ccy) DO NOTHING; + +-- 등급 통계 (최근 7일) +INSERT INTO mm_grade_stat (stat_date, grade, merch_cnt) +SELECT d::date, e.grade, count(*) +FROM generate_series('2026-07-13'::date, '2026-07-19'::date, '1 day') d +CROSS JOIN LATERAL (SELECT grade FROM mm_merchant_ext) e +GROUP BY d::date, e.grade +ON CONFLICT (stat_date, grade) DO NOTHING; + +-- =========================================================================== +-- 8. vl 모듈 (정합성 검증) +-- =========================================================================== +-- 검증 로그: 매입건별 온라인 검증 + 일배치 검증 +INSERT INTO vl_check_log (log_id, check_type, target_id, result_code, detail, biz_date, created_at) +SELECT row_number() OVER (ORDER BY purchase_id), + (ARRAY['VL_SALES','VL_DUPCHK','VL_LIMITCHK','VL_LEDGER'])[purchase_id % 4 + 1], + purchase_id, + CASE WHEN purchase_id % 211 = 0 THEN purchase_id % 3 + 1 ELSE 0 END, + CASE WHEN purchase_id % 211 = 0 + THEN (ARRAY['금액 검증 실패','중복 의심','한도 초과 경고'])[purchase_id % 3 + 1] + ELSE '정상' END, + biz_date, created_at + interval '30 seconds' +FROM ms_px +ON CONFLICT (log_id) DO NOTHING; + +-- 일배치 검증 이력 (영업일 x 배치 3종) +INSERT INTO vl_check_log (log_id, check_type, target_id, result_code, detail, biz_date, created_at) +SELECT 100000 + row_number() OVER (ORDER BY d, bt.ord), + bt.check_type, 0, 0, '일배치 검증 완료', d::date, + d::date::timestamptz + interval '22 hours' + (bt.ord || ' minutes')::interval +FROM generate_series('2026-04-20'::date, '2026-07-19'::date, '1 day') d +CROSS JOIN (VALUES (1, 'VL_DAILY_SALES'), (2, 'VL_DAILY_LEDGER'), (3, 'VL_DAILY_SETTLE')) AS bt(ord, check_type) +ON CONFLICT (log_id) DO NOTHING; + +-- 의심거래: 일별 최고액 2건(한도성 경보) + 주기적 중복 의심 (최근 건 OPEN 유지) +INSERT INTO vl_suspect (suspect_id, purchase_id, rule_code, severity, detail, status, biz_date, created_at) +SELECT row_number() OVER (ORDER BY purchase_id), purchase_id, rule_code, + CASE rule_code WHEN 'LIMIT_OVR' THEN 'WARN' WHEN 'AMT_MAX' THEN 'ERROR' ELSE 'WARN' END, + detail, CASE WHEN biz_date >= '2026-07-10' THEN 'OPEN' + WHEN purchase_id % 3 = 0 THEN 'CONFIRMED' ELSE 'CLOSED' END, + biz_date, biz_date::timestamptz + interval '21 hours' +FROM ( + SELECT purchase_id, biz_date, 'LIMIT_OVR' AS rule_code, + '일 최고액 거래 검출: ' || merchant_id || ' ' || amount || '원' AS detail + FROM (SELECT purchase_id, merchant_id, amount, biz_date, + row_number() OVER (PARTITION BY biz_date ORDER BY amount DESC, purchase_id) AS rk + FROM ms_px) t + WHERE rk <= 2 + UNION ALL + SELECT purchase_id, biz_date, 'DUP_TXN', + '동일 가맹점/금액 중복 의심: ' || merchant_id || ' ' || amount || '원' + FROM ms_px WHERE purchase_id % 523 = 0 +) s +ON CONFLICT (suspect_id) DO NOTHING; + +-- 검증 규칙 보강 (기존 5종 유지) +INSERT INTO vl_rule (rule_code, rule_name, threshold, severity) VALUES + ('FX_RATE', '환율 급변동 검증', 5, 'WARN'), + ('SETTLE_T1','T+1 정산 지연', 1, 'ERROR') +ON CONFLICT (rule_code) DO NOTHING; + +-- =========================================================================== +-- 9. mg 모듈 (전문 게이트웨이) +-- =========================================================================== +-- 전문 로그: 매입건별 0200(IN)/0210(OUT) 페어, 미승인건 응답코드 05 +INSERT INTO mg_msg_log (msg_id, stan, mti, channel, direction, proc_code, bitmap, + amount, pan, mac, rc, raw_msg, status, biz_date, created_at) +SELECT x.purchase_id * 2 + d.ord - 2, + x.purchase_id, + d.mti, x.mg_chan, d.direction, '000000', 'F23A448108E08000', + x.amount, + substr(x.card_no, 1, 6) || '******' || substr(x.card_no, 13, 4), + substr(md5(x.purchase_id::text || d.mti), 1, 16), + CASE WHEN d.mti = '0200' THEN '00' + WHEN NOT coalesce(a.approved, true) THEN '05' + WHEN x.purchase_id % 331 = 0 THEN '91' + ELSE '00' END, + d.mti || '000000' || lpad(x.amount::text, 12, '0') || lpad((x.purchase_id % 1000000)::text, 6, '0'), + CASE d.direction WHEN 'IN' THEN 'R' ELSE 'S' END, + x.biz_date, + x.created_at + (d.ord || ' seconds')::interval +FROM ms_px x +LEFT JOIN approval a ON a.purchase_id = x.purchase_id +CROSS JOIN (VALUES (1, '0200', 'IN'), (2, '0210', 'OUT')) AS d(ord, mti, direction) +ON CONFLICT (msg_id) DO NOTHING; + +-- 망관리 전문 (0800/0810 에코, 채널 x 영업일) +INSERT INTO mg_msg_log (msg_id, stan, mti, channel, direction, proc_code, + amount, rc, status, biz_date, created_at) +SELECT 900000 + row_number() OVER (ORDER BY d, c.channel, e.ord), + 800000 + row_number() OVER (ORDER BY d, c.channel, e.ord), + e.mti, c.channel, e.direction, '990000', 0, '00', + CASE e.direction WHEN 'OUT' THEN 'S' ELSE 'A' END, + d::date, d::date::timestamptz + interval '6 hours' + (e.ord || ' seconds')::interval +FROM generate_series('2026-04-20'::date, '2026-07-19'::date, '1 day') d +CROSS JOIN (VALUES ('CH01'), ('CH02'), ('CH03')) AS c(channel) +CROSS JOIN (VALUES (1, '0800', 'OUT'), (2, '0810', 'IN')) AS e(ord, mti, direction) +ON CONFLICT (msg_id) DO NOTHING; + +-- 재전송 큐: 응답 오류/타임아웃 건 +INSERT INTO mg_queue (queue_id, stan, channel, mti, payload, status, retry_cnt, biz_date, created_at, updated_at) +SELECT row_number() OVER (ORDER BY msg_id), stan, channel, mti, raw_msg, + CASE WHEN biz_date >= '2026-07-18' THEN 'Q' + WHEN stan % 3 = 0 THEN 'F' ELSE 'D' END, + stan % 3 + 1, biz_date, created_at, created_at + interval '10 minutes' +FROM mg_msg_log WHERE rc = '91' +ON CONFLICT (queue_id) DO NOTHING; + +-- 채널 일별 통계 +INSERT INTO mg_stat (channel, biz_date, in_cnt, out_cnt, err_cnt, amount_sum) +SELECT channel, biz_date, + count(*) FILTER (WHERE direction = 'IN'), + count(*) FILTER (WHERE direction = 'OUT'), + count(*) FILTER (WHERE rc <> '00'), + coalesce(sum(amount) FILTER (WHERE mti = '0200'), 0) +FROM mg_msg_log GROUP BY channel, biz_date +ON CONFLICT (channel, biz_date) DO NOTHING; + +-- 라우팅 보강 + 채널 사인온 상태 + 세션 채번 반영 +INSERT INTO mg_route (route_id, channel, mti, dest, priority, active, biz_date) +SELECT nextval('mg_route_seq'), v.channel, v.mti, v.dest, v.pri, true, '2026-07-19' +FROM (VALUES ('CH02', '0800', 'MG_ECHO', 20), ('CH03', '0200', 'ACQ_FOREIGN', 10), + ('CH03', '0800', 'MG_ECHO', 20), ('CH01', '0400', 'ACQ_CANCEL', 15)) AS v(channel, mti, dest, pri) +WHERE NOT EXISTS (SELECT 1 FROM mg_route r WHERE r.channel = v.channel AND r.mti = v.mti); + +UPDATE mg_channel c SET status = 'UP', + session_key = substr(md5('sess' || c.channel), 1, 16), + last_stan = coalesce((SELECT max(stan) FROM mg_msg_log l WHERE l.channel = c.channel), 0), + signon_at = '2026-07-19 06:00:00+09', updated_at = now(); + +UPDATE mg_seq SET seq_val = 273, updated_at = now() WHERE seq_name = 'SESSION' AND seq_val = 0; +UPDATE mg_seq SET seq_val = 91, updated_at = now() WHERE seq_name = 'KEYVER' AND seq_val = 0; + +-- =========================================================================== +-- 10. cm 모듈 (공통) +-- =========================================================================== +-- 운영 로그: 온라인 서비스 호출 + 배치 + 경고/오류 혼합 (~1.5만) +INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date, created_at) +SELECT row_number() OVER (ORDER BY ord, id), svc_name, log_level, msg, biz_date, ts +FROM ( + -- 매입 접수 로그 (2건 중 1건) + SELECT 1 AS ord, purchase_id AS id, 'ACQUIRE' AS svc_name, 'I' AS log_level, + '매입 접수 완료 pid=' || purchase_id || ' ' || merchant_id || ' ' || amount || '원' AS msg, + biz_date, created_at AS ts + FROM ms_px WHERE purchase_id % 2 = 0 + UNION ALL + -- 지급 처리 로그 + SELECT 2, payout_id, 'PAY_REMIT', + CASE WHEN status = 'J' THEN 'E' ELSE 'I' END, + CASE WHEN status = 'J' THEN '지급 반려 payout=' || payout_id || ' 사유=' || reject_reason + ELSE '지급 처리 payout=' || payout_id || ' ' || merchant_id || ' ' || net || '원' END, + biz_date, created_at + FROM py_payout + UNION ALL + -- 일배치 로그 (영업일 x 배치 6종) + SELECT 3, row_number() OVER (ORDER BY d, b.ord), b.svc, 'I', + b.svc || ' 배치 완료 (' || to_char(d, 'YYYY-MM-DD') || ')', d::date, + d::date::timestamptz + interval '23 hours' + (b.ord || ' minutes')::interval + FROM generate_series('2026-04-20'::date, '2026-07-19'::date, '1 day') d + CROSS JOIN (VALUES (1, 'rc_recon_batch'), (2, 'st_settle_batch'), (3, 'lg_post_batch'), + (4, 'cl_daily_batch'), (5, 'mg_stat_batch'), (6, 'cm_purge_batch')) AS b(ord, svc) + UNION ALL + -- 산발적 경고/오류 + SELECT 4, gs, (ARRAY['AUTH_LIMIT','RC_MATCH','ST_CALC','MG_SEND','VL_SALES'])[gs % 5 + 1], + CASE WHEN gs % 9 = 0 THEN 'E' ELSE 'W' END, + CASE WHEN gs % 9 = 0 THEN 'DB 응답 지연 재시도 후 성공 (attempt=2)' + ELSE (ARRAY['한도 임계치 80% 도달','대사 지연 경고','수수료 재계산 수행', + '채널 응답 지연 1.2s','검증 경고 검출'])[gs % 5 + 1] END, + ('2026-04-20'::date + (gs % 91)), + ('2026-04-20'::date + (gs % 91))::timestamptz + interval '9 hours' + (gs % 600 || ' minutes')::interval + FROM generate_series(1, 2800) gs +) l +ON CONFLICT (log_id) DO NOTHING; + +-- 공휴일 보강 +INSERT INTO cm_holiday (holi_date, holi_name, nation) VALUES + ('2026-02-16', '설날 연휴', 'KR'), ('2026-02-17', '설날', 'KR'), + ('2026-02-18', '설날 연휴', 'KR'), ('2026-05-24', '부처님오신날', 'KR'), + ('2026-06-06', '현충일', 'KR'), ('2026-09-24', '추석 연휴', 'KR'), + ('2026-09-25', '추석', 'KR'), ('2026-09-26', '추석 연휴', 'KR'), + ('2026-10-03', '개천절', 'KR'), ('2026-10-09', '한글날', 'KR') +ON CONFLICT (holi_date) DO NOTHING; + +-- 일별 환율 이력 (기존 2026-07-19 시드 유지) +INSERT INTO cm_fx_rate (ccy, base_date, rate_bps) +SELECT c.ccy, d::date, + c.base + ((extract(day FROM d)::int * c.step) % c.span) - c.span / 2 +FROM generate_series('2026-04-20'::date, '2026-07-18'::date, '1 day') d +CROSS JOIN (VALUES ('USD', 135000, 137, 2600), ('JPY', 900, 7, 40), + ('EUR', 147000, 211, 3200)) AS c(ccy, base, step, span) +ON CONFLICT (ccy, base_date) DO NOTHING; + +-- 공통코드/파라미터 보강 +INSERT INTO cm_code (code_grp, code, code_name, sort_ord) VALUES + ('STATUS', 'M', '대사완료', 4), ('STATUS', 'H', '보류', 5), ('STATUS', 'V', '무효', 6), + ('PAYCH', 'STD', '일반지급', 1), ('PAYCH', 'BULK', '대량지급', 2), + ('PAYCH', 'ADVANCE', '선지급', 3), + ('MTI', '0200', '승인요청', 1), ('MTI', '0210', '승인응답', 2), + ('MTI', '0800', '망관리요청', 3), ('MTI', '0810', '망관리응답', 4) +ON CONFLICT (code_grp, code) DO NOTHING; + +INSERT INTO cm_param (param_key, param_val, descr) VALUES + ('PAYOUT_FEE', '500', '건당 이체수수료(원)'), + ('PAYOUT_CYCLE', 'T+1', '지급 주기'), + ('CLOSE_TIME', '23:00', '일마감 기준시각'), + ('FRAUD_SCORE', '700', '부정거래 탐지 임계 점수'), + ('MAX_RETRY', '3', '전문 재전송 최대 횟수') +ON CONFLICT (param_key) DO NOTHING; + +UPDATE cm_seq SET seq_val = 1840, updated_at = now() WHERE seq_name = 'BATCH' AND seq_val = 0; +UPDATE cm_seq SET seq_val = 9190, updated_at = now() WHERE seq_name = 'SLIP' AND seq_val = 0; +UPDATE cm_seq SET seq_val = 47, updated_at = now() WHERE seq_name = 'TICKET' AND seq_val = 0; + +-- =========================================================================== +-- 11. ac 상세 테이블 (00-base) - 업무화면 공백 방지 보강 +-- =========================================================================== +INSERT INTO ac_cancel (cancel_id, purchase_id, cancel_type, amount, reason, biz_date, created_at) +SELECT row_number() OVER (ORDER BY purchase_id), purchase_id, + CASE WHEN status = 'V' THEN 'VOID' + WHEN purchase_id % 5 = 0 THEN 'PARTIAL' ELSE 'FULL' END, + CASE WHEN status <> 'V' AND purchase_id % 5 = 0 THEN amount / 2 / 100 * 100 ELSE amount END, + (ARRAY['고객 변심 반품','이중 결제 취소','가맹점 요청 취소','민원 처리 환불'])[purchase_id % 4 + 1], + biz_date, created_at + interval '4 hours' +FROM ms_px WHERE status IN ('C','V') +ON CONFLICT (cancel_id) DO NOTHING; + +-- 할부: 고액 일부 건 3/6/12개월 회차 전개 +INSERT INTO ac_install (install_id, purchase_id, seq_no, months, month_amount, biz_date) +SELECT row_number() OVER (ORDER BY t.purchase_id, gs), t.purchase_id, gs, t.months, + t.amount / t.months / 100 * 100, t.biz_date +FROM (SELECT purchase_id, amount, biz_date, + (ARRAY[3, 6, 12])[purchase_id % 3 + 1] AS months + FROM ms_px WHERE purchase_id % 11 = 0 AND amount >= 500000) t +CROSS JOIN LATERAL generate_series(1, t.months) gs +ON CONFLICT (install_id) DO NOTHING; + +INSERT INTO ac_fee_adj (adj_id, purchase_id, delta, reason, biz_date, created_at) +SELECT row_number() OVER (ORDER BY purchase_id), purchase_id, + CASE WHEN purchase_id % 2 = 0 THEN 1 ELSE -1 END * ((purchase_id % 8 + 1) * 100), + (ARRAY['요율 소급 적용','프로모션 수수료 감면','정정 재계산'])[purchase_id % 3 + 1], + biz_date, created_at + interval '1 day' +FROM ms_px WHERE purchase_id % 201 = 0 +ON CONFLICT (adj_id) DO NOTHING; + +-- 원천징수: 의료/약국 가맹점 정산건 +INSERT INTO ac_wht (wht_id, purchase_id, tax_amount, biz_date) +SELECT row_number() OVER (ORDER BY purchase_id), purchase_id, net * 220 / 10000, biz_date +FROM ms_px +WHERE status = 'S' AND merchant_id IN ('M0005','M0008','M0022','M0024') +ON CONFLICT (wht_id) DO NOTHING; + +-- 세금계산서: EDI 청구 채널 +INSERT INTO ac_tax_invoice (inv_id, purchase_id, merchant_id, supply, vat, biz_date) +SELECT row_number() OVER (ORDER BY purchase_id), purchase_id, merchant_id, + amount * 10 / 11, amount - amount * 10 / 11, biz_date +FROM ms_px WHERE channel = 'EDI' +ON CONFLICT (inv_id) DO NOTHING; + +-- 해외 매입 환산 +INSERT INTO ac_fx (fx_id, purchase_id, ccy, fx_amount, krw_amount, rate_bps, biz_date) +SELECT row_number() OVER (ORDER BY purchase_id), purchase_id, c.ccy, + amount * 100 / c.rate, amount, c.rate, biz_date +FROM ms_px +CROSS JOIN LATERAL (SELECT (ARRAY['USD','JPY','EUR'])[purchase_id % 3 + 1] AS ccy, + (ARRAY[135000, 900, 147000])[purchase_id % 3 + 1]::bigint AS rate) c +WHERE channel = 'FOREIGN' +ON CONFLICT (fx_id) DO NOTHING; + +-- EDI 청구 문서 +INSERT INTO ac_edi (edi_id, merchant_id, doc_no, amount, biz_date) +SELECT row_number() OVER (ORDER BY purchase_id), merchant_id, + 'EDI' || to_char(biz_date, 'YYYYMMDD') || '-' || lpad((purchase_id % 10000)::text, 4, '0'), + amount, biz_date +FROM ms_px WHERE channel = 'EDI' +ON CONFLICT (edi_id) DO NOTHING; + +-- 미매입 (보류건) +INSERT INTO ac_unmatch (unmatch_id, purchase_id, reason, biz_date) +SELECT row_number() OVER (ORDER BY purchase_id), purchase_id, + (ARRAY['승인 매핑 대기','매입 전문 미도래','금액 확인 필요'])[purchase_id % 3 + 1], + biz_date +FROM ms_px WHERE status = 'H' +ON CONFLICT (unmatch_id) DO NOTHING; + +-- =========================================================================== +-- 12. 시퀀스 정렬 (라이브 트래픽이 시드 뒤 번호를 받도록) +-- =========================================================================== +SELECT setval('au_auth_seq', coalesce((SELECT max(auth_id) FROM au_authorization), 1)); +SELECT setval('au_fraud_seq', coalesce((SELECT max(fraud_id) FROM au_fraud_log), 1)); +SELECT setval('au_preauth_seq', coalesce((SELECT max(preauth_id) FROM au_preauth), 1)); +SELECT setval('au_cancel_seq', coalesce((SELECT max(cancel_id) FROM au_cancel), 1)); +SELECT setval('py_payout_seq', coalesce((SELECT max(payout_id) FROM py_payout), 1)); +SELECT setval('py_file_seq', coalesce((SELECT max(file_id) FROM py_file), 1)); +SELECT setval('py_schedule_seq', coalesce((SELECT max(schedule_id) FROM py_schedule), 1)); +SELECT setval('py_adjust_seq', coalesce((SELECT max(adj_id) FROM py_adjust), 1)); +SELECT setval('py_ledger_seq', coalesce((SELECT max(ledger_id) FROM py_ledger), 1)); +SELECT setval('rc_match_seq', coalesce((SELECT max(match_id) FROM rc_match_log), 1)); +SELECT setval('rc_exc_seq', coalesce((SELECT max(exc_id) FROM rc_exception), 1)); +SELECT setval('rc_diff_seq', coalesce((SELECT max(diff_id) FROM rc_diff), 1)); +SELECT setval('rc_alert_seq', coalesce((SELECT max(alert_id) FROM rc_alert), 1)); +SELECT setval('st_dtl_seq', coalesce((SELECT max(dtl_id) FROM st_settle_dtl), 1)); +SELECT setval('st_adj_seq', coalesce((SELECT max(adj_id) FROM st_adjust), 1)); +SELECT setval('st_pay_seq', coalesce((SELECT max(pay_id) FROM st_payment), 1)); +SELECT setval('lg_voucher_seq', coalesce((SELECT max(voucher_id) FROM lg_voucher), 1)); +SELECT setval('lg_line_seq', coalesce((SELECT max(line_id) FROM lg_ledger_line), 1)); +SELECT setval('cl_close_seq', coalesce((SELECT max(close_id) FROM cl_close_log), 1)); +SELECT setval('cl_snapshot_seq', coalesce((SELECT max(snapshot_id) FROM cl_snapshot), 1)); +SELECT setval('mm_merch_hist_seq', coalesce((SELECT max(hist_id) FROM mm_merch_hist), 1)); +SELECT setval('mm_feerate_hist_seq', coalesce((SELECT max(hist_id) FROM mm_fee_rate_hist), 1)); +SELECT setval('vl_suspect_seq', coalesce((SELECT max(suspect_id) FROM vl_suspect), 1)); +SELECT setval('vl_checklog_seq', coalesce((SELECT max(log_id) FROM vl_check_log), 1)); +SELECT setval('mg_msg_seq', coalesce((SELECT max(msg_id) FROM mg_msg_log), 1)); +SELECT setval('mg_stan_seq', coalesce((SELECT max(stan) FROM mg_msg_log), 1)); +SELECT setval('mg_queue_seq', coalesce((SELECT max(queue_id) FROM mg_queue), 1)); +SELECT setval('cm_log_seq', coalesce((SELECT max(log_id) FROM cm_log), 1)); +SELECT setval('ac_cancel_seq', coalesce((SELECT max(cancel_id) FROM ac_cancel), 1)); +SELECT setval('ac_install_seq', coalesce((SELECT max(install_id) FROM ac_install), 1)); +SELECT setval('ac_feeadj_seq', coalesce((SELECT max(adj_id) FROM ac_fee_adj), 1)); +SELECT setval('ac_wht_seq', coalesce((SELECT max(wht_id) FROM ac_wht), 1)); +SELECT setval('ac_taxinv_seq', coalesce((SELECT max(inv_id) FROM ac_tax_invoice), 1)); +SELECT setval('ac_fx_seq', coalesce((SELECT max(fx_id) FROM ac_fx), 1)); +SELECT setval('ac_edi_seq', coalesce((SELECT max(edi_id) FROM ac_edi), 1)); +SELECT setval('ac_unmatch_seq', coalesce((SELECT max(unmatch_id) FROM ac_unmatch), 1)); + +DROP TABLE ms_px; +DROP TABLE ms_ms;