From fc16ea64c8f8fe8144de15b056cd50c895d9f970 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sun, 19 Jul 2026 14:37:10 +0000 Subject: [PATCH] =?UTF-8?q?=EA=B3=A0=EB=8F=84=ED=99=94:=20=EC=A0=84=20?= =?UTF-8?q?=EB=AA=A8=EB=93=88=20dbio=2025=E2=86=9240=20=C2=B7=20batch=2017?= =?UTF-8?q?=E2=86=9222=20=EC=84=B8=EB=B6=84=ED=99=94=20(+432=EB=B3=B8,=20?= =?UTF-8?q?=EC=A0=84=EB=B6=80=20=EA=B3=A0=EC=9C=A0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 11개 모듈 각 dbio +15(→40), batch +5(→22) 새 고유 ECPG 프로그램 + 카피북 헤더 (window/HAVING/JOIN/FILTER/NOT EXISTS/upsert/correlated/INSERT..SELECT 등 구조 다양) - 전 코퍼스 .pgc 1004/1004 정규화-고유 (dup 그룹 0) - 통합 검증: 12서버 runok, 333 서비스, 242 배치 바이너리, 매입체인 XA 커밋, prepared_xacts=0 Co-Authored-By: Claude Opus 4.8 (1M context) --- app/src/ac/batch/ac_amountband_batch.pgc | 60 +++++++++++++++++ app/src/ac/batch/ac_concentration_batch.pgc | 58 +++++++++++++++++ app/src/ac/batch/ac_fxweight_batch.pgc | 53 +++++++++++++++ app/src/ac/batch/ac_summaryrebuild_batch.pgc | 63 ++++++++++++++++++ app/src/ac/batch/ac_unmatchpurge_batch.pgc | 49 ++++++++++++++ app/src/ac/dbio/ac_amountband_batch.h | 17 +++++ app/src/ac/dbio/ac_cancel_casebucket_dbio.h | 14 ++++ app/src/ac/dbio/ac_cancel_casebucket_dbio.pgc | 32 +++++++++ app/src/ac/dbio/ac_concentration_batch.h | 18 +++++ app/src/ac/dbio/ac_edi_intoledger_dbio.h | 14 ++++ app/src/ac/dbio/ac_edi_intoledger_dbio.pgc | 27 ++++++++ app/src/ac/dbio/ac_feeadj_repeat_dbio.h | 14 ++++ app/src/ac/dbio/ac_feeadj_repeat_dbio.pgc | 34 ++++++++++ app/src/ac/dbio/ac_fx_weightavg_dbio.h | 14 ++++ app/src/ac/dbio/ac_fx_weightavg_dbio.pgc | 25 +++++++ app/src/ac/dbio/ac_fxweight_batch.h | 17 +++++ app/src/ac/dbio/ac_install_corr_dbio.h | 14 ++++ app/src/ac/dbio/ac_install_corr_dbio.pgc | 24 +++++++ app/src/ac/dbio/ac_merchant_bigday_dbio.h | 14 ++++ app/src/ac/dbio/ac_merchant_bigday_dbio.pgc | 34 ++++++++++ app/src/ac/dbio/ac_purchase_incancel_dbio.h | 15 +++++ app/src/ac/dbio/ac_purchase_incancel_dbio.pgc | 38 +++++++++++ app/src/ac/dbio/ac_purchase_rankpid_dbio.h | 14 ++++ app/src/ac/dbio/ac_purchase_rankpid_dbio.pgc | 31 +++++++++ app/src/ac/dbio/ac_purchase_topn_dbio.h | 14 ++++ app/src/ac/dbio/ac_purchase_topn_dbio.pgc | 31 +++++++++ app/src/ac/dbio/ac_settle_join_dbio.h | 15 +++++ app/src/ac/dbio/ac_settle_join_dbio.pgc | 39 +++++++++++ app/src/ac/dbio/ac_settle_notexists_dbio.h | 14 ++++ app/src/ac/dbio/ac_settle_notexists_dbio.pgc | 23 +++++++ app/src/ac/dbio/ac_summary_rebuild_dbio.h | 14 ++++ app/src/ac/dbio/ac_summary_rebuild_dbio.pgc | 36 ++++++++++ app/src/ac/dbio/ac_summaryrebuild_batch.h | 16 +++++ app/src/ac/dbio/ac_taxinv_spread_dbio.h | 15 +++++ app/src/ac/dbio/ac_taxinv_spread_dbio.pgc | 35 ++++++++++ app/src/ac/dbio/ac_unmatch_purge_dbio.h | 14 ++++ app/src/ac/dbio/ac_unmatch_purge_dbio.pgc | 25 +++++++ app/src/ac/dbio/ac_unmatchpurge_batch.h | 15 +++++ app/src/ac/dbio/ac_wht_distinct_dbio.h | 14 ++++ app/src/ac/dbio/ac_wht_distinct_dbio.pgc | 27 ++++++++ app/src/au/batch/au_approverate_batch.pgc | 53 +++++++++++++++ app/src/au/batch/au_fraudpurge_batch.pgc | 52 +++++++++++++++ app/src/au/batch/au_scorebucket_batch.pgc | 58 +++++++++++++++++ app/src/au/batch/au_summaryrebuild_batch.pgc | 65 +++++++++++++++++++ app/src/au/batch/au_topcard_batch.pgc | 62 ++++++++++++++++++ app/src/au/dbio/au_approverate_batch.h | 17 +++++ app/src/au/dbio/au_auth_distinctcard_dbio.h | 14 ++++ app/src/au/dbio/au_auth_distinctcard_dbio.pgc | 28 ++++++++ app/src/au/dbio/au_auth_incancel_dbio.h | 14 ++++ app/src/au/dbio/au_auth_incancel_dbio.pgc | 23 +++++++ app/src/au/dbio/au_auth_rankcard_dbio.h | 14 ++++ app/src/au/dbio/au_auth_rankcard_dbio.pgc | 31 +++++++++ app/src/au/dbio/au_auth_topcard_dbio.h | 14 ++++ app/src/au/dbio/au_auth_topcard_dbio.pgc | 35 ++++++++++ app/src/au/dbio/au_cancel_corr_dbio.h | 14 ++++ app/src/au/dbio/au_cancel_corr_dbio.pgc | 23 +++++++ app/src/au/dbio/au_capture_join_dbio.h | 14 ++++ app/src/au/dbio/au_capture_join_dbio.pgc | 23 +++++++ app/src/au/dbio/au_fraud_casescore_dbio.h | 14 ++++ app/src/au/dbio/au_fraud_casescore_dbio.pgc | 25 +++++++ app/src/au/dbio/au_fraud_intolog_dbio.h | 14 ++++ app/src/au/dbio/au_fraud_intolog_dbio.pgc | 28 ++++++++ app/src/au/dbio/au_fraud_purge_dbio.h | 14 ++++ app/src/au/dbio/au_fraud_purge_dbio.pgc | 25 +++++++ app/src/au/dbio/au_fraudpurge_batch.h | 16 +++++ app/src/au/dbio/au_limit_spread_dbio.h | 14 ++++ app/src/au/dbio/au_limit_spread_dbio.pgc | 22 +++++++ app/src/au/dbio/au_merchant_active_dbio.h | 14 ++++ app/src/au/dbio/au_merchant_active_dbio.pgc | 32 +++++++++ app/src/au/dbio/au_preauth_notexists_dbio.h | 14 ++++ app/src/au/dbio/au_preauth_notexists_dbio.pgc | 24 +++++++ app/src/au/dbio/au_scorebucket_batch.h | 17 +++++ app/src/au/dbio/au_summary_rebuild_dbio.h | 14 ++++ app/src/au/dbio/au_summary_rebuild_dbio.pgc | 38 +++++++++++ app/src/au/dbio/au_summary_riskmerch_dbio.h | 14 ++++ app/src/au/dbio/au_summary_riskmerch_dbio.pgc | 32 +++++++++ app/src/au/dbio/au_summary_weightavg_dbio.h | 14 ++++ app/src/au/dbio/au_summary_weightavg_dbio.pgc | 33 ++++++++++ app/src/au/dbio/au_summaryrebuild_batch.h | 16 +++++ app/src/au/dbio/au_topcard_batch.h | 16 +++++ app/src/cl/batch/cl_gap_batch.pgc | 50 ++++++++++++++ app/src/cl/batch/cl_gap_cpy.h | 8 +++ app/src/cl/batch/cl_purgeold_batch.pgc | 35 ++++++++++ app/src/cl/batch/cl_purgeold_cpy.h | 5 ++ app/src/cl/batch/cl_relock_batch.pgc | 47 ++++++++++++++ app/src/cl/batch/cl_relock_cpy.h | 4 ++ app/src/cl/batch/cl_reweigh_batch.pgc | 51 +++++++++++++++ app/src/cl/batch/cl_reweigh_cpy.h | 6 ++ app/src/cl/batch/cl_tierstat_batch.pgc | 39 +++++++++++ app/src/cl/batch/cl_tierstat_cpy.h | 7 ++ app/src/cl/dbio/cl_corr_sub_dbio.h | 9 +++ app/src/cl/dbio/cl_corr_sub_dbio.pgc | 34 ++++++++++ app/src/cl/dbio/cl_lock_upsert_dbio.h | 9 +++ app/src/cl/dbio/cl_lock_upsert_dbio.pgc | 24 +++++++ app/src/cl/dbio/cl_minmax_dbio.h | 9 +++ app/src/cl/dbio/cl_minmax_dbio.pgc | 24 +++++++ app/src/cl/dbio/cl_snap_distinct_dbio.h | 9 +++ app/src/cl/dbio/cl_snap_distinct_dbio.pgc | 20 ++++++ app/src/cl/dbio/cl_snap_in_dbio.h | 9 +++ app/src/cl/dbio/cl_snap_in_dbio.pgc | 23 +++++++ app/src/cl/dbio/cl_snap_insert_sel_dbio.h | 9 +++ app/src/cl/dbio/cl_snap_insert_sel_dbio.pgc | 28 ++++++++ app/src/cl/dbio/cl_snap_join_dbio.h | 9 +++ app/src/cl/dbio/cl_snap_join_dbio.pgc | 23 +++++++ app/src/cl/dbio/cl_snap_purge_dbio.h | 9 +++ app/src/cl/dbio/cl_snap_purge_dbio.pgc | 20 ++++++ app/src/cl/dbio/cl_snap_rank_dbio.h | 9 +++ app/src/cl/dbio/cl_snap_rank_dbio.pgc | 25 +++++++ app/src/cl/dbio/cl_status_filter_dbio.h | 9 +++ app/src/cl/dbio/cl_status_filter_dbio.pgc | 27 ++++++++ app/src/cl/dbio/cl_streak_cursor_dbio.h | 9 +++ app/src/cl/dbio/cl_streak_cursor_dbio.pgc | 33 ++++++++++ app/src/cl/dbio/cl_tier_case_dbio.h | 9 +++ app/src/cl/dbio/cl_tier_case_dbio.pgc | 24 +++++++ app/src/cl/dbio/cl_type_having_dbio.h | 9 +++ app/src/cl/dbio/cl_type_having_dbio.pgc | 32 +++++++++ app/src/cl/dbio/cl_unlocked_dbio.h | 9 +++ app/src/cl/dbio/cl_unlocked_dbio.pgc | 22 +++++++ app/src/cl/dbio/cl_wavg_dbio.h | 9 +++ app/src/cl/dbio/cl_wavg_dbio.pgc | 21 ++++++ app/src/cm/batch/cm_fxlog_batch.pgc | 40 ++++++++++++ app/src/cm/batch/cm_fxpurge_batch.pgc | 39 +++++++++++ app/src/cm/batch/cm_levelgrp_batch.pgc | 41 ++++++++++++ app/src/cm/batch/cm_nofx_batch.pgc | 41 ++++++++++++ app/src/cm/batch/cm_rankfx_batch.pgc | 49 ++++++++++++++ app/src/cm/dbio/cm_ccycase_dbio.h | 11 ++++ app/src/cm/dbio/cm_ccycase_dbio.pgc | 24 +++++++ app/src/cm/dbio/cm_codefxjoin_dbio.h | 11 ++++ app/src/cm/dbio/cm_codefxjoin_dbio.pgc | 32 +++++++++ app/src/cm/dbio/cm_errfilter_dbio.h | 11 ++++ app/src/cm/dbio/cm_errfilter_dbio.pgc | 22 +++++++ app/src/cm/dbio/cm_fxabov_dbio.h | 11 ++++ app/src/cm/dbio/cm_fxabov_dbio.pgc | 22 +++++++ app/src/cm/dbio/cm_fxlog_batch.h | 10 +++ app/src/cm/dbio/cm_fxpurge_batch.h | 11 ++++ app/src/cm/dbio/cm_grpdistinct_dbio.h | 11 ++++ app/src/cm/dbio/cm_grpdistinct_dbio.pgc | 18 +++++ app/src/cm/dbio/cm_levelgroup_dbio.h | 11 ++++ app/src/cm/dbio/cm_levelgroup_dbio.pgc | 32 +++++++++ app/src/cm/dbio/cm_levelgrp_batch.h | 10 +++ app/src/cm/dbio/cm_logfromfx_dbio.h | 11 ++++ app/src/cm/dbio/cm_logfromfx_dbio.pgc | 22 +++++++ app/src/cm/dbio/cm_login_dbio.h | 11 ++++ app/src/cm/dbio/cm_login_dbio.pgc | 23 +++++++ app/src/cm/dbio/cm_nofx_batch.h | 10 +++ app/src/cm/dbio/cm_nofx_dbio.h | 11 ++++ app/src/cm/dbio/cm_nofx_dbio.pgc | 23 +++++++ app/src/cm/dbio/cm_purgefx_dbio.h | 11 ++++ app/src/cm/dbio/cm_purgefx_dbio.pgc | 19 ++++++ app/src/cm/dbio/cm_rankfx_batch.h | 10 +++ app/src/cm/dbio/cm_rankfx_dbio.h | 11 ++++ app/src/cm/dbio/cm_rankfx_dbio.pgc | 31 +++++++++ app/src/cm/dbio/cm_sortrange_dbio.h | 11 ++++ app/src/cm/dbio/cm_sortrange_dbio.pgc | 21 ++++++ app/src/lg/batch/lg_dcweight_batch.pgc | 47 ++++++++++++++ app/src/lg/batch/lg_imbalscan_batch.pgc | 54 +++++++++++++++ app/src/lg/batch/lg_linepurge_batch.pgc | 43 ++++++++++++ app/src/lg/batch/lg_reversepair_batch.pgc | 44 +++++++++++++ app/src/lg/batch/lg_trialload_batch.pgc | 51 +++++++++++++++ app/src/lg/dbio/lg_dc_ratio_dbio.h | 11 ++++ app/src/lg/dbio/lg_dc_ratio_dbio.pgc | 25 +++++++ app/src/lg/dbio/lg_dcweight_batch.h | 14 ++++ app/src/lg/dbio/lg_imbalscan_batch.h | 14 ++++ app/src/lg/dbio/lg_line_corr_dbio.h | 11 ++++ app/src/lg/dbio/lg_line_corr_dbio.pgc | 24 +++++++ app/src/lg/dbio/lg_line_delrange_dbio.h | 11 ++++ app/src/lg/dbio/lg_line_delrange_dbio.pgc | 21 ++++++ app/src/lg/dbio/lg_line_span_dbio.h | 11 ++++ app/src/lg/dbio/lg_line_span_dbio.pgc | 25 +++++++ app/src/lg/dbio/lg_line_window_dbio.h | 11 ++++ app/src/lg/dbio/lg_line_window_dbio.pgc | 25 +++++++ app/src/lg/dbio/lg_linepurge_batch.h | 13 ++++ app/src/lg/dbio/lg_merch_accum_dbio.h | 11 ++++ app/src/lg/dbio/lg_merch_accum_dbio.pgc | 29 +++++++++ app/src/lg/dbio/lg_merch_having_dbio.h | 11 ++++ app/src/lg/dbio/lg_merch_having_dbio.pgc | 25 +++++++ app/src/lg/dbio/lg_posted_join_dbio.h | 11 ++++ app/src/lg/dbio/lg_posted_join_dbio.pgc | 23 +++++++ app/src/lg/dbio/lg_reverse_pair_dbio.h | 11 ++++ app/src/lg/dbio/lg_reverse_pair_dbio.pgc | 24 +++++++ app/src/lg/dbio/lg_reversepair_batch.h | 13 ++++ app/src/lg/dbio/lg_settle_lines_dbio.h | 11 ++++ app/src/lg/dbio/lg_settle_lines_dbio.pgc | 23 +++++++ app/src/lg/dbio/lg_trial_load_dbio.h | 11 ++++ app/src/lg/dbio/lg_trial_load_dbio.pgc | 29 +++++++++ app/src/lg/dbio/lg_trialload_batch.h | 13 ++++ app/src/lg/dbio/lg_type_walk_dbio.h | 11 ++++ app/src/lg/dbio/lg_type_walk_dbio.pgc | 33 ++++++++++ app/src/lg/dbio/lg_voucher_empty_dbio.h | 11 ++++ app/src/lg/dbio/lg_voucher_empty_dbio.pgc | 23 +++++++ app/src/lg/dbio/lg_voucher_mix_dbio.h | 11 ++++ app/src/lg/dbio/lg_voucher_mix_dbio.pgc | 24 +++++++ app/src/lg/dbio/lg_worst_acct_dbio.h | 11 ++++ app/src/lg/dbio/lg_worst_acct_dbio.pgc | 26 ++++++++ app/src/mg/batch/mg_ackpurge_batch.pgc | 39 +++++++++++ app/src/mg/batch/mg_mtierr_batch.pgc | 41 ++++++++++++ app/src/mg/batch/mg_rankamt_batch.pgc | 49 ++++++++++++++ app/src/mg/batch/mg_requeue_batch.pgc | 41 ++++++++++++ app/src/mg/batch/mg_unrouted_batch.pgc | 41 ++++++++++++ app/src/mg/dbio/mg_abovavg_dbio.h | 11 ++++ app/src/mg/dbio/mg_abovavg_dbio.pgc | 23 +++++++ app/src/mg/dbio/mg_ackpurge_batch.h | 11 ++++ app/src/mg/dbio/mg_ackpurge_dbio.h | 11 ++++ app/src/mg/dbio/mg_ackpurge_dbio.pgc | 20 ++++++ app/src/mg/dbio/mg_chdistinct_dbio.h | 11 ++++ app/src/mg/dbio/mg_chdistinct_dbio.pgc | 23 +++++++ app/src/mg/dbio/mg_dircase_dbio.h | 11 ++++ app/src/mg/dbio/mg_dircase_dbio.pgc | 24 +++++++ app/src/mg/dbio/mg_mtierr_batch.h | 10 +++ app/src/mg/dbio/mg_mtigroup_dbio.h | 11 ++++ app/src/mg/dbio/mg_mtigroup_dbio.pgc | 32 +++++++++ app/src/mg/dbio/mg_nackfilter_dbio.h | 11 ++++ app/src/mg/dbio/mg_nackfilter_dbio.pgc | 22 +++++++ app/src/mg/dbio/mg_qforup_dbio.h | 11 ++++ app/src/mg/dbio/mg_qforup_dbio.pgc | 22 +++++++ app/src/mg/dbio/mg_rankamt_batch.h | 10 +++ app/src/mg/dbio/mg_rankamt_dbio.h | 11 ++++ app/src/mg/dbio/mg_rankamt_dbio.pgc | 31 +++++++++ app/src/mg/dbio/mg_reqfail_dbio.h | 11 ++++ app/src/mg/dbio/mg_reqfail_dbio.pgc | 23 +++++++ app/src/mg/dbio/mg_requeue_batch.h | 10 +++ app/src/mg/dbio/mg_routegrp_dbio.h | 11 ++++ app/src/mg/dbio/mg_routegrp_dbio.pgc | 32 +++++++++ app/src/mg/dbio/mg_stanrange_dbio.h | 11 ++++ app/src/mg/dbio/mg_stanrange_dbio.pgc | 21 ++++++ app/src/mg/dbio/mg_unrouted_batch.h | 10 +++ app/src/mg/dbio/mg_unrouted_dbio.h | 11 ++++ app/src/mg/dbio/mg_unrouted_dbio.pgc | 23 +++++++ app/src/mm/batch/mm_bin_dedup_batch.pgc | 35 ++++++++++ app/src/mm/batch/mm_bin_dedup_cpy.h | 5 ++ app/src/mm/batch/mm_fee_bump_batch.pgc | 50 ++++++++++++++ app/src/mm/batch/mm_fee_bump_cpy.h | 6 ++ app/src/mm/batch/mm_hist_archive_batch.pgc | 39 +++++++++++ app/src/mm/batch/mm_hist_archive_cpy.h | 5 ++ app/src/mm/batch/mm_limit_scale_batch.pgc | 40 ++++++++++++ app/src/mm/batch/mm_limit_scale_cpy.h | 6 ++ app/src/mm/batch/mm_risk_rollup_batch.pgc | 47 ++++++++++++++ app/src/mm/batch/mm_risk_rollup_cpy.h | 6 ++ app/src/mm/dbio/mm_bin_distinct_dbio.h | 9 +++ app/src/mm/dbio/mm_bin_distinct_dbio.pgc | 22 +++++++ app/src/mm/dbio/mm_fee_in_dbio.h | 9 +++ app/src/mm/dbio/mm_fee_in_dbio.pgc | 23 +++++++ app/src/mm/dbio/mm_fee_rank_dbio.h | 9 +++ app/src/mm/dbio/mm_fee_rank_dbio.pgc | 26 ++++++++ app/src/mm/dbio/mm_fx_upsert_dbio.h | 9 +++ app/src/mm/dbio/mm_fx_upsert_dbio.pgc | 25 +++++++ app/src/mm/dbio/mm_grade_having_dbio.h | 9 +++ app/src/mm/dbio/mm_grade_having_dbio.pgc | 32 +++++++++ app/src/mm/dbio/mm_grade_insert_sel_dbio.h | 9 +++ app/src/mm/dbio/mm_grade_insert_sel_dbio.pgc | 24 +++++++ app/src/mm/dbio/mm_hist_purge_dbio.h | 9 +++ app/src/mm/dbio/mm_hist_purge_dbio.pgc | 20 ++++++ app/src/mm/dbio/mm_issuer_corr_dbio.h | 9 +++ app/src/mm/dbio/mm_issuer_corr_dbio.pgc | 33 ++++++++++ app/src/mm/dbio/mm_mcc_risk_cursor_dbio.h | 9 +++ app/src/mm/dbio/mm_mcc_risk_cursor_dbio.pgc | 32 +++++++++ app/src/mm/dbio/mm_noterm_dbio.h | 9 +++ app/src/mm/dbio/mm_noterm_dbio.pgc | 22 +++++++ app/src/mm/dbio/mm_rate_minmax_dbio.h | 9 +++ app/src/mm/dbio/mm_rate_minmax_dbio.pgc | 23 +++++++ app/src/mm/dbio/mm_risk_case_dbio.h | 9 +++ app/src/mm/dbio/mm_risk_case_dbio.pgc | 24 +++++++ app/src/mm/dbio/mm_status_filter_dbio.h | 9 +++ app/src/mm/dbio/mm_status_filter_dbio.pgc | 24 +++++++ app/src/mm/dbio/mm_term_join_dbio.h | 9 +++ app/src/mm/dbio/mm_term_join_dbio.pgc | 21 ++++++ app/src/mm/dbio/mm_wavg_limit_dbio.h | 9 +++ app/src/mm/dbio/mm_wavg_limit_dbio.pgc | 23 +++++++ app/src/py/batch/py_cancelpurge_batch.pgc | 46 +++++++++++++ app/src/py/batch/py_chanwalk_batch.pgc | 52 +++++++++++++++ app/src/py/batch/py_ledgerpost_batch.pgc | 44 +++++++++++++ app/src/py/batch/py_recon_batch.pgc | 49 ++++++++++++++ app/src/py/batch/py_winrank_batch.pgc | 43 ++++++++++++ app/src/py/dbio/py_account_having_dbio.h | 11 ++++ app/src/py/dbio/py_account_having_dbio.pgc | 21 ++++++ app/src/py/dbio/py_cancelpurge_batch.h | 14 ++++ app/src/py/dbio/py_channel_walk_dbio.h | 11 ++++ app/src/py/dbio/py_channel_walk_dbio.pgc | 32 +++++++++ app/src/py/dbio/py_chanwalk_batch.h | 14 ++++ app/src/py/dbio/py_file_span_dbio.h | 11 ++++ app/src/py/dbio/py_file_span_dbio.pgc | 23 +++++++ app/src/py/dbio/py_ledger_mix_dbio.h | 11 ++++ app/src/py/dbio/py_ledger_mix_dbio.pgc | 24 +++++++ app/src/py/dbio/py_ledger_post_dbio.h | 11 ++++ app/src/py/dbio/py_ledger_post_dbio.pgc | 22 +++++++ app/src/py/dbio/py_ledgerpost_batch.h | 13 ++++ app/src/py/dbio/py_payout_corr_dbio.h | 11 ++++ app/src/py/dbio/py_payout_corr_dbio.pgc | 24 +++++++ app/src/py/dbio/py_payout_delrange_dbio.h | 11 ++++ app/src/py/dbio/py_payout_delrange_dbio.pgc | 22 +++++++ app/src/py/dbio/py_payout_notexists_dbio.h | 11 ++++ app/src/py/dbio/py_payout_notexists_dbio.pgc | 23 +++++++ app/src/py/dbio/py_payout_rejrate_dbio.h | 11 ++++ app/src/py/dbio/py_payout_rejrate_dbio.pgc | 24 +++++++ app/src/py/dbio/py_payout_retryhist_dbio.h | 11 ++++ app/src/py/dbio/py_payout_retryhist_dbio.pgc | 23 +++++++ app/src/py/dbio/py_payout_weighted_dbio.h | 11 ++++ app/src/py/dbio/py_payout_weighted_dbio.pgc | 23 +++++++ app/src/py/dbio/py_payout_window_dbio.h | 11 ++++ app/src/py/dbio/py_payout_window_dbio.pgc | 23 +++++++ app/src/py/dbio/py_recon_batch.h | 15 +++++ app/src/py/dbio/py_schedule_due_dbio.h | 11 ++++ app/src/py/dbio/py_schedule_due_dbio.pgc | 23 +++++++ app/src/py/dbio/py_summary_accum_dbio.h | 11 ++++ app/src/py/dbio/py_summary_accum_dbio.pgc | 27 ++++++++ app/src/py/dbio/py_summary_gap_dbio.h | 12 ++++ app/src/py/dbio/py_summary_gap_dbio.pgc | 29 +++++++++ app/src/py/dbio/py_winrank_batch.h | 13 ++++ app/src/rc/batch/rc_audit_batch.pgc | 55 ++++++++++++++++ app/src/rc/batch/rc_backfill_batch.pgc | 45 +++++++++++++ app/src/rc/batch/rc_purgeold_batch.pgc | 39 +++++++++++ app/src/rc/batch/rc_reclass_batch.pgc | 40 ++++++++++++ app/src/rc/batch/rc_windowload_batch.pgc | 58 +++++++++++++++++ app/src/rc/dbio/rc_alertpurge_dbio.h | 5 ++ app/src/rc/dbio/rc_alertpurge_dbio.pgc | 18 +++++ app/src/rc/dbio/rc_amtcase_dbio.h | 5 ++ app/src/rc/dbio/rc_amtcase_dbio.pgc | 32 +++++++++ app/src/rc/dbio/rc_amtminmax_dbio.h | 5 ++ app/src/rc/dbio/rc_amtminmax_dbio.pgc | 23 +++++++ app/src/rc/dbio/rc_amtwindow_dbio.h | 5 ++ app/src/rc/dbio/rc_amtwindow_dbio.pgc | 31 +++++++++ app/src/rc/dbio/rc_audit_batch.h | 6 ++ app/src/rc/dbio/rc_backfill_batch.h | 6 ++ app/src/rc/dbio/rc_diffhaving_dbio.h | 5 ++ app/src/rc/dbio/rc_diffhaving_dbio.pgc | 29 +++++++++ app/src/rc/dbio/rc_diffinsert_dbio.h | 5 ++ app/src/rc/dbio/rc_diffinsert_dbio.pgc | 20 ++++++ app/src/rc/dbio/rc_diffweight_dbio.h | 5 ++ app/src/rc/dbio/rc_diffweight_dbio.pgc | 20 ++++++ app/src/rc/dbio/rc_excinsub_dbio.h | 5 ++ app/src/rc/dbio/rc_excinsub_dbio.pgc | 21 ++++++ app/src/rc/dbio/rc_excnotexists_dbio.h | 5 ++ app/src/rc/dbio/rc_excnotexists_dbio.pgc | 21 ++++++ app/src/rc/dbio/rc_excupdate_dbio.h | 5 ++ app/src/rc/dbio/rc_excupdate_dbio.pgc | 31 +++++++++ app/src/rc/dbio/rc_lvlfilter_dbio.h | 5 ++ app/src/rc/dbio/rc_lvlfilter_dbio.pgc | 21 ++++++ app/src/rc/dbio/rc_matchcorr_dbio.h | 5 ++ app/src/rc/dbio/rc_matchcorr_dbio.pgc | 30 +++++++++ app/src/rc/dbio/rc_matchsummaryjoin_dbio.h | 5 ++ app/src/rc/dbio/rc_matchsummaryjoin_dbio.pgc | 23 +++++++ app/src/rc/dbio/rc_pidcount_dbio.h | 5 ++ app/src/rc/dbio/rc_pidcount_dbio.pgc | 21 ++++++ app/src/rc/dbio/rc_purgeold_batch.h | 6 ++ app/src/rc/dbio/rc_reclass_batch.h | 6 ++ app/src/rc/dbio/rc_sumupsert_dbio.h | 5 ++ app/src/rc/dbio/rc_sumupsert_dbio.pgc | 24 +++++++ app/src/rc/dbio/rc_windowload_batch.h | 6 ++ app/src/st/batch/st_adjnet_batch.pgc | 42 ++++++++++++ app/src/st/batch/st_paysweep_batch.pgc | 41 ++++++++++++ app/src/st/batch/st_purgeadj_batch.pgc | 40 ++++++++++++ app/src/st/batch/st_rankpay_batch.pgc | 57 ++++++++++++++++ app/src/st/batch/st_rateapply_batch.pgc | 54 +++++++++++++++ app/src/st/dbio/st_adjcorr_dbio.h | 5 ++ app/src/st/dbio/st_adjcorr_dbio.pgc | 30 +++++++++ app/src/st/dbio/st_adjnet_batch.h | 6 ++ app/src/st/dbio/st_adjpurge_dbio.h | 5 ++ app/src/st/dbio/st_adjpurge_dbio.pgc | 19 ++++++ app/src/st/dbio/st_dtlinsert_dbio.h | 5 ++ app/src/st/dbio/st_dtlinsert_dbio.pgc | 20 ++++++ app/src/st/dbio/st_feecase_dbio.h | 5 ++ app/src/st/dbio/st_feecase_dbio.pgc | 22 +++++++ app/src/st/dbio/st_feehaving_dbio.h | 5 ++ app/src/st/dbio/st_feehaving_dbio.pgc | 30 +++++++++ app/src/st/dbio/st_feeweight_dbio.h | 5 ++ app/src/st/dbio/st_feeweight_dbio.pgc | 22 +++++++ app/src/st/dbio/st_feewindow_dbio.h | 5 ++ app/src/st/dbio/st_feewindow_dbio.pgc | 31 +++++++++ app/src/st/dbio/st_merchdistinct_dbio.h | 5 ++ app/src/st/dbio/st_merchdistinct_dbio.pgc | 21 ++++++ app/src/st/dbio/st_merchupsert_dbio.h | 5 ++ app/src/st/dbio/st_merchupsert_dbio.pgc | 27 ++++++++ app/src/st/dbio/st_netminmax_dbio.h | 5 ++ app/src/st/dbio/st_netminmax_dbio.pgc | 24 +++++++ app/src/st/dbio/st_paycursor2_dbio.h | 5 ++ app/src/st/dbio/st_paycursor2_dbio.pgc | 33 ++++++++++ app/src/st/dbio/st_payinsub_dbio.h | 5 ++ app/src/st/dbio/st_payinsub_dbio.pgc | 22 +++++++ app/src/st/dbio/st_paynotexists_dbio.h | 5 ++ app/src/st/dbio/st_paynotexists_dbio.pgc | 22 +++++++ app/src/st/dbio/st_paysweep_batch.h | 6 ++ app/src/st/dbio/st_purgeadj_batch.h | 6 ++ app/src/st/dbio/st_rankpay_batch.h | 6 ++ app/src/st/dbio/st_rateapply_batch.h | 6 ++ app/src/st/dbio/st_ratejoin_dbio.h | 5 ++ app/src/st/dbio/st_ratejoin_dbio.pgc | 32 +++++++++ app/src/st/dbio/st_statusfilter_dbio.h | 5 ++ app/src/st/dbio/st_statusfilter_dbio.pgc | 24 +++++++ app/src/vl/batch/vl_errgrp_batch.pgc | 41 ++++++++++++ app/src/vl/batch/vl_logpurge_batch.pgc | 40 ++++++++++++ app/src/vl/batch/vl_orphan_batch.pgc | 40 ++++++++++++ app/src/vl/batch/vl_promote_batch.pgc | 41 ++++++++++++ app/src/vl/batch/vl_rankscan_batch.pgc | 49 ++++++++++++++ app/src/vl/dbio/vl_corr_dbio.h | 11 ++++ app/src/vl/dbio/vl_corr_dbio.pgc | 22 +++++++ app/src/vl/dbio/vl_errdistinct_dbio.h | 11 ++++ app/src/vl/dbio/vl_errdistinct_dbio.pgc | 21 ++++++ app/src/vl/dbio/vl_errfilter_dbio.h | 11 ++++ app/src/vl/dbio/vl_errfilter_dbio.pgc | 22 +++++++ app/src/vl/dbio/vl_errgroup_dbio.h | 11 ++++ app/src/vl/dbio/vl_errgroup_dbio.pgc | 33 ++++++++++ app/src/vl/dbio/vl_errgrp_batch.h | 10 +++ app/src/vl/dbio/vl_errminmax_dbio.h | 11 ++++ app/src/vl/dbio/vl_errminmax_dbio.pgc | 22 +++++++ app/src/vl/dbio/vl_inset_dbio.h | 11 ++++ app/src/vl/dbio/vl_inset_dbio.pgc | 22 +++++++ app/src/vl/dbio/vl_logpurge_batch.h | 11 ++++ app/src/vl/dbio/vl_orphan_batch.h | 10 +++ app/src/vl/dbio/vl_orphan_dbio.h | 11 ++++ app/src/vl/dbio/vl_orphan_dbio.pgc | 22 +++++++ app/src/vl/dbio/vl_promote_batch.h | 10 +++ app/src/vl/dbio/vl_promote_dbio.h | 11 ++++ app/src/vl/dbio/vl_promote_dbio.pgc | 23 +++++++ app/src/vl/dbio/vl_purgelog_dbio.h | 11 ++++ app/src/vl/dbio/vl_purgelog_dbio.pgc | 20 ++++++ app/src/vl/dbio/vl_rank_dbio.h | 11 ++++ app/src/vl/dbio/vl_rank_dbio.pgc | 31 +++++++++ app/src/vl/dbio/vl_rankscan_batch.h | 10 +++ app/src/vl/dbio/vl_rulejoin_dbio.h | 11 ++++ app/src/vl/dbio/vl_rulejoin_dbio.pgc | 22 +++++++ app/src/vl/dbio/vl_sevcase_dbio.h | 11 ++++ app/src/vl/dbio/vl_sevcase_dbio.pgc | 25 +++++++ 422 files changed, 8717 insertions(+) create mode 100644 app/src/ac/batch/ac_amountband_batch.pgc create mode 100644 app/src/ac/batch/ac_concentration_batch.pgc create mode 100644 app/src/ac/batch/ac_fxweight_batch.pgc create mode 100644 app/src/ac/batch/ac_summaryrebuild_batch.pgc create mode 100644 app/src/ac/batch/ac_unmatchpurge_batch.pgc create mode 100644 app/src/ac/dbio/ac_amountband_batch.h create mode 100644 app/src/ac/dbio/ac_cancel_casebucket_dbio.h create mode 100644 app/src/ac/dbio/ac_cancel_casebucket_dbio.pgc create mode 100644 app/src/ac/dbio/ac_concentration_batch.h create mode 100644 app/src/ac/dbio/ac_edi_intoledger_dbio.h create mode 100644 app/src/ac/dbio/ac_edi_intoledger_dbio.pgc create mode 100644 app/src/ac/dbio/ac_feeadj_repeat_dbio.h create mode 100644 app/src/ac/dbio/ac_feeadj_repeat_dbio.pgc create mode 100644 app/src/ac/dbio/ac_fx_weightavg_dbio.h create mode 100644 app/src/ac/dbio/ac_fx_weightavg_dbio.pgc create mode 100644 app/src/ac/dbio/ac_fxweight_batch.h create mode 100644 app/src/ac/dbio/ac_install_corr_dbio.h create mode 100644 app/src/ac/dbio/ac_install_corr_dbio.pgc create mode 100644 app/src/ac/dbio/ac_merchant_bigday_dbio.h create mode 100644 app/src/ac/dbio/ac_merchant_bigday_dbio.pgc create mode 100644 app/src/ac/dbio/ac_purchase_incancel_dbio.h create mode 100644 app/src/ac/dbio/ac_purchase_incancel_dbio.pgc create mode 100644 app/src/ac/dbio/ac_purchase_rankpid_dbio.h create mode 100644 app/src/ac/dbio/ac_purchase_rankpid_dbio.pgc create mode 100644 app/src/ac/dbio/ac_purchase_topn_dbio.h create mode 100644 app/src/ac/dbio/ac_purchase_topn_dbio.pgc create mode 100644 app/src/ac/dbio/ac_settle_join_dbio.h create mode 100644 app/src/ac/dbio/ac_settle_join_dbio.pgc create mode 100644 app/src/ac/dbio/ac_settle_notexists_dbio.h create mode 100644 app/src/ac/dbio/ac_settle_notexists_dbio.pgc create mode 100644 app/src/ac/dbio/ac_summary_rebuild_dbio.h create mode 100644 app/src/ac/dbio/ac_summary_rebuild_dbio.pgc create mode 100644 app/src/ac/dbio/ac_summaryrebuild_batch.h create mode 100644 app/src/ac/dbio/ac_taxinv_spread_dbio.h create mode 100644 app/src/ac/dbio/ac_taxinv_spread_dbio.pgc create mode 100644 app/src/ac/dbio/ac_unmatch_purge_dbio.h create mode 100644 app/src/ac/dbio/ac_unmatch_purge_dbio.pgc create mode 100644 app/src/ac/dbio/ac_unmatchpurge_batch.h create mode 100644 app/src/ac/dbio/ac_wht_distinct_dbio.h create mode 100644 app/src/ac/dbio/ac_wht_distinct_dbio.pgc create mode 100644 app/src/au/batch/au_approverate_batch.pgc create mode 100644 app/src/au/batch/au_fraudpurge_batch.pgc create mode 100644 app/src/au/batch/au_scorebucket_batch.pgc create mode 100644 app/src/au/batch/au_summaryrebuild_batch.pgc create mode 100644 app/src/au/batch/au_topcard_batch.pgc create mode 100644 app/src/au/dbio/au_approverate_batch.h create mode 100644 app/src/au/dbio/au_auth_distinctcard_dbio.h create mode 100644 app/src/au/dbio/au_auth_distinctcard_dbio.pgc create mode 100644 app/src/au/dbio/au_auth_incancel_dbio.h create mode 100644 app/src/au/dbio/au_auth_incancel_dbio.pgc create mode 100644 app/src/au/dbio/au_auth_rankcard_dbio.h create mode 100644 app/src/au/dbio/au_auth_rankcard_dbio.pgc create mode 100644 app/src/au/dbio/au_auth_topcard_dbio.h create mode 100644 app/src/au/dbio/au_auth_topcard_dbio.pgc create mode 100644 app/src/au/dbio/au_cancel_corr_dbio.h create mode 100644 app/src/au/dbio/au_cancel_corr_dbio.pgc create mode 100644 app/src/au/dbio/au_capture_join_dbio.h create mode 100644 app/src/au/dbio/au_capture_join_dbio.pgc create mode 100644 app/src/au/dbio/au_fraud_casescore_dbio.h create mode 100644 app/src/au/dbio/au_fraud_casescore_dbio.pgc create mode 100644 app/src/au/dbio/au_fraud_intolog_dbio.h create mode 100644 app/src/au/dbio/au_fraud_intolog_dbio.pgc create mode 100644 app/src/au/dbio/au_fraud_purge_dbio.h create mode 100644 app/src/au/dbio/au_fraud_purge_dbio.pgc create mode 100644 app/src/au/dbio/au_fraudpurge_batch.h create mode 100644 app/src/au/dbio/au_limit_spread_dbio.h create mode 100644 app/src/au/dbio/au_limit_spread_dbio.pgc create mode 100644 app/src/au/dbio/au_merchant_active_dbio.h create mode 100644 app/src/au/dbio/au_merchant_active_dbio.pgc create mode 100644 app/src/au/dbio/au_preauth_notexists_dbio.h create mode 100644 app/src/au/dbio/au_preauth_notexists_dbio.pgc create mode 100644 app/src/au/dbio/au_scorebucket_batch.h create mode 100644 app/src/au/dbio/au_summary_rebuild_dbio.h create mode 100644 app/src/au/dbio/au_summary_rebuild_dbio.pgc create mode 100644 app/src/au/dbio/au_summary_riskmerch_dbio.h create mode 100644 app/src/au/dbio/au_summary_riskmerch_dbio.pgc create mode 100644 app/src/au/dbio/au_summary_weightavg_dbio.h create mode 100644 app/src/au/dbio/au_summary_weightavg_dbio.pgc create mode 100644 app/src/au/dbio/au_summaryrebuild_batch.h create mode 100644 app/src/au/dbio/au_topcard_batch.h create mode 100644 app/src/cl/batch/cl_gap_batch.pgc create mode 100644 app/src/cl/batch/cl_gap_cpy.h create mode 100644 app/src/cl/batch/cl_purgeold_batch.pgc create mode 100644 app/src/cl/batch/cl_purgeold_cpy.h create mode 100644 app/src/cl/batch/cl_relock_batch.pgc create mode 100644 app/src/cl/batch/cl_relock_cpy.h create mode 100644 app/src/cl/batch/cl_reweigh_batch.pgc create mode 100644 app/src/cl/batch/cl_reweigh_cpy.h create mode 100644 app/src/cl/batch/cl_tierstat_batch.pgc create mode 100644 app/src/cl/batch/cl_tierstat_cpy.h create mode 100644 app/src/cl/dbio/cl_corr_sub_dbio.h create mode 100644 app/src/cl/dbio/cl_corr_sub_dbio.pgc create mode 100644 app/src/cl/dbio/cl_lock_upsert_dbio.h create mode 100644 app/src/cl/dbio/cl_lock_upsert_dbio.pgc create mode 100644 app/src/cl/dbio/cl_minmax_dbio.h create mode 100644 app/src/cl/dbio/cl_minmax_dbio.pgc create mode 100644 app/src/cl/dbio/cl_snap_distinct_dbio.h create mode 100644 app/src/cl/dbio/cl_snap_distinct_dbio.pgc create mode 100644 app/src/cl/dbio/cl_snap_in_dbio.h create mode 100644 app/src/cl/dbio/cl_snap_in_dbio.pgc create mode 100644 app/src/cl/dbio/cl_snap_insert_sel_dbio.h create mode 100644 app/src/cl/dbio/cl_snap_insert_sel_dbio.pgc create mode 100644 app/src/cl/dbio/cl_snap_join_dbio.h create mode 100644 app/src/cl/dbio/cl_snap_join_dbio.pgc create mode 100644 app/src/cl/dbio/cl_snap_purge_dbio.h create mode 100644 app/src/cl/dbio/cl_snap_purge_dbio.pgc create mode 100644 app/src/cl/dbio/cl_snap_rank_dbio.h create mode 100644 app/src/cl/dbio/cl_snap_rank_dbio.pgc create mode 100644 app/src/cl/dbio/cl_status_filter_dbio.h create mode 100644 app/src/cl/dbio/cl_status_filter_dbio.pgc create mode 100644 app/src/cl/dbio/cl_streak_cursor_dbio.h create mode 100644 app/src/cl/dbio/cl_streak_cursor_dbio.pgc create mode 100644 app/src/cl/dbio/cl_tier_case_dbio.h create mode 100644 app/src/cl/dbio/cl_tier_case_dbio.pgc create mode 100644 app/src/cl/dbio/cl_type_having_dbio.h create mode 100644 app/src/cl/dbio/cl_type_having_dbio.pgc create mode 100644 app/src/cl/dbio/cl_unlocked_dbio.h create mode 100644 app/src/cl/dbio/cl_unlocked_dbio.pgc create mode 100644 app/src/cl/dbio/cl_wavg_dbio.h create mode 100644 app/src/cl/dbio/cl_wavg_dbio.pgc create mode 100644 app/src/cm/batch/cm_fxlog_batch.pgc create mode 100644 app/src/cm/batch/cm_fxpurge_batch.pgc create mode 100644 app/src/cm/batch/cm_levelgrp_batch.pgc create mode 100644 app/src/cm/batch/cm_nofx_batch.pgc create mode 100644 app/src/cm/batch/cm_rankfx_batch.pgc create mode 100644 app/src/cm/dbio/cm_ccycase_dbio.h create mode 100644 app/src/cm/dbio/cm_ccycase_dbio.pgc create mode 100644 app/src/cm/dbio/cm_codefxjoin_dbio.h create mode 100644 app/src/cm/dbio/cm_codefxjoin_dbio.pgc create mode 100644 app/src/cm/dbio/cm_errfilter_dbio.h create mode 100644 app/src/cm/dbio/cm_errfilter_dbio.pgc create mode 100644 app/src/cm/dbio/cm_fxabov_dbio.h create mode 100644 app/src/cm/dbio/cm_fxabov_dbio.pgc create mode 100644 app/src/cm/dbio/cm_fxlog_batch.h create mode 100644 app/src/cm/dbio/cm_fxpurge_batch.h create mode 100644 app/src/cm/dbio/cm_grpdistinct_dbio.h create mode 100644 app/src/cm/dbio/cm_grpdistinct_dbio.pgc create mode 100644 app/src/cm/dbio/cm_levelgroup_dbio.h create mode 100644 app/src/cm/dbio/cm_levelgroup_dbio.pgc create mode 100644 app/src/cm/dbio/cm_levelgrp_batch.h create mode 100644 app/src/cm/dbio/cm_logfromfx_dbio.h create mode 100644 app/src/cm/dbio/cm_logfromfx_dbio.pgc create mode 100644 app/src/cm/dbio/cm_login_dbio.h create mode 100644 app/src/cm/dbio/cm_login_dbio.pgc create mode 100644 app/src/cm/dbio/cm_nofx_batch.h create mode 100644 app/src/cm/dbio/cm_nofx_dbio.h create mode 100644 app/src/cm/dbio/cm_nofx_dbio.pgc create mode 100644 app/src/cm/dbio/cm_purgefx_dbio.h create mode 100644 app/src/cm/dbio/cm_purgefx_dbio.pgc create mode 100644 app/src/cm/dbio/cm_rankfx_batch.h create mode 100644 app/src/cm/dbio/cm_rankfx_dbio.h create mode 100644 app/src/cm/dbio/cm_rankfx_dbio.pgc create mode 100644 app/src/cm/dbio/cm_sortrange_dbio.h create mode 100644 app/src/cm/dbio/cm_sortrange_dbio.pgc create mode 100644 app/src/lg/batch/lg_dcweight_batch.pgc create mode 100644 app/src/lg/batch/lg_imbalscan_batch.pgc create mode 100644 app/src/lg/batch/lg_linepurge_batch.pgc create mode 100644 app/src/lg/batch/lg_reversepair_batch.pgc create mode 100644 app/src/lg/batch/lg_trialload_batch.pgc create mode 100644 app/src/lg/dbio/lg_dc_ratio_dbio.h create mode 100644 app/src/lg/dbio/lg_dc_ratio_dbio.pgc create mode 100644 app/src/lg/dbio/lg_dcweight_batch.h create mode 100644 app/src/lg/dbio/lg_imbalscan_batch.h create mode 100644 app/src/lg/dbio/lg_line_corr_dbio.h create mode 100644 app/src/lg/dbio/lg_line_corr_dbio.pgc create mode 100644 app/src/lg/dbio/lg_line_delrange_dbio.h create mode 100644 app/src/lg/dbio/lg_line_delrange_dbio.pgc create mode 100644 app/src/lg/dbio/lg_line_span_dbio.h create mode 100644 app/src/lg/dbio/lg_line_span_dbio.pgc create mode 100644 app/src/lg/dbio/lg_line_window_dbio.h create mode 100644 app/src/lg/dbio/lg_line_window_dbio.pgc create mode 100644 app/src/lg/dbio/lg_linepurge_batch.h create mode 100644 app/src/lg/dbio/lg_merch_accum_dbio.h create mode 100644 app/src/lg/dbio/lg_merch_accum_dbio.pgc create mode 100644 app/src/lg/dbio/lg_merch_having_dbio.h create mode 100644 app/src/lg/dbio/lg_merch_having_dbio.pgc create mode 100644 app/src/lg/dbio/lg_posted_join_dbio.h create mode 100644 app/src/lg/dbio/lg_posted_join_dbio.pgc create mode 100644 app/src/lg/dbio/lg_reverse_pair_dbio.h create mode 100644 app/src/lg/dbio/lg_reverse_pair_dbio.pgc create mode 100644 app/src/lg/dbio/lg_reversepair_batch.h create mode 100644 app/src/lg/dbio/lg_settle_lines_dbio.h create mode 100644 app/src/lg/dbio/lg_settle_lines_dbio.pgc create mode 100644 app/src/lg/dbio/lg_trial_load_dbio.h create mode 100644 app/src/lg/dbio/lg_trial_load_dbio.pgc create mode 100644 app/src/lg/dbio/lg_trialload_batch.h create mode 100644 app/src/lg/dbio/lg_type_walk_dbio.h create mode 100644 app/src/lg/dbio/lg_type_walk_dbio.pgc create mode 100644 app/src/lg/dbio/lg_voucher_empty_dbio.h create mode 100644 app/src/lg/dbio/lg_voucher_empty_dbio.pgc create mode 100644 app/src/lg/dbio/lg_voucher_mix_dbio.h create mode 100644 app/src/lg/dbio/lg_voucher_mix_dbio.pgc create mode 100644 app/src/lg/dbio/lg_worst_acct_dbio.h create mode 100644 app/src/lg/dbio/lg_worst_acct_dbio.pgc create mode 100644 app/src/mg/batch/mg_ackpurge_batch.pgc create mode 100644 app/src/mg/batch/mg_mtierr_batch.pgc create mode 100644 app/src/mg/batch/mg_rankamt_batch.pgc create mode 100644 app/src/mg/batch/mg_requeue_batch.pgc create mode 100644 app/src/mg/batch/mg_unrouted_batch.pgc create mode 100644 app/src/mg/dbio/mg_abovavg_dbio.h create mode 100644 app/src/mg/dbio/mg_abovavg_dbio.pgc create mode 100644 app/src/mg/dbio/mg_ackpurge_batch.h create mode 100644 app/src/mg/dbio/mg_ackpurge_dbio.h create mode 100644 app/src/mg/dbio/mg_ackpurge_dbio.pgc create mode 100644 app/src/mg/dbio/mg_chdistinct_dbio.h create mode 100644 app/src/mg/dbio/mg_chdistinct_dbio.pgc create mode 100644 app/src/mg/dbio/mg_dircase_dbio.h create mode 100644 app/src/mg/dbio/mg_dircase_dbio.pgc create mode 100644 app/src/mg/dbio/mg_mtierr_batch.h create mode 100644 app/src/mg/dbio/mg_mtigroup_dbio.h create mode 100644 app/src/mg/dbio/mg_mtigroup_dbio.pgc create mode 100644 app/src/mg/dbio/mg_nackfilter_dbio.h create mode 100644 app/src/mg/dbio/mg_nackfilter_dbio.pgc create mode 100644 app/src/mg/dbio/mg_qforup_dbio.h create mode 100644 app/src/mg/dbio/mg_qforup_dbio.pgc create mode 100644 app/src/mg/dbio/mg_rankamt_batch.h create mode 100644 app/src/mg/dbio/mg_rankamt_dbio.h create mode 100644 app/src/mg/dbio/mg_rankamt_dbio.pgc create mode 100644 app/src/mg/dbio/mg_reqfail_dbio.h create mode 100644 app/src/mg/dbio/mg_reqfail_dbio.pgc create mode 100644 app/src/mg/dbio/mg_requeue_batch.h create mode 100644 app/src/mg/dbio/mg_routegrp_dbio.h create mode 100644 app/src/mg/dbio/mg_routegrp_dbio.pgc create mode 100644 app/src/mg/dbio/mg_stanrange_dbio.h create mode 100644 app/src/mg/dbio/mg_stanrange_dbio.pgc create mode 100644 app/src/mg/dbio/mg_unrouted_batch.h create mode 100644 app/src/mg/dbio/mg_unrouted_dbio.h create mode 100644 app/src/mg/dbio/mg_unrouted_dbio.pgc create mode 100644 app/src/mm/batch/mm_bin_dedup_batch.pgc create mode 100644 app/src/mm/batch/mm_bin_dedup_cpy.h create mode 100644 app/src/mm/batch/mm_fee_bump_batch.pgc create mode 100644 app/src/mm/batch/mm_fee_bump_cpy.h create mode 100644 app/src/mm/batch/mm_hist_archive_batch.pgc create mode 100644 app/src/mm/batch/mm_hist_archive_cpy.h create mode 100644 app/src/mm/batch/mm_limit_scale_batch.pgc create mode 100644 app/src/mm/batch/mm_limit_scale_cpy.h create mode 100644 app/src/mm/batch/mm_risk_rollup_batch.pgc create mode 100644 app/src/mm/batch/mm_risk_rollup_cpy.h create mode 100644 app/src/mm/dbio/mm_bin_distinct_dbio.h create mode 100644 app/src/mm/dbio/mm_bin_distinct_dbio.pgc create mode 100644 app/src/mm/dbio/mm_fee_in_dbio.h create mode 100644 app/src/mm/dbio/mm_fee_in_dbio.pgc create mode 100644 app/src/mm/dbio/mm_fee_rank_dbio.h create mode 100644 app/src/mm/dbio/mm_fee_rank_dbio.pgc create mode 100644 app/src/mm/dbio/mm_fx_upsert_dbio.h create mode 100644 app/src/mm/dbio/mm_fx_upsert_dbio.pgc create mode 100644 app/src/mm/dbio/mm_grade_having_dbio.h create mode 100644 app/src/mm/dbio/mm_grade_having_dbio.pgc create mode 100644 app/src/mm/dbio/mm_grade_insert_sel_dbio.h create mode 100644 app/src/mm/dbio/mm_grade_insert_sel_dbio.pgc create mode 100644 app/src/mm/dbio/mm_hist_purge_dbio.h create mode 100644 app/src/mm/dbio/mm_hist_purge_dbio.pgc create mode 100644 app/src/mm/dbio/mm_issuer_corr_dbio.h create mode 100644 app/src/mm/dbio/mm_issuer_corr_dbio.pgc create mode 100644 app/src/mm/dbio/mm_mcc_risk_cursor_dbio.h create mode 100644 app/src/mm/dbio/mm_mcc_risk_cursor_dbio.pgc create mode 100644 app/src/mm/dbio/mm_noterm_dbio.h create mode 100644 app/src/mm/dbio/mm_noterm_dbio.pgc create mode 100644 app/src/mm/dbio/mm_rate_minmax_dbio.h create mode 100644 app/src/mm/dbio/mm_rate_minmax_dbio.pgc create mode 100644 app/src/mm/dbio/mm_risk_case_dbio.h create mode 100644 app/src/mm/dbio/mm_risk_case_dbio.pgc create mode 100644 app/src/mm/dbio/mm_status_filter_dbio.h create mode 100644 app/src/mm/dbio/mm_status_filter_dbio.pgc create mode 100644 app/src/mm/dbio/mm_term_join_dbio.h create mode 100644 app/src/mm/dbio/mm_term_join_dbio.pgc create mode 100644 app/src/mm/dbio/mm_wavg_limit_dbio.h create mode 100644 app/src/mm/dbio/mm_wavg_limit_dbio.pgc create mode 100644 app/src/py/batch/py_cancelpurge_batch.pgc create mode 100644 app/src/py/batch/py_chanwalk_batch.pgc create mode 100644 app/src/py/batch/py_ledgerpost_batch.pgc create mode 100644 app/src/py/batch/py_recon_batch.pgc create mode 100644 app/src/py/batch/py_winrank_batch.pgc create mode 100644 app/src/py/dbio/py_account_having_dbio.h create mode 100644 app/src/py/dbio/py_account_having_dbio.pgc create mode 100644 app/src/py/dbio/py_cancelpurge_batch.h create mode 100644 app/src/py/dbio/py_channel_walk_dbio.h create mode 100644 app/src/py/dbio/py_channel_walk_dbio.pgc create mode 100644 app/src/py/dbio/py_chanwalk_batch.h create mode 100644 app/src/py/dbio/py_file_span_dbio.h create mode 100644 app/src/py/dbio/py_file_span_dbio.pgc create mode 100644 app/src/py/dbio/py_ledger_mix_dbio.h create mode 100644 app/src/py/dbio/py_ledger_mix_dbio.pgc create mode 100644 app/src/py/dbio/py_ledger_post_dbio.h create mode 100644 app/src/py/dbio/py_ledger_post_dbio.pgc create mode 100644 app/src/py/dbio/py_ledgerpost_batch.h create mode 100644 app/src/py/dbio/py_payout_corr_dbio.h create mode 100644 app/src/py/dbio/py_payout_corr_dbio.pgc create mode 100644 app/src/py/dbio/py_payout_delrange_dbio.h create mode 100644 app/src/py/dbio/py_payout_delrange_dbio.pgc create mode 100644 app/src/py/dbio/py_payout_notexists_dbio.h create mode 100644 app/src/py/dbio/py_payout_notexists_dbio.pgc create mode 100644 app/src/py/dbio/py_payout_rejrate_dbio.h create mode 100644 app/src/py/dbio/py_payout_rejrate_dbio.pgc create mode 100644 app/src/py/dbio/py_payout_retryhist_dbio.h create mode 100644 app/src/py/dbio/py_payout_retryhist_dbio.pgc create mode 100644 app/src/py/dbio/py_payout_weighted_dbio.h create mode 100644 app/src/py/dbio/py_payout_weighted_dbio.pgc create mode 100644 app/src/py/dbio/py_payout_window_dbio.h create mode 100644 app/src/py/dbio/py_payout_window_dbio.pgc create mode 100644 app/src/py/dbio/py_recon_batch.h create mode 100644 app/src/py/dbio/py_schedule_due_dbio.h create mode 100644 app/src/py/dbio/py_schedule_due_dbio.pgc create mode 100644 app/src/py/dbio/py_summary_accum_dbio.h create mode 100644 app/src/py/dbio/py_summary_accum_dbio.pgc create mode 100644 app/src/py/dbio/py_summary_gap_dbio.h create mode 100644 app/src/py/dbio/py_summary_gap_dbio.pgc create mode 100644 app/src/py/dbio/py_winrank_batch.h create mode 100644 app/src/rc/batch/rc_audit_batch.pgc create mode 100644 app/src/rc/batch/rc_backfill_batch.pgc create mode 100644 app/src/rc/batch/rc_purgeold_batch.pgc create mode 100644 app/src/rc/batch/rc_reclass_batch.pgc create mode 100644 app/src/rc/batch/rc_windowload_batch.pgc create mode 100644 app/src/rc/dbio/rc_alertpurge_dbio.h create mode 100644 app/src/rc/dbio/rc_alertpurge_dbio.pgc create mode 100644 app/src/rc/dbio/rc_amtcase_dbio.h create mode 100644 app/src/rc/dbio/rc_amtcase_dbio.pgc create mode 100644 app/src/rc/dbio/rc_amtminmax_dbio.h create mode 100644 app/src/rc/dbio/rc_amtminmax_dbio.pgc create mode 100644 app/src/rc/dbio/rc_amtwindow_dbio.h create mode 100644 app/src/rc/dbio/rc_amtwindow_dbio.pgc create mode 100644 app/src/rc/dbio/rc_audit_batch.h create mode 100644 app/src/rc/dbio/rc_backfill_batch.h create mode 100644 app/src/rc/dbio/rc_diffhaving_dbio.h create mode 100644 app/src/rc/dbio/rc_diffhaving_dbio.pgc create mode 100644 app/src/rc/dbio/rc_diffinsert_dbio.h create mode 100644 app/src/rc/dbio/rc_diffinsert_dbio.pgc create mode 100644 app/src/rc/dbio/rc_diffweight_dbio.h create mode 100644 app/src/rc/dbio/rc_diffweight_dbio.pgc create mode 100644 app/src/rc/dbio/rc_excinsub_dbio.h create mode 100644 app/src/rc/dbio/rc_excinsub_dbio.pgc create mode 100644 app/src/rc/dbio/rc_excnotexists_dbio.h create mode 100644 app/src/rc/dbio/rc_excnotexists_dbio.pgc create mode 100644 app/src/rc/dbio/rc_excupdate_dbio.h create mode 100644 app/src/rc/dbio/rc_excupdate_dbio.pgc create mode 100644 app/src/rc/dbio/rc_lvlfilter_dbio.h create mode 100644 app/src/rc/dbio/rc_lvlfilter_dbio.pgc create mode 100644 app/src/rc/dbio/rc_matchcorr_dbio.h create mode 100644 app/src/rc/dbio/rc_matchcorr_dbio.pgc create mode 100644 app/src/rc/dbio/rc_matchsummaryjoin_dbio.h create mode 100644 app/src/rc/dbio/rc_matchsummaryjoin_dbio.pgc create mode 100644 app/src/rc/dbio/rc_pidcount_dbio.h create mode 100644 app/src/rc/dbio/rc_pidcount_dbio.pgc create mode 100644 app/src/rc/dbio/rc_purgeold_batch.h create mode 100644 app/src/rc/dbio/rc_reclass_batch.h create mode 100644 app/src/rc/dbio/rc_sumupsert_dbio.h create mode 100644 app/src/rc/dbio/rc_sumupsert_dbio.pgc create mode 100644 app/src/rc/dbio/rc_windowload_batch.h create mode 100644 app/src/st/batch/st_adjnet_batch.pgc create mode 100644 app/src/st/batch/st_paysweep_batch.pgc create mode 100644 app/src/st/batch/st_purgeadj_batch.pgc create mode 100644 app/src/st/batch/st_rankpay_batch.pgc create mode 100644 app/src/st/batch/st_rateapply_batch.pgc create mode 100644 app/src/st/dbio/st_adjcorr_dbio.h create mode 100644 app/src/st/dbio/st_adjcorr_dbio.pgc create mode 100644 app/src/st/dbio/st_adjnet_batch.h create mode 100644 app/src/st/dbio/st_adjpurge_dbio.h create mode 100644 app/src/st/dbio/st_adjpurge_dbio.pgc create mode 100644 app/src/st/dbio/st_dtlinsert_dbio.h create mode 100644 app/src/st/dbio/st_dtlinsert_dbio.pgc create mode 100644 app/src/st/dbio/st_feecase_dbio.h create mode 100644 app/src/st/dbio/st_feecase_dbio.pgc create mode 100644 app/src/st/dbio/st_feehaving_dbio.h create mode 100644 app/src/st/dbio/st_feehaving_dbio.pgc create mode 100644 app/src/st/dbio/st_feeweight_dbio.h create mode 100644 app/src/st/dbio/st_feeweight_dbio.pgc create mode 100644 app/src/st/dbio/st_feewindow_dbio.h create mode 100644 app/src/st/dbio/st_feewindow_dbio.pgc create mode 100644 app/src/st/dbio/st_merchdistinct_dbio.h create mode 100644 app/src/st/dbio/st_merchdistinct_dbio.pgc create mode 100644 app/src/st/dbio/st_merchupsert_dbio.h create mode 100644 app/src/st/dbio/st_merchupsert_dbio.pgc create mode 100644 app/src/st/dbio/st_netminmax_dbio.h create mode 100644 app/src/st/dbio/st_netminmax_dbio.pgc create mode 100644 app/src/st/dbio/st_paycursor2_dbio.h create mode 100644 app/src/st/dbio/st_paycursor2_dbio.pgc create mode 100644 app/src/st/dbio/st_payinsub_dbio.h create mode 100644 app/src/st/dbio/st_payinsub_dbio.pgc create mode 100644 app/src/st/dbio/st_paynotexists_dbio.h create mode 100644 app/src/st/dbio/st_paynotexists_dbio.pgc create mode 100644 app/src/st/dbio/st_paysweep_batch.h create mode 100644 app/src/st/dbio/st_purgeadj_batch.h create mode 100644 app/src/st/dbio/st_rankpay_batch.h create mode 100644 app/src/st/dbio/st_rateapply_batch.h create mode 100644 app/src/st/dbio/st_ratejoin_dbio.h create mode 100644 app/src/st/dbio/st_ratejoin_dbio.pgc create mode 100644 app/src/st/dbio/st_statusfilter_dbio.h create mode 100644 app/src/st/dbio/st_statusfilter_dbio.pgc create mode 100644 app/src/vl/batch/vl_errgrp_batch.pgc create mode 100644 app/src/vl/batch/vl_logpurge_batch.pgc create mode 100644 app/src/vl/batch/vl_orphan_batch.pgc create mode 100644 app/src/vl/batch/vl_promote_batch.pgc create mode 100644 app/src/vl/batch/vl_rankscan_batch.pgc create mode 100644 app/src/vl/dbio/vl_corr_dbio.h create mode 100644 app/src/vl/dbio/vl_corr_dbio.pgc create mode 100644 app/src/vl/dbio/vl_errdistinct_dbio.h create mode 100644 app/src/vl/dbio/vl_errdistinct_dbio.pgc create mode 100644 app/src/vl/dbio/vl_errfilter_dbio.h create mode 100644 app/src/vl/dbio/vl_errfilter_dbio.pgc create mode 100644 app/src/vl/dbio/vl_errgroup_dbio.h create mode 100644 app/src/vl/dbio/vl_errgroup_dbio.pgc create mode 100644 app/src/vl/dbio/vl_errgrp_batch.h create mode 100644 app/src/vl/dbio/vl_errminmax_dbio.h create mode 100644 app/src/vl/dbio/vl_errminmax_dbio.pgc create mode 100644 app/src/vl/dbio/vl_inset_dbio.h create mode 100644 app/src/vl/dbio/vl_inset_dbio.pgc create mode 100644 app/src/vl/dbio/vl_logpurge_batch.h create mode 100644 app/src/vl/dbio/vl_orphan_batch.h create mode 100644 app/src/vl/dbio/vl_orphan_dbio.h create mode 100644 app/src/vl/dbio/vl_orphan_dbio.pgc create mode 100644 app/src/vl/dbio/vl_promote_batch.h create mode 100644 app/src/vl/dbio/vl_promote_dbio.h create mode 100644 app/src/vl/dbio/vl_promote_dbio.pgc create mode 100644 app/src/vl/dbio/vl_purgelog_dbio.h create mode 100644 app/src/vl/dbio/vl_purgelog_dbio.pgc create mode 100644 app/src/vl/dbio/vl_rank_dbio.h create mode 100644 app/src/vl/dbio/vl_rank_dbio.pgc create mode 100644 app/src/vl/dbio/vl_rankscan_batch.h create mode 100644 app/src/vl/dbio/vl_rulejoin_dbio.h create mode 100644 app/src/vl/dbio/vl_rulejoin_dbio.pgc create mode 100644 app/src/vl/dbio/vl_sevcase_dbio.h create mode 100644 app/src/vl/dbio/vl_sevcase_dbio.pgc diff --git a/app/src/ac/batch/ac_amountband_batch.pgc b/app/src/ac/batch/ac_amountband_batch.pgc new file mode 100644 index 0000000..8497fcb --- /dev/null +++ b/app/src/ac/batch/ac_amountband_batch.pgc @@ -0,0 +1,60 @@ +/* + * ac_amountband_batch.pgc - ac 매입 금액대 분포 배치 (CASE 버킷 + XA). + * + * ATMI client: one global transaction; a single CASE-bucketed aggregate splits + * the day's purchases into three amount bands. The C helper mirrors the band + * thresholds for reporting. tpcommit drives XA 2PC. + * Usage: ac_amountband_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "ac_amountband_batch.h" + +/* 금액을 3구간으로 분류. */ +int acb_band_classify(long amount) +{ + if (amount < 50000L) return 0; + if (amount < 500000L) return 1; + return 2; +} + +int main(int argc, char **argv) +{ + ac_band_ctx_t ctx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_lo = 0, h_mid = 0, h_hi = 0; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_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 amount < 50000), + count(*) FILTER (WHERE amount >= 50000 AND amount < 500000), + count(*) FILTER (WHERE amount >= 500000) + INTO :h_lo, :h_mid, :h_hi + FROM purchase WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_amountband_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + ctx.band_lo = h_lo; + ctx.band_mid = h_mid; + ctx.band_hi = h_hi; + (void)acb_band_classify(ctx.band_hi); + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> ac_amountband_batch COMMIT: bizdate=%s lo=%ld mid=%ld hi=%ld\n", + ctx.biz_date, ctx.band_lo, ctx.band_mid, ctx.band_hi); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/ac/batch/ac_concentration_batch.pgc b/app/src/ac/batch/ac_concentration_batch.pgc new file mode 100644 index 0000000..5d70e5d --- /dev/null +++ b/app/src/ac/batch/ac_concentration_batch.pgc @@ -0,0 +1,58 @@ +/* + * ac_concentration_batch.pgc - ac 상위매입 집중도 배치 (window + XA). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, a windowed subquery ranks purchases and a scalar sums the top-3 + * against the daily total; tpcommit drives XA 2PC. + * Usage: ac_concentration_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "ac_concentration_batch.h" + +/* 집중도(bps) = top_sum / total * 10000. total<=0 이면 0. */ +long acb_conc_ratio_bps(long top_sum, long total) +{ + if (total <= 0) return 0; + return (top_sum * 10000L) / total; +} + +int main(int argc, char **argv) +{ + ac_conc_ctx_t ctx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_top = 0, h_total = 0; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_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 coalesce(sum(amount),0) INTO :h_top FROM ( + SELECT amount, row_number() OVER (ORDER BY amount DESC) AS rn + FROM purchase WHERE biz_date = :h_bizdate) t WHERE t.rn <= 3; + if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_concentration_batch TOP FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_total FROM purchase WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_concentration_batch TOT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + ctx.top_sum = h_top; + ctx.total = h_total; + ctx.ratio_bps = acb_conc_ratio_bps(ctx.top_sum, ctx.total); + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> ac_concentration_batch COMMIT: bizdate=%s top3=%ld total=%ld ratio_bps=%ld\n", + ctx.biz_date, ctx.top_sum, ctx.total, ctx.ratio_bps); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/ac/batch/ac_fxweight_batch.pgc b/app/src/ac/batch/ac_fxweight_batch.pgc new file mode 100644 index 0000000..1f1e85a --- /dev/null +++ b/app/src/ac/batch/ac_fxweight_batch.pgc @@ -0,0 +1,53 @@ +/* + * ac_fxweight_batch.pgc - ac 해외매입 가중 평균 환율 배치 (XA). + * + * ATMI client: one global transaction; sums rate_bps weighted by krw_amount and + * the weight itself, then a C helper computes the weighted average bps. + * tpcommit drives XA 2PC. Usage: ac_fxweight_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "ac_fxweight_batch.h" + +/* 가중합/가중치 나눗셈 (0 보호). */ +long acb_fxw_divide(long num, long den) +{ + if (den <= 0) return 0; + return num / den; +} + +int main(int argc, char **argv) +{ + ac_fxw_ctx_t ctx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_num = 0, h_den = 0; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_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 coalesce(sum(rate_bps * krw_amount),0), coalesce(sum(krw_amount),0) + INTO :h_num, :h_den FROM ac_fx WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_fxweight_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + ctx.num = h_num; + ctx.den = h_den; + ctx.avg_bps = acb_fxw_divide(ctx.num, ctx.den); + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> ac_fxweight_batch COMMIT: bizdate=%s weight=%ld avg_bps=%ld\n", + ctx.biz_date, ctx.den, ctx.avg_bps); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/ac/batch/ac_summaryrebuild_batch.pgc b/app/src/ac/batch/ac_summaryrebuild_batch.pgc new file mode 100644 index 0000000..ca1574a --- /dev/null +++ b/app/src/ac/batch/ac_summaryrebuild_batch.pgc @@ -0,0 +1,63 @@ +/* + * ac_summaryrebuild_batch.pgc - ac 가맹점 일집계 재구축 배치 (INSERT..SELECT + upsert + XA). + * + * ATMI client: one global transaction; an INSERT..SELECT..GROUP BY re-aggregates + * purchase into merchant_summary via ON CONFLICT DO UPDATE, then reads back the + * rebuilt gross. tpcommit drives XA 2PC. Usage: ac_summaryrebuild_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "ac_summaryrebuild_batch.h" + +/* 반영 행수가 음수가 아니면 그대로 반환. */ +long acb_rebuild_ok(long affected) +{ + return (affected < 0) ? 0 : affected; +} + +int main(int argc, char **argv) +{ + ac_rebuild_ctx_t ctx; + 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(&ctx, 0, sizeof(ctx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_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 INSERT INTO merchant_summary + (merchant_id, biz_date, txn_count, gross_amount, fee_amount, net_amount) + SELECT merchant_id, biz_date, count(*), coalesce(sum(amount),0), + coalesce(sum(fee),0), coalesce(sum(net),0) + FROM purchase WHERE biz_date = :h_bizdate + GROUP BY merchant_id, biz_date + 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_summaryrebuild_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + ctx.rows_affected = acb_rebuild_ok(sqlca.sqlerrd[2]); + + EXEC SQL SELECT coalesce(sum(gross_amount),0) INTO :h_gross FROM merchant_summary WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_summaryrebuild_batch READ FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + ctx.total_gross = h_gross; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> ac_summaryrebuild_batch COMMIT: bizdate=%s rows=%ld gross=%ld\n", + ctx.biz_date, ctx.rows_affected, ctx.total_gross); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/ac/batch/ac_unmatchpurge_batch.pgc b/app/src/ac/batch/ac_unmatchpurge_batch.pgc new file mode 100644 index 0000000..900e2ee --- /dev/null +++ b/app/src/ac/batch/ac_unmatchpurge_batch.pgc @@ -0,0 +1,49 @@ +/* + * ac_unmatchpurge_batch.pgc - ac 미매입 로그 정리 배치 (구간 DELETE + XA). + * + * ATMI client: one global transaction; a range DELETE removes ac_unmatch rows + * older than the cutoff. The deleted count comes from sqlca.sqlerrd[2]. + * tpcommit drives XA 2PC. Usage: ac_unmatchpurge_batch [cutoff-YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "ac_unmatchpurge_batch.h" + +/* 배치 인자를 cutoff 문자열로 복사 (그대로 사용). */ +void acb_purge_cutoff(const char *bizdate, char *out, int outsz) +{ + strncpy(out, bizdate, outsz - 1); + out[outsz - 1] = 0; +} + +int main(int argc, char **argv) +{ + ac_purge_ctx_t ctx; + const char *cutoff = (argc > 1) ? argv[1] : "2026-07-01"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_cut[16]; + long h_deleted = 0; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + acb_purge_cutoff(cutoff, ctx.cutoff, (int)sizeof(ctx.cutoff)); + strncpy(h_cut, ctx.cutoff, sizeof(h_cut)-1); h_cut[sizeof(h_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 DELETE FROM ac_unmatch WHERE biz_date < :h_cut; + if (sqlca.sqlcode < 0) { fprintf(stderr, "ac_unmatchpurge_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_deleted = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + ctx.deleted = h_deleted; + printf(">>> ac_unmatchpurge_batch COMMIT: cutoff=%s deleted=%ld\n", ctx.cutoff, ctx.deleted); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/ac/dbio/ac_amountband_batch.h b/app/src/ac/dbio/ac_amountband_batch.h new file mode 100644 index 0000000..4c617c9 --- /dev/null +++ b/app/src/ac/dbio/ac_amountband_batch.h @@ -0,0 +1,17 @@ +/* + * ac_amountband_batch.h - copybook for ac_amountband_batch (XA client). + * CASE 금액대 분포 구조체 + 분류 헬퍼. Kept in dbio/ for -I resolution. + */ +#ifndef AC_AMOUNTBAND_BATCH_H +#define AC_AMOUNTBAND_BATCH_H + +typedef struct { + char biz_date[16]; + long band_lo; /* < 5만 */ + long band_mid; /* 5만~50만 */ + long band_hi; /* >= 50만 */ +} ac_band_ctx_t; + +int acb_band_classify(long amount); /* 0=lo, 1=mid, 2=hi */ + +#endif /* AC_AMOUNTBAND_BATCH_H */ diff --git a/app/src/ac/dbio/ac_cancel_casebucket_dbio.h b/app/src/ac/dbio/ac_cancel_casebucket_dbio.h new file mode 100644 index 0000000..c634232 --- /dev/null +++ b/app/src/ac/dbio/ac_cancel_casebucket_dbio.h @@ -0,0 +1,14 @@ +/* + * ac_cancel_casebucket_dbio.h - ac 모듈 DB 접근 계층 copybook (ac_cancel_casebucket_dbio, part of libacdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: CASE 금액 구간 버킷 분류. + */ +#ifndef AC_CANCEL_CASEBUCKET_DBIO_H +#define AC_CANCEL_CASEBUCKET_DBIO_H + +typedef struct { long small; long mid; long large; } ac_cancel_bucket_rec_t; + +int acdb_cancel_buckets(const char *bizdate, ac_cancel_bucket_rec_t *out); + +#endif /* AC_CANCEL_CASEBUCKET_DBIO_H */ diff --git a/app/src/ac/dbio/ac_cancel_casebucket_dbio.pgc b/app/src/ac/dbio/ac_cancel_casebucket_dbio.pgc new file mode 100644 index 0000000..41dfa70 --- /dev/null +++ b/app/src/ac/dbio/ac_cancel_casebucket_dbio.pgc @@ -0,0 +1,32 @@ +/* + * ac_cancel_casebucket_dbio.pgc - ac 모듈 DB 접근 함수 세트 (ac_cancel_casebucket_dbio), archived into libacdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: CASE WHEN 금액 구간별 건수를 한 번의 집계로 산출. + */ +#include +#include +#include "ac_cancel_casebucket_dbio.h" + +/* 취소 금액을 소액(<1만)/중액(<10만)/고액 3구간으로 CASE 버킷팅하여 out 에 채움. */ +int acdb_cancel_buckets(const char *bizdate, ac_cancel_bucket_rec_t *out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_sm = 0, h_md = 0, h_lg = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT + count(*) FILTER (WHERE amount < 10000), + count(*) FILTER (WHERE amount >= 10000 AND amount < 100000), + count(*) FILTER (WHERE amount >= 100000) + INTO :h_sm, :h_md, :h_lg + FROM ac_cancel WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { + userlog("acdb_cancel_buckets FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); + return -1; + } + out->small = h_sm; + out->mid = h_md; + out->large = h_lg; + return 0; +} diff --git a/app/src/ac/dbio/ac_concentration_batch.h b/app/src/ac/dbio/ac_concentration_batch.h new file mode 100644 index 0000000..6271858 --- /dev/null +++ b/app/src/ac/dbio/ac_concentration_batch.h @@ -0,0 +1,18 @@ +/* + * ac_concentration_batch.h - copybook for ac_concentration_batch (XA client). + * Struct + helper prototype #include'd by the batch; kept in dbio/ so both ecpg + * (-I dbio) and gcc (CFLAGS -I dbio) resolve it. The helper is defined in the batch TU. + */ +#ifndef AC_CONCENTRATION_BATCH_H +#define AC_CONCENTRATION_BATCH_H + +typedef struct { + char biz_date[16]; + long top_sum; /* 상위 N 매입 금액 합 */ + long total; /* 당일 총 매입 금액 */ + long ratio_bps; /* 집중도 = top_sum/total (bps) */ +} ac_conc_ctx_t; + +long acb_conc_ratio_bps(long top_sum, long total); + +#endif /* AC_CONCENTRATION_BATCH_H */ diff --git a/app/src/ac/dbio/ac_edi_intoledger_dbio.h b/app/src/ac/dbio/ac_edi_intoledger_dbio.h new file mode 100644 index 0000000..646c357 --- /dev/null +++ b/app/src/ac/dbio/ac_edi_intoledger_dbio.h @@ -0,0 +1,14 @@ +/* + * ac_edi_intoledger_dbio.h - ac 모듈 DB 접근 계층 copybook (ac_edi_intoledger_dbio, part of libacdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: INSERT INTO ledger SELECT ... FROM ac_edi. + */ +#ifndef AC_EDI_INTOLEDGER_DBIO_H +#define AC_EDI_INTOLEDGER_DBIO_H + +typedef struct { long inserted; long total_amount; } ac_edi_post_rec_t; + +long acdb_edi_post_to_ledger(const char *bizdate); + +#endif /* AC_EDI_INTOLEDGER_DBIO_H */ diff --git a/app/src/ac/dbio/ac_edi_intoledger_dbio.pgc b/app/src/ac/dbio/ac_edi_intoledger_dbio.pgc new file mode 100644 index 0000000..61c7974 --- /dev/null +++ b/app/src/ac/dbio/ac_edi_intoledger_dbio.pgc @@ -0,0 +1,27 @@ +/* + * ac_edi_intoledger_dbio.pgc - ac 모듈 DB 접근 함수 세트 (ac_edi_intoledger_dbio), archived into libacdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: INSERT INTO ledger SELECT (set-based 전기), 삽입 건수 반환. + */ +#include +#include +#include "ac_edi_intoledger_dbio.h" + +/* 당일 EDI 문서를 RECON 원장 엔트리로 set-based 전기하고 삽입 건수를 반환. */ +long acdb_edi_post_to_ledger(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long inserted = 0; + EXEC SQL END DECLARE SECTION; + 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) + SELECT nextval('ledger_seq'), 0, 'RECON', e.amount, e.biz_date + FROM ac_edi e WHERE e.biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { + userlog("acdb_edi_post_to_ledger FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); + return -1; + } + inserted = sqlca.sqlerrd[2]; + return inserted; +} diff --git a/app/src/ac/dbio/ac_feeadj_repeat_dbio.h b/app/src/ac/dbio/ac_feeadj_repeat_dbio.h new file mode 100644 index 0000000..4b7ad57 --- /dev/null +++ b/app/src/ac/dbio/ac_feeadj_repeat_dbio.h @@ -0,0 +1,14 @@ +/* + * ac_feeadj_repeat_dbio.h - ac 모듈 DB 접근 계층 copybook (ac_feeadj_repeat_dbio, part of libacdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: GROUP BY purchase_id HAVING count(*) > 1 (복수 수수료조정). + */ +#ifndef AC_FEEADJ_REPEAT_DBIO_H +#define AC_FEEADJ_REPEAT_DBIO_H + +typedef struct { long purchase_id; long adj_count; long delta_sum; } ac_feeadj_repeat_rec_t; + +long acdb_feeadj_repeat_purchases(const char *bizdate, long *delta_out); + +#endif /* AC_FEEADJ_REPEAT_DBIO_H */ diff --git a/app/src/ac/dbio/ac_feeadj_repeat_dbio.pgc b/app/src/ac/dbio/ac_feeadj_repeat_dbio.pgc new file mode 100644 index 0000000..8f1dbb1 --- /dev/null +++ b/app/src/ac/dbio/ac_feeadj_repeat_dbio.pgc @@ -0,0 +1,34 @@ +/* + * ac_feeadj_repeat_dbio.pgc - ac 모듈 DB 접근 함수 세트 (ac_feeadj_repeat_dbio), archived into libacdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: GROUP BY purchase_id HAVING count(*) > 1 커서로 반복조정 매입 수 + delta 합. + */ +#include +#include +#include "ac_feeadj_repeat_dbio.h" + +/* 수수료조정이 2회 이상 발생한 매입 수를 세고, delta 총합을 delta_out 에 반환. */ +long acdb_feeadj_repeat_purchases(const char *bizdate, long *delta_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_pid, h_cnt, h_delta, purchases = 0, delta_total = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE frc CURSOR FOR + SELECT purchase_id, count(*), coalesce(sum(delta),0) FROM ac_fee_adj + WHERE biz_date = :h_bizdate + GROUP BY purchase_id HAVING count(*) > 1; + EXEC SQL OPEN frc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH frc INTO :h_pid, :h_cnt, :h_delta; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE frc; return -1; } + purchases++; + delta_total += h_delta; + } + EXEC SQL CLOSE frc; + *delta_out = delta_total; + return purchases; +} diff --git a/app/src/ac/dbio/ac_fx_weightavg_dbio.h b/app/src/ac/dbio/ac_fx_weightavg_dbio.h new file mode 100644 index 0000000..b2e9c3d --- /dev/null +++ b/app/src/ac/dbio/ac_fx_weightavg_dbio.h @@ -0,0 +1,14 @@ +/* + * ac_fx_weightavg_dbio.h - ac 모듈 DB 접근 계층 copybook (ac_fx_weightavg_dbio, part of libacdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: KRW 가중 평균 환율(bps). + */ +#ifndef AC_FX_WEIGHTAVG_DBIO_H +#define AC_FX_WEIGHTAVG_DBIO_H + +typedef struct { long weighted_num; long weight; long avg_bps; } ac_fx_weight_rec_t; + +long acdb_fx_weighted_rate(const char *bizdate); + +#endif /* AC_FX_WEIGHTAVG_DBIO_H */ diff --git a/app/src/ac/dbio/ac_fx_weightavg_dbio.pgc b/app/src/ac/dbio/ac_fx_weightavg_dbio.pgc new file mode 100644 index 0000000..02347a5 --- /dev/null +++ b/app/src/ac/dbio/ac_fx_weightavg_dbio.pgc @@ -0,0 +1,25 @@ +/* + * ac_fx_weightavg_dbio.pgc - ac 모듈 DB 접근 함수 세트 (ac_fx_weightavg_dbio), archived into libacdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: KRW 금액 가중 평균 환율 = sum(rate_bps*krw_amount)/sum(krw_amount). + */ +#include +#include +#include "ac_fx_weightavg_dbio.h" + +/* 원화 환산액을 가중치로 하는 평균 환율(bps). 가중치 0 이면 0 반환. */ +long acdb_fx_weighted_rate(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_num = 0, h_den = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT + coalesce(sum(rate_bps * krw_amount), 0), + coalesce(sum(krw_amount), 0) + INTO :h_num, :h_den FROM ac_fx WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + if (h_den <= 0) return 0; + return h_num / h_den; +} diff --git a/app/src/ac/dbio/ac_fxweight_batch.h b/app/src/ac/dbio/ac_fxweight_batch.h new file mode 100644 index 0000000..3e593ce --- /dev/null +++ b/app/src/ac/dbio/ac_fxweight_batch.h @@ -0,0 +1,17 @@ +/* + * ac_fxweight_batch.h - copybook for ac_fxweight_batch (XA client). + * KRW 가중 평균 환율 누산 구조체 + 헬퍼. Kept in dbio/ for -I resolution. + */ +#ifndef AC_FXWEIGHT_BATCH_H +#define AC_FXWEIGHT_BATCH_H + +typedef struct { + char biz_date[16]; + long num; /* sum(rate_bps*krw) */ + long den; /* sum(krw) */ + long avg_bps; /* 가중 평균 */ +} ac_fxw_ctx_t; + +long acb_fxw_divide(long num, long den); + +#endif /* AC_FXWEIGHT_BATCH_H */ diff --git a/app/src/ac/dbio/ac_install_corr_dbio.h b/app/src/ac/dbio/ac_install_corr_dbio.h new file mode 100644 index 0000000..36dcb7a --- /dev/null +++ b/app/src/ac/dbio/ac_install_corr_dbio.h @@ -0,0 +1,14 @@ +/* + * ac_install_corr_dbio.h - ac 모듈 DB 접근 계층 copybook (ac_install_corr_dbio, part of libacdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: 상관 서브쿼리 - 자기 매입 평균 초과 할부회차. + */ +#ifndef AC_INSTALL_CORR_DBIO_H +#define AC_INSTALL_CORR_DBIO_H + +typedef struct { long install_id; long month_amount; } ac_install_corr_rec_t; + +long acdb_install_above_own_avg(const char *bizdate); + +#endif /* AC_INSTALL_CORR_DBIO_H */ diff --git a/app/src/ac/dbio/ac_install_corr_dbio.pgc b/app/src/ac/dbio/ac_install_corr_dbio.pgc new file mode 100644 index 0000000..07c2c7e --- /dev/null +++ b/app/src/ac/dbio/ac_install_corr_dbio.pgc @@ -0,0 +1,24 @@ +/* + * ac_install_corr_dbio.pgc - ac 모듈 DB 접근 함수 세트 (ac_install_corr_dbio), archived into libacdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: 상관(correlated) 서브쿼리 - 동일 매입의 평균 월납입액을 초과한 회차 수. + */ +#include +#include +#include "ac_install_corr_dbio.h" + +/* 같은 purchase_id 의 평균 month_amount 를 초과하는 할부 회차 건수. */ +long acdb_install_above_own_avg(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM ac_install i + WHERE i.biz_date = :h_bizdate + AND i.month_amount > (SELECT avg(j.month_amount) FROM ac_install j + WHERE j.purchase_id = i.purchase_id); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/ac/dbio/ac_merchant_bigday_dbio.h b/app/src/ac/dbio/ac_merchant_bigday_dbio.h new file mode 100644 index 0000000..106a1ee --- /dev/null +++ b/app/src/ac/dbio/ac_merchant_bigday_dbio.h @@ -0,0 +1,14 @@ +/* + * ac_merchant_bigday_dbio.h - ac 모듈 DB 접근 계층 copybook (ac_merchant_bigday_dbio, part of libacdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: GROUP BY merchant_id HAVING sum(amount) >= 임계. + */ +#ifndef AC_MERCHANT_BIGDAY_DBIO_H +#define AC_MERCHANT_BIGDAY_DBIO_H + +typedef struct { char merchant_id[64]; long gross; } ac_bigday_rec_t; + +long acdb_bigday_merchant_count(const char *bizdate, long threshold); + +#endif /* AC_MERCHANT_BIGDAY_DBIO_H */ diff --git a/app/src/ac/dbio/ac_merchant_bigday_dbio.pgc b/app/src/ac/dbio/ac_merchant_bigday_dbio.pgc new file mode 100644 index 0000000..2e6c571 --- /dev/null +++ b/app/src/ac/dbio/ac_merchant_bigday_dbio.pgc @@ -0,0 +1,34 @@ +/* + * ac_merchant_bigday_dbio.pgc - ac 모듈 DB 접근 함수 세트 (ac_merchant_bigday_dbio), archived into libacdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: GROUP BY ... HAVING sum(amount) >= 임계, 커서로 가맹점 계수 + 최대 group 합. + */ +#include +#include +#include "ac_merchant_bigday_dbio.h" + +/* 당일 매입합이 threshold 이상인 가맹점 수를 세고, 최대 group 합은 peak 로 보고. */ +long acdb_bigday_merchant_count(const char *bizdate, long threshold) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_gross, h_thr = threshold, cnt = 0, peak = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE bdc CURSOR FOR + SELECT merchant_id, sum(amount) FROM purchase + WHERE biz_date = :h_bizdate + GROUP BY merchant_id HAVING sum(amount) >= :h_thr; + EXEC SQL OPEN bdc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH bdc INTO :h_merch, :h_gross; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE bdc; return -1; } + cnt++; + if (h_gross > peak) peak = h_gross; + } + EXEC SQL CLOSE bdc; + if (peak < 0) return -1; + return cnt; +} diff --git a/app/src/ac/dbio/ac_purchase_incancel_dbio.h b/app/src/ac/dbio/ac_purchase_incancel_dbio.h new file mode 100644 index 0000000..acc3105 --- /dev/null +++ b/app/src/ac/dbio/ac_purchase_incancel_dbio.h @@ -0,0 +1,15 @@ +/* + * ac_purchase_incancel_dbio.h - ac 모듈 DB 접근 계층 copybook (ac_purchase_incancel_dbio, part of libacdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: WHERE purchase_id IN (SELECT ... FROM ac_cancel). + */ +#ifndef AC_PURCHASE_INCANCEL_DBIO_H +#define AC_PURCHASE_INCANCEL_DBIO_H + +typedef struct { long cancelled_cnt; long cancelled_amount; } ac_incancel_rec_t; + +long acdb_cancelled_purchase_count(const char *bizdate); +long acdb_cancelled_purchase_amount(const char *bizdate); + +#endif /* AC_PURCHASE_INCANCEL_DBIO_H */ diff --git a/app/src/ac/dbio/ac_purchase_incancel_dbio.pgc b/app/src/ac/dbio/ac_purchase_incancel_dbio.pgc new file mode 100644 index 0000000..421ec13 --- /dev/null +++ b/app/src/ac/dbio/ac_purchase_incancel_dbio.pgc @@ -0,0 +1,38 @@ +/* + * ac_purchase_incancel_dbio.pgc - ac 모듈 DB 접근 함수 세트 (ac_purchase_incancel_dbio), archived into libacdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: IN 서브셀렉트 - 취소 이력이 있는 매입 집계. + */ +#include +#include +#include "ac_purchase_incancel_dbio.h" + +/* 취소 테이블에 존재하는 purchase_id 를 가진 매입 건수. */ +long acdb_cancelled_purchase_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM purchase + WHERE biz_date = :h_bizdate + AND purchase_id IN (SELECT purchase_id FROM ac_cancel); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} + +/* 취소 이력이 있는 매입들의 원매입 금액 합계. */ +long acdb_cancelled_purchase_amount(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_v FROM purchase + WHERE biz_date = :h_bizdate + AND purchase_id IN (SELECT purchase_id FROM ac_cancel); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/ac/dbio/ac_purchase_rankpid_dbio.h b/app/src/ac/dbio/ac_purchase_rankpid_dbio.h new file mode 100644 index 0000000..81ff8c6 --- /dev/null +++ b/app/src/ac/dbio/ac_purchase_rankpid_dbio.h @@ -0,0 +1,14 @@ +/* + * ac_purchase_rankpid_dbio.h - ac 모듈 DB 접근 계층 copybook (ac_purchase_rankpid_dbio, part of libacdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: window rank() 로 특정 매입의 순위 산정. + */ +#ifndef AC_PURCHASE_RANKPID_DBIO_H +#define AC_PURCHASE_RANKPID_DBIO_H + +typedef struct { long rk; long purchase_id; long amount; } ac_rankpid_rec_t; + +long acdb_purchase_rank_of(const char *bizdate, long pid); + +#endif /* AC_PURCHASE_RANKPID_DBIO_H */ diff --git a/app/src/ac/dbio/ac_purchase_rankpid_dbio.pgc b/app/src/ac/dbio/ac_purchase_rankpid_dbio.pgc new file mode 100644 index 0000000..63b2c3b --- /dev/null +++ b/app/src/ac/dbio/ac_purchase_rankpid_dbio.pgc @@ -0,0 +1,31 @@ +/* + * ac_purchase_rankpid_dbio.pgc - ac 모듈 DB 접근 함수 세트 (ac_purchase_rankpid_dbio), archived into libacdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: rank() OVER (ORDER BY amount DESC) 커서 스캔 후 대상 pid 의 순위 반환. + */ +#include +#include +#include "ac_purchase_rankpid_dbio.h" + +/* 금액 순위표에서 지정 매입(pid)의 rank 를 찾아 반환. 미발견 시 0. */ +long acdb_purchase_rank_of(const char *bizdate, long pid) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_rk, h_pid, h_amt, target = pid, found = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE rkc CURSOR FOR + SELECT rank() OVER (ORDER BY amount DESC) AS rk, purchase_id, amount + FROM purchase WHERE biz_date = :h_bizdate; + EXEC SQL OPEN rkc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH rkc INTO :h_rk, :h_pid, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE rkc; return -1; } + if (h_pid == target) { found = h_rk; break; } + } + EXEC SQL CLOSE rkc; + return found; +} diff --git a/app/src/ac/dbio/ac_purchase_topn_dbio.h b/app/src/ac/dbio/ac_purchase_topn_dbio.h new file mode 100644 index 0000000..c0d8484 --- /dev/null +++ b/app/src/ac/dbio/ac_purchase_topn_dbio.h @@ -0,0 +1,14 @@ +/* + * ac_purchase_topn_dbio.h - ac 모듈 DB 접근 계층 copybook (ac_purchase_topn_dbio, part of libacdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: window row_number() 상위 N 매입 누적. + */ +#ifndef AC_PURCHASE_TOPN_DBIO_H +#define AC_PURCHASE_TOPN_DBIO_H + +typedef struct { long rn; long purchase_id; long amount; } ac_topn_rec_t; + +long acdb_topn_amount_sum(const char *bizdate, long topn); + +#endif /* AC_PURCHASE_TOPN_DBIO_H */ diff --git a/app/src/ac/dbio/ac_purchase_topn_dbio.pgc b/app/src/ac/dbio/ac_purchase_topn_dbio.pgc new file mode 100644 index 0000000..416381b --- /dev/null +++ b/app/src/ac/dbio/ac_purchase_topn_dbio.pgc @@ -0,0 +1,31 @@ +/* + * ac_purchase_topn_dbio.pgc - ac 모듈 DB 접근 함수 세트 (ac_purchase_topn_dbio), archived into libacdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: window row_number() OVER (ORDER BY amount DESC) 상위 N 매입 금액 누적. + */ +#include +#include +#include "ac_purchase_topn_dbio.h" + +/* 금액 내림차순 상위 topn 매입의 amount 합계를 커서 누적으로 반환. */ +long acdb_topn_amount_sum(const char *bizdate, long topn) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_rn, h_amt, acc = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE tnc CURSOR FOR + SELECT row_number() OVER (ORDER BY amount DESC) AS rn, amount + FROM purchase WHERE biz_date = :h_bizdate; + EXEC SQL OPEN tnc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH tnc INTO :h_rn, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE tnc; return -1; } + if (h_rn <= topn) acc += h_amt; + } + EXEC SQL CLOSE tnc; + return acc; +} diff --git a/app/src/ac/dbio/ac_settle_join_dbio.h b/app/src/ac/dbio/ac_settle_join_dbio.h new file mode 100644 index 0000000..a7167a5 --- /dev/null +++ b/app/src/ac/dbio/ac_settle_join_dbio.h @@ -0,0 +1,15 @@ +/* + * ac_settle_join_dbio.h - ac 모듈 DB 접근 계층 copybook (ac_settle_join_dbio, part of libacdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: purchase JOIN settlement 집계. + */ +#ifndef AC_SETTLE_JOIN_DBIO_H +#define AC_SETTLE_JOIN_DBIO_H + +typedef struct { char merchant_id[64]; long settled_net; long matched; } ac_settle_join_rec_t; + +long acdb_settled_net_by_merchant(const char *merch, const char *bizdate); +long acdb_settled_match_count(const char *bizdate); + +#endif /* AC_SETTLE_JOIN_DBIO_H */ diff --git a/app/src/ac/dbio/ac_settle_join_dbio.pgc b/app/src/ac/dbio/ac_settle_join_dbio.pgc new file mode 100644 index 0000000..ba7243c --- /dev/null +++ b/app/src/ac/dbio/ac_settle_join_dbio.pgc @@ -0,0 +1,39 @@ +/* + * ac_settle_join_dbio.pgc - ac 모듈 DB 접근 함수 세트 (ac_settle_join_dbio), archived into libacdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: purchase p JOIN settlement s ON p.purchase_id=s.purchase_id 집계. + */ +#include +#include +#include "ac_settle_join_dbio.h" + +/* 특정 가맹점의 정산 완료된 매입 net 합계 (조인 기준 매입측 net). */ +long acdb_settled_net_by_merchant(const char *merch, const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + 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(p.net),0) INTO :h_v + FROM purchase p JOIN settlement s ON p.purchase_id = s.purchase_id + WHERE p.merchant_id = :h_merch AND s.biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} + +/* 당일 정산행과 매입행이 조인 매칭된 건수. */ +long acdb_settled_match_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v + FROM settlement s JOIN purchase p ON s.purchase_id = p.purchase_id + WHERE s.biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/ac/dbio/ac_settle_notexists_dbio.h b/app/src/ac/dbio/ac_settle_notexists_dbio.h new file mode 100644 index 0000000..d073efa --- /dev/null +++ b/app/src/ac/dbio/ac_settle_notexists_dbio.h @@ -0,0 +1,14 @@ +/* + * ac_settle_notexists_dbio.h - ac 모듈 DB 접근 계층 copybook (ac_settle_notexists_dbio, part of libacdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: NOT EXISTS 정산 누락 매입. + */ +#ifndef AC_SETTLE_NOTEXISTS_DBIO_H +#define AC_SETTLE_NOTEXISTS_DBIO_H + +typedef struct { long purchase_id; long net; } ac_settle_gap_rec_t; + +long acdb_unsettled_count(const char *bizdate); + +#endif /* AC_SETTLE_NOTEXISTS_DBIO_H */ diff --git a/app/src/ac/dbio/ac_settle_notexists_dbio.pgc b/app/src/ac/dbio/ac_settle_notexists_dbio.pgc new file mode 100644 index 0000000..0fd8889 --- /dev/null +++ b/app/src/ac/dbio/ac_settle_notexists_dbio.pgc @@ -0,0 +1,23 @@ +/* + * ac_settle_notexists_dbio.pgc - ac 모듈 DB 접근 함수 세트 (ac_settle_notexists_dbio), archived into libacdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: NOT EXISTS 상관 서브쿼리 - 대사완료(M) 이나 정산행이 없는 매입. + */ +#include +#include +#include "ac_settle_notexists_dbio.h" + +/* 상태가 대사완료(M) 이지만 settlement 에 대응행이 없는 매입 건수. */ +long acdb_unsettled_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM purchase p + WHERE 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) return -1; + return h_v; +} diff --git a/app/src/ac/dbio/ac_summary_rebuild_dbio.h b/app/src/ac/dbio/ac_summary_rebuild_dbio.h new file mode 100644 index 0000000..664759c --- /dev/null +++ b/app/src/ac/dbio/ac_summary_rebuild_dbio.h @@ -0,0 +1,14 @@ +/* + * ac_summary_rebuild_dbio.h - ac 모듈 DB 접근 계층 copybook (ac_summary_rebuild_dbio, part of libacdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: INSERT INTO ... SELECT ... GROUP BY ... ON CONFLICT DO UPDATE (재집계 upsert). + */ +#ifndef AC_SUMMARY_REBUILD_DBIO_H +#define AC_SUMMARY_REBUILD_DBIO_H + +typedef struct { char biz_date[16]; long merchants; } ac_summary_rebuild_rec_t; + +long acdb_summary_rebuild_from_purchase(const char *bizdate); + +#endif /* AC_SUMMARY_REBUILD_DBIO_H */ diff --git a/app/src/ac/dbio/ac_summary_rebuild_dbio.pgc b/app/src/ac/dbio/ac_summary_rebuild_dbio.pgc new file mode 100644 index 0000000..d71da0e --- /dev/null +++ b/app/src/ac/dbio/ac_summary_rebuild_dbio.pgc @@ -0,0 +1,36 @@ +/* + * ac_summary_rebuild_dbio.pgc - ac 모듈 DB 접근 함수 세트 (ac_summary_rebuild_dbio), archived into libacdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: INSERT INTO merchant_summary SELECT ... GROUP BY ... ON CONFLICT DO UPDATE (재집계 upsert). + */ +#include +#include +#include "ac_summary_rebuild_dbio.h" + +/* 당일 purchase 를 가맹점별로 재집계하여 merchant_summary 에 upsert. 반영 행수 반환. */ +long acdb_summary_rebuild_from_purchase(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long affected = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL INSERT INTO merchant_summary + (merchant_id, biz_date, txn_count, gross_amount, fee_amount, net_amount) + SELECT merchant_id, biz_date, count(*), coalesce(sum(amount),0), + coalesce(sum(fee),0), coalesce(sum(net),0) + FROM purchase WHERE biz_date = :h_bizdate + GROUP BY merchant_id, biz_date + 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) { + userlog("acdb_summary_rebuild_from_purchase FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); + return -1; + } + affected = sqlca.sqlerrd[2]; + return affected; +} diff --git a/app/src/ac/dbio/ac_summaryrebuild_batch.h b/app/src/ac/dbio/ac_summaryrebuild_batch.h new file mode 100644 index 0000000..5f155fa --- /dev/null +++ b/app/src/ac/dbio/ac_summaryrebuild_batch.h @@ -0,0 +1,16 @@ +/* + * ac_summaryrebuild_batch.h - copybook for ac_summaryrebuild_batch (XA client). + * merchant_summary 재집계 구조체 + 헬퍼. Kept in dbio/ for -I resolution. + */ +#ifndef AC_SUMMARYREBUILD_BATCH_H +#define AC_SUMMARYREBUILD_BATCH_H + +typedef struct { + char biz_date[16]; + long rows_affected; + long total_gross; +} ac_rebuild_ctx_t; + +long acb_rebuild_ok(long affected); + +#endif /* AC_SUMMARYREBUILD_BATCH_H */ diff --git a/app/src/ac/dbio/ac_taxinv_spread_dbio.h b/app/src/ac/dbio/ac_taxinv_spread_dbio.h new file mode 100644 index 0000000..9a6d4ef --- /dev/null +++ b/app/src/ac/dbio/ac_taxinv_spread_dbio.h @@ -0,0 +1,15 @@ +/* + * ac_taxinv_spread_dbio.h - ac 모듈 DB 접근 계층 copybook (ac_taxinv_spread_dbio, part of libacdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: max(vat)-min(vat) 스프레드. + */ +#ifndef AC_TAXINV_SPREAD_DBIO_H +#define AC_TAXINV_SPREAD_DBIO_H + +typedef struct { long vat_max; long vat_min; long spread; } ac_taxinv_spread_rec_t; + +long acdb_taxinv_vat_spread(const char *bizdate); +long acdb_taxinv_count(const char *bizdate); + +#endif /* AC_TAXINV_SPREAD_DBIO_H */ diff --git a/app/src/ac/dbio/ac_taxinv_spread_dbio.pgc b/app/src/ac/dbio/ac_taxinv_spread_dbio.pgc new file mode 100644 index 0000000..7f8a43d --- /dev/null +++ b/app/src/ac/dbio/ac_taxinv_spread_dbio.pgc @@ -0,0 +1,35 @@ +/* + * ac_taxinv_spread_dbio.pgc - ac 모듈 DB 접근 함수 세트 (ac_taxinv_spread_dbio), archived into libacdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: 세금계산서 VAT 최대-최소 스프레드 및 건수. + */ +#include +#include +#include "ac_taxinv_spread_dbio.h" + +/* 당일 세금계산서 VAT 의 max-min 스프레드. 데이터 없으면 0. */ +long acdb_taxinv_vat_spread(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_max = 0, h_min = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(max(vat),0), coalesce(min(vat),0) + INTO :h_max, :h_min FROM ac_tax_invoice WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_max - h_min; +} + +/* 당일 발행된 세금계산서 건수. */ +long acdb_taxinv_count(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM ac_tax_invoice WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/ac/dbio/ac_unmatch_purge_dbio.h b/app/src/ac/dbio/ac_unmatch_purge_dbio.h new file mode 100644 index 0000000..96a9892 --- /dev/null +++ b/app/src/ac/dbio/ac_unmatch_purge_dbio.h @@ -0,0 +1,14 @@ +/* + * ac_unmatch_purge_dbio.h - ac 모듈 DB 접근 계층 copybook (ac_unmatch_purge_dbio, part of libacdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: DELETE ... WHERE biz_date < 기준 (구간 삭제). + */ +#ifndef AC_UNMATCH_PURGE_DBIO_H +#define AC_UNMATCH_PURGE_DBIO_H + +typedef struct { char cutoff[16]; long deleted; } ac_unmatch_purge_rec_t; + +long acdb_unmatch_purge_before(const char *cutoff); + +#endif /* AC_UNMATCH_PURGE_DBIO_H */ diff --git a/app/src/ac/dbio/ac_unmatch_purge_dbio.pgc b/app/src/ac/dbio/ac_unmatch_purge_dbio.pgc new file mode 100644 index 0000000..cbe718f --- /dev/null +++ b/app/src/ac/dbio/ac_unmatch_purge_dbio.pgc @@ -0,0 +1,25 @@ +/* + * ac_unmatch_purge_dbio.pgc - ac 모듈 DB 접근 함수 세트 (ac_unmatch_purge_dbio), archived into libacdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: DELETE 구간 삭제 - 기준일 이전 미매입 로그 정리, 삭제 건수 반환. + */ +#include +#include +#include "ac_unmatch_purge_dbio.h" + +/* cutoff(YYYY-MM-DD) 이전의 ac_unmatch 로그를 삭제하고 삭제 건수를 반환. */ +long acdb_unmatch_purge_before(const char *cutoff) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_cut[16]; + long deleted = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_cut, cutoff, sizeof(h_cut)-1); h_cut[sizeof(h_cut)-1] = 0; + EXEC SQL DELETE FROM ac_unmatch WHERE biz_date < :h_cut; + if (sqlca.sqlcode < 0) { + userlog("acdb_unmatch_purge_before FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); + return -1; + } + deleted = sqlca.sqlerrd[2]; + return deleted; +} diff --git a/app/src/ac/dbio/ac_unmatchpurge_batch.h b/app/src/ac/dbio/ac_unmatchpurge_batch.h new file mode 100644 index 0000000..a9b6193 --- /dev/null +++ b/app/src/ac/dbio/ac_unmatchpurge_batch.h @@ -0,0 +1,15 @@ +/* + * ac_unmatchpurge_batch.h - copybook for ac_unmatchpurge_batch (XA client). + * 미매입 로그 구간 삭제 구조체 + 기준일 계산 헬퍼. Kept in dbio/ for -I resolution. + */ +#ifndef AC_UNMATCHPURGE_BATCH_H +#define AC_UNMATCHPURGE_BATCH_H + +typedef struct { + char cutoff[16]; + long deleted; +} ac_purge_ctx_t; + +void acb_purge_cutoff(const char *bizdate, char *out, int outsz); + +#endif /* AC_UNMATCHPURGE_BATCH_H */ diff --git a/app/src/ac/dbio/ac_wht_distinct_dbio.h b/app/src/ac/dbio/ac_wht_distinct_dbio.h new file mode 100644 index 0000000..b017c49 --- /dev/null +++ b/app/src/ac/dbio/ac_wht_distinct_dbio.h @@ -0,0 +1,14 @@ +/* + * ac_wht_distinct_dbio.h - ac 모듈 DB 접근 계층 copybook (ac_wht_distinct_dbio, part of libacdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: count(DISTINCT purchase_id) + sum(tax_amount). + */ +#ifndef AC_WHT_DISTINCT_DBIO_H +#define AC_WHT_DISTINCT_DBIO_H + +typedef struct { long distinct_purchases; long total_tax; } ac_wht_distinct_rec_t; + +int acdb_wht_distinct_summary(const char *bizdate, ac_wht_distinct_rec_t *out); + +#endif /* AC_WHT_DISTINCT_DBIO_H */ diff --git a/app/src/ac/dbio/ac_wht_distinct_dbio.pgc b/app/src/ac/dbio/ac_wht_distinct_dbio.pgc new file mode 100644 index 0000000..e42d872 --- /dev/null +++ b/app/src/ac/dbio/ac_wht_distinct_dbio.pgc @@ -0,0 +1,27 @@ +/* + * ac_wht_distinct_dbio.pgc - ac 모듈 DB 접근 함수 세트 (ac_wht_distinct_dbio), archived into libacdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: count(DISTINCT purchase_id) 와 sum(tax_amount) 를 한 번에 산출하여 구조체로 반환. + */ +#include +#include +#include "ac_wht_distinct_dbio.h" + +/* 원천징수가 걸린 서로 다른 매입 수와 총 원천세액을 out 에 채운다. */ +int acdb_wht_distinct_summary(const char *bizdate, ac_wht_distinct_rec_t *out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_dp = 0, h_tax = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(DISTINCT purchase_id), coalesce(sum(tax_amount),0) + INTO :h_dp, :h_tax FROM ac_wht WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { + userlog("acdb_wht_distinct_summary FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); + return -1; + } + out->distinct_purchases = h_dp; + out->total_tax = h_tax; + return 0; +} diff --git a/app/src/au/batch/au_approverate_batch.pgc b/app/src/au/batch/au_approverate_batch.pgc new file mode 100644 index 0000000..7379172 --- /dev/null +++ b/app/src/au/batch/au_approverate_batch.pgc @@ -0,0 +1,53 @@ +/* + * au_approverate_batch.pgc - au 일일 승인율 배치 (FILTER 집계 + XA). + * + * ATMI client: one global transaction; a FILTER aggregate counts approved vs. + * total authorizations, then a C helper computes the approval rate in bps. + * tpcommit drives XA 2PC. Usage: au_approverate_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_approverate_batch.h" + +/* 승인율(bps) = approved/total * 10000. total<=0 이면 0. */ +long aub_rate_bps(long approved, long total) +{ + if (total <= 0) return 0; + return (approved * 10000L) / total; +} + +int main(int argc, char **argv) +{ + au_rate_ctx_t ctx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_appr = 0, h_total = 0; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_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 status = 'A'), count(*) + INTO :h_appr, :h_total FROM au_authorization WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "au_approverate_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + ctx.approved = h_appr; + ctx.total = h_total; + ctx.rate_bps = aub_rate_bps(ctx.approved, ctx.total); + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_approverate_batch COMMIT: bizdate=%s approved=%ld total=%ld rate_bps=%ld\n", + ctx.biz_date, ctx.approved, ctx.total, ctx.rate_bps); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_fraudpurge_batch.pgc b/app/src/au/batch/au_fraudpurge_batch.pgc new file mode 100644 index 0000000..ec7064a --- /dev/null +++ b/app/src/au/batch/au_fraudpurge_batch.pgc @@ -0,0 +1,52 @@ +/* + * au_fraudpurge_batch.pgc - au 저점수 부정로그 정리 배치 (복합조건 DELETE + XA). + * + * ATMI client: one global transaction; a range DELETE removes fraud-log rows that + * are both older than the cutoff and below the score floor. Deleted count comes + * from sqlca.sqlerrd[2]. Usage: au_fraudpurge_batch [cutoff] [min_score] + */ +#include +#include +#include +#include +#include "au_fraudpurge_batch.h" + +/* 최소 점수 하한 (음수 방지). */ +long aub_fpurge_floor(long score) +{ + return (score < 0) ? 0 : score; +} + +int main(int argc, char **argv) +{ + au_fpurge_ctx_t ctx; + const char *cutoff = (argc > 1) ? argv[1] : "2026-07-01"; + long min_score = (argc > 2) ? atol(argv[2]) : 50; + + EXEC SQL BEGIN DECLARE SECTION; + char h_cut[16]; + long h_min, h_deleted = 0; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(ctx.cutoff, cutoff, sizeof(ctx.cutoff)-1); + ctx.min_score = aub_fpurge_floor(min_score); + strncpy(h_cut, ctx.cutoff, sizeof(h_cut)-1); h_cut[sizeof(h_cut)-1] = 0; + h_min = ctx.min_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(60, 0) < 0) { fprintf(stderr, "tpbegin FAIL: %s\n", tpstrerror(tperrno)); return 1; } + + EXEC SQL DELETE FROM au_fraud_log WHERE biz_date < :h_cut AND score < :h_min; + if (sqlca.sqlcode < 0) { fprintf(stderr, "au_fraudpurge_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_deleted = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + ctx.deleted = h_deleted; + printf(">>> au_fraudpurge_batch COMMIT: cutoff=%s min_score=%ld deleted=%ld\n", + ctx.cutoff, ctx.min_score, ctx.deleted); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_scorebucket_batch.pgc b/app/src/au/batch/au_scorebucket_batch.pgc new file mode 100644 index 0000000..3c4c554 --- /dev/null +++ b/app/src/au/batch/au_scorebucket_batch.pgc @@ -0,0 +1,58 @@ +/* + * au_scorebucket_batch.pgc - au 부정거래 점수대 분포 배치 (CASE + XA). + * + * ATMI client: one global transaction; a CASE-summed aggregate splits fraud-log + * scores into three bands. The C helper maps a score to a label. tpcommit drives + * XA 2PC. Usage: au_scorebucket_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_scorebucket_batch.h" + +/* 점수 → 라벨. */ +const char *aub_score_label(long score) +{ + if (score < 50) return "LOW"; + if (score < 80) return "MID"; + return "HIGH"; +} + +int main(int argc, char **argv) +{ + au_score_ctx_t ctx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_low = 0, h_mid = 0, h_high = 0; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_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 + coalesce(sum(CASE WHEN score < 50 THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN score >= 50 AND score < 80 THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN score >= 80 THEN 1 ELSE 0 END),0) + INTO :h_low, :h_mid, :h_high + FROM au_fraud_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "au_scorebucket_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + ctx.low = h_low; + ctx.mid = h_mid; + ctx.high = h_high; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_scorebucket_batch COMMIT: bizdate=%s low=%ld mid=%ld high=%ld peak=%s\n", + ctx.biz_date, ctx.low, ctx.mid, ctx.high, aub_score_label(ctx.high > 0 ? 80 : 0)); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_summaryrebuild_batch.pgc b/app/src/au/batch/au_summaryrebuild_batch.pgc new file mode 100644 index 0000000..acadab2 --- /dev/null +++ b/app/src/au/batch/au_summaryrebuild_batch.pgc @@ -0,0 +1,65 @@ +/* + * au_summaryrebuild_batch.pgc - au 가맹점 일집계 재구축 배치 (INSERT..SELECT FILTER upsert + XA). + * + * ATMI client: one global transaction; an INSERT..SELECT with FILTER re-aggregates + * au_authorization into au_summary via ON CONFLICT DO UPDATE, then reads back the + * approve total. tpcommit drives XA 2PC. Usage: au_summaryrebuild_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_summaryrebuild_batch.h" + +/* 음수 반영행수는 0 으로 클립. */ +long aub_rebuild_clip(long affected) +{ + return (affected < 0) ? 0 : affected; +} + +int main(int argc, char **argv) +{ + au_rebuild_ctx_t ctx; + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_appr = 0; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_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 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 = 'A'), + count(*) FILTER (WHERE status = 'D'), + coalesce(sum(amount),0) + FROM au_authorization WHERE biz_date = :h_bizdate + GROUP BY merchant_id, biz_date + ON CONFLICT (merchant_id, biz_date) DO UPDATE + SET auth_count = EXCLUDED.auth_count, + approve_count = EXCLUDED.approve_count, + decline_count = EXCLUDED.decline_count, + auth_amount = EXCLUDED.auth_amount, + updated_at = now(); + if (sqlca.sqlcode < 0) { fprintf(stderr, "au_summaryrebuild_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + ctx.rows_affected = aub_rebuild_clip(sqlca.sqlerrd[2]); + + EXEC SQL SELECT coalesce(sum(approve_count),0) INTO :h_appr FROM au_summary WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "au_summaryrebuild_batch READ FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + ctx.total_approved = h_appr; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_summaryrebuild_batch COMMIT: bizdate=%s rows=%ld approved=%ld\n", + ctx.biz_date, ctx.rows_affected, ctx.total_approved); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/batch/au_topcard_batch.pgc b/app/src/au/batch/au_topcard_batch.pgc new file mode 100644 index 0000000..17fd23d --- /dev/null +++ b/app/src/au/batch/au_topcard_batch.pgc @@ -0,0 +1,62 @@ +/* + * au_topcard_batch.pgc - au 최고 지출 카드 배치 (window + XA). + * + * ATMI client: one global transaction; a windowed subquery ranks card spend and + * picks the rn=1 card. tpcommit drives XA 2PC. Usage: au_topcard_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "au_topcard_batch.h" + +/* 지출액이 양수면 유효한 결과로 간주. */ +int aub_topcard_valid(long spend) +{ + return (spend > 0) ? 1 : 0; +} + +int main(int argc, char **argv) +{ + au_topcard_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_spend = 0; + EXEC SQL END DECLARE SECTION; + + memset(&ctx, 0, sizeof(ctx)); + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(h_card, "NONE", sizeof(h_card)-1); h_card[sizeof(h_card)-1] = 0; + strncpy(ctx.biz_date, bizdate, sizeof(ctx.biz_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 card_no, spend FROM ( + SELECT card_no, sum(amount) AS spend, + row_number() OVER (ORDER BY sum(amount) DESC) AS rn + FROM au_authorization WHERE biz_date = :h_bizdate + GROUP BY card_no) t WHERE t.rn = 1; + if (sqlca.sqlcode < 0) { fprintf(stderr, "au_topcard_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + if (sqlca.sqlcode != 100) { + EXEC SQL SELECT card_no, spend INTO :h_card, :h_spend FROM ( + SELECT card_no, sum(amount) AS spend, + row_number() OVER (ORDER BY sum(amount) DESC) AS rn + FROM au_authorization WHERE biz_date = :h_bizdate + GROUP BY card_no) t WHERE t.rn = 1; + if (sqlca.sqlcode < 0) { fprintf(stderr, "au_topcard_batch FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + } + + strncpy(ctx.top_card, h_card, sizeof(ctx.top_card)-1); + ctx.top_spend = h_spend; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> au_topcard_batch COMMIT: bizdate=%s top_card=%s spend=%ld valid=%d\n", + ctx.biz_date, ctx.top_card, ctx.top_spend, aub_topcard_valid(ctx.top_spend)); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/au/dbio/au_approverate_batch.h b/app/src/au/dbio/au_approverate_batch.h new file mode 100644 index 0000000..f67ab1f --- /dev/null +++ b/app/src/au/dbio/au_approverate_batch.h @@ -0,0 +1,17 @@ +/* + * au_approverate_batch.h - copybook for au_approverate_batch (XA client). + * 승인율 산출 구조체 + 헬퍼. Kept in dbio/ for -I resolution. + */ +#ifndef AU_APPROVERATE_BATCH_H +#define AU_APPROVERATE_BATCH_H + +typedef struct { + char biz_date[16]; + long approved; + long total; + long rate_bps; /* approved/total (bps) */ +} au_rate_ctx_t; + +long aub_rate_bps(long approved, long total); + +#endif /* AU_APPROVERATE_BATCH_H */ diff --git a/app/src/au/dbio/au_auth_distinctcard_dbio.h b/app/src/au/dbio/au_auth_distinctcard_dbio.h new file mode 100644 index 0000000..668ad1a --- /dev/null +++ b/app/src/au/dbio/au_auth_distinctcard_dbio.h @@ -0,0 +1,14 @@ +/* + * au_auth_distinctcard_dbio.h - au 모듈 DB 접근 계층 copybook (au_auth_distinctcard_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: count(DISTINCT card_no) + count(DISTINCT merchant_id). + */ +#ifndef AU_AUTH_DISTINCTCARD_DBIO_H +#define AU_AUTH_DISTINCTCARD_DBIO_H + +typedef struct { long distinct_cards; long distinct_merchants; } au_distinct_rec_t; + +int audb_auth_distinct_counts(const char *bizdate, au_distinct_rec_t *out); + +#endif /* AU_AUTH_DISTINCTCARD_DBIO_H */ diff --git a/app/src/au/dbio/au_auth_distinctcard_dbio.pgc b/app/src/au/dbio/au_auth_distinctcard_dbio.pgc new file mode 100644 index 0000000..6b9b639 --- /dev/null +++ b/app/src/au/dbio/au_auth_distinctcard_dbio.pgc @@ -0,0 +1,28 @@ +/* + * au_auth_distinctcard_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_auth_distinctcard_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: 승인건에서 서로 다른 카드/가맹점 수를 count(DISTINCT) 로 동시 산출. + */ +#include +#include +#include "au_auth_distinctcard_dbio.h" + +/* 당일 승인(A) 건에서 사용된 서로 다른 카드/가맹점 수를 out 에 채운다. */ +int audb_auth_distinct_counts(const char *bizdate, au_distinct_rec_t *out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cards = 0, h_merch = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(DISTINCT card_no), count(DISTINCT merchant_id) + INTO :h_cards, :h_merch FROM au_authorization + WHERE biz_date = :h_bizdate AND status = 'A'; + if (sqlca.sqlcode < 0) { + userlog("audb_auth_distinct_counts FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); + return -1; + } + out->distinct_cards = h_cards; + out->distinct_merchants = h_merch; + return 0; +} diff --git a/app/src/au/dbio/au_auth_incancel_dbio.h b/app/src/au/dbio/au_auth_incancel_dbio.h new file mode 100644 index 0000000..4ae9015 --- /dev/null +++ b/app/src/au/dbio/au_auth_incancel_dbio.h @@ -0,0 +1,14 @@ +/* + * au_auth_incancel_dbio.h - au 모듈 DB 접근 계층 copybook (au_auth_incancel_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: WHERE auth_id IN (SELECT auth_id FROM au_cancel WHERE ...). + */ +#ifndef AU_AUTH_INCANCEL_DBIO_H +#define AU_AUTH_INCANCEL_DBIO_H + +typedef struct { long auth_id; long amount; } au_incancel_rec_t; + +long audb_fullcancelled_auth_amount(const char *bizdate); + +#endif /* AU_AUTH_INCANCEL_DBIO_H */ diff --git a/app/src/au/dbio/au_auth_incancel_dbio.pgc b/app/src/au/dbio/au_auth_incancel_dbio.pgc new file mode 100644 index 0000000..cf75073 --- /dev/null +++ b/app/src/au/dbio/au_auth_incancel_dbio.pgc @@ -0,0 +1,23 @@ +/* + * au_auth_incancel_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_auth_incancel_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: IN 서브셀렉트(내부 WHERE 조건 포함) - 전액취소된 승인 금액 합. + */ +#include +#include +#include "au_auth_incancel_dbio.h" + +/* 전액취소(FULL) 이력이 있는 승인들의 원승인 금액 합계. */ +long audb_fullcancelled_auth_amount(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_v FROM au_authorization + WHERE biz_date = :h_bizdate + AND auth_id IN (SELECT auth_id FROM au_cancel WHERE cancel_type = 'FULL'); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/au/dbio/au_auth_rankcard_dbio.h b/app/src/au/dbio/au_auth_rankcard_dbio.h new file mode 100644 index 0000000..d72fa6c --- /dev/null +++ b/app/src/au/dbio/au_auth_rankcard_dbio.h @@ -0,0 +1,14 @@ +/* + * au_auth_rankcard_dbio.h - au 모듈 DB 접근 계층 copybook (au_auth_rankcard_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: window rank() 로 N위 승인 금액 조회. + */ +#ifndef AU_AUTH_RANKCARD_DBIO_H +#define AU_AUTH_RANKCARD_DBIO_H + +typedef struct { long rk; long auth_id; long amount; } au_rankcard_rec_t; + +long audb_auth_amount_at_rank(const char *bizdate, long target_rank); + +#endif /* AU_AUTH_RANKCARD_DBIO_H */ diff --git a/app/src/au/dbio/au_auth_rankcard_dbio.pgc b/app/src/au/dbio/au_auth_rankcard_dbio.pgc new file mode 100644 index 0000000..2db6139 --- /dev/null +++ b/app/src/au/dbio/au_auth_rankcard_dbio.pgc @@ -0,0 +1,31 @@ +/* + * au_auth_rankcard_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_auth_rankcard_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: rank() OVER (ORDER BY amount DESC) 커서에서 목표 순위의 금액 반환. + */ +#include +#include +#include "au_auth_rankcard_dbio.h" + +/* 금액 순위표에서 target_rank 위에 해당하는 승인 금액을 반환. 없으면 0. */ +long audb_auth_amount_at_rank(const char *bizdate, long target_rank) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_rk, h_aid, h_amt, tr = target_rank, hit = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE arc CURSOR FOR + SELECT rank() OVER (ORDER BY amount DESC) AS rk, auth_id, amount + FROM au_authorization WHERE biz_date = :h_bizdate AND status = 'A'; + EXEC SQL OPEN arc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH arc INTO :h_rk, :h_aid, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE arc; return -1; } + if (h_rk == tr) { hit = h_amt; break; } + } + EXEC SQL CLOSE arc; + return hit; +} diff --git a/app/src/au/dbio/au_auth_topcard_dbio.h b/app/src/au/dbio/au_auth_topcard_dbio.h new file mode 100644 index 0000000..fee38b3 --- /dev/null +++ b/app/src/au/dbio/au_auth_topcard_dbio.h @@ -0,0 +1,14 @@ +/* + * au_auth_topcard_dbio.h - au 모듈 DB 접근 계층 copybook (au_auth_topcard_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: window row_number() PARTITION BY card_no (카드별 최고 승인). + */ +#ifndef AU_AUTH_TOPCARD_DBIO_H +#define AU_AUTH_TOPCARD_DBIO_H + +typedef struct { long rn; char card_no[24]; long amount; } au_topcard_rec_t; + +long audb_topcard_distinct(const char *bizdate, long *peak_out); + +#endif /* AU_AUTH_TOPCARD_DBIO_H */ diff --git a/app/src/au/dbio/au_auth_topcard_dbio.pgc b/app/src/au/dbio/au_auth_topcard_dbio.pgc new file mode 100644 index 0000000..0d26bf2 --- /dev/null +++ b/app/src/au/dbio/au_auth_topcard_dbio.pgc @@ -0,0 +1,35 @@ +/* + * au_auth_topcard_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_auth_topcard_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: row_number() OVER (PARTITION BY card_no ORDER BY amount DESC) 커서 스캔. + */ +#include +#include +#include "au_auth_topcard_dbio.h" + +/* 카드별 최고 승인행(rn=1)만 세어 서로 다른 카드 수를 반환하고 최고 금액은 peak_out. */ +long audb_topcard_distinct(const char *bizdate, long *peak_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_card[24]; + long h_rn, h_amt, cards = 0, peak = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE tcc CURSOR FOR + SELECT row_number() OVER (PARTITION BY card_no ORDER BY amount DESC) AS rn, card_no, amount + FROM au_authorization WHERE biz_date = :h_bizdate; + EXEC SQL OPEN tcc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH tcc INTO :h_rn, :h_card, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE tcc; return -1; } + if (h_rn == 1) { + cards++; + if (h_amt > peak) peak = h_amt; + } + } + EXEC SQL CLOSE tcc; + *peak_out = peak; + return cards; +} diff --git a/app/src/au/dbio/au_cancel_corr_dbio.h b/app/src/au/dbio/au_cancel_corr_dbio.h new file mode 100644 index 0000000..4ff510f --- /dev/null +++ b/app/src/au/dbio/au_cancel_corr_dbio.h @@ -0,0 +1,14 @@ +/* + * au_cancel_corr_dbio.h - au 모듈 DB 접근 계층 copybook (au_cancel_corr_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: 상관 서브쿼리 - 동일 승인의 최대 취소액 초과 부분취소. + */ +#ifndef AU_CANCEL_CORR_DBIO_H +#define AU_CANCEL_CORR_DBIO_H + +typedef struct { long cancel_id; long amount; } au_cancel_corr_rec_t; + +long audb_cancel_over_peer_max(const char *bizdate); + +#endif /* AU_CANCEL_CORR_DBIO_H */ diff --git a/app/src/au/dbio/au_cancel_corr_dbio.pgc b/app/src/au/dbio/au_cancel_corr_dbio.pgc new file mode 100644 index 0000000..122a669 --- /dev/null +++ b/app/src/au/dbio/au_cancel_corr_dbio.pgc @@ -0,0 +1,23 @@ +/* + * au_cancel_corr_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_cancel_corr_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: 상관(correlated) 서브쿼리 + 부가조건 - 동일 승인 부분취소 평균 초과 건수. + */ +#include +#include +#include "au_cancel_corr_dbio.h" + +/* 부분취소(PARTIAL) 중, 같은 auth_id 의 평균 취소액을 초과하는 건수. */ +long audb_cancel_over_peer_max(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM au_cancel c + WHERE c.biz_date = :h_bizdate AND c.cancel_type = 'PARTIAL' + AND c.amount > (SELECT avg(d.amount) FROM au_cancel d WHERE d.auth_id = c.auth_id); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/au/dbio/au_capture_join_dbio.h b/app/src/au/dbio/au_capture_join_dbio.h new file mode 100644 index 0000000..f961e05 --- /dev/null +++ b/app/src/au/dbio/au_capture_join_dbio.h @@ -0,0 +1,14 @@ +/* + * au_capture_join_dbio.h - au 모듈 DB 접근 계층 copybook (au_capture_join_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: au_authorization JOIN au_cancel 집계. + */ +#ifndef AU_CAPTURE_JOIN_DBIO_H +#define AU_CAPTURE_JOIN_DBIO_H + +typedef struct { long auth_id; long cancel_amount; } au_capture_join_rec_t; + +long audb_cancelled_capture_amount(const char *bizdate); + +#endif /* AU_CAPTURE_JOIN_DBIO_H */ diff --git a/app/src/au/dbio/au_capture_join_dbio.pgc b/app/src/au/dbio/au_capture_join_dbio.pgc new file mode 100644 index 0000000..d36aa3d --- /dev/null +++ b/app/src/au/dbio/au_capture_join_dbio.pgc @@ -0,0 +1,23 @@ +/* + * au_capture_join_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_capture_join_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: au_authorization a JOIN au_cancel c ON a.auth_id=c.auth_id 집계 (매입확정 취소액). + */ +#include +#include +#include "au_capture_join_dbio.h" + +/* 매입확정(M)/정산반영(S) 승인에 대한 취소 금액 합계 (조인 기준 취소측 amount). */ +long audb_cancelled_capture_amount(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(c.amount),0) INTO :h_v + FROM au_authorization a JOIN au_cancel c ON a.auth_id = c.auth_id + WHERE c.biz_date = :h_bizdate AND a.status IN ('M', 'S'); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/au/dbio/au_fraud_casescore_dbio.h b/app/src/au/dbio/au_fraud_casescore_dbio.h new file mode 100644 index 0000000..456e04e --- /dev/null +++ b/app/src/au/dbio/au_fraud_casescore_dbio.h @@ -0,0 +1,14 @@ +/* + * au_fraud_casescore_dbio.h - au 모듈 DB 접근 계층 copybook (au_fraud_casescore_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: CASE WHEN score 구간 sum (고위험 건수). + */ +#ifndef AU_FRAUD_CASESCORE_DBIO_H +#define AU_FRAUD_CASESCORE_DBIO_H + +typedef struct { long weighted; long high; } au_casescore_rec_t; + +long audb_fraud_high_weight(const char *bizdate); + +#endif /* AU_FRAUD_CASESCORE_DBIO_H */ diff --git a/app/src/au/dbio/au_fraud_casescore_dbio.pgc b/app/src/au/dbio/au_fraud_casescore_dbio.pgc new file mode 100644 index 0000000..b0a67b2 --- /dev/null +++ b/app/src/au/dbio/au_fraud_casescore_dbio.pgc @@ -0,0 +1,25 @@ +/* + * au_fraud_casescore_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_fraud_casescore_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: CASE WHEN 으로 점수 구간별 가중치를 합산하여 위험 점수 총합 반환. + */ +#include +#include +#include "au_fraud_casescore_dbio.h" + +/* score 구간에 3/2/1 가중치를 부여한 CASE 합계(위험 가중 점수)를 반환. */ +long audb_fraud_high_weight(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum( + CASE WHEN score >= 80 THEN 3 + WHEN score >= 50 THEN 2 + ELSE 1 END), 0) + INTO :h_v FROM au_fraud_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/au/dbio/au_fraud_intolog_dbio.h b/app/src/au/dbio/au_fraud_intolog_dbio.h new file mode 100644 index 0000000..b1d2165 --- /dev/null +++ b/app/src/au/dbio/au_fraud_intolog_dbio.h @@ -0,0 +1,14 @@ +/* + * au_fraud_intolog_dbio.h - au 모듈 DB 접근 계층 copybook (au_fraud_intolog_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: INSERT INTO au_fraud_log SELECT ... FROM au_authorization. + */ +#ifndef AU_FRAUD_INTOLOG_DBIO_H +#define AU_FRAUD_INTOLOG_DBIO_H + +typedef struct { long flagged; long threshold; } au_autoflag_rec_t; + +long audb_autoflag_high_amount(const char *bizdate, long threshold); + +#endif /* AU_FRAUD_INTOLOG_DBIO_H */ diff --git a/app/src/au/dbio/au_fraud_intolog_dbio.pgc b/app/src/au/dbio/au_fraud_intolog_dbio.pgc new file mode 100644 index 0000000..bc74b5a --- /dev/null +++ b/app/src/au/dbio/au_fraud_intolog_dbio.pgc @@ -0,0 +1,28 @@ +/* + * au_fraud_intolog_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_fraud_intolog_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: INSERT INTO au_fraud_log SELECT (고액 승인 자동 플래그), 삽입 건수 반환. + */ +#include +#include +#include "au_fraud_intolog_dbio.h" + +/* threshold 초과 고액 승인을 부정거래 로그에 자동 등록(set-based)하고 건수 반환. */ +long audb_autoflag_high_amount(const char *bizdate, long threshold) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_thr = threshold, flagged = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL INSERT INTO au_fraud_log (fraud_id, auth_id, card_no, score, reason, biz_date) + SELECT nextval('au_fraud_seq'), a.auth_id, a.card_no, 90, 'AUTOFLAG_HIGH', a.biz_date + FROM au_authorization a + WHERE a.biz_date = :h_bizdate AND a.amount > :h_thr; + if (sqlca.sqlcode < 0) { + userlog("audb_autoflag_high_amount FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); + return -1; + } + flagged = sqlca.sqlerrd[2]; + return flagged; +} diff --git a/app/src/au/dbio/au_fraud_purge_dbio.h b/app/src/au/dbio/au_fraud_purge_dbio.h new file mode 100644 index 0000000..ea3ff66 --- /dev/null +++ b/app/src/au/dbio/au_fraud_purge_dbio.h @@ -0,0 +1,14 @@ +/* + * au_fraud_purge_dbio.h - au 모듈 DB 접근 계층 copybook (au_fraud_purge_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: DELETE ... WHERE biz_date < 기준 AND score < 임계 (구간 삭제). + */ +#ifndef AU_FRAUD_PURGE_DBIO_H +#define AU_FRAUD_PURGE_DBIO_H + +typedef struct { char cutoff[16]; long min_score; long deleted; } au_fraud_purge_rec_t; + +long audb_fraud_purge_lowscore(const char *cutoff, long min_score); + +#endif /* AU_FRAUD_PURGE_DBIO_H */ diff --git a/app/src/au/dbio/au_fraud_purge_dbio.pgc b/app/src/au/dbio/au_fraud_purge_dbio.pgc new file mode 100644 index 0000000..42e4ec8 --- /dev/null +++ b/app/src/au/dbio/au_fraud_purge_dbio.pgc @@ -0,0 +1,25 @@ +/* + * au_fraud_purge_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_fraud_purge_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: DELETE 복합조건 구간 삭제 - 기준일 이전 저점수 부정로그 정리. + */ +#include +#include +#include "au_fraud_purge_dbio.h" + +/* cutoff 이전이면서 점수가 min_score 미만인 부정로그를 삭제하고 삭제 건수를 반환. */ +long audb_fraud_purge_lowscore(const char *cutoff, long min_score) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_cut[16]; + long h_min = min_score, deleted = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_cut, cutoff, sizeof(h_cut)-1); h_cut[sizeof(h_cut)-1] = 0; + EXEC SQL DELETE FROM au_fraud_log WHERE biz_date < :h_cut AND score < :h_min; + if (sqlca.sqlcode < 0) { + userlog("audb_fraud_purge_lowscore FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); + return -1; + } + deleted = sqlca.sqlerrd[2]; + return deleted; +} diff --git a/app/src/au/dbio/au_fraudpurge_batch.h b/app/src/au/dbio/au_fraudpurge_batch.h new file mode 100644 index 0000000..028ab6e --- /dev/null +++ b/app/src/au/dbio/au_fraudpurge_batch.h @@ -0,0 +1,16 @@ +/* + * au_fraudpurge_batch.h - copybook for au_fraudpurge_batch (XA client). + * 저점수 부정로그 정리 구조체 + 헬퍼. Kept in dbio/ for -I resolution. + */ +#ifndef AU_FRAUDPURGE_BATCH_H +#define AU_FRAUDPURGE_BATCH_H + +typedef struct { + char cutoff[16]; + long min_score; + long deleted; +} au_fpurge_ctx_t; + +long aub_fpurge_floor(long score); + +#endif /* AU_FRAUDPURGE_BATCH_H */ diff --git a/app/src/au/dbio/au_limit_spread_dbio.h b/app/src/au/dbio/au_limit_spread_dbio.h new file mode 100644 index 0000000..43c0ce2 --- /dev/null +++ b/app/src/au/dbio/au_limit_spread_dbio.h @@ -0,0 +1,14 @@ +/* + * au_limit_spread_dbio.h - au 모듈 DB 접근 계층 copybook (au_limit_spread_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: max/min/avg 3중 집계로 한도 스프레드. + */ +#ifndef AU_LIMIT_SPREAD_DBIO_H +#define AU_LIMIT_SPREAD_DBIO_H + +typedef struct { long lim_max; long lim_min; long used_avg; } au_limit_spread_rec_t; + +long audb_limit_spread(long *used_avg_out); + +#endif /* AU_LIMIT_SPREAD_DBIO_H */ diff --git a/app/src/au/dbio/au_limit_spread_dbio.pgc b/app/src/au/dbio/au_limit_spread_dbio.pgc new file mode 100644 index 0000000..d3ed030 --- /dev/null +++ b/app/src/au/dbio/au_limit_spread_dbio.pgc @@ -0,0 +1,22 @@ +/* + * au_limit_spread_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_limit_spread_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: 한 번의 SELECT 로 max/min/avg 3중 집계 - 한도 스프레드 + 평균 사용액. + */ +#include +#include +#include "au_limit_spread_dbio.h" + +/* 활성 카드 한도의 max-min 스프레드를 반환하고 평균 사용액을 used_avg_out 에 채운다. */ +long audb_limit_spread(long *used_avg_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_max = 0, h_min = 0, h_avg = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT coalesce(max(daily_limit),0), coalesce(min(daily_limit),0), + coalesce(avg(used_amount),0)::bigint + INTO :h_max, :h_min, :h_avg FROM card_limit WHERE status = 'ACTIVE'; + if (sqlca.sqlcode < 0) return -1; + *used_avg_out = h_avg; + return h_max - h_min; +} diff --git a/app/src/au/dbio/au_merchant_active_dbio.h b/app/src/au/dbio/au_merchant_active_dbio.h new file mode 100644 index 0000000..3902b57 --- /dev/null +++ b/app/src/au/dbio/au_merchant_active_dbio.h @@ -0,0 +1,14 @@ +/* + * au_merchant_active_dbio.h - au 모듈 DB 접근 계층 copybook (au_merchant_active_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: GROUP BY merchant_id HAVING count(*) >= 최소건수. + */ +#ifndef AU_MERCHANT_ACTIVE_DBIO_H +#define AU_MERCHANT_ACTIVE_DBIO_H + +typedef struct { char merchant_id[64]; long auths; } au_active_rec_t; + +long audb_active_merchant_count(const char *bizdate, long min_auths); + +#endif /* AU_MERCHANT_ACTIVE_DBIO_H */ diff --git a/app/src/au/dbio/au_merchant_active_dbio.pgc b/app/src/au/dbio/au_merchant_active_dbio.pgc new file mode 100644 index 0000000..15a6c41 --- /dev/null +++ b/app/src/au/dbio/au_merchant_active_dbio.pgc @@ -0,0 +1,32 @@ +/* + * au_merchant_active_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_merchant_active_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: GROUP BY merchant_id HAVING count(*) >= 최소건수, 커서로 활성 가맹점 계수. + */ +#include +#include +#include "au_merchant_active_dbio.h" + +/* 당일 승인 건수가 min_auths 이상인 가맹점 수. */ +long audb_active_merchant_count(const char *bizdate, long min_auths) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_cnt, h_min = min_auths, active = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE amc CURSOR FOR + SELECT merchant_id, count(*) FROM au_authorization + WHERE biz_date = :h_bizdate + GROUP BY merchant_id HAVING count(*) >= :h_min; + EXEC SQL OPEN amc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH amc INTO :h_merch, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE amc; return -1; } + active++; + } + EXEC SQL CLOSE amc; + return active; +} diff --git a/app/src/au/dbio/au_preauth_notexists_dbio.h b/app/src/au/dbio/au_preauth_notexists_dbio.h new file mode 100644 index 0000000..6015999 --- /dev/null +++ b/app/src/au/dbio/au_preauth_notexists_dbio.h @@ -0,0 +1,14 @@ +/* + * au_preauth_notexists_dbio.h - au 모듈 DB 접근 계층 copybook (au_preauth_notexists_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: NOT EXISTS 확정 승인 없는 예비승인. + */ +#ifndef AU_PREAUTH_NOTEXISTS_DBIO_H +#define AU_PREAUTH_NOTEXISTS_DBIO_H + +typedef struct { long preauth_id; long amount; } au_preauth_gap_rec_t; + +long audb_preauth_unconfirmed(const char *bizdate); + +#endif /* AU_PREAUTH_NOTEXISTS_DBIO_H */ diff --git a/app/src/au/dbio/au_preauth_notexists_dbio.pgc b/app/src/au/dbio/au_preauth_notexists_dbio.pgc new file mode 100644 index 0000000..223c47a --- /dev/null +++ b/app/src/au/dbio/au_preauth_notexists_dbio.pgc @@ -0,0 +1,24 @@ +/* + * au_preauth_notexists_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_preauth_notexists_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: NOT EXISTS 상관 서브쿼리(복합조건) - 대응 승인이 없는 미확정 예비승인. + */ +#include +#include +#include "au_preauth_notexists_dbio.h" + +/* 상태 P(예비) 이면서 같은 카드/금액의 승인행이 없는 예비승인 건수. */ +long audb_preauth_unconfirmed(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM au_preauth p + WHERE p.biz_date = :h_bizdate AND p.status = 'P' + AND NOT EXISTS (SELECT 1 FROM au_authorization a + WHERE a.card_no = p.card_no AND a.amount = p.amount); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/au/dbio/au_scorebucket_batch.h b/app/src/au/dbio/au_scorebucket_batch.h new file mode 100644 index 0000000..0278890 --- /dev/null +++ b/app/src/au/dbio/au_scorebucket_batch.h @@ -0,0 +1,17 @@ +/* + * au_scorebucket_batch.h - copybook for au_scorebucket_batch (XA client). + * CASE 점수대 분포 구조체 + 분류 헬퍼. Kept in dbio/ for -I resolution. + */ +#ifndef AU_SCOREBUCKET_BATCH_H +#define AU_SCOREBUCKET_BATCH_H + +typedef struct { + char biz_date[16]; + long low; /* < 50 */ + long mid; /* 50~79 */ + long high; /* >= 80 */ +} au_score_ctx_t; + +const char *aub_score_label(long score); + +#endif /* AU_SCOREBUCKET_BATCH_H */ diff --git a/app/src/au/dbio/au_summary_rebuild_dbio.h b/app/src/au/dbio/au_summary_rebuild_dbio.h new file mode 100644 index 0000000..e6db4e6 --- /dev/null +++ b/app/src/au/dbio/au_summary_rebuild_dbio.h @@ -0,0 +1,14 @@ +/* + * au_summary_rebuild_dbio.h - au 모듈 DB 접근 계층 copybook (au_summary_rebuild_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: INSERT INTO au_summary SELECT ... FILTER ... GROUP BY ... ON CONFLICT DO UPDATE. + */ +#ifndef AU_SUMMARY_REBUILD_DBIO_H +#define AU_SUMMARY_REBUILD_DBIO_H + +typedef struct { char biz_date[16]; long merchants; } au_summary_rebuild_rec_t; + +long audb_summary_rebuild_from_auth(const char *bizdate); + +#endif /* AU_SUMMARY_REBUILD_DBIO_H */ diff --git a/app/src/au/dbio/au_summary_rebuild_dbio.pgc b/app/src/au/dbio/au_summary_rebuild_dbio.pgc new file mode 100644 index 0000000..8ddc3d1 --- /dev/null +++ b/app/src/au/dbio/au_summary_rebuild_dbio.pgc @@ -0,0 +1,38 @@ +/* + * au_summary_rebuild_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_summary_rebuild_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: INSERT INTO au_summary SELECT count(*) FILTER (...) GROUP BY ... ON CONFLICT DO UPDATE. + */ +#include +#include +#include "au_summary_rebuild_dbio.h" + +/* 당일 au_authorization 을 가맹점별 승인/거절/금액으로 재집계하여 au_summary 에 upsert. */ +long audb_summary_rebuild_from_auth(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long affected = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL 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 = 'A'), + count(*) FILTER (WHERE status = 'D'), + coalesce(sum(amount),0) + FROM au_authorization WHERE biz_date = :h_bizdate + GROUP BY merchant_id, biz_date + ON CONFLICT (merchant_id, biz_date) DO UPDATE + SET auth_count = EXCLUDED.auth_count, + approve_count = EXCLUDED.approve_count, + decline_count = EXCLUDED.decline_count, + auth_amount = EXCLUDED.auth_amount, + updated_at = now(); + if (sqlca.sqlcode < 0) { + userlog("audb_summary_rebuild_from_auth FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); + return -1; + } + affected = sqlca.sqlerrd[2]; + return affected; +} diff --git a/app/src/au/dbio/au_summary_riskmerch_dbio.h b/app/src/au/dbio/au_summary_riskmerch_dbio.h new file mode 100644 index 0000000..7bea62a --- /dev/null +++ b/app/src/au/dbio/au_summary_riskmerch_dbio.h @@ -0,0 +1,14 @@ +/* + * au_summary_riskmerch_dbio.h - au 모듈 DB 접근 계층 copybook (au_summary_riskmerch_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: GROUP BY merchant_id HAVING sum(decline) > sum(approve). + */ +#ifndef AU_SUMMARY_RISKMERCH_DBIO_H +#define AU_SUMMARY_RISKMERCH_DBIO_H + +typedef struct { char merchant_id[64]; long declines; long approves; } au_riskmerch_rec_t; + +long audb_decline_heavy_merchants(const char *bizdate); + +#endif /* AU_SUMMARY_RISKMERCH_DBIO_H */ diff --git a/app/src/au/dbio/au_summary_riskmerch_dbio.pgc b/app/src/au/dbio/au_summary_riskmerch_dbio.pgc new file mode 100644 index 0000000..43b4c91 --- /dev/null +++ b/app/src/au/dbio/au_summary_riskmerch_dbio.pgc @@ -0,0 +1,32 @@ +/* + * au_summary_riskmerch_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_summary_riskmerch_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: GROUP BY merchant_id HAVING sum(decline_count) > sum(approve_count) 커서 계수. + */ +#include +#include +#include "au_summary_riskmerch_dbio.h" + +/* 거절 건수가 승인 건수보다 많은(위험) 가맹점 수를 GROUP BY/HAVING 커서로 계수. */ +long audb_decline_heavy_merchants(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_dec, h_app, risky = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE rmc CURSOR FOR + SELECT merchant_id, sum(decline_count), sum(approve_count) FROM au_summary + WHERE biz_date = :h_bizdate + GROUP BY merchant_id HAVING sum(decline_count) > sum(approve_count); + EXEC SQL OPEN rmc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH rmc INTO :h_merch, :h_dec, :h_app; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE rmc; return -1; } + risky++; + } + EXEC SQL CLOSE rmc; + return risky; +} diff --git a/app/src/au/dbio/au_summary_weightavg_dbio.h b/app/src/au/dbio/au_summary_weightavg_dbio.h new file mode 100644 index 0000000..f1e2602 --- /dev/null +++ b/app/src/au/dbio/au_summary_weightavg_dbio.h @@ -0,0 +1,14 @@ +/* + * au_summary_weightavg_dbio.h - au 모듈 DB 접근 계층 copybook (au_summary_weightavg_dbio, part of libaudbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + * Shape: 커서 누산 기반 건수 가중 평균 티켓. + */ +#ifndef AU_SUMMARY_WEIGHTAVG_DBIO_H +#define AU_SUMMARY_WEIGHTAVG_DBIO_H + +typedef struct { char merchant_id[64]; long auth_count; long auth_amount; } au_weight_rec_t; + +long audb_summary_weighted_ticket(const char *bizdate); + +#endif /* AU_SUMMARY_WEIGHTAVG_DBIO_H */ diff --git a/app/src/au/dbio/au_summary_weightavg_dbio.pgc b/app/src/au/dbio/au_summary_weightavg_dbio.pgc new file mode 100644 index 0000000..82cffa7 --- /dev/null +++ b/app/src/au/dbio/au_summary_weightavg_dbio.pgc @@ -0,0 +1,33 @@ +/* + * au_summary_weightavg_dbio.pgc - au 모듈 DB 접근 함수 세트 (au_summary_weightavg_dbio), archived into libaudbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * Shape: 커서로 가맹점별 행을 순회하며 건수 가중 평균 티켓을 C 측에서 누산/산출. + */ +#include +#include +#include "au_summary_weightavg_dbio.h" + +/* 건수(auth_count)를 가중치로 하는 평균 승인 티켓을 커서 누산으로 계산. */ +long audb_summary_weighted_ticket(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_merch[64]; + long h_cnt, h_amt; + EXEC SQL END DECLARE SECTION; + long num = 0, den = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE swc CURSOR FOR + SELECT merchant_id, auth_count, auth_amount FROM au_summary WHERE biz_date = :h_bizdate; + EXEC SQL OPEN swc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH swc INTO :h_merch, :h_cnt, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE swc; return -1; } + num += h_amt; + den += h_cnt; + } + EXEC SQL CLOSE swc; + if (den <= 0) return 0; + return num / den; +} diff --git a/app/src/au/dbio/au_summaryrebuild_batch.h b/app/src/au/dbio/au_summaryrebuild_batch.h new file mode 100644 index 0000000..746b37e --- /dev/null +++ b/app/src/au/dbio/au_summaryrebuild_batch.h @@ -0,0 +1,16 @@ +/* + * au_summaryrebuild_batch.h - copybook for au_summaryrebuild_batch (XA client). + * au_summary 재집계 구조체 + 헬퍼. Kept in dbio/ for -I resolution. + */ +#ifndef AU_SUMMARYREBUILD_BATCH_H +#define AU_SUMMARYREBUILD_BATCH_H + +typedef struct { + char biz_date[16]; + long rows_affected; + long total_approved; +} au_rebuild_ctx_t; + +long aub_rebuild_clip(long affected); + +#endif /* AU_SUMMARYREBUILD_BATCH_H */ diff --git a/app/src/au/dbio/au_topcard_batch.h b/app/src/au/dbio/au_topcard_batch.h new file mode 100644 index 0000000..5e851fc --- /dev/null +++ b/app/src/au/dbio/au_topcard_batch.h @@ -0,0 +1,16 @@ +/* + * au_topcard_batch.h - copybook for au_topcard_batch (XA client). + * 최고 지출 카드 구조체 + 헬퍼. Kept in dbio/ for -I resolution. + */ +#ifndef AU_TOPCARD_BATCH_H +#define AU_TOPCARD_BATCH_H + +typedef struct { + char biz_date[16]; + char top_card[24]; + long top_spend; +} au_topcard_ctx_t; + +int aub_topcard_valid(long spend); + +#endif /* AU_TOPCARD_BATCH_H */ diff --git a/app/src/cl/batch/cl_gap_batch.pgc b/app/src/cl/batch/cl_gap_batch.pgc new file mode 100644 index 0000000..dfb0c0f --- /dev/null +++ b/app/src/cl/batch/cl_gap_batch.pgc @@ -0,0 +1,50 @@ +/* + * cl_gap_batch.pgc - cl 일마감 저장건수 vs 실매입 대사 배치 (커서 + XA). + * + * period_key 프리픽스의 DAILY 마감을 커서로 훑어, 각 마감의 저장 txn_count 와 + * 실제 purchase 건수를 비교하여 불일치 마감 수를 집계한다. 전 구간 ONE global XA. + * host 변수는 전용 카피북 cl_gap_cpy.h 를 EXEC SQL INCLUDE 로 가져온다. + * Usage: cl_gap_batch [YYYY-MM] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *prefix = (argc > 1) ? argv[1] : "2026-07"; + long checked = 0, mismatch = 0; + + EXEC SQL INCLUDE cl_gap_cpy; + + strncpy(g_prefix, prefix, sizeof(g_prefix)-1); g_prefix[sizeof(g_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 cgap CURSOR FOR + SELECT close_id, to_char(biz_date,'YYYY-MM-DD'), txn_count FROM cl_close_log + WHERE close_type = 'DAILY' AND period_key LIKE :g_prefix || '%' AND status <> 'X' + ORDER BY biz_date; + EXEC SQL OPEN cgap; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH cgap INTO :g_cid, :g_bizdate, :g_stored; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE cgap; tpabort(0); return 1; } + EXEC SQL SELECT count(*) INTO :g_actual FROM purchase WHERE biz_date = :g_bizdate; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cgap; tpabort(0); return 1; } + checked++; + if (g_stored != g_actual) mismatch++; + } + EXEC SQL CLOSE cgap; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_gap_batch COMMIT: prefix=%s 검증=%ld 불일치=%ld\n", prefix, checked, mismatch); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_gap_cpy.h b/app/src/cl/batch/cl_gap_cpy.h new file mode 100644 index 0000000..2bf9198 --- /dev/null +++ b/app/src/cl/batch/cl_gap_cpy.h @@ -0,0 +1,8 @@ +/* cl_gap_cpy.h - cl_gap_batch 전용 카피북 (host 변수 선언, EXEC SQL INCLUDE 대상). */ +EXEC SQL BEGIN DECLARE SECTION; +char g_prefix[16]; +char g_bizdate[16]; +long g_cid; +long g_stored; +long g_actual; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/cl/batch/cl_purgeold_batch.pgc b/app/src/cl/batch/cl_purgeold_batch.pgc new file mode 100644 index 0000000..f70dbd2 --- /dev/null +++ b/app/src/cl/batch/cl_purgeold_batch.pgc @@ -0,0 +1,35 @@ +/* + * cl_purgeold_batch.pgc - cl 오래된 마감 스냅샷 정리 배치 (DELETE range + XA). + * + * cutoff 이전(biz_date < cutoff) 마감 스냅샷을 한 문장으로 삭제하고 삭제행수를 + * sqlca.sqlerrd[2] 로 확인한다. ONE global XA transaction 하에서 수행. + * host 변수는 전용 카피북 cl_purgeold_cpy.h 를 EXEC SQL INCLUDE. + * Usage: cl_purgeold_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *cutoff = (argc > 1) ? argv[1] : "2026-01-01"; + + EXEC SQL INCLUDE cl_purgeold_cpy; + + strncpy(p_cutoff, cutoff, sizeof(p_cutoff)-1); p_cutoff[sizeof(p_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; } + + EXEC SQL DELETE FROM cl_snapshot WHERE biz_date < :p_cutoff; + if (sqlca.sqlcode < 0) { fprintf(stderr, "DELETE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + p_deleted = (long) sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_purgeold_batch COMMIT: cutoff=%s 삭제=%ld건\n", cutoff, p_deleted); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_purgeold_cpy.h b/app/src/cl/batch/cl_purgeold_cpy.h new file mode 100644 index 0000000..0dd4933 --- /dev/null +++ b/app/src/cl/batch/cl_purgeold_cpy.h @@ -0,0 +1,5 @@ +/* cl_purgeold_cpy.h - cl_purgeold_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char p_cutoff[16]; +long p_deleted; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/cl/batch/cl_relock_batch.pgc b/app/src/cl/batch/cl_relock_batch.pgc new file mode 100644 index 0000000..880b495 --- /dev/null +++ b/app/src/cl/batch/cl_relock_batch.pgc @@ -0,0 +1,47 @@ +/* + * cl_relock_batch.pgc - cl 완료 기간 일괄 잠금 배치 (커서 + upsert + XA). + * + * status='C' 로 마감된 고유 period_key 를 커서로 훑어 cl_period_lock 에 + * upsert(ON CONFLICT) 하여 기간을 잠근다. 전 구간 ONE global XA transaction. + * host 변수는 전용 카피북 cl_relock_cpy.h 를 EXEC SQL INCLUDE. + * Usage: cl_relock_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long locked = 0; + (void) argc; (void) argv; + + EXEC SQL INCLUDE cl_relock_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 crel CURSOR FOR + SELECT DISTINCT period_key FROM cl_close_log WHERE status = 'C' + ORDER BY period_key; + EXEC SQL OPEN crel; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH crel INTO :r_period; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE crel; tpabort(0); return 1; } + EXEC SQL INSERT INTO cl_period_lock (period_key, locked) VALUES (:r_period, true) + ON CONFLICT (period_key) DO UPDATE SET locked = true, locked_at = now(); + if (sqlca.sqlcode < 0) { fprintf(stderr, "UPSERT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE crel; tpabort(0); return 1; } + locked++; + } + EXEC SQL CLOSE crel; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_relock_batch COMMIT: 잠금기간=%ld\n", locked); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_relock_cpy.h b/app/src/cl/batch/cl_relock_cpy.h new file mode 100644 index 0000000..95e3126 --- /dev/null +++ b/app/src/cl/batch/cl_relock_cpy.h @@ -0,0 +1,4 @@ +/* cl_relock_cpy.h - cl_relock_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char r_period[16]; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/cl/batch/cl_reweigh_batch.pgc b/app/src/cl/batch/cl_reweigh_batch.pgc new file mode 100644 index 0000000..113f35d --- /dev/null +++ b/app/src/cl/batch/cl_reweigh_batch.pgc @@ -0,0 +1,51 @@ +/* + * cl_reweigh_batch.pgc - cl 마감 총액 스냅샷 재계산 배치 (커서 + UPDATE + XA). + * + * period_key 프리픽스 마감을 커서로 훑어, 각 마감의 스냅샷 net 합을 다시 계산하여 + * cl_close_log.total_amount 를 갱신한다. 전 구간 ONE global XA transaction. + * host 변수는 전용 카피북 cl_reweigh_cpy.h 를 EXEC SQL INCLUDE. + * Usage: cl_reweigh_batch [YYYY-MM] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *prefix = (argc > 1) ? argv[1] : "2026-07"; + long updated = 0; + + EXEC SQL INCLUDE cl_reweigh_cpy; + + strncpy(w_prefix, prefix, sizeof(w_prefix)-1); w_prefix[sizeof(w_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 crw CURSOR FOR + SELECT close_id FROM cl_close_log + WHERE period_key LIKE :w_prefix || '%' AND status <> 'X' + ORDER BY close_id; + EXEC SQL OPEN crw; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH crw INTO :w_cid; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE crw; tpabort(0); return 1; } + EXEC SQL SELECT coalesce(sum(net_amount),0) INTO :w_sum FROM cl_snapshot WHERE close_id = :w_cid; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE crw; tpabort(0); return 1; } + EXEC SQL UPDATE cl_close_log SET total_amount = :w_sum, updated_at = now() WHERE close_id = :w_cid; + if (sqlca.sqlcode < 0) { fprintf(stderr, "UPDATE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE crw; tpabort(0); return 1; } + updated++; + } + EXEC SQL CLOSE crw; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cl_reweigh_batch COMMIT: prefix=%s 재계산=%ld건\n", prefix, updated); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_reweigh_cpy.h b/app/src/cl/batch/cl_reweigh_cpy.h new file mode 100644 index 0000000..915cb06 --- /dev/null +++ b/app/src/cl/batch/cl_reweigh_cpy.h @@ -0,0 +1,6 @@ +/* cl_reweigh_cpy.h - cl_reweigh_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char w_prefix[16]; +long w_cid; +long w_sum; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/cl/batch/cl_tierstat_batch.pgc b/app/src/cl/batch/cl_tierstat_batch.pgc new file mode 100644 index 0000000..ab697c8 --- /dev/null +++ b/app/src/cl/batch/cl_tierstat_batch.pgc @@ -0,0 +1,39 @@ +/* + * cl_tierstat_batch.pgc - cl 마감 총액 구간 분포 배치 (CASE 집계 + XA). + * + * period_key 프리픽스 마감을 총액 소/중/대 구간으로 CASE 분류하여 각 구간 건수를 + * 한 행으로 집계해 보고한다. ONE global XA transaction (read-committed). + * host 변수는 전용 카피북 cl_tierstat_cpy.h 를 EXEC SQL INCLUDE. + * Usage: cl_tierstat_batch [YYYY-MM] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *prefix = (argc > 1) ? argv[1] : "2026-07"; + + EXEC SQL INCLUDE cl_tierstat_cpy; + + strncpy(t_prefix, prefix, sizeof(t_prefix)-1); t_prefix[sizeof(t_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 SELECT + coalesce(sum(CASE WHEN total_amount < 1000000 THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN total_amount >= 1000000 AND total_amount < 100000000 THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN total_amount >= 100000000 THEN 1 ELSE 0 END),0) + INTO :t_lo, :t_mid, :t_hi FROM cl_close_log + WHERE period_key LIKE :t_prefix || '%' AND status <> 'X'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "AGG 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(">>> cl_tierstat_batch COMMIT: prefix=%s 소=%ld 중=%ld 대=%ld\n", prefix, t_lo, t_mid, t_hi); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cl/batch/cl_tierstat_cpy.h b/app/src/cl/batch/cl_tierstat_cpy.h new file mode 100644 index 0000000..f726a8c --- /dev/null +++ b/app/src/cl/batch/cl_tierstat_cpy.h @@ -0,0 +1,7 @@ +/* cl_tierstat_cpy.h - cl_tierstat_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char t_prefix[16]; +long t_lo; +long t_mid; +long t_hi; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/cl/dbio/cl_corr_sub_dbio.h b/app/src/cl/dbio/cl_corr_sub_dbio.h new file mode 100644 index 0000000..28bce3a --- /dev/null +++ b/app/src/cl/dbio/cl_corr_sub_dbio.h @@ -0,0 +1,9 @@ +/* + * cl_corr_sub_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_corr_sub_dbio, part of libcldbio.a). + * 마감별 스냅샷 수 상관 서브쿼리 순회 최대치 (correlated subquery + cursor). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_CORR_SUB_DBIO_H +#define CL_CORR_SUB_DBIO_H +long clqdb_corr_max_snap(const char *ctype); +#endif /* CL_CORR_SUB_DBIO_H */ diff --git a/app/src/cl/dbio/cl_corr_sub_dbio.pgc b/app/src/cl/dbio/cl_corr_sub_dbio.pgc new file mode 100644 index 0000000..fb85053 --- /dev/null +++ b/app/src/cl/dbio/cl_corr_sub_dbio.pgc @@ -0,0 +1,34 @@ +/* + * cl_corr_sub_dbio.pgc - cl 모듈 DB 접근 함수 (cl_corr_sub_dbio), archived into libcldbio.a. + * 마감별 스냅샷 수 상관 서브쿼리 순회 최대치 (correlated subquery + cursor). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_corr_sub_dbio.h" + +/* 각 마감의 스냅샷 수를 상관 서브쿼리로 뽑아 커서로 훑어 최대값 반환. */ +long clqdb_corr_max_snap(const char *ctype) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_type[16]; + long h_cid, h_snaps; + EXEC SQL END DECLARE SECTION; + long peak = 0; + strncpy(h_type, ctype, sizeof(h_type)-1); h_type[sizeof(h_type)-1] = 0; + EXEC SQL DECLARE ccs CURSOR FOR + SELECT c.close_id, + (SELECT count(*) FROM cl_snapshot s WHERE s.close_id = c.close_id) + FROM cl_close_log c WHERE c.close_type = :h_type AND c.status <> 'X' + ORDER BY c.close_id; + EXEC SQL OPEN ccs; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH ccs INTO :h_cid, :h_snaps; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE ccs; return -1; } + if (h_snaps > peak) peak = h_snaps; + } + EXEC SQL CLOSE ccs; + return peak; +} diff --git a/app/src/cl/dbio/cl_lock_upsert_dbio.h b/app/src/cl/dbio/cl_lock_upsert_dbio.h new file mode 100644 index 0000000..3dcd931 --- /dev/null +++ b/app/src/cl/dbio/cl_lock_upsert_dbio.h @@ -0,0 +1,9 @@ +/* + * cl_lock_upsert_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_lock_upsert_dbio, part of libcldbio.a). + * 기간잠금 upsert (ON CONFLICT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_LOCK_UPSERT_DBIO_H +#define CL_LOCK_UPSERT_DBIO_H +int cldb_lock_upsert(const char *period, int locked); +#endif /* CL_LOCK_UPSERT_DBIO_H */ diff --git a/app/src/cl/dbio/cl_lock_upsert_dbio.pgc b/app/src/cl/dbio/cl_lock_upsert_dbio.pgc new file mode 100644 index 0000000..b56fab6 --- /dev/null +++ b/app/src/cl/dbio/cl_lock_upsert_dbio.pgc @@ -0,0 +1,24 @@ +/* + * cl_lock_upsert_dbio.pgc - cl 모듈 DB 접근 함수 (cl_lock_upsert_dbio), archived into libcldbio.a. + * 기간잠금 upsert (ON CONFLICT). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_lock_upsert_dbio.h" + +/* cl_period_lock 에 잠금 여부를 upsert. */ +int cldb_lock_upsert(const char *period, int locked) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_period[16]; + int h_locked = locked; + EXEC SQL END DECLARE SECTION; + strncpy(h_period, period, sizeof(h_period)-1); h_period[sizeof(h_period)-1] = 0; + EXEC SQL INSERT INTO cl_period_lock (period_key, locked) + VALUES (:h_period, :h_locked) + ON CONFLICT (period_key) DO UPDATE + SET locked = EXCLUDED.locked, locked_at = now(); + if (sqlca.sqlcode < 0) { userlog("cldb_lock_upsert FAIL [%d]", sqlca.sqlcode); return -1; } + return 0; +} diff --git a/app/src/cl/dbio/cl_minmax_dbio.h b/app/src/cl/dbio/cl_minmax_dbio.h new file mode 100644 index 0000000..febc1d9 --- /dev/null +++ b/app/src/cl/dbio/cl_minmax_dbio.h @@ -0,0 +1,9 @@ +/* + * cl_minmax_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_minmax_dbio, part of libcldbio.a). + * 기간 프리픽스 마감 총액 최소/최대 (min/max). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_MINMAX_DBIO_H +#define CL_MINMAX_DBIO_H +int clqdb_minmax(const char *prefix, long *lo, long *hi); +#endif /* CL_MINMAX_DBIO_H */ diff --git a/app/src/cl/dbio/cl_minmax_dbio.pgc b/app/src/cl/dbio/cl_minmax_dbio.pgc new file mode 100644 index 0000000..f62b129 --- /dev/null +++ b/app/src/cl/dbio/cl_minmax_dbio.pgc @@ -0,0 +1,24 @@ +/* + * cl_minmax_dbio.pgc - cl 모듈 DB 접근 함수 (cl_minmax_dbio), archived into libcldbio.a. + * 기간 프리픽스 마감 총액 최소/최대 (min/max). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_minmax_dbio.h" + +/* period_key 프리픽스 마감의 total_amount 최소/최대를 반환. */ +int clqdb_minmax(const char *prefix, long *lo, long *hi) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_prefix[16]; + long h_lo = 0, h_hi = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_prefix, prefix, sizeof(h_prefix)-1); h_prefix[sizeof(h_prefix)-1] = 0; + EXEC SQL SELECT coalesce(min(total_amount),0), coalesce(max(total_amount),0) + INTO :h_lo, :h_hi FROM cl_close_log + WHERE period_key LIKE :h_prefix || '%' AND status <> 'X'; + if (sqlca.sqlcode < 0) return -1; + *lo = h_lo; *hi = h_hi; + return 0; +} diff --git a/app/src/cl/dbio/cl_snap_distinct_dbio.h b/app/src/cl/dbio/cl_snap_distinct_dbio.h new file mode 100644 index 0000000..9ef10bd --- /dev/null +++ b/app/src/cl/dbio/cl_snap_distinct_dbio.h @@ -0,0 +1,9 @@ +/* + * cl_snap_distinct_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_snap_distinct_dbio, part of libcldbio.a). + * 마감 스냅샷 고유 가맹점 수 (count DISTINCT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_SNAP_DISTINCT_DBIO_H +#define CL_SNAP_DISTINCT_DBIO_H +long clqdb_snap_distinct(long cid); +#endif /* CL_SNAP_DISTINCT_DBIO_H */ diff --git a/app/src/cl/dbio/cl_snap_distinct_dbio.pgc b/app/src/cl/dbio/cl_snap_distinct_dbio.pgc new file mode 100644 index 0000000..a3750fb --- /dev/null +++ b/app/src/cl/dbio/cl_snap_distinct_dbio.pgc @@ -0,0 +1,20 @@ +/* + * cl_snap_distinct_dbio.pgc - cl 모듈 DB 접근 함수 (cl_snap_distinct_dbio), archived into libcldbio.a. + * 마감 스냅샷 고유 가맹점 수 (count DISTINCT). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_snap_distinct_dbio.h" + +/* close_id 스냅샷의 고유 가맹점 수. */ +long clqdb_snap_distinct(long cid) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cid = cid, h_v = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(DISTINCT merchant_id) INTO :h_v FROM cl_snapshot + WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/cl/dbio/cl_snap_in_dbio.h b/app/src/cl/dbio/cl_snap_in_dbio.h new file mode 100644 index 0000000..9dc6413 --- /dev/null +++ b/app/src/cl/dbio/cl_snap_in_dbio.h @@ -0,0 +1,9 @@ +/* + * cl_snap_in_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_snap_in_dbio, part of libcldbio.a). + * 특정 마감유형에 속한 스냅샷 net 합 (IN 서브셀렉트). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_SNAP_IN_DBIO_H +#define CL_SNAP_IN_DBIO_H +long clqdb_snap_in_net(const char *ctype); +#endif /* CL_SNAP_IN_DBIO_H */ diff --git a/app/src/cl/dbio/cl_snap_in_dbio.pgc b/app/src/cl/dbio/cl_snap_in_dbio.pgc new file mode 100644 index 0000000..c20265f --- /dev/null +++ b/app/src/cl/dbio/cl_snap_in_dbio.pgc @@ -0,0 +1,23 @@ +/* + * cl_snap_in_dbio.pgc - cl 모듈 DB 접근 함수 (cl_snap_in_dbio), archived into libcldbio.a. + * 특정 마감유형에 속한 스냅샷 net 합 (IN 서브셀렉트). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_snap_in_dbio.h" + +/* close_type 에 해당하는 close_id 집합(IN)의 스냅샷 net 합계. */ +long clqdb_snap_in_net(const char *ctype) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_type[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_type, ctype, sizeof(h_type)-1); h_type[sizeof(h_type)-1] = 0; + EXEC SQL SELECT coalesce(sum(net_amount),0) INTO :h_v FROM cl_snapshot + WHERE close_id IN (SELECT close_id FROM cl_close_log + WHERE close_type = :h_type AND status <> 'X'); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/cl/dbio/cl_snap_insert_sel_dbio.h b/app/src/cl/dbio/cl_snap_insert_sel_dbio.h new file mode 100644 index 0000000..d560f82 --- /dev/null +++ b/app/src/cl/dbio/cl_snap_insert_sel_dbio.h @@ -0,0 +1,9 @@ +/* + * cl_snap_insert_sel_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_snap_insert_sel_dbio, part of libcldbio.a). + * merchant_summary 로부터 스냅샷 일괄 적재 (INSERT..SELECT). + * Return convention: >=0 ok(적재건수), -1 = SQL error. + */ +#ifndef CL_SNAP_INSERT_SEL_DBIO_H +#define CL_SNAP_INSERT_SEL_DBIO_H +long cldb_snap_insert_sel(long cid, long sid_base, const char *period, const char *bizdate); +#endif /* CL_SNAP_INSERT_SEL_DBIO_H */ diff --git a/app/src/cl/dbio/cl_snap_insert_sel_dbio.pgc b/app/src/cl/dbio/cl_snap_insert_sel_dbio.pgc new file mode 100644 index 0000000..70020f7 --- /dev/null +++ b/app/src/cl/dbio/cl_snap_insert_sel_dbio.pgc @@ -0,0 +1,28 @@ +/* + * cl_snap_insert_sel_dbio.pgc - cl 모듈 DB 접근 함수 (cl_snap_insert_sel_dbio), archived into libcldbio.a. + * merchant_summary 로부터 스냅샷 일괄 적재 (INSERT..SELECT). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_snap_insert_sel_dbio.h" + +/* 당일 merchant_summary 를 스냅샷으로 한 문장에 적재, 적재건수 반환. */ +long cldb_snap_insert_sel(long cid, long sid_base, const char *period, const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cid = cid, h_base = sid_base; + char h_period[16], h_bizdate[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_period, period, sizeof(h_period)-1); h_period[sizeof(h_period)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL INSERT INTO cl_snapshot + (snapshot_id, close_id, period_key, biz_date, merchant_id, + gross_amount, fee_amount, net_amount, txn_count) + SELECT :h_base + row_number() OVER (ORDER BY merchant_id), + :h_cid, :h_period, :h_bizdate, merchant_id, + gross_amount, fee_amount, net_amount, txn_count + FROM merchant_summary WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("cldb_snap_insert_sel FAIL [%d]", sqlca.sqlcode); return -1; } + return (long) sqlca.sqlerrd[2]; +} diff --git a/app/src/cl/dbio/cl_snap_join_dbio.h b/app/src/cl/dbio/cl_snap_join_dbio.h new file mode 100644 index 0000000..24c4658 --- /dev/null +++ b/app/src/cl/dbio/cl_snap_join_dbio.h @@ -0,0 +1,9 @@ +/* + * cl_snap_join_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_snap_join_dbio, part of libcldbio.a). + * 완료 마감(JOIN)에 속한 가맹점 gross 합계. + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_SNAP_JOIN_DBIO_H +#define CL_SNAP_JOIN_DBIO_H +long clqdb_snap_join_gross(const char *merch); +#endif /* CL_SNAP_JOIN_DBIO_H */ diff --git a/app/src/cl/dbio/cl_snap_join_dbio.pgc b/app/src/cl/dbio/cl_snap_join_dbio.pgc new file mode 100644 index 0000000..d019ec4 --- /dev/null +++ b/app/src/cl/dbio/cl_snap_join_dbio.pgc @@ -0,0 +1,23 @@ +/* + * cl_snap_join_dbio.pgc - cl 모듈 DB 접근 함수 (cl_snap_join_dbio), archived into libcldbio.a. + * 완료 마감(JOIN)에 속한 가맹점 gross 합계. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_snap_join_dbio.h" + +/* cl_snapshot 을 cl_close_log 와 조인해 status='C' 마감의 가맹점 gross 합. */ +long clqdb_snap_join_gross(const char *merch) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_merch, merch, sizeof(h_merch)-1); h_merch[sizeof(h_merch)-1] = 0; + EXEC SQL SELECT coalesce(sum(s.gross_amount),0) INTO :h_v + FROM cl_snapshot s JOIN cl_close_log c ON s.close_id = c.close_id + WHERE c.status = 'C' AND s.merchant_id = :h_merch; + if (sqlca.sqlcode < 0) { userlog("clqdb_snap_join_gross FAIL [%d]", sqlca.sqlcode); return -1; } + return h_v; +} diff --git a/app/src/cl/dbio/cl_snap_purge_dbio.h b/app/src/cl/dbio/cl_snap_purge_dbio.h new file mode 100644 index 0000000..1f3715f --- /dev/null +++ b/app/src/cl/dbio/cl_snap_purge_dbio.h @@ -0,0 +1,9 @@ +/* + * cl_snap_purge_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_snap_purge_dbio, part of libcldbio.a). + * 마감 스냅샷 범위 삭제 후 삭제건수 반환 (DELETE range). + * Return convention: >=0 ok(삭제건수), -1 = SQL error. + */ +#ifndef CL_SNAP_PURGE_DBIO_H +#define CL_SNAP_PURGE_DBIO_H +long cldb_snap_purge(const char *bizdate); +#endif /* CL_SNAP_PURGE_DBIO_H */ diff --git a/app/src/cl/dbio/cl_snap_purge_dbio.pgc b/app/src/cl/dbio/cl_snap_purge_dbio.pgc new file mode 100644 index 0000000..988e7e7 --- /dev/null +++ b/app/src/cl/dbio/cl_snap_purge_dbio.pgc @@ -0,0 +1,20 @@ +/* + * cl_snap_purge_dbio.pgc - cl 모듈 DB 접근 함수 (cl_snap_purge_dbio), archived into libcldbio.a. + * 마감 스냅샷 범위 삭제 후 삭제건수 반환 (DELETE range). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_snap_purge_dbio.h" + +/* biz_date 이하 스냅샷 삭제, 삭제 행수 반환. */ +long cldb_snap_purge(const char *bizdate) +{ + 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; + EXEC SQL DELETE FROM cl_snapshot WHERE biz_date <= :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("cldb_snap_purge FAIL [%d]", sqlca.sqlcode); return -1; } + return (long) sqlca.sqlerrd[2]; +} diff --git a/app/src/cl/dbio/cl_snap_rank_dbio.h b/app/src/cl/dbio/cl_snap_rank_dbio.h new file mode 100644 index 0000000..45b39cf --- /dev/null +++ b/app/src/cl/dbio/cl_snap_rank_dbio.h @@ -0,0 +1,9 @@ +/* + * cl_snap_rank_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_snap_rank_dbio, part of libcldbio.a). + * 마감 스냅샷 gross 순위 n번째 가맹점 (window ROW_NUMBER). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_SNAP_RANK_DBIO_H +#define CL_SNAP_RANK_DBIO_H +int clqdb_snap_rank(long cid, long rank, char *merch_out); +#endif /* CL_SNAP_RANK_DBIO_H */ diff --git a/app/src/cl/dbio/cl_snap_rank_dbio.pgc b/app/src/cl/dbio/cl_snap_rank_dbio.pgc new file mode 100644 index 0000000..e3314cf --- /dev/null +++ b/app/src/cl/dbio/cl_snap_rank_dbio.pgc @@ -0,0 +1,25 @@ +/* + * cl_snap_rank_dbio.pgc - cl 모듈 DB 접근 함수 (cl_snap_rank_dbio), archived into libcldbio.a. + * 마감 스냅샷 gross 순위 n번째 가맹점 (window ROW_NUMBER). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_snap_rank_dbio.h" + +/* close_id 스냅샷에서 gross 내림차순 rank 번째 가맹점 반환. */ +int clqdb_snap_rank(long cid, long rank, char *merch_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cid = cid, h_rank = rank; + char h_merch[64]; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT merchant_id INTO :h_merch FROM ( + SELECT merchant_id, row_number() OVER (ORDER BY gross_amount DESC) AS rn + FROM cl_snapshot WHERE close_id = :h_cid) t + WHERE t.rn = :h_rank; + if (sqlca.sqlcode == 100) { merch_out[0] = 0; return 0; } + if (sqlca.sqlcode < 0) return -1; + strcpy(merch_out, h_merch); + return 0; +} diff --git a/app/src/cl/dbio/cl_status_filter_dbio.h b/app/src/cl/dbio/cl_status_filter_dbio.h new file mode 100644 index 0000000..ba7a127 --- /dev/null +++ b/app/src/cl/dbio/cl_status_filter_dbio.h @@ -0,0 +1,9 @@ +/* + * cl_status_filter_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_status_filter_dbio, part of libcldbio.a). + * 마감 상태별 건수 한 행 분리집계 (FILTER). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_STATUS_FILTER_DBIO_H +#define CL_STATUS_FILTER_DBIO_H +int clqdb_status_filter(const char *prefix, long *opn, long *closed, long *locked); +#endif /* CL_STATUS_FILTER_DBIO_H */ diff --git a/app/src/cl/dbio/cl_status_filter_dbio.pgc b/app/src/cl/dbio/cl_status_filter_dbio.pgc new file mode 100644 index 0000000..725116b --- /dev/null +++ b/app/src/cl/dbio/cl_status_filter_dbio.pgc @@ -0,0 +1,27 @@ +/* + * cl_status_filter_dbio.pgc - cl 모듈 DB 접근 함수 (cl_status_filter_dbio), archived into libcldbio.a. + * 마감 상태별 건수 한 행 분리집계 (FILTER). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_status_filter_dbio.h" + +/* 개시(O)/완료(C)/잠금(L) 건수를 FILTER 로 한 번에 반환. */ +int clqdb_status_filter(const char *prefix, long *opn, long *closed, long *locked) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_prefix[16]; + long h_o = 0, h_c = 0, h_l = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_prefix, prefix, sizeof(h_prefix)-1); h_prefix[sizeof(h_prefix)-1] = 0; + EXEC SQL SELECT + count(*) FILTER (WHERE status = 'O'), + count(*) FILTER (WHERE status = 'C'), + count(*) FILTER (WHERE status = 'L') + INTO :h_o, :h_c, :h_l FROM cl_close_log + WHERE period_key LIKE :h_prefix || '%'; + if (sqlca.sqlcode < 0) return -1; + *opn = h_o; *closed = h_c; *locked = h_l; + return 0; +} diff --git a/app/src/cl/dbio/cl_streak_cursor_dbio.h b/app/src/cl/dbio/cl_streak_cursor_dbio.h new file mode 100644 index 0000000..a15da9d --- /dev/null +++ b/app/src/cl/dbio/cl_streak_cursor_dbio.h @@ -0,0 +1,9 @@ +/* + * cl_streak_cursor_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_streak_cursor_dbio, part of libcldbio.a). + * 일마감 연속 완료 최장 구간 (cursor + 상태 누적). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_STREAK_CURSOR_DBIO_H +#define CL_STREAK_CURSOR_DBIO_H +long clqdb_close_streak(const char *prefix); +#endif /* CL_STREAK_CURSOR_DBIO_H */ diff --git a/app/src/cl/dbio/cl_streak_cursor_dbio.pgc b/app/src/cl/dbio/cl_streak_cursor_dbio.pgc new file mode 100644 index 0000000..9bffe78 --- /dev/null +++ b/app/src/cl/dbio/cl_streak_cursor_dbio.pgc @@ -0,0 +1,33 @@ +/* + * cl_streak_cursor_dbio.pgc - cl 모듈 DB 접근 함수 (cl_streak_cursor_dbio), archived into libcldbio.a. + * 일마감 연속 완료 최장 구간 (cursor + 상태 누적). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_streak_cursor_dbio.h" + +/* 날짜순 DAILY 마감을 훑어 status='C' 연속 최장 구간 길이 반환. */ +long clqdb_close_streak(const char *prefix) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_prefix[16], h_status[4]; + EXEC SQL END DECLARE SECTION; + long cur = 0, best = 0; + strncpy(h_prefix, prefix, sizeof(h_prefix)-1); h_prefix[sizeof(h_prefix)-1] = 0; + EXEC SQL DECLARE csk CURSOR FOR + SELECT status FROM cl_close_log + WHERE close_type = 'DAILY' AND period_key LIKE :h_prefix || '%' + ORDER BY biz_date; + EXEC SQL OPEN csk; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH csk INTO :h_status; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE csk; return -1; } + if (h_status[0] == 'C') { cur++; if (cur > best) best = cur; } + else cur = 0; + } + EXEC SQL CLOSE csk; + return best; +} diff --git a/app/src/cl/dbio/cl_tier_case_dbio.h b/app/src/cl/dbio/cl_tier_case_dbio.h new file mode 100644 index 0000000..d66a487 --- /dev/null +++ b/app/src/cl/dbio/cl_tier_case_dbio.h @@ -0,0 +1,9 @@ +/* + * cl_tier_case_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_tier_case_dbio, part of libcldbio.a). + * 마감 총액 구간별 건수 (CASE 집계). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_TIER_CASE_DBIO_H +#define CL_TIER_CASE_DBIO_H +int clqdb_tier_case(long small, long large, long *lo, long *mid, long *hi); +#endif /* CL_TIER_CASE_DBIO_H */ diff --git a/app/src/cl/dbio/cl_tier_case_dbio.pgc b/app/src/cl/dbio/cl_tier_case_dbio.pgc new file mode 100644 index 0000000..8ad1026 --- /dev/null +++ b/app/src/cl/dbio/cl_tier_case_dbio.pgc @@ -0,0 +1,24 @@ +/* + * cl_tier_case_dbio.pgc - cl 모듈 DB 접근 함수 (cl_tier_case_dbio), archived into libcldbio.a. + * 마감 총액 구간별 건수 (CASE 집계). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_tier_case_dbio.h" + +/* total_amount 을 소/중/대 구간으로 CASE 분류해 각 건수 반환. */ +int clqdb_tier_case(long small, long large, long *lo, long *mid, long *hi) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_small = small, h_large = large, h_lo = 0, h_mid = 0, h_hi = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT + coalesce(sum(CASE WHEN total_amount < :h_small THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN total_amount >= :h_small AND total_amount < :h_large THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN total_amount >= :h_large THEN 1 ELSE 0 END),0) + INTO :h_lo, :h_mid, :h_hi FROM cl_close_log WHERE status <> 'X'; + if (sqlca.sqlcode < 0) return -1; + *lo = h_lo; *mid = h_mid; *hi = h_hi; + return 0; +} diff --git a/app/src/cl/dbio/cl_type_having_dbio.h b/app/src/cl/dbio/cl_type_having_dbio.h new file mode 100644 index 0000000..ef1ce9f --- /dev/null +++ b/app/src/cl/dbio/cl_type_having_dbio.h @@ -0,0 +1,9 @@ +/* + * cl_type_having_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_type_having_dbio, part of libcldbio.a). + * 마감유형별 건수 임계 초과 유형 수 (GROUP BY ... HAVING). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_TYPE_HAVING_DBIO_H +#define CL_TYPE_HAVING_DBIO_H +long clqdb_type_having(long min_cnt); +#endif /* CL_TYPE_HAVING_DBIO_H */ diff --git a/app/src/cl/dbio/cl_type_having_dbio.pgc b/app/src/cl/dbio/cl_type_having_dbio.pgc new file mode 100644 index 0000000..5f71615 --- /dev/null +++ b/app/src/cl/dbio/cl_type_having_dbio.pgc @@ -0,0 +1,32 @@ +/* + * cl_type_having_dbio.pgc - cl 모듈 DB 접근 함수 (cl_type_having_dbio), archived into libcldbio.a. + * 마감유형별 건수 임계 초과 유형 수 (GROUP BY ... HAVING 커서). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_type_having_dbio.h" + +/* close_type 별 마감 건수가 min_cnt 이상인 유형 개수. */ +long clqdb_type_having(long min_cnt) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_min = min_cnt, h_cnt; + char h_type[16]; + EXEC SQL END DECLARE SECTION; + long types = 0; + EXEC SQL DECLARE cth CURSOR FOR + SELECT close_type, count(*) FROM cl_close_log + WHERE status <> 'X' GROUP BY close_type HAVING count(*) >= :h_min + ORDER BY close_type; + EXEC SQL OPEN cth; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH cth INTO :h_type, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cth; return -1; } + types++; + } + EXEC SQL CLOSE cth; + return types; +} diff --git a/app/src/cl/dbio/cl_unlocked_dbio.h b/app/src/cl/dbio/cl_unlocked_dbio.h new file mode 100644 index 0000000..8cfe129 --- /dev/null +++ b/app/src/cl/dbio/cl_unlocked_dbio.h @@ -0,0 +1,9 @@ +/* + * cl_unlocked_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_unlocked_dbio, part of libcldbio.a). + * 완료됐지만 기간잠금이 없는 마감 기간 수 (NOT EXISTS). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_UNLOCKED_DBIO_H +#define CL_UNLOCKED_DBIO_H +long clqdb_unlocked(void); +#endif /* CL_UNLOCKED_DBIO_H */ diff --git a/app/src/cl/dbio/cl_unlocked_dbio.pgc b/app/src/cl/dbio/cl_unlocked_dbio.pgc new file mode 100644 index 0000000..c9f33b2 --- /dev/null +++ b/app/src/cl/dbio/cl_unlocked_dbio.pgc @@ -0,0 +1,22 @@ +/* + * cl_unlocked_dbio.pgc - cl 모듈 DB 접근 함수 (cl_unlocked_dbio), archived into libcldbio.a. + * 완료됐지만 기간잠금이 없는 마감 기간 수 (NOT EXISTS). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_unlocked_dbio.h" + +/* status='C' 마감 중 cl_period_lock 에 잠금이 없는 기간 수. */ +long clqdb_unlocked(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(DISTINCT c.period_key) INTO :h_v FROM cl_close_log c + WHERE c.status = 'C' + AND NOT EXISTS (SELECT 1 FROM cl_period_lock l + WHERE l.period_key = c.period_key AND l.locked); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/cl/dbio/cl_wavg_dbio.h b/app/src/cl/dbio/cl_wavg_dbio.h new file mode 100644 index 0000000..e971852 --- /dev/null +++ b/app/src/cl/dbio/cl_wavg_dbio.h @@ -0,0 +1,9 @@ +/* + * cl_wavg_dbio.h - cl 모듈 DB 접근 계층 copybook (cl_wavg_dbio, part of libcldbio.a). + * 건수 가중 평균 gross (weighted average). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CL_WAVG_DBIO_H +#define CL_WAVG_DBIO_H +long clqdb_wavg_gross(long cid); +#endif /* CL_WAVG_DBIO_H */ diff --git a/app/src/cl/dbio/cl_wavg_dbio.pgc b/app/src/cl/dbio/cl_wavg_dbio.pgc new file mode 100644 index 0000000..da833bd --- /dev/null +++ b/app/src/cl/dbio/cl_wavg_dbio.pgc @@ -0,0 +1,21 @@ +/* + * cl_wavg_dbio.pgc - cl 모듈 DB 접근 함수 (cl_wavg_dbio), archived into libcldbio.a. + * 건수 가중 평균 gross (weighted average). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "cl_wavg_dbio.h" + +/* 스냅샷 gross 를 txn_count 로 가중한 평균 = sum(gross*cnt)/sum(cnt). */ +long clqdb_wavg_gross(long cid) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_cid = cid, h_num = 0, h_den = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT coalesce(sum(gross_amount * txn_count),0), coalesce(sum(txn_count),0) + INTO :h_num, :h_den FROM cl_snapshot WHERE close_id = :h_cid; + if (sqlca.sqlcode < 0) return -1; + if (h_den <= 0) return 0; + return h_num / h_den; +} diff --git a/app/src/cm/batch/cm_fxlog_batch.pgc b/app/src/cm/batch/cm_fxlog_batch.pgc new file mode 100644 index 0000000..3fbde47 --- /dev/null +++ b/app/src/cm/batch/cm_fxlog_batch.pgc @@ -0,0 +1,40 @@ +/* + * cm_fxlog_batch.pgc - cm 환율 공통로그 기록 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, EXEC SQL runs on the biz_date, then tpcommit drives XA 2PC. + * Usage: cm_fxlog_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_fxlog_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; + cm_fxlog_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 INSERT INTO cm_log + (log_id, svc_name, log_level, msg, biz_date) + SELECT nextval('cm_log_seq'), 'CM_FX', 'I', f.ccy, f.base_date + FROM cm_fx_rate f WHERE f.base_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_fxlog_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.logged = (long) sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_fxlog_batch COMMIT: bizdate=%s 기록=%ld\n", bizdate, rec.logged); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_fxpurge_batch.pgc b/app/src/cm/batch/cm_fxpurge_batch.pgc new file mode 100644 index 0000000..620c8af --- /dev/null +++ b/app/src/cm/batch/cm_fxpurge_batch.pgc @@ -0,0 +1,39 @@ +/* + * cm_fxpurge_batch.pgc - cm 환율 보존기간 삭제 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, EXEC SQL runs on the biz_date, then tpcommit drives XA 2PC. + * Usage: cm_fxpurge_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_fxpurge_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + const char *cutoff = (argc > 2) ? argv[2] : CM_FXPURGE_CUTOFF; + EXEC SQL BEGIN DECLARE SECTION; + char h_cut[16]; + EXEC SQL END DECLARE SECTION; + cm_fxpurge_rec_t rec; + (void) bizdate; + + strncpy(h_cut, cutoff, sizeof(h_cut)-1); h_cut[sizeof(h_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 DELETE FROM cm_fx_rate WHERE base_date < :h_cut; + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_fxpurge_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.deleted = (long) sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_fxpurge_batch COMMIT: cutoff=%s 삭제=%ld\n", cutoff, rec.deleted); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_levelgrp_batch.pgc b/app/src/cm/batch/cm_levelgrp_batch.pgc new file mode 100644 index 0000000..5c92999 --- /dev/null +++ b/app/src/cm/batch/cm_levelgrp_batch.pgc @@ -0,0 +1,41 @@ +/* + * cm_levelgrp_batch.pgc - cm 로그 레벨 그룹 집계 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, EXEC SQL runs on the biz_date, then tpcommit drives XA 2PC. + * Usage: cm_levelgrp_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_levelgrp_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_min = 1, h_groups = 0; + EXEC SQL END DECLARE SECTION; + cm_levelgrp_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_groups FROM ( + SELECT log_level FROM cm_log + WHERE biz_date = :h_bizdate + GROUP BY log_level HAVING count(*) >= :h_min) t; + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_levelgrp_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.threshold = h_min; rec.groups = h_groups; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_levelgrp_batch COMMIT: bizdate=%s 임계=%ld 레벨그룹=%ld\n", bizdate, rec.threshold, rec.groups); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_nofx_batch.pgc b/app/src/cm/batch/cm_nofx_batch.pgc new file mode 100644 index 0000000..d1ecec1 --- /dev/null +++ b/app/src/cm/batch/cm_nofx_batch.pgc @@ -0,0 +1,41 @@ +/* + * cm_nofx_batch.pgc - cm 환율 미등록 통화 점검 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, EXEC SQL runs on the biz_date, then tpcommit drives XA 2PC. + * Usage: cm_nofx_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_nofx_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_v = 0; + EXEC SQL END DECLARE SECTION; + cm_nofx_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_v FROM cm_code c + WHERE c.code_grp = 'CCY' AND c.code <> 'KRW' + AND NOT EXISTS (SELECT 1 FROM cm_fx_rate f + WHERE f.ccy = c.code AND f.base_date = :h_bizdate); + if (sqlca.sqlcode < 0) { fprintf(stderr, "cm_nofx_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.missing = h_v; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_nofx_batch COMMIT: bizdate=%s 환율미등록=%ld\n", bizdate, rec.missing); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/batch/cm_rankfx_batch.pgc b/app/src/cm/batch/cm_rankfx_batch.pgc new file mode 100644 index 0000000..b0a43f8 --- /dev/null +++ b/app/src/cm/batch/cm_rankfx_batch.pgc @@ -0,0 +1,49 @@ +/* + * cm_rankfx_batch.pgc - cm 기준일 최고환율 랭크 스캔 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, EXEC SQL runs on the biz_date, then tpcommit drives XA 2PC. + * Usage: cm_rankfx_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "cm_rankfx_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_ccy[8]; + long h_rn; + EXEC SQL END DECLARE SECTION; + cm_rankfx_rec_t rec; rec.scanned = 0; rec.tops = 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 cra CURSOR FOR + SELECT ccy, + ROW_NUMBER() OVER (PARTITION BY base_date ORDER BY rate_bps DESC) + FROM cm_fx_rate WHERE base_date = :h_bizdate; + EXEC SQL OPEN cra; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH cra INTO :h_ccy, :h_rn; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cra; tpabort(0); return 1; } + rec.scanned++; + if (h_rn == 1) rec.tops++; + } + EXEC SQL CLOSE cra; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> cm_rankfx_batch COMMIT: bizdate=%s 스캔=%ld 최고=%ld\n", bizdate, rec.scanned, rec.tops); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/cm/dbio/cm_ccycase_dbio.h b/app/src/cm/dbio/cm_ccycase_dbio.h new file mode 100644 index 0000000..e7c1705 --- /dev/null +++ b/app/src/cm/dbio/cm_ccycase_dbio.h @@ -0,0 +1,11 @@ +/* + * cm_ccycase_dbio.h - cm 모듈 DB 접근 copybook (cm_ccycase_dbio). 통화별 가중치를 적용한 환율 가중합 (CASE 식). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_CCYCASE_DBIO_H +#define CM_CCYCASE_DBIO_H + +long cmdb_ccy_case_weight(const char *bizdate); + +#endif /* CM_CCYCASE_DBIO_H */ diff --git a/app/src/cm/dbio/cm_ccycase_dbio.pgc b/app/src/cm/dbio/cm_ccycase_dbio.pgc new file mode 100644 index 0000000..c747baf --- /dev/null +++ b/app/src/cm/dbio/cm_ccycase_dbio.pgc @@ -0,0 +1,24 @@ +/* + * cm_ccycase_dbio.pgc - cm 모듈 DB 접근 함수 (cm_ccycase_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 통화별 가중치를 적용한 환율 가중합 (CASE 식). + */ +#include +#include +#include "cm_ccycase_dbio.h" + +long cmdb_ccy_case_weight(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(CASE ccy + WHEN 'USD' THEN rate_bps * 3 + WHEN 'EUR' THEN rate_bps * 2 + ELSE rate_bps END), 0) + INTO :h_v FROM cm_fx_rate WHERE base_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/cm/dbio/cm_codefxjoin_dbio.h b/app/src/cm/dbio/cm_codefxjoin_dbio.h new file mode 100644 index 0000000..a006d97 --- /dev/null +++ b/app/src/cm/dbio/cm_codefxjoin_dbio.h @@ -0,0 +1,11 @@ +/* + * cm_codefxjoin_dbio.h - cm 모듈 DB 접근 copybook (cm_codefxjoin_dbio). 통화코드와 환율을 조인한 코드그룹별 합계 그룹 수 (JOIN + GROUP BY 커서). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_CODEFXJOIN_DBIO_H +#define CM_CODEFXJOIN_DBIO_H + +long cmdb_code_fx_groups(const char *bizdate); + +#endif /* CM_CODEFXJOIN_DBIO_H */ diff --git a/app/src/cm/dbio/cm_codefxjoin_dbio.pgc b/app/src/cm/dbio/cm_codefxjoin_dbio.pgc new file mode 100644 index 0000000..d782f4f --- /dev/null +++ b/app/src/cm/dbio/cm_codefxjoin_dbio.pgc @@ -0,0 +1,32 @@ +/* + * cm_codefxjoin_dbio.pgc - cm 모듈 DB 접근 함수 (cm_codefxjoin_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 통화코드와 환율을 조인한 코드그룹별 합계 그룹 수 (JOIN + GROUP BY 커서). + */ +#include +#include +#include "cm_codefxjoin_dbio.h" + +long cmdb_code_fx_groups(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_grp[16]; + long h_sum, groups = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE cfg CURSOR FOR + SELECT c.code_grp, coalesce(sum(f.rate_bps),0) + FROM cm_code c JOIN cm_fx_rate f ON f.ccy = c.code + WHERE c.code_grp = 'CCY' AND f.base_date = :h_bizdate + GROUP BY c.code_grp; + EXEC SQL OPEN cfg; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH cfg INTO :h_grp, :h_sum; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cfg; return -1; } + groups++; + } + EXEC SQL CLOSE cfg; + return groups; +} diff --git a/app/src/cm/dbio/cm_errfilter_dbio.h b/app/src/cm/dbio/cm_errfilter_dbio.h new file mode 100644 index 0000000..f6eeb1c --- /dev/null +++ b/app/src/cm/dbio/cm_errfilter_dbio.h @@ -0,0 +1,11 @@ +/* + * cm_errfilter_dbio.h - cm 모듈 DB 접근 copybook (cm_errfilter_dbio). 전체 대비 오류(E) 로그 비율(bps) - FILTER 절. + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_ERRFILTER_DBIO_H +#define CM_ERRFILTER_DBIO_H + +long cmdb_log_err_filter(const char *bizdate); + +#endif /* CM_ERRFILTER_DBIO_H */ diff --git a/app/src/cm/dbio/cm_errfilter_dbio.pgc b/app/src/cm/dbio/cm_errfilter_dbio.pgc new file mode 100644 index 0000000..76f5a8c --- /dev/null +++ b/app/src/cm/dbio/cm_errfilter_dbio.pgc @@ -0,0 +1,22 @@ +/* + * cm_errfilter_dbio.pgc - cm 모듈 DB 접근 함수 (cm_errfilter_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 전체 대비 오류(E) 로그 비율(bps) - FILTER 절. + */ +#include +#include +#include "cm_errfilter_dbio.h" + +long cmdb_log_err_filter(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_err = 0, h_tot = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) FILTER (WHERE log_level = 'E'), count(*) + INTO :h_err, :h_tot FROM cm_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + if (h_tot <= 0) return 0; + return h_err * 10000 / h_tot; +} diff --git a/app/src/cm/dbio/cm_fxabov_dbio.h b/app/src/cm/dbio/cm_fxabov_dbio.h new file mode 100644 index 0000000..6a45c8e --- /dev/null +++ b/app/src/cm/dbio/cm_fxabov_dbio.h @@ -0,0 +1,11 @@ +/* + * cm_fxabov_dbio.h - cm 모듈 DB 접근 copybook (cm_fxabov_dbio). 통화 평균환율을 초과한 환율 레코드 수 (상관 서브쿼리 avg). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_FXABOV_DBIO_H +#define CM_FXABOV_DBIO_H + +long cmdb_fx_above_avg(const char *bizdate); + +#endif /* CM_FXABOV_DBIO_H */ diff --git a/app/src/cm/dbio/cm_fxabov_dbio.pgc b/app/src/cm/dbio/cm_fxabov_dbio.pgc new file mode 100644 index 0000000..a8060e7 --- /dev/null +++ b/app/src/cm/dbio/cm_fxabov_dbio.pgc @@ -0,0 +1,22 @@ +/* + * cm_fxabov_dbio.pgc - cm 모듈 DB 접근 함수 (cm_fxabov_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 통화 평균환율을 초과한 환율 레코드 수 (상관 서브쿼리 avg). + */ +#include +#include +#include "cm_fxabov_dbio.h" + +long cmdb_fx_above_avg(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM cm_fx_rate f + WHERE f.base_date = :h_bizdate + AND f.rate_bps > (SELECT avg(f2.rate_bps) FROM cm_fx_rate f2 WHERE f2.ccy = f.ccy); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/cm/dbio/cm_fxlog_batch.h b/app/src/cm/dbio/cm_fxlog_batch.h new file mode 100644 index 0000000..a7ad068 --- /dev/null +++ b/app/src/cm/dbio/cm_fxlog_batch.h @@ -0,0 +1,10 @@ +/* + * cm_fxlog_batch.h - cm cm_fxlog_batch 배치 전용 카피북 (copybook). cm 환율 공통로그 기록 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by cm_fxlog_batch.pgc. + */ +#ifndef CM_FXLOG_BATCH_H +#define CM_FXLOG_BATCH_H + +typedef struct { long logged; } cm_fxlog_rec_t; + +#endif /* CM_FXLOG_BATCH_H */ diff --git a/app/src/cm/dbio/cm_fxpurge_batch.h b/app/src/cm/dbio/cm_fxpurge_batch.h new file mode 100644 index 0000000..aa2225b --- /dev/null +++ b/app/src/cm/dbio/cm_fxpurge_batch.h @@ -0,0 +1,11 @@ +/* + * cm_fxpurge_batch.h - cm cm_fxpurge_batch 배치 전용 카피북 (copybook). cm 환율 보존기간 삭제 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by cm_fxpurge_batch.pgc. + */ +#ifndef CM_FXPURGE_BATCH_H +#define CM_FXPURGE_BATCH_H + +typedef struct { long deleted; } cm_fxpurge_rec_t; +#define CM_FXPURGE_CUTOFF "2026-07-01" + +#endif /* CM_FXPURGE_BATCH_H */ diff --git a/app/src/cm/dbio/cm_grpdistinct_dbio.h b/app/src/cm/dbio/cm_grpdistinct_dbio.h new file mode 100644 index 0000000..42bef7f --- /dev/null +++ b/app/src/cm/dbio/cm_grpdistinct_dbio.h @@ -0,0 +1,11 @@ +/* + * cm_grpdistinct_dbio.h - cm 모듈 DB 접근 copybook (cm_grpdistinct_dbio). 사용중인 고유 코드그룹 수 (count DISTINCT). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_GRPDISTINCT_DBIO_H +#define CM_GRPDISTINCT_DBIO_H + +long cmdb_distinct_grp(void); + +#endif /* CM_GRPDISTINCT_DBIO_H */ diff --git a/app/src/cm/dbio/cm_grpdistinct_dbio.pgc b/app/src/cm/dbio/cm_grpdistinct_dbio.pgc new file mode 100644 index 0000000..a6000da --- /dev/null +++ b/app/src/cm/dbio/cm_grpdistinct_dbio.pgc @@ -0,0 +1,18 @@ +/* + * cm_grpdistinct_dbio.pgc - cm 모듈 DB 접근 함수 (cm_grpdistinct_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 사용중인 고유 코드그룹 수 (count DISTINCT). + */ +#include +#include +#include "cm_grpdistinct_dbio.h" + +long cmdb_distinct_grp(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(DISTINCT code_grp) INTO :h_v FROM cm_code WHERE use_yn = 'Y'; + if (sqlca.sqlcode < 0) { userlog("cmdb_distinct_grp FAIL [%d]", sqlca.sqlcode); return -1; } + return h_v; +} diff --git a/app/src/cm/dbio/cm_levelgroup_dbio.h b/app/src/cm/dbio/cm_levelgroup_dbio.h new file mode 100644 index 0000000..d1654dd --- /dev/null +++ b/app/src/cm/dbio/cm_levelgroup_dbio.h @@ -0,0 +1,11 @@ +/* + * cm_levelgroup_dbio.h - cm 모듈 DB 접근 copybook (cm_levelgroup_dbio). 임계 이상 발생한 로그 레벨 그룹 수 (GROUP BY HAVING 커서). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_LEVELGROUP_DBIO_H +#define CM_LEVELGROUP_DBIO_H + +long cmdb_level_groups(const char *bizdate, long minc); + +#endif /* CM_LEVELGROUP_DBIO_H */ diff --git a/app/src/cm/dbio/cm_levelgroup_dbio.pgc b/app/src/cm/dbio/cm_levelgroup_dbio.pgc new file mode 100644 index 0000000..b97afdf --- /dev/null +++ b/app/src/cm/dbio/cm_levelgroup_dbio.pgc @@ -0,0 +1,32 @@ +/* + * cm_levelgroup_dbio.pgc - cm 모듈 DB 접근 함수 (cm_levelgroup_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 임계 이상 발생한 로그 레벨 그룹 수 (GROUP BY HAVING 커서). + */ +#include +#include +#include "cm_levelgroup_dbio.h" + +long cmdb_level_groups(const char *bizdate, long minc) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_lvl[4]; + long h_min, h_cnt, groups = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + h_min = minc; + EXEC SQL DECLARE clg CURSOR FOR + SELECT log_level, count(*) FROM cm_log + WHERE biz_date = :h_bizdate + GROUP BY log_level HAVING count(*) >= :h_min; + EXEC SQL OPEN clg; + if (sqlca.sqlcode < 0) { userlog("cmdb_level_groups OPEN [%d]", sqlca.sqlcode); return -1; } + for (;;) { + EXEC SQL FETCH clg INTO :h_lvl, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE clg; return -1; } + groups++; + } + EXEC SQL CLOSE clg; + return groups; +} diff --git a/app/src/cm/dbio/cm_levelgrp_batch.h b/app/src/cm/dbio/cm_levelgrp_batch.h new file mode 100644 index 0000000..da3a27e --- /dev/null +++ b/app/src/cm/dbio/cm_levelgrp_batch.h @@ -0,0 +1,10 @@ +/* + * cm_levelgrp_batch.h - cm cm_levelgrp_batch 배치 전용 카피북 (copybook). cm 로그 레벨 그룹 집계 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by cm_levelgrp_batch.pgc. + */ +#ifndef CM_LEVELGRP_BATCH_H +#define CM_LEVELGRP_BATCH_H + +typedef struct { long threshold; long groups; } cm_levelgrp_rec_t; + +#endif /* CM_LEVELGRP_BATCH_H */ diff --git a/app/src/cm/dbio/cm_logfromfx_dbio.h b/app/src/cm/dbio/cm_logfromfx_dbio.h new file mode 100644 index 0000000..928d46c --- /dev/null +++ b/app/src/cm/dbio/cm_logfromfx_dbio.h @@ -0,0 +1,11 @@ +/* + * cm_logfromfx_dbio.h - cm 모듈 DB 접근 copybook (cm_logfromfx_dbio). 당일 환율을 공통로그로 기록 (INSERT..SELECT). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_LOGFROMFX_DBIO_H +#define CM_LOGFROMFX_DBIO_H + +long cmdb_log_from_fx(const char *bizdate); + +#endif /* CM_LOGFROMFX_DBIO_H */ diff --git a/app/src/cm/dbio/cm_logfromfx_dbio.pgc b/app/src/cm/dbio/cm_logfromfx_dbio.pgc new file mode 100644 index 0000000..c10873b --- /dev/null +++ b/app/src/cm/dbio/cm_logfromfx_dbio.pgc @@ -0,0 +1,22 @@ +/* + * cm_logfromfx_dbio.pgc - cm 모듈 DB 접근 함수 (cm_logfromfx_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 당일 환율을 공통로그로 기록 (INSERT..SELECT). + */ +#include +#include +#include "cm_logfromfx_dbio.h" + +long cmdb_log_from_fx(const char *bizdate) +{ + 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; + EXEC SQL INSERT INTO cm_log + (log_id, svc_name, log_level, msg, biz_date) + SELECT nextval('cm_log_seq'), 'CM_FX', 'I', f.ccy, f.base_date + FROM cm_fx_rate f WHERE f.base_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("cmdb_log_from_fx FAIL [%d]", sqlca.sqlcode); return -1; } + return (long) sqlca.sqlerrd[2]; +} diff --git a/app/src/cm/dbio/cm_login_dbio.h b/app/src/cm/dbio/cm_login_dbio.h new file mode 100644 index 0000000..78c333a --- /dev/null +++ b/app/src/cm/dbio/cm_login_dbio.h @@ -0,0 +1,11 @@ +/* + * cm_login_dbio.h - cm 모듈 DB 접근 copybook (cm_login_dbio). 오류 로그를 남긴 서비스가 낸 전체 로그 건수 (IN 서브셀렉트). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_LOGIN_DBIO_H +#define CM_LOGIN_DBIO_H + +long cmdb_log_for_errsvc(const char *bizdate); + +#endif /* CM_LOGIN_DBIO_H */ diff --git a/app/src/cm/dbio/cm_login_dbio.pgc b/app/src/cm/dbio/cm_login_dbio.pgc new file mode 100644 index 0000000..24c545c --- /dev/null +++ b/app/src/cm/dbio/cm_login_dbio.pgc @@ -0,0 +1,23 @@ +/* + * cm_login_dbio.pgc - cm 모듈 DB 접근 함수 (cm_login_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 오류 로그를 남긴 서비스가 낸 전체 로그 건수 (IN 서브셀렉트). + */ +#include +#include +#include "cm_login_dbio.h" + +long cmdb_log_for_errsvc(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM cm_log + WHERE biz_date = :h_bizdate + AND svc_name IN (SELECT svc_name FROM cm_log + WHERE biz_date = :h_bizdate AND log_level = 'E'); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/cm/dbio/cm_nofx_batch.h b/app/src/cm/dbio/cm_nofx_batch.h new file mode 100644 index 0000000..e4406f9 --- /dev/null +++ b/app/src/cm/dbio/cm_nofx_batch.h @@ -0,0 +1,10 @@ +/* + * cm_nofx_batch.h - cm cm_nofx_batch 배치 전용 카피북 (copybook). cm 환율 미등록 통화 점검 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by cm_nofx_batch.pgc. + */ +#ifndef CM_NOFX_BATCH_H +#define CM_NOFX_BATCH_H + +typedef struct { long missing; } cm_nofx_rec_t; + +#endif /* CM_NOFX_BATCH_H */ diff --git a/app/src/cm/dbio/cm_nofx_dbio.h b/app/src/cm/dbio/cm_nofx_dbio.h new file mode 100644 index 0000000..1fca0da --- /dev/null +++ b/app/src/cm/dbio/cm_nofx_dbio.h @@ -0,0 +1,11 @@ +/* + * cm_nofx_dbio.h - cm 모듈 DB 접근 copybook (cm_nofx_dbio). 당일 환율이 등록되지 않은 통화코드 수 (NOT EXISTS cm_fx_rate). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_NOFX_DBIO_H +#define CM_NOFX_DBIO_H + +long cmdb_code_no_fx(const char *bizdate); + +#endif /* CM_NOFX_DBIO_H */ diff --git a/app/src/cm/dbio/cm_nofx_dbio.pgc b/app/src/cm/dbio/cm_nofx_dbio.pgc new file mode 100644 index 0000000..9140c46 --- /dev/null +++ b/app/src/cm/dbio/cm_nofx_dbio.pgc @@ -0,0 +1,23 @@ +/* + * cm_nofx_dbio.pgc - cm 모듈 DB 접근 함수 (cm_nofx_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 당일 환율이 등록되지 않은 통화코드 수 (NOT EXISTS cm_fx_rate). + */ +#include +#include +#include "cm_nofx_dbio.h" + +long cmdb_code_no_fx(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM cm_code c + WHERE c.code_grp = 'CCY' AND c.code <> 'KRW' + AND NOT EXISTS (SELECT 1 FROM cm_fx_rate f + WHERE f.ccy = c.code AND f.base_date = :h_bizdate); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/cm/dbio/cm_purgefx_dbio.h b/app/src/cm/dbio/cm_purgefx_dbio.h new file mode 100644 index 0000000..b9f92a1 --- /dev/null +++ b/app/src/cm/dbio/cm_purgefx_dbio.h @@ -0,0 +1,11 @@ +/* + * cm_purgefx_dbio.h - cm 모듈 DB 접근 copybook (cm_purgefx_dbio). 기준일 이전 환율 레코드 삭제 후 삭제건수 반환 (DELETE 범위). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_PURGEFX_DBIO_H +#define CM_PURGEFX_DBIO_H + +long cmdb_purge_fx(const char *cutoff); + +#endif /* CM_PURGEFX_DBIO_H */ diff --git a/app/src/cm/dbio/cm_purgefx_dbio.pgc b/app/src/cm/dbio/cm_purgefx_dbio.pgc new file mode 100644 index 0000000..aa326b4 --- /dev/null +++ b/app/src/cm/dbio/cm_purgefx_dbio.pgc @@ -0,0 +1,19 @@ +/* + * cm_purgefx_dbio.pgc - cm 모듈 DB 접근 함수 (cm_purgefx_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 기준일 이전 환율 레코드 삭제 후 삭제건수 반환 (DELETE 범위). + */ +#include +#include +#include "cm_purgefx_dbio.h" + +long cmdb_purge_fx(const char *cutoff) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_cut[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_cut, cutoff, sizeof(h_cut)-1); h_cut[sizeof(h_cut)-1] = 0; + EXEC SQL DELETE FROM cm_fx_rate WHERE base_date < :h_cut; + if (sqlca.sqlcode < 0) { userlog("cmdb_purge_fx FAIL [%d]", sqlca.sqlcode); return -1; } + return (long) sqlca.sqlerrd[2]; +} diff --git a/app/src/cm/dbio/cm_rankfx_batch.h b/app/src/cm/dbio/cm_rankfx_batch.h new file mode 100644 index 0000000..cf9d558 --- /dev/null +++ b/app/src/cm/dbio/cm_rankfx_batch.h @@ -0,0 +1,10 @@ +/* + * cm_rankfx_batch.h - cm cm_rankfx_batch 배치 전용 카피북 (copybook). cm 기준일 최고환율 랭크 스캔 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by cm_rankfx_batch.pgc. + */ +#ifndef CM_RANKFX_BATCH_H +#define CM_RANKFX_BATCH_H + +typedef struct { long scanned; long tops; } cm_rankfx_rec_t; + +#endif /* CM_RANKFX_BATCH_H */ diff --git a/app/src/cm/dbio/cm_rankfx_dbio.h b/app/src/cm/dbio/cm_rankfx_dbio.h new file mode 100644 index 0000000..1139c77 --- /dev/null +++ b/app/src/cm/dbio/cm_rankfx_dbio.h @@ -0,0 +1,11 @@ +/* + * cm_rankfx_dbio.h - cm 모듈 DB 접근 copybook (cm_rankfx_dbio). 기준일별 최고환율(랭크1) 통화 수 (ROW_NUMBER 윈도우 커서). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_RANKFX_DBIO_H +#define CM_RANKFX_DBIO_H + +long cmdb_rank_fx(const char *bizdate); + +#endif /* CM_RANKFX_DBIO_H */ diff --git a/app/src/cm/dbio/cm_rankfx_dbio.pgc b/app/src/cm/dbio/cm_rankfx_dbio.pgc new file mode 100644 index 0000000..14c5673 --- /dev/null +++ b/app/src/cm/dbio/cm_rankfx_dbio.pgc @@ -0,0 +1,31 @@ +/* + * cm_rankfx_dbio.pgc - cm 모듈 DB 접근 함수 (cm_rankfx_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 기준일별 최고환율(랭크1) 통화 수 (ROW_NUMBER 윈도우 커서). + */ +#include +#include +#include "cm_rankfx_dbio.h" + +long cmdb_rank_fx(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_ccy[8]; + long h_rn, tops = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE crk CURSOR FOR + SELECT ccy, + ROW_NUMBER() OVER (PARTITION BY base_date ORDER BY rate_bps DESC) + FROM cm_fx_rate WHERE base_date = :h_bizdate; + EXEC SQL OPEN crk; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH crk INTO :h_ccy, :h_rn; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE crk; return -1; } + if (h_rn == 1) tops++; + } + EXEC SQL CLOSE crk; + return tops; +} diff --git a/app/src/cm/dbio/cm_sortrange_dbio.h b/app/src/cm/dbio/cm_sortrange_dbio.h new file mode 100644 index 0000000..f9a54af --- /dev/null +++ b/app/src/cm/dbio/cm_sortrange_dbio.h @@ -0,0 +1,11 @@ +/* + * cm_sortrange_dbio.h - cm 모듈 DB 접근 copybook (cm_sortrange_dbio). 코드그룹 내 정렬순서 최대-최소 범위 (min/max). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef CM_SORTRANGE_DBIO_H +#define CM_SORTRANGE_DBIO_H + +long cmdb_sort_spread(const char *grp); + +#endif /* CM_SORTRANGE_DBIO_H */ diff --git a/app/src/cm/dbio/cm_sortrange_dbio.pgc b/app/src/cm/dbio/cm_sortrange_dbio.pgc new file mode 100644 index 0000000..20269b2 --- /dev/null +++ b/app/src/cm/dbio/cm_sortrange_dbio.pgc @@ -0,0 +1,21 @@ +/* + * cm_sortrange_dbio.pgc - cm 모듈 DB 접근 함수 (cm_sortrange_dbio), archived into libcmdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 코드그룹 내 정렬순서 최대-최소 범위 (min/max). + */ +#include +#include +#include "cm_sortrange_dbio.h" + +long cmdb_sort_spread(const char *grp) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_grp[32]; + long h_lo = 0, h_hi = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_grp, grp, sizeof(h_grp)-1); h_grp[sizeof(h_grp)-1] = 0; + EXEC SQL SELECT coalesce(min(sort_ord),0), coalesce(max(sort_ord),0) + INTO :h_lo, :h_hi FROM cm_code WHERE code_grp = :h_grp AND use_yn = 'Y'; + if (sqlca.sqlcode < 0) return -1; + return h_hi - h_lo; +} diff --git a/app/src/lg/batch/lg_dcweight_batch.pgc b/app/src/lg/batch/lg_dcweight_batch.pgc new file mode 100644 index 0000000..8be7b1e --- /dev/null +++ b/app/src/lg/batch/lg_dcweight_batch.pgc @@ -0,0 +1,47 @@ +/* + * lg_dcweight_batch.pgc - lg 계정 진행잔액 최고점 (윈도우 배치, XA). + * + * Computes the peak running balance (debit +, credit -) of an account ordered + * by line_id using a SUM() OVER window, inside ONE XA transaction. + * Usage: lg_dcweight_batch [ACCT] [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_dcweight_batch.h" + +int main(int argc, char **argv) +{ + const char *acct = (argc > 1) ? argv[1] : "RECV"; + const char *bizdate = (argc > 2) ? argv[2] : "2026-07-19"; + lg_dcweight_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_acct[32], h_bizdate[16]; + long h_peak; + EXEC SQL END DECLARE SECTION; + + 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; + + 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(max(run_bal),0) INTO :h_peak FROM ( + SELECT sum(CASE WHEN dc = 'D' THEN amount ELSE -amount END) + OVER (ORDER BY line_id) AS run_bal + FROM lg_ledger_line WHERE acct = :h_acct AND biz_date = :h_bizdate) w; + if (sqlca.sqlcode < 0) { fprintf(stderr, "lg_dcweight_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; } + + strncpy(rec.acct, acct, sizeof(rec.acct)-1); rec.acct[sizeof(rec.acct)-1] = 0; + strncpy(rec.biz_date, bizdate, sizeof(rec.biz_date)-1); rec.biz_date[sizeof(rec.biz_date)-1] = 0; + rec.peak = h_peak; + printf(">>> lg_dcweight_batch COMMIT: acct=%s date=%s peak=%ld\n", rec.acct, rec.biz_date, rec.peak); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_imbalscan_batch.pgc b/app/src/lg/batch/lg_imbalscan_batch.pgc new file mode 100644 index 0000000..7bac80c --- /dev/null +++ b/app/src/lg/batch/lg_imbalscan_batch.pgc @@ -0,0 +1,54 @@ +/* + * lg_imbalscan_batch.pgc - lg 계정 대차 불균형 스캔 (커서 배치, XA). + * + * Walks an account-grouped cursor summing debit/credit per account and counts + * how many accounts are out of balance, inside ONE XA transaction. + * Usage: lg_imbalscan_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_imbalscan_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + lg_imbalscan_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_acct[32]; + long h_dr, h_cr; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + rec.accts = 0; rec.imbalanced = 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 iscur CURSOR FOR + SELECT acct, coalesce(sum(amount) FILTER (WHERE dc = 'D'),0), + coalesce(sum(amount) FILTER (WHERE dc = 'C'),0) + FROM lg_ledger_line WHERE biz_date = :h_bizdate GROUP BY acct ORDER BY acct; + EXEC SQL OPEN iscur; + if (sqlca.sqlcode < 0) { fprintf(stderr, "lg_imbalscan_batch OPEN FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH iscur INTO :h_acct, :h_dr, :h_cr; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE iscur; fprintf(stderr, "lg_imbalscan_batch FETCH FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + rec.accts++; + if (h_dr != h_cr) rec.imbalanced++; + } + EXEC SQL CLOSE iscur; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + + strncpy(rec.biz_date, bizdate, sizeof(rec.biz_date)-1); rec.biz_date[sizeof(rec.biz_date)-1] = 0; + printf(">>> lg_imbalscan_batch COMMIT: date=%s accts=%ld imbalanced=%ld\n", + rec.biz_date, rec.accts, rec.imbalanced); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_linepurge_batch.pgc b/app/src/lg/batch/lg_linepurge_batch.pgc new file mode 100644 index 0000000..5963697 --- /dev/null +++ b/app/src/lg/batch/lg_linepurge_batch.pgc @@ -0,0 +1,43 @@ +/* + * lg_linepurge_batch.pgc - lg 마감 분개라인 정리 (기간 DELETE 배치, XA). + * + * Removes closed ledger lines older than a cutoff date with a single ranged + * DELETE, inside ONE XA transaction. + * Usage: lg_linepurge_batch BEFORE_DATE + */ +#include +#include +#include +#include +#include "lg_linepurge_batch.h" + +int main(int argc, char **argv) +{ + const char *before = (argc > 1) ? argv[1] : "2026-01-01"; + lg_linepurge_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_before[16]; + long h_removed; + EXEC SQL END DECLARE SECTION; + + strncpy(h_before, before, sizeof(h_before)-1); h_before[sizeof(h_before)-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 lg_ledger_line + WHERE status = 'closed' AND biz_date < :h_before; + if (sqlca.sqlcode < 0) { fprintf(stderr, "lg_linepurge_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_removed = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + + strncpy(rec.before, before, sizeof(rec.before)-1); rec.before[sizeof(rec.before)-1] = 0; + rec.removed = h_removed; + printf(">>> lg_linepurge_batch COMMIT: before=%s removed=%ld\n", rec.before, rec.removed); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_reversepair_batch.pgc b/app/src/lg/batch/lg_reversepair_batch.pgc new file mode 100644 index 0000000..a4dca20 --- /dev/null +++ b/app/src/lg/batch/lg_reversepair_batch.pgc @@ -0,0 +1,44 @@ +/* + * lg_reversepair_batch.pgc - lg 역분개 상쇄 집계 (EXISTS 상관 배치, XA). + * + * Counts POST vouchers that have a matching REVERSE voucher via a correlated + * EXISTS subquery, inside ONE XA transaction. + * Usage: lg_reversepair_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_reversepair_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + lg_reversepair_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + 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 SELECT count(*) INTO :h_cnt FROM lg_voucher v + WHERE v.voucher_type = 'POST' AND v.biz_date = :h_bizdate + AND EXISTS (SELECT 1 FROM lg_voucher r + WHERE r.voucher_type = 'REVERSE' AND r.ref_id = v.voucher_id); + if (sqlca.sqlcode < 0) { fprintf(stderr, "lg_reversepair_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; } + + strncpy(rec.biz_date, bizdate, sizeof(rec.biz_date)-1); rec.biz_date[sizeof(rec.biz_date)-1] = 0; + rec.reversed_posts = h_cnt; + printf(">>> lg_reversepair_batch COMMIT: date=%s reversed_posts=%ld\n", rec.biz_date, rec.reversed_posts); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/batch/lg_trialload_batch.pgc b/app/src/lg/batch/lg_trialload_batch.pgc new file mode 100644 index 0000000..06f2c39 --- /dev/null +++ b/app/src/lg/batch/lg_trialload_batch.pgc @@ -0,0 +1,51 @@ +/* + * lg_trialload_batch.pgc - lg 시산표 집계 적재 (INSERT..SELECT 배치, XA). + * + * Aggregates ledger lines into per-account debit/credit/diff and upserts the + * trial balance with a single INSERT..SELECT, inside ONE XA transaction. + * Usage: lg_trialload_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "lg_trialload_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + lg_trialload_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_accts; + 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 lg_trial_bal (acct, biz_date, debit_total, credit_total, diff) + SELECT acct, :h_bizdate, + 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 WHERE biz_date = :h_bizdate 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, "lg_trialload_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_accts = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + + strncpy(rec.biz_date, bizdate, sizeof(rec.biz_date)-1); rec.biz_date[sizeof(rec.biz_date)-1] = 0; + rec.accts_loaded = h_accts; + printf(">>> lg_trialload_batch COMMIT: date=%s accts_loaded=%ld\n", rec.biz_date, rec.accts_loaded); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/lg/dbio/lg_dc_ratio_dbio.h b/app/src/lg/dbio/lg_dc_ratio_dbio.h new file mode 100644 index 0000000..b45136d --- /dev/null +++ b/app/src/lg/dbio/lg_dc_ratio_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_dc_ratio_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_dc_ratio_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_DC_RATIO_DBIO_H +#define LG_DC_RATIO_DBIO_H + +long lgqdb_credit_debit_pct(const char *bizdate); + +#endif /* LG_DC_RATIO_DBIO_H */ diff --git a/app/src/lg/dbio/lg_dc_ratio_dbio.pgc b/app/src/lg/dbio/lg_dc_ratio_dbio.pgc new file mode 100644 index 0000000..012b63d --- /dev/null +++ b/app/src/lg/dbio/lg_dc_ratio_dbio.pgc @@ -0,0 +1,25 @@ +/* + * lg_dc_ratio_dbio.pgc - lg 모듈 DB 접근 함수 (lg_dc_ratio_dbio), archived into liblgdbio.a. + * 당일 대변합이 차변합에서 차지하는 비율(퍼센트)을 산출한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_dc_ratio_dbio.h" + +/* 대차 비율(%): 대변합 * 100 / 차변합, CASE 로 0 나눗셈 방지. */ +long lgqdb_credit_debit_pct(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_pct = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT CASE WHEN coalesce(sum(amount) FILTER (WHERE dc = 'D'),0) > 0 + THEN coalesce(sum(amount) FILTER (WHERE dc = 'C'),0) * 100 + / sum(amount) FILTER (WHERE dc = 'D') + ELSE 0 END + INTO :h_pct FROM lg_ledger_line WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("lgqdb_credit_debit_pct FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_pct; +} diff --git a/app/src/lg/dbio/lg_dcweight_batch.h b/app/src/lg/dbio/lg_dcweight_batch.h new file mode 100644 index 0000000..75f2b06 --- /dev/null +++ b/app/src/lg/dbio/lg_dcweight_batch.h @@ -0,0 +1,14 @@ +/* + * lg_dcweight_batch.h - lg 진행잔액 최고점 배치 전용 copybook. + * 계정 누계잔액 최고점 레이아웃. + */ +#ifndef LG_DCWEIGHT_BATCH_H +#define LG_DCWEIGHT_BATCH_H + +typedef struct { + char acct[32]; + char biz_date[16]; + long peak; +} lg_dcweight_rec_t; + +#endif /* LG_DCWEIGHT_BATCH_H */ diff --git a/app/src/lg/dbio/lg_imbalscan_batch.h b/app/src/lg/dbio/lg_imbalscan_batch.h new file mode 100644 index 0000000..3b07374 --- /dev/null +++ b/app/src/lg/dbio/lg_imbalscan_batch.h @@ -0,0 +1,14 @@ +/* + * lg_imbalscan_batch.h - lg 계정 불균형 스캔 배치 전용 copybook. + * 계정별 차/대 순회 결과 누적 레이아웃. + */ +#ifndef LG_IMBALSCAN_BATCH_H +#define LG_IMBALSCAN_BATCH_H + +typedef struct { + char biz_date[16]; + long accts; + long imbalanced; +} lg_imbalscan_rec_t; + +#endif /* LG_IMBALSCAN_BATCH_H */ diff --git a/app/src/lg/dbio/lg_line_corr_dbio.h b/app/src/lg/dbio/lg_line_corr_dbio.h new file mode 100644 index 0000000..7a099e5 --- /dev/null +++ b/app/src/lg/dbio/lg_line_corr_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_line_corr_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_line_corr_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_LINE_CORR_DBIO_H +#define LG_LINE_CORR_DBIO_H + +long lgqdb_line_above_acct_avg(const char *bizdate); + +#endif /* LG_LINE_CORR_DBIO_H */ diff --git a/app/src/lg/dbio/lg_line_corr_dbio.pgc b/app/src/lg/dbio/lg_line_corr_dbio.pgc new file mode 100644 index 0000000..3bd94d3 --- /dev/null +++ b/app/src/lg/dbio/lg_line_corr_dbio.pgc @@ -0,0 +1,24 @@ +/* + * lg_line_corr_dbio.pgc - lg 모듈 DB 접근 함수 (lg_line_corr_dbio), archived into liblgdbio.a. + * 같은 계정과목 평균 분개액을 초과하는 라인 수를 상관 서브쿼리로 센다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_line_corr_dbio.h" + +/* 상관 서브쿼리: 자기 계정 평균 분개액 초과 라인 수. */ +long lgqdb_line_above_acct_avg(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_cnt FROM lg_ledger_line l1 + WHERE l1.biz_date = :h_bizdate + AND l1.amount > (SELECT avg(l2.amount) FROM lg_ledger_line l2 + WHERE l2.acct = l1.acct AND l2.biz_date = l1.biz_date); + if (sqlca.sqlcode < 0) { userlog("lgqdb_line_above_acct_avg FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_cnt; +} diff --git a/app/src/lg/dbio/lg_line_delrange_dbio.h b/app/src/lg/dbio/lg_line_delrange_dbio.h new file mode 100644 index 0000000..0065bba --- /dev/null +++ b/app/src/lg/dbio/lg_line_delrange_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_line_delrange_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_line_delrange_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_LINE_DELRANGE_DBIO_H +#define LG_LINE_DELRANGE_DBIO_H + +long lgqdb_purge_closed_lines(const char *before); + +#endif /* LG_LINE_DELRANGE_DBIO_H */ diff --git a/app/src/lg/dbio/lg_line_delrange_dbio.pgc b/app/src/lg/dbio/lg_line_delrange_dbio.pgc new file mode 100644 index 0000000..a0b7e4f --- /dev/null +++ b/app/src/lg/dbio/lg_line_delrange_dbio.pgc @@ -0,0 +1,21 @@ +/* + * lg_line_delrange_dbio.pgc - lg 모듈 DB 접근 함수 (lg_line_delrange_dbio), archived into liblgdbio.a. + * 기준일 이전의 마감(closed) 분개 라인을 범위 삭제한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_line_delrange_dbio.h" + +/* DELETE 범위: before 이전 closed 라인 삭제, 삭제 건수 반환. */ +long lgqdb_purge_closed_lines(const char *before) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_before[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_before, before, sizeof(h_before)-1); h_before[sizeof(h_before)-1] = 0; + EXEC SQL DELETE FROM lg_ledger_line + WHERE status = 'closed' AND biz_date < :h_before; + if (sqlca.sqlcode < 0) { userlog("lgqdb_purge_closed_lines FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return sqlca.sqlerrd[2]; +} diff --git a/app/src/lg/dbio/lg_line_span_dbio.h b/app/src/lg/dbio/lg_line_span_dbio.h new file mode 100644 index 0000000..deaae07 --- /dev/null +++ b/app/src/lg/dbio/lg_line_span_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_line_span_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_line_span_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_LINE_SPAN_DBIO_H +#define LG_LINE_SPAN_DBIO_H + +int lgqdb_line_amount_span(const char *acct, const char *bizdate, long *mn, long *mx); + +#endif /* LG_LINE_SPAN_DBIO_H */ diff --git a/app/src/lg/dbio/lg_line_span_dbio.pgc b/app/src/lg/dbio/lg_line_span_dbio.pgc new file mode 100644 index 0000000..1cd9a3d --- /dev/null +++ b/app/src/lg/dbio/lg_line_span_dbio.pgc @@ -0,0 +1,25 @@ +/* + * lg_line_span_dbio.pgc - lg 모듈 DB 접근 함수 (lg_line_span_dbio), archived into liblgdbio.a. + * 계정 x 영업일 분개액의 최소/최대를 한 번에 조회한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_line_span_dbio.h" + +/* min/max: 계정 분개액 범위를 out 파라미터로 반환. */ +int lgqdb_line_amount_span(const char *acct, const char *bizdate, long *mn, long *mx) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_acct[32], h_bizdate[16]; + long h_mn = 0, h_mx = 0; + EXEC SQL END DECLARE SECTION; + 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 SELECT coalesce(min(amount),0), coalesce(max(amount),0) + INTO :h_mn, :h_mx FROM lg_ledger_line + WHERE acct = :h_acct AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("lgqdb_line_amount_span FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + *mn = h_mn; *mx = h_mx; + return 0; +} diff --git a/app/src/lg/dbio/lg_line_window_dbio.h b/app/src/lg/dbio/lg_line_window_dbio.h new file mode 100644 index 0000000..b83b545 --- /dev/null +++ b/app/src/lg/dbio/lg_line_window_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_line_window_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_line_window_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_LINE_WINDOW_DBIO_H +#define LG_LINE_WINDOW_DBIO_H + +long lgqdb_running_balance_peak(const char *acct, const char *bizdate); + +#endif /* LG_LINE_WINDOW_DBIO_H */ diff --git a/app/src/lg/dbio/lg_line_window_dbio.pgc b/app/src/lg/dbio/lg_line_window_dbio.pgc new file mode 100644 index 0000000..8ebf20c --- /dev/null +++ b/app/src/lg/dbio/lg_line_window_dbio.pgc @@ -0,0 +1,25 @@ +/* + * lg_line_window_dbio.pgc - lg 모듈 DB 접근 함수 (lg_line_window_dbio), archived into liblgdbio.a. + * 계정 분개를 라인 순서로 누계(차변 +, 대변 -)한 잔액의 최대치를 윈도우로 구한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_line_window_dbio.h" + +/* 윈도우 누계: 계정 분개 진행잔액(차-대) 의 최고점. */ +long lgqdb_running_balance_peak(const char *acct, const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_acct[32], h_bizdate[16]; + long h_peak = 0; + EXEC SQL END DECLARE SECTION; + 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 SELECT coalesce(max(run_bal),0) INTO :h_peak FROM ( + SELECT sum(CASE WHEN dc = 'D' THEN amount ELSE -amount END) + OVER (ORDER BY line_id) AS run_bal + FROM lg_ledger_line WHERE acct = :h_acct AND biz_date = :h_bizdate) w; + if (sqlca.sqlcode < 0) { userlog("lgqdb_running_balance_peak FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_peak; +} diff --git a/app/src/lg/dbio/lg_linepurge_batch.h b/app/src/lg/dbio/lg_linepurge_batch.h new file mode 100644 index 0000000..91d8f84 --- /dev/null +++ b/app/src/lg/dbio/lg_linepurge_batch.h @@ -0,0 +1,13 @@ +/* + * lg_linepurge_batch.h - lg 마감라인 정리 배치 전용 copybook. + * 삭제 라인 수 레이아웃. + */ +#ifndef LG_LINEPURGE_BATCH_H +#define LG_LINEPURGE_BATCH_H + +typedef struct { + char before[16]; + long removed; +} lg_linepurge_rec_t; + +#endif /* LG_LINEPURGE_BATCH_H */ diff --git a/app/src/lg/dbio/lg_merch_accum_dbio.h b/app/src/lg/dbio/lg_merch_accum_dbio.h new file mode 100644 index 0000000..9d4c420 --- /dev/null +++ b/app/src/lg/dbio/lg_merch_accum_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_merch_accum_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_merch_accum_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_MERCH_ACCUM_DBIO_H +#define LG_MERCH_ACCUM_DBIO_H + +int lgqdb_merch_accumulate(const char *merch, const char *bizdate, long debit, long credit); + +#endif /* LG_MERCH_ACCUM_DBIO_H */ diff --git a/app/src/lg/dbio/lg_merch_accum_dbio.pgc b/app/src/lg/dbio/lg_merch_accum_dbio.pgc new file mode 100644 index 0000000..16e6b0e --- /dev/null +++ b/app/src/lg/dbio/lg_merch_accum_dbio.pgc @@ -0,0 +1,29 @@ +/* + * lg_merch_accum_dbio.pgc - lg 모듈 DB 접근 함수 (lg_merch_accum_dbio), archived into liblgdbio.a. + * 가맹점 잔액에 차변/대변 증분을 누적 가산하고 잔액(차-대)을 갱신한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_merch_accum_dbio.h" + +/* 누적 upsert: 가맹점 잔액에 차/대 증분을 더하고 balance 재계산. */ +int lgqdb_merch_accumulate(const char *merch, const char *bizdate, long debit, long credit) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16]; + long h_dr = debit, h_cr = credit; + EXEC SQL END DECLARE SECTION; + 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 INSERT INTO lg_merch_bal (merchant_id, biz_date, debit_total, credit_total, balance) + VALUES (:h_merch, :h_bizdate, :h_dr, :h_cr, :h_dr - :h_cr) + ON CONFLICT (merchant_id, biz_date) DO UPDATE + SET debit_total = lg_merch_bal.debit_total + EXCLUDED.debit_total, + credit_total = lg_merch_bal.credit_total + EXCLUDED.credit_total, + balance = lg_merch_bal.debit_total + EXCLUDED.debit_total + - lg_merch_bal.credit_total - EXCLUDED.credit_total, + updated_at = now(); + if (sqlca.sqlcode < 0) { userlog("lgqdb_merch_accumulate FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return 0; +} diff --git a/app/src/lg/dbio/lg_merch_having_dbio.h b/app/src/lg/dbio/lg_merch_having_dbio.h new file mode 100644 index 0000000..ff0d816 --- /dev/null +++ b/app/src/lg/dbio/lg_merch_having_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_merch_having_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_merch_having_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_MERCH_HAVING_DBIO_H +#define LG_MERCH_HAVING_DBIO_H + +long lgqdb_merch_imbalanced(const char *bizdate); + +#endif /* LG_MERCH_HAVING_DBIO_H */ diff --git a/app/src/lg/dbio/lg_merch_having_dbio.pgc b/app/src/lg/dbio/lg_merch_having_dbio.pgc new file mode 100644 index 0000000..5d01bc7 --- /dev/null +++ b/app/src/lg/dbio/lg_merch_having_dbio.pgc @@ -0,0 +1,25 @@ +/* + * lg_merch_having_dbio.pgc - lg 모듈 DB 접근 함수 (lg_merch_having_dbio), archived into liblgdbio.a. + * 가맹점별 차변합과 대변합이 일치하지 않는 가맹점 수를 HAVING 으로 센다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_merch_having_dbio.h" + +/* GROUP BY HAVING: 차/대 불일치 가맹점 수. */ +long lgqdb_merch_imbalanced(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_cnt FROM ( + SELECT merchant_id FROM lg_ledger_line WHERE biz_date = :h_bizdate + GROUP BY merchant_id + HAVING coalesce(sum(amount) FILTER (WHERE dc = 'D'),0) + <> coalesce(sum(amount) FILTER (WHERE dc = 'C'),0)) m; + if (sqlca.sqlcode < 0) { userlog("lgqdb_merch_imbalanced FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_cnt; +} diff --git a/app/src/lg/dbio/lg_posted_join_dbio.h b/app/src/lg/dbio/lg_posted_join_dbio.h new file mode 100644 index 0000000..f38b2bf --- /dev/null +++ b/app/src/lg/dbio/lg_posted_join_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_posted_join_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_posted_join_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_POSTED_JOIN_DBIO_H +#define LG_POSTED_JOIN_DBIO_H + +long lgqdb_posted_debit(const char *bizdate); + +#endif /* LG_POSTED_JOIN_DBIO_H */ diff --git a/app/src/lg/dbio/lg_posted_join_dbio.pgc b/app/src/lg/dbio/lg_posted_join_dbio.pgc new file mode 100644 index 0000000..275725e --- /dev/null +++ b/app/src/lg/dbio/lg_posted_join_dbio.pgc @@ -0,0 +1,23 @@ +/* + * lg_posted_join_dbio.pgc - lg 모듈 DB 접근 함수 (lg_posted_join_dbio), archived into liblgdbio.a. + * 전기완료(P) 전표와 조인해 차변(D) 라인 금액 합을 구한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_posted_join_dbio.h" + +/* JOIN: 전기완료 전표의 차변 라인 합계. */ +long lgqdb_posted_debit(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_sum = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(l.amount),0) INTO :h_sum + FROM lg_ledger_line l JOIN lg_voucher v ON l.voucher_id = v.voucher_id + WHERE v.status = 'P' AND v.biz_date = :h_bizdate AND l.dc = 'D'; + if (sqlca.sqlcode < 0) { userlog("lgqdb_posted_debit FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_sum; +} diff --git a/app/src/lg/dbio/lg_reverse_pair_dbio.h b/app/src/lg/dbio/lg_reverse_pair_dbio.h new file mode 100644 index 0000000..a34f7a8 --- /dev/null +++ b/app/src/lg/dbio/lg_reverse_pair_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_reverse_pair_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_reverse_pair_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_REVERSE_PAIR_DBIO_H +#define LG_REVERSE_PAIR_DBIO_H + +long lgqdb_reversed_posts(const char *bizdate); + +#endif /* LG_REVERSE_PAIR_DBIO_H */ diff --git a/app/src/lg/dbio/lg_reverse_pair_dbio.pgc b/app/src/lg/dbio/lg_reverse_pair_dbio.pgc new file mode 100644 index 0000000..2cac4a2 --- /dev/null +++ b/app/src/lg/dbio/lg_reverse_pair_dbio.pgc @@ -0,0 +1,24 @@ +/* + * lg_reverse_pair_dbio.pgc - lg 모듈 DB 접근 함수 (lg_reverse_pair_dbio), archived into liblgdbio.a. + * 자신을 참조하는 역분개(REVERSE) 전표가 존재하는 전기 전표 수를 EXISTS 로 센다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_reverse_pair_dbio.h" + +/* EXISTS 상관: REVERSE 로 상쇄된 POST 전표 수. */ +long lgqdb_reversed_posts(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_cnt FROM lg_voucher v + WHERE v.voucher_type = 'POST' AND v.biz_date = :h_bizdate + AND EXISTS (SELECT 1 FROM lg_voucher r + WHERE r.voucher_type = 'REVERSE' AND r.ref_id = v.voucher_id); + if (sqlca.sqlcode < 0) { userlog("lgqdb_reversed_posts FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_cnt; +} diff --git a/app/src/lg/dbio/lg_reversepair_batch.h b/app/src/lg/dbio/lg_reversepair_batch.h new file mode 100644 index 0000000..7be693a --- /dev/null +++ b/app/src/lg/dbio/lg_reversepair_batch.h @@ -0,0 +1,13 @@ +/* + * lg_reversepair_batch.h - lg 역분개 대응 배치 전용 copybook. + * 상쇄 전표 수 레이아웃. + */ +#ifndef LG_REVERSEPAIR_BATCH_H +#define LG_REVERSEPAIR_BATCH_H + +typedef struct { + char biz_date[16]; + long reversed_posts; +} lg_reversepair_rec_t; + +#endif /* LG_REVERSEPAIR_BATCH_H */ diff --git a/app/src/lg/dbio/lg_settle_lines_dbio.h b/app/src/lg/dbio/lg_settle_lines_dbio.h new file mode 100644 index 0000000..8d34f32 --- /dev/null +++ b/app/src/lg/dbio/lg_settle_lines_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_settle_lines_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_settle_lines_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_SETTLE_LINES_DBIO_H +#define LG_SETTLE_LINES_DBIO_H + +long lgqdb_settle_line_sum(const char *bizdate); + +#endif /* LG_SETTLE_LINES_DBIO_H */ diff --git a/app/src/lg/dbio/lg_settle_lines_dbio.pgc b/app/src/lg/dbio/lg_settle_lines_dbio.pgc new file mode 100644 index 0000000..edaadda --- /dev/null +++ b/app/src/lg/dbio/lg_settle_lines_dbio.pgc @@ -0,0 +1,23 @@ +/* + * lg_settle_lines_dbio.pgc - lg 모듈 DB 접근 함수 (lg_settle_lines_dbio), archived into liblgdbio.a. + * 정산연계(SETTLE) 전표에 속한 분개 라인 금액 합을 IN 서브쿼리로 구한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_settle_lines_dbio.h" + +/* IN 서브쿼리: SETTLE 전표 소속 라인 금액 합. */ +long lgqdb_settle_line_sum(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_sum = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_sum FROM lg_ledger_line + WHERE voucher_id IN (SELECT voucher_id FROM lg_voucher + WHERE voucher_type = 'SETTLE' AND biz_date = :h_bizdate); + if (sqlca.sqlcode < 0) { userlog("lgqdb_settle_line_sum FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_sum; +} diff --git a/app/src/lg/dbio/lg_trial_load_dbio.h b/app/src/lg/dbio/lg_trial_load_dbio.h new file mode 100644 index 0000000..12e73cd --- /dev/null +++ b/app/src/lg/dbio/lg_trial_load_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_trial_load_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_trial_load_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_TRIAL_LOAD_DBIO_H +#define LG_TRIAL_LOAD_DBIO_H + +long lgqdb_trial_load(const char *bizdate); + +#endif /* LG_TRIAL_LOAD_DBIO_H */ diff --git a/app/src/lg/dbio/lg_trial_load_dbio.pgc b/app/src/lg/dbio/lg_trial_load_dbio.pgc new file mode 100644 index 0000000..27133c0 --- /dev/null +++ b/app/src/lg/dbio/lg_trial_load_dbio.pgc @@ -0,0 +1,29 @@ +/* + * lg_trial_load_dbio.pgc - lg 모듈 DB 접근 함수 (lg_trial_load_dbio), archived into liblgdbio.a. + * 원장 라인을 계정별 차/대 합으로 집계해 시산표에 INSERT..SELECT 적재한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_trial_load_dbio.h" + +/* INSERT..SELECT: 계정별 차변/대변/차액을 시산표에 upsert 적재, 계정 수 반환. */ +long lgqdb_trial_load(const char *bizdate) +{ + 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; + EXEC SQL INSERT INTO lg_trial_bal (acct, biz_date, debit_total, credit_total, diff) + SELECT acct, :h_bizdate, + 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 WHERE biz_date = :h_bizdate 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) { userlog("lgqdb_trial_load FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return sqlca.sqlerrd[2]; +} diff --git a/app/src/lg/dbio/lg_trialload_batch.h b/app/src/lg/dbio/lg_trialload_batch.h new file mode 100644 index 0000000..4bb4973 --- /dev/null +++ b/app/src/lg/dbio/lg_trialload_batch.h @@ -0,0 +1,13 @@ +/* + * lg_trialload_batch.h - lg 시산표 적재 배치 전용 copybook. + * 적재 계정 수 레이아웃. + */ +#ifndef LG_TRIALLOAD_BATCH_H +#define LG_TRIALLOAD_BATCH_H + +typedef struct { + char biz_date[16]; + long accts_loaded; +} lg_trialload_rec_t; + +#endif /* LG_TRIALLOAD_BATCH_H */ diff --git a/app/src/lg/dbio/lg_type_walk_dbio.h b/app/src/lg/dbio/lg_type_walk_dbio.h new file mode 100644 index 0000000..7cc25b0 --- /dev/null +++ b/app/src/lg/dbio/lg_type_walk_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_type_walk_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_type_walk_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_TYPE_WALK_DBIO_H +#define LG_TYPE_WALK_DBIO_H + +long lgqdb_type_walk_heavy(const char *bizdate, long floor_amt); + +#endif /* LG_TYPE_WALK_DBIO_H */ diff --git a/app/src/lg/dbio/lg_type_walk_dbio.pgc b/app/src/lg/dbio/lg_type_walk_dbio.pgc new file mode 100644 index 0000000..fa6d823 --- /dev/null +++ b/app/src/lg/dbio/lg_type_walk_dbio.pgc @@ -0,0 +1,33 @@ +/* + * lg_type_walk_dbio.pgc - lg 모듈 DB 접근 함수 (lg_type_walk_dbio), archived into liblgdbio.a. + * 전표 유형별 라인 합계를 커서로 순회해 큰 유형(기준액 이상) 수를 센다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_type_walk_dbio.h" + +/* 커서 순회: 전표 유형별 (건수, 합계) 를 훑어 floor_amt 이상 유형 수 반환. */ +long lgqdb_type_walk_heavy(const char *bizdate, long floor_amt) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_type[16]; + long h_floor = floor_amt, h_cnt, h_sum; + EXEC SQL END DECLARE SECTION; + long heavy = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE lgtw CURSOR FOR + SELECT v.voucher_type, count(l.line_id), coalesce(sum(l.amount),0) + FROM lg_voucher v JOIN lg_ledger_line l ON l.voucher_id = v.voucher_id + WHERE v.biz_date = :h_bizdate GROUP BY v.voucher_type ORDER BY v.voucher_type; + EXEC SQL OPEN lgtw; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH lgtw INTO :h_type, :h_cnt, :h_sum; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE lgtw; return -1; } + if (h_sum >= h_floor) heavy++; + } + EXEC SQL CLOSE lgtw; + return heavy; +} diff --git a/app/src/lg/dbio/lg_voucher_empty_dbio.h b/app/src/lg/dbio/lg_voucher_empty_dbio.h new file mode 100644 index 0000000..a9cd2ca --- /dev/null +++ b/app/src/lg/dbio/lg_voucher_empty_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_voucher_empty_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_voucher_empty_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_VOUCHER_EMPTY_DBIO_H +#define LG_VOUCHER_EMPTY_DBIO_H + +long lgqdb_voucher_no_lines(const char *bizdate); + +#endif /* LG_VOUCHER_EMPTY_DBIO_H */ diff --git a/app/src/lg/dbio/lg_voucher_empty_dbio.pgc b/app/src/lg/dbio/lg_voucher_empty_dbio.pgc new file mode 100644 index 0000000..8d94904 --- /dev/null +++ b/app/src/lg/dbio/lg_voucher_empty_dbio.pgc @@ -0,0 +1,23 @@ +/* + * lg_voucher_empty_dbio.pgc - lg 모듈 DB 접근 함수 (lg_voucher_empty_dbio), archived into liblgdbio.a. + * 분개 라인이 하나도 없는 빈 전표를 NOT EXISTS 로 색출한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_voucher_empty_dbio.h" + +/* NOT EXISTS: 라인이 없는 당일 전표(미분개 헤더) 수. */ +long lgqdb_voucher_no_lines(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_cnt FROM lg_voucher v + WHERE v.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM lg_ledger_line l WHERE l.voucher_id = v.voucher_id); + if (sqlca.sqlcode < 0) { userlog("lgqdb_voucher_no_lines FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_cnt; +} diff --git a/app/src/lg/dbio/lg_voucher_mix_dbio.h b/app/src/lg/dbio/lg_voucher_mix_dbio.h new file mode 100644 index 0000000..34ecf27 --- /dev/null +++ b/app/src/lg/dbio/lg_voucher_mix_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_voucher_mix_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_voucher_mix_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_VOUCHER_MIX_DBIO_H +#define LG_VOUCHER_MIX_DBIO_H + +int lgqdb_voucher_type_mix(const char *bizdate, long *kinds, long *reverses); + +#endif /* LG_VOUCHER_MIX_DBIO_H */ diff --git a/app/src/lg/dbio/lg_voucher_mix_dbio.pgc b/app/src/lg/dbio/lg_voucher_mix_dbio.pgc new file mode 100644 index 0000000..7438bfc --- /dev/null +++ b/app/src/lg/dbio/lg_voucher_mix_dbio.pgc @@ -0,0 +1,24 @@ +/* + * lg_voucher_mix_dbio.pgc - lg 모듈 DB 접근 함수 (lg_voucher_mix_dbio), archived into liblgdbio.a. + * 당일 전표 유형 가짓수와 역분개(REVERSE) 전표 수를 함께 산출한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_voucher_mix_dbio.h" + +/* count DISTINCT + FILTER: 전표 유형 수와 역분개 전표 수. */ +int lgqdb_voucher_type_mix(const char *bizdate, long *kinds, long *reverses) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_k = 0, h_r = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(DISTINCT voucher_type), + count(*) FILTER (WHERE voucher_type = 'REVERSE') + INTO :h_k, :h_r FROM lg_voucher WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("lgqdb_voucher_type_mix FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + *kinds = h_k; *reverses = h_r; + return 0; +} diff --git a/app/src/lg/dbio/lg_worst_acct_dbio.h b/app/src/lg/dbio/lg_worst_acct_dbio.h new file mode 100644 index 0000000..eb1c874 --- /dev/null +++ b/app/src/lg/dbio/lg_worst_acct_dbio.h @@ -0,0 +1,11 @@ +/* + * lg_worst_acct_dbio.h - lg 모듈 DB 접근 계층 copybook (lg_worst_acct_dbio, part of liblgdbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef LG_WORST_ACCT_DBIO_H +#define LG_WORST_ACCT_DBIO_H + +int lgqdb_worst_acct(const char *bizdate, char *acct_out, long *diff_out); + +#endif /* LG_WORST_ACCT_DBIO_H */ diff --git a/app/src/lg/dbio/lg_worst_acct_dbio.pgc b/app/src/lg/dbio/lg_worst_acct_dbio.pgc new file mode 100644 index 0000000..ea14bbf --- /dev/null +++ b/app/src/lg/dbio/lg_worst_acct_dbio.pgc @@ -0,0 +1,26 @@ +/* + * lg_worst_acct_dbio.pgc - lg 모듈 DB 접근 함수 (lg_worst_acct_dbio), archived into liblgdbio.a. + * 당일 차대 불일치 절대값이 가장 큰 계정과목과 그 차액을 찾는다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "lg_worst_acct_dbio.h" + +/* GROUP BY + CASE + abs + ORDER LIMIT: 최대 불균형 계정. */ +int lgqdb_worst_acct(const char *bizdate, char *acct_out, long *diff_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_acct[32]; + long h_diff = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT acct, abs(coalesce(sum(CASE WHEN dc='D' THEN amount ELSE 0 END),0) + - coalesce(sum(CASE WHEN dc='C' THEN amount ELSE 0 END),0)) AS d + INTO :h_acct, :h_diff FROM lg_ledger_line WHERE biz_date = :h_bizdate + GROUP BY acct ORDER BY d DESC LIMIT 1; + if (sqlca.sqlcode == 100) { acct_out[0] = 0; *diff_out = 0; return 0; } + if (sqlca.sqlcode < 0) { userlog("lgqdb_worst_acct FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + strcpy(acct_out, h_acct); *diff_out = h_diff; + return 0; +} diff --git a/app/src/mg/batch/mg_ackpurge_batch.pgc b/app/src/mg/batch/mg_ackpurge_batch.pgc new file mode 100644 index 0000000..0a3f223 --- /dev/null +++ b/app/src/mg/batch/mg_ackpurge_batch.pgc @@ -0,0 +1,39 @@ +/* + * mg_ackpurge_batch.pgc - mg 완료 큐 보존기간 삭제 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, EXEC SQL runs on the biz_date, then tpcommit drives XA 2PC. + * Usage: mg_ackpurge_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_ackpurge_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + const char *cutoff = (argc > 2) ? argv[2] : MG_ACKPURGE_CUTOFF; + EXEC SQL BEGIN DECLARE SECTION; + char h_cut[16]; + EXEC SQL END DECLARE SECTION; + mg_ackpurge_rec_t rec; + (void) bizdate; + + strncpy(h_cut, cutoff, sizeof(h_cut)-1); h_cut[sizeof(h_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 DELETE FROM mg_queue WHERE biz_date < :h_cut AND status = 'D'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mg_ackpurge_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.deleted = (long) sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_ackpurge_batch COMMIT: cutoff=%s 삭제=%ld\n", cutoff, rec.deleted); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_mtierr_batch.pgc b/app/src/mg/batch/mg_mtierr_batch.pgc new file mode 100644 index 0000000..4405b6d --- /dev/null +++ b/app/src/mg/batch/mg_mtierr_batch.pgc @@ -0,0 +1,41 @@ +/* + * mg_mtierr_batch.pgc - mg 응답오류 MTI 그룹 집계 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, EXEC SQL runs on the biz_date, then tpcommit drives XA 2PC. + * Usage: mg_mtierr_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_mtierr_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_min = 2, h_groups = 0; + EXEC SQL END DECLARE SECTION; + mg_mtierr_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_groups FROM ( + SELECT mti FROM mg_msg_log + WHERE biz_date = :h_bizdate AND rc <> '00' + GROUP BY mti HAVING count(*) >= :h_min) t; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mg_mtierr_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.threshold = h_min; rec.groups = h_groups; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_mtierr_batch COMMIT: bizdate=%s 임계=%ld 오류MTI그룹=%ld\n", bizdate, rec.threshold, rec.groups); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_rankamt_batch.pgc b/app/src/mg/batch/mg_rankamt_batch.pgc new file mode 100644 index 0000000..cc5029c --- /dev/null +++ b/app/src/mg/batch/mg_rankamt_batch.pgc @@ -0,0 +1,49 @@ +/* + * mg_rankamt_batch.pgc - mg 채널별 최고금액 전문 랭크 스캔 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, EXEC SQL runs on the biz_date, then tpcommit drives XA 2PC. + * Usage: mg_rankamt_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_rankamt_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_mid, h_rn; + EXEC SQL END DECLARE SECTION; + mg_rankamt_rec_t rec; rec.scanned = 0; rec.tops = 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 mra CURSOR FOR + SELECT msg_id, + ROW_NUMBER() OVER (PARTITION BY channel ORDER BY amount DESC) + FROM mg_msg_log WHERE biz_date = :h_bizdate; + EXEC SQL OPEN mra; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH mra INTO :h_mid, :h_rn; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE mra; tpabort(0); return 1; } + rec.scanned++; + if (h_rn == 1) rec.tops++; + } + EXEC SQL CLOSE mra; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_rankamt_batch COMMIT: bizdate=%s 스캔=%ld 최고=%ld\n", bizdate, rec.scanned, rec.tops); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_requeue_batch.pgc b/app/src/mg/batch/mg_requeue_batch.pgc new file mode 100644 index 0000000..65df338 --- /dev/null +++ b/app/src/mg/batch/mg_requeue_batch.pgc @@ -0,0 +1,41 @@ +/* + * mg_requeue_batch.pgc - mg 응답오류 전문 재전송 큐 적재 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, EXEC SQL runs on the biz_date, then tpcommit drives XA 2PC. + * Usage: mg_requeue_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_requeue_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_requeue_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 INSERT INTO mg_queue + (queue_id, stan, channel, mti, payload, status, biz_date) + SELECT nextval('mg_queue_seq'), m.stan, m.channel, m.mti, m.raw_msg, 'Q', m.biz_date + FROM mg_msg_log m + WHERE m.biz_date = :h_bizdate AND m.rc <> '00' AND m.direction = 'IN'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "mg_requeue_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.requeued = (long) sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_requeue_batch COMMIT: bizdate=%s 재적재=%ld\n", bizdate, rec.requeued); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/batch/mg_unrouted_batch.pgc b/app/src/mg/batch/mg_unrouted_batch.pgc new file mode 100644 index 0000000..e4b3b20 --- /dev/null +++ b/app/src/mg/batch/mg_unrouted_batch.pgc @@ -0,0 +1,41 @@ +/* + * mg_unrouted_batch.pgc - mg 라우팅 미지정 전문 점검 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, EXEC SQL runs on the biz_date, then tpcommit drives XA 2PC. + * Usage: mg_unrouted_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "mg_unrouted_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_v = 0; + EXEC SQL END DECLARE SECTION; + mg_unrouted_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_v FROM mg_msg_log m + WHERE m.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM mg_route r + WHERE r.channel = m.channel AND r.mti = m.mti AND r.active); + if (sqlca.sqlcode < 0) { fprintf(stderr, "mg_unrouted_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.unrouted = h_v; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mg_unrouted_batch COMMIT: bizdate=%s 미라우팅=%ld\n", bizdate, rec.unrouted); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mg/dbio/mg_abovavg_dbio.h b/app/src/mg/dbio/mg_abovavg_dbio.h new file mode 100644 index 0000000..7b4fe9c --- /dev/null +++ b/app/src/mg/dbio/mg_abovavg_dbio.h @@ -0,0 +1,11 @@ +/* + * mg_abovavg_dbio.h - mg 모듈 DB 접근 copybook (mg_abovavg_dbio). 채널 평균금액을 초과한 전문 건수 (상관 서브쿼리 avg). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_ABOVAVG_DBIO_H +#define MG_ABOVAVG_DBIO_H + +long mgdb_above_chan_avg(const char *bizdate); + +#endif /* MG_ABOVAVG_DBIO_H */ diff --git a/app/src/mg/dbio/mg_abovavg_dbio.pgc b/app/src/mg/dbio/mg_abovavg_dbio.pgc new file mode 100644 index 0000000..6f9e8a0 --- /dev/null +++ b/app/src/mg/dbio/mg_abovavg_dbio.pgc @@ -0,0 +1,23 @@ +/* + * mg_abovavg_dbio.pgc - mg 모듈 DB 접근 함수 (mg_abovavg_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 채널 평균금액을 초과한 전문 건수 (상관 서브쿼리 avg). + */ +#include +#include +#include "mg_abovavg_dbio.h" + +long mgdb_above_chan_avg(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM mg_msg_log m + WHERE m.biz_date = :h_bizdate + AND m.amount > (SELECT avg(m2.amount) FROM mg_msg_log m2 + WHERE m2.channel = m.channel AND m2.biz_date = :h_bizdate); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/mg/dbio/mg_ackpurge_batch.h b/app/src/mg/dbio/mg_ackpurge_batch.h new file mode 100644 index 0000000..8a2135f --- /dev/null +++ b/app/src/mg/dbio/mg_ackpurge_batch.h @@ -0,0 +1,11 @@ +/* + * mg_ackpurge_batch.h - mg mg_ackpurge_batch 배치 전용 카피북 (copybook). mg 완료 큐 보존기간 삭제 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by mg_ackpurge_batch.pgc. + */ +#ifndef MG_ACKPURGE_BATCH_H +#define MG_ACKPURGE_BATCH_H + +typedef struct { long deleted; } mg_ackpurge_rec_t; +#define MG_ACKPURGE_CUTOFF "2026-07-01" + +#endif /* MG_ACKPURGE_BATCH_H */ diff --git a/app/src/mg/dbio/mg_ackpurge_dbio.h b/app/src/mg/dbio/mg_ackpurge_dbio.h new file mode 100644 index 0000000..4432c20 --- /dev/null +++ b/app/src/mg/dbio/mg_ackpurge_dbio.h @@ -0,0 +1,11 @@ +/* + * mg_ackpurge_dbio.h - mg 모듈 DB 접근 copybook (mg_ackpurge_dbio). 기준일 이전 완료(D) 큐 삭제 후 삭제건수 반환 (DELETE 범위). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_ACKPURGE_DBIO_H +#define MG_ACKPURGE_DBIO_H + +long mgdb_purge_acked(const char *cutoff); + +#endif /* MG_ACKPURGE_DBIO_H */ diff --git a/app/src/mg/dbio/mg_ackpurge_dbio.pgc b/app/src/mg/dbio/mg_ackpurge_dbio.pgc new file mode 100644 index 0000000..41a21e7 --- /dev/null +++ b/app/src/mg/dbio/mg_ackpurge_dbio.pgc @@ -0,0 +1,20 @@ +/* + * mg_ackpurge_dbio.pgc - mg 모듈 DB 접근 함수 (mg_ackpurge_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 기준일 이전 완료(D) 큐 삭제 후 삭제건수 반환 (DELETE 범위). + */ +#include +#include +#include "mg_ackpurge_dbio.h" + +long mgdb_purge_acked(const char *cutoff) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_cut[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_cut, cutoff, sizeof(h_cut)-1); h_cut[sizeof(h_cut)-1] = 0; + EXEC SQL DELETE FROM mg_queue + WHERE biz_date < :h_cut AND status = 'D'; + if (sqlca.sqlcode < 0) { userlog("mgdb_purge_acked FAIL [%d]", sqlca.sqlcode); return -1; } + return (long) sqlca.sqlerrd[2]; +} diff --git a/app/src/mg/dbio/mg_chdistinct_dbio.h b/app/src/mg/dbio/mg_chdistinct_dbio.h new file mode 100644 index 0000000..7d900c9 --- /dev/null +++ b/app/src/mg/dbio/mg_chdistinct_dbio.h @@ -0,0 +1,11 @@ +/* + * mg_chdistinct_dbio.h - mg 모듈 DB 접근 copybook (mg_chdistinct_dbio). 2건 이상 전문이 오간 고유 채널 수 (count DISTINCT + 서브셀렉트). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_CHDISTINCT_DBIO_H +#define MG_CHDISTINCT_DBIO_H + +long mgdb_chan_mti_distinct(const char *bizdate); + +#endif /* MG_CHDISTINCT_DBIO_H */ diff --git a/app/src/mg/dbio/mg_chdistinct_dbio.pgc b/app/src/mg/dbio/mg_chdistinct_dbio.pgc new file mode 100644 index 0000000..971beab --- /dev/null +++ b/app/src/mg/dbio/mg_chdistinct_dbio.pgc @@ -0,0 +1,23 @@ +/* + * mg_chdistinct_dbio.pgc - mg 모듈 DB 접근 함수 (mg_chdistinct_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 2건 이상 전문이 오간 고유 채널 수 (count DISTINCT + 서브셀렉트). + */ +#include +#include +#include "mg_chdistinct_dbio.h" + +long mgdb_chan_mti_distinct(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(DISTINCT channel) INTO :h_v FROM mg_msg_log + WHERE biz_date = :h_bizdate + AND channel IN (SELECT channel FROM mg_msg_log + WHERE biz_date = :h_bizdate GROUP BY channel HAVING count(*) >= 2); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/mg/dbio/mg_dircase_dbio.h b/app/src/mg/dbio/mg_dircase_dbio.h new file mode 100644 index 0000000..9c4d8db --- /dev/null +++ b/app/src/mg/dbio/mg_dircase_dbio.h @@ -0,0 +1,11 @@ +/* + * mg_dircase_dbio.h - mg 모듈 DB 접근 copybook (mg_dircase_dbio). 방향별 부호가중 금액합 (IN +, OUT -) - CASE 식. + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_DIRCASE_DBIO_H +#define MG_DIRCASE_DBIO_H + +long mgdb_dir_case_amount(const char *bizdate); + +#endif /* MG_DIRCASE_DBIO_H */ diff --git a/app/src/mg/dbio/mg_dircase_dbio.pgc b/app/src/mg/dbio/mg_dircase_dbio.pgc new file mode 100644 index 0000000..9d0ee7d --- /dev/null +++ b/app/src/mg/dbio/mg_dircase_dbio.pgc @@ -0,0 +1,24 @@ +/* + * mg_dircase_dbio.pgc - mg 모듈 DB 접근 함수 (mg_dircase_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 방향별 부호가중 금액합 (IN +, OUT -) - CASE 식. + */ +#include +#include +#include "mg_dircase_dbio.h" + +long mgdb_dir_case_amount(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(CASE direction + WHEN 'IN' THEN amount + WHEN 'OUT' THEN -amount + ELSE 0 END), 0) + INTO :h_v FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/mg/dbio/mg_mtierr_batch.h b/app/src/mg/dbio/mg_mtierr_batch.h new file mode 100644 index 0000000..351fdf1 --- /dev/null +++ b/app/src/mg/dbio/mg_mtierr_batch.h @@ -0,0 +1,10 @@ +/* + * mg_mtierr_batch.h - mg mg_mtierr_batch 배치 전용 카피북 (copybook). mg 응답오류 MTI 그룹 집계 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by mg_mtierr_batch.pgc. + */ +#ifndef MG_MTIERR_BATCH_H +#define MG_MTIERR_BATCH_H + +typedef struct { long threshold; long groups; } mg_mtierr_rec_t; + +#endif /* MG_MTIERR_BATCH_H */ diff --git a/app/src/mg/dbio/mg_mtigroup_dbio.h b/app/src/mg/dbio/mg_mtigroup_dbio.h new file mode 100644 index 0000000..5a3e88e --- /dev/null +++ b/app/src/mg/dbio/mg_mtigroup_dbio.h @@ -0,0 +1,11 @@ +/* + * mg_mtigroup_dbio.h - mg 모듈 DB 접근 copybook (mg_mtigroup_dbio). 임계 이상 응답오류가 난 전문유형(MTI) 그룹 수 (GROUP BY HAVING 커서). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_MTIGROUP_DBIO_H +#define MG_MTIGROUP_DBIO_H + +long mgdb_mti_err_groups(const char *bizdate, long minc); + +#endif /* MG_MTIGROUP_DBIO_H */ diff --git a/app/src/mg/dbio/mg_mtigroup_dbio.pgc b/app/src/mg/dbio/mg_mtigroup_dbio.pgc new file mode 100644 index 0000000..ae54cb3 --- /dev/null +++ b/app/src/mg/dbio/mg_mtigroup_dbio.pgc @@ -0,0 +1,32 @@ +/* + * mg_mtigroup_dbio.pgc - mg 모듈 DB 접근 함수 (mg_mtigroup_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 임계 이상 응답오류가 난 전문유형(MTI) 그룹 수 (GROUP BY HAVING 커서). + */ +#include +#include +#include "mg_mtigroup_dbio.h" + +long mgdb_mti_err_groups(const char *bizdate, long minc) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_mti[8]; + long h_min, h_cnt, groups = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + h_min = minc; + EXEC SQL DECLARE meg CURSOR FOR + SELECT mti, count(*) FROM mg_msg_log + WHERE biz_date = :h_bizdate AND rc <> '00' + GROUP BY mti HAVING count(*) >= :h_min; + EXEC SQL OPEN meg; + if (sqlca.sqlcode < 0) { userlog("mgdb_mti_err_groups OPEN [%d]", sqlca.sqlcode); return -1; } + for (;;) { + EXEC SQL FETCH meg INTO :h_mti, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE meg; return -1; } + groups++; + } + EXEC SQL CLOSE meg; + return groups; +} diff --git a/app/src/mg/dbio/mg_nackfilter_dbio.h b/app/src/mg/dbio/mg_nackfilter_dbio.h new file mode 100644 index 0000000..e354522 --- /dev/null +++ b/app/src/mg/dbio/mg_nackfilter_dbio.h @@ -0,0 +1,11 @@ +/* + * mg_nackfilter_dbio.h - mg 모듈 DB 접근 copybook (mg_nackfilter_dbio). 전체 대비 NACK(status=N) 비율(bps) - FILTER 절. + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_NACKFILTER_DBIO_H +#define MG_NACKFILTER_DBIO_H + +long mgdb_nack_filter(const char *bizdate); + +#endif /* MG_NACKFILTER_DBIO_H */ diff --git a/app/src/mg/dbio/mg_nackfilter_dbio.pgc b/app/src/mg/dbio/mg_nackfilter_dbio.pgc new file mode 100644 index 0000000..682d909 --- /dev/null +++ b/app/src/mg/dbio/mg_nackfilter_dbio.pgc @@ -0,0 +1,22 @@ +/* + * mg_nackfilter_dbio.pgc - mg 모듈 DB 접근 함수 (mg_nackfilter_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 전체 대비 NACK(status=N) 비율(bps) - FILTER 절. + */ +#include +#include +#include "mg_nackfilter_dbio.h" + +long mgdb_nack_filter(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_nack = 0, h_tot = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) FILTER (WHERE status = 'N'), count(*) + INTO :h_nack, :h_tot FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + if (h_tot <= 0) return 0; + return h_nack * 10000 / h_tot; +} diff --git a/app/src/mg/dbio/mg_qforup_dbio.h b/app/src/mg/dbio/mg_qforup_dbio.h new file mode 100644 index 0000000..e23c68c --- /dev/null +++ b/app/src/mg/dbio/mg_qforup_dbio.h @@ -0,0 +1,11 @@ +/* + * mg_qforup_dbio.h - mg 모듈 DB 접근 copybook (mg_qforup_dbio). UP 상태 채널에 걸린 대기 큐 건수 (IN 서브셀렉트). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_QFORUP_DBIO_H +#define MG_QFORUP_DBIO_H + +long mgdb_queue_for_up(const char *bizdate); + +#endif /* MG_QFORUP_DBIO_H */ diff --git a/app/src/mg/dbio/mg_qforup_dbio.pgc b/app/src/mg/dbio/mg_qforup_dbio.pgc new file mode 100644 index 0000000..6a56f55 --- /dev/null +++ b/app/src/mg/dbio/mg_qforup_dbio.pgc @@ -0,0 +1,22 @@ +/* + * mg_qforup_dbio.pgc - mg 모듈 DB 접근 함수 (mg_qforup_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * UP 상태 채널에 걸린 대기 큐 건수 (IN 서브셀렉트). + */ +#include +#include +#include "mg_qforup_dbio.h" + +long mgdb_queue_for_up(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM mg_queue + WHERE biz_date = :h_bizdate AND status = 'Q' + AND channel IN (SELECT channel FROM mg_channel WHERE status = 'UP'); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/mg/dbio/mg_rankamt_batch.h b/app/src/mg/dbio/mg_rankamt_batch.h new file mode 100644 index 0000000..9144718 --- /dev/null +++ b/app/src/mg/dbio/mg_rankamt_batch.h @@ -0,0 +1,10 @@ +/* + * mg_rankamt_batch.h - mg mg_rankamt_batch 배치 전용 카피북 (copybook). mg 채널별 최고금액 전문 랭크 스캔 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by mg_rankamt_batch.pgc. + */ +#ifndef MG_RANKAMT_BATCH_H +#define MG_RANKAMT_BATCH_H + +typedef struct { long scanned; long tops; } mg_rankamt_rec_t; + +#endif /* MG_RANKAMT_BATCH_H */ diff --git a/app/src/mg/dbio/mg_rankamt_dbio.h b/app/src/mg/dbio/mg_rankamt_dbio.h new file mode 100644 index 0000000..97a734f --- /dev/null +++ b/app/src/mg/dbio/mg_rankamt_dbio.h @@ -0,0 +1,11 @@ +/* + * mg_rankamt_dbio.h - mg 모듈 DB 접근 copybook (mg_rankamt_dbio). 채널별 최고금액(랭크1) 전문 수 (ROW_NUMBER 윈도우 커서). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_RANKAMT_DBIO_H +#define MG_RANKAMT_DBIO_H + +long mgdb_rank_by_amount(const char *bizdate); + +#endif /* MG_RANKAMT_DBIO_H */ diff --git a/app/src/mg/dbio/mg_rankamt_dbio.pgc b/app/src/mg/dbio/mg_rankamt_dbio.pgc new file mode 100644 index 0000000..b62feaf --- /dev/null +++ b/app/src/mg/dbio/mg_rankamt_dbio.pgc @@ -0,0 +1,31 @@ +/* + * mg_rankamt_dbio.pgc - mg 모듈 DB 접근 함수 (mg_rankamt_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 채널별 최고금액(랭크1) 전문 수 (ROW_NUMBER 윈도우 커서). + */ +#include +#include +#include "mg_rankamt_dbio.h" + +long mgdb_rank_by_amount(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_mid, h_rn, tops = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE mrk CURSOR FOR + SELECT msg_id, + ROW_NUMBER() OVER (PARTITION BY channel ORDER BY amount DESC) + FROM mg_msg_log WHERE biz_date = :h_bizdate; + EXEC SQL OPEN mrk; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH mrk INTO :h_mid, :h_rn; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE mrk; return -1; } + if (h_rn == 1) tops++; + } + EXEC SQL CLOSE mrk; + return tops; +} diff --git a/app/src/mg/dbio/mg_reqfail_dbio.h b/app/src/mg/dbio/mg_reqfail_dbio.h new file mode 100644 index 0000000..3a7f16a --- /dev/null +++ b/app/src/mg/dbio/mg_reqfail_dbio.h @@ -0,0 +1,11 @@ +/* + * mg_reqfail_dbio.h - mg 모듈 DB 접근 copybook (mg_reqfail_dbio). 응답오류(rc<>00) 전문을 재전송 큐로 적재 (INSERT..SELECT). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_REQFAIL_DBIO_H +#define MG_REQFAIL_DBIO_H + +long mgdb_enqueue_failed(const char *bizdate); + +#endif /* MG_REQFAIL_DBIO_H */ diff --git a/app/src/mg/dbio/mg_reqfail_dbio.pgc b/app/src/mg/dbio/mg_reqfail_dbio.pgc new file mode 100644 index 0000000..73144b6 --- /dev/null +++ b/app/src/mg/dbio/mg_reqfail_dbio.pgc @@ -0,0 +1,23 @@ +/* + * mg_reqfail_dbio.pgc - mg 모듈 DB 접근 함수 (mg_reqfail_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 응답오류(rc<>00) 전문을 재전송 큐로 적재 (INSERT..SELECT). + */ +#include +#include +#include "mg_reqfail_dbio.h" + +long mgdb_enqueue_failed(const char *bizdate) +{ + 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; + EXEC SQL INSERT INTO mg_queue + (queue_id, stan, channel, mti, payload, status, biz_date) + SELECT nextval('mg_queue_seq'), m.stan, m.channel, m.mti, m.raw_msg, 'Q', m.biz_date + FROM mg_msg_log m + WHERE m.biz_date = :h_bizdate AND m.rc <> '00' AND m.direction = 'IN'; + if (sqlca.sqlcode < 0) { userlog("mgdb_enqueue_failed FAIL [%d]", sqlca.sqlcode); return -1; } + return (long) sqlca.sqlerrd[2]; +} diff --git a/app/src/mg/dbio/mg_requeue_batch.h b/app/src/mg/dbio/mg_requeue_batch.h new file mode 100644 index 0000000..0bd0d76 --- /dev/null +++ b/app/src/mg/dbio/mg_requeue_batch.h @@ -0,0 +1,10 @@ +/* + * mg_requeue_batch.h - mg mg_requeue_batch 배치 전용 카피북 (copybook). mg 응답오류 전문 재전송 큐 적재 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by mg_requeue_batch.pgc. + */ +#ifndef MG_REQUEUE_BATCH_H +#define MG_REQUEUE_BATCH_H + +typedef struct { long requeued; } mg_requeue_rec_t; + +#endif /* MG_REQUEUE_BATCH_H */ diff --git a/app/src/mg/dbio/mg_routegrp_dbio.h b/app/src/mg/dbio/mg_routegrp_dbio.h new file mode 100644 index 0000000..be14269 --- /dev/null +++ b/app/src/mg/dbio/mg_routegrp_dbio.h @@ -0,0 +1,11 @@ +/* + * mg_routegrp_dbio.h - mg 모듈 DB 접근 copybook (mg_routegrp_dbio). 라우팅 매칭 전문의 목적지별 최대 금액그룹 수 (JOIN + GROUP BY 커서). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_ROUTEGRP_DBIO_H +#define MG_ROUTEGRP_DBIO_H + +long mgdb_route_amount_groups(const char *bizdate); + +#endif /* MG_ROUTEGRP_DBIO_H */ diff --git a/app/src/mg/dbio/mg_routegrp_dbio.pgc b/app/src/mg/dbio/mg_routegrp_dbio.pgc new file mode 100644 index 0000000..7a27525 --- /dev/null +++ b/app/src/mg/dbio/mg_routegrp_dbio.pgc @@ -0,0 +1,32 @@ +/* + * mg_routegrp_dbio.pgc - mg 모듈 DB 접근 함수 (mg_routegrp_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 라우팅 매칭 전문의 목적지별 최대 금액그룹 수 (JOIN + GROUP BY 커서). + */ +#include +#include +#include "mg_routegrp_dbio.h" + +long mgdb_route_amount_groups(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_dest[64]; + long h_sum, groups = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE mrg CURSOR FOR + SELECT r.dest, coalesce(sum(m.amount),0) + FROM mg_msg_log m JOIN mg_route r ON r.channel = m.channel AND r.mti = m.mti + WHERE m.biz_date = :h_bizdate AND r.active + GROUP BY r.dest; + EXEC SQL OPEN mrg; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH mrg INTO :h_dest, :h_sum; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE mrg; return -1; } + groups++; + } + EXEC SQL CLOSE mrg; + return groups; +} diff --git a/app/src/mg/dbio/mg_stanrange_dbio.h b/app/src/mg/dbio/mg_stanrange_dbio.h new file mode 100644 index 0000000..2a4a0db --- /dev/null +++ b/app/src/mg/dbio/mg_stanrange_dbio.h @@ -0,0 +1,11 @@ +/* + * mg_stanrange_dbio.h - mg 모듈 DB 접근 copybook (mg_stanrange_dbio). 당일 전문 추적번호(STAN) 최대-최소 범위 (min/max). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_STANRANGE_DBIO_H +#define MG_STANRANGE_DBIO_H + +long mgdb_stan_spread(const char *bizdate); + +#endif /* MG_STANRANGE_DBIO_H */ diff --git a/app/src/mg/dbio/mg_stanrange_dbio.pgc b/app/src/mg/dbio/mg_stanrange_dbio.pgc new file mode 100644 index 0000000..2fecae6 --- /dev/null +++ b/app/src/mg/dbio/mg_stanrange_dbio.pgc @@ -0,0 +1,21 @@ +/* + * mg_stanrange_dbio.pgc - mg 모듈 DB 접근 함수 (mg_stanrange_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 당일 전문 추적번호(STAN) 최대-최소 범위 (min/max). + */ +#include +#include +#include "mg_stanrange_dbio.h" + +long mgdb_stan_spread(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_lo = 0, h_hi = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(min(stan),0), coalesce(max(stan),0) + INTO :h_lo, :h_hi FROM mg_msg_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_hi - h_lo; +} diff --git a/app/src/mg/dbio/mg_unrouted_batch.h b/app/src/mg/dbio/mg_unrouted_batch.h new file mode 100644 index 0000000..7a419dc --- /dev/null +++ b/app/src/mg/dbio/mg_unrouted_batch.h @@ -0,0 +1,10 @@ +/* + * mg_unrouted_batch.h - mg mg_unrouted_batch 배치 전용 카피북 (copybook). mg 라우팅 미지정 전문 점검 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by mg_unrouted_batch.pgc. + */ +#ifndef MG_UNROUTED_BATCH_H +#define MG_UNROUTED_BATCH_H + +typedef struct { long unrouted; } mg_unrouted_rec_t; + +#endif /* MG_UNROUTED_BATCH_H */ diff --git a/app/src/mg/dbio/mg_unrouted_dbio.h b/app/src/mg/dbio/mg_unrouted_dbio.h new file mode 100644 index 0000000..9e4b148 --- /dev/null +++ b/app/src/mg/dbio/mg_unrouted_dbio.h @@ -0,0 +1,11 @@ +/* + * mg_unrouted_dbio.h - mg 모듈 DB 접근 copybook (mg_unrouted_dbio). 라우팅 룰이 없는 전문 건수 (NOT EXISTS mg_route). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MG_UNROUTED_DBIO_H +#define MG_UNROUTED_DBIO_H + +long mgdb_unrouted_msgs(const char *bizdate); + +#endif /* MG_UNROUTED_DBIO_H */ diff --git a/app/src/mg/dbio/mg_unrouted_dbio.pgc b/app/src/mg/dbio/mg_unrouted_dbio.pgc new file mode 100644 index 0000000..44c33af --- /dev/null +++ b/app/src/mg/dbio/mg_unrouted_dbio.pgc @@ -0,0 +1,23 @@ +/* + * mg_unrouted_dbio.pgc - mg 모듈 DB 접근 함수 (mg_unrouted_dbio), archived into libmgdbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 라우팅 룰이 없는 전문 건수 (NOT EXISTS mg_route). + */ +#include +#include +#include "mg_unrouted_dbio.h" + +long mgdb_unrouted_msgs(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM mg_msg_log m + WHERE m.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM mg_route r + WHERE r.channel = m.channel AND r.mti = m.mti AND r.active); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/mm/batch/mm_bin_dedup_batch.pgc b/app/src/mm/batch/mm_bin_dedup_batch.pgc new file mode 100644 index 0000000..2487654 --- /dev/null +++ b/app/src/mm/batch/mm_bin_dedup_batch.pgc @@ -0,0 +1,35 @@ +/* + * mm_bin_dedup_batch.pgc - mm 비활성 BIN 정리 배치 (DELETE range + XA). + * + * 지정 카드브랜드의 비활성(status<>'ACTIVE') BIN 행을 한 문장으로 삭제하고 + * 삭제행수를 sqlca.sqlerrd[2] 로 확인한다. ONE global XA transaction. + * host 변수는 전용 카피북 mm_bin_dedup_cpy.h 를 EXEC SQL INCLUDE. + * Usage: mm_bin_dedup_batch [CARD_BRAND] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *brand = (argc > 1) ? argv[1] : "LOCAL"; + + EXEC SQL INCLUDE mm_bin_dedup_cpy; + + strncpy(d_brand, brand, sizeof(d_brand)-1); d_brand[sizeof(d_brand)-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 mm_bin WHERE card_brand = :d_brand AND status <> 'ACTIVE'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "DELETE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + d_deleted = (long) sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_bin_dedup_batch COMMIT: brand=%s 삭제=%ld건\n", brand, d_deleted); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_bin_dedup_cpy.h b/app/src/mm/batch/mm_bin_dedup_cpy.h new file mode 100644 index 0000000..7bf0f03 --- /dev/null +++ b/app/src/mm/batch/mm_bin_dedup_cpy.h @@ -0,0 +1,5 @@ +/* mm_bin_dedup_cpy.h - mm_bin_dedup_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char d_brand[16]; +long d_deleted; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/mm/batch/mm_fee_bump_batch.pgc b/app/src/mm/batch/mm_fee_bump_batch.pgc new file mode 100644 index 0000000..02ae2b7 --- /dev/null +++ b/app/src/mm/batch/mm_fee_bump_batch.pgc @@ -0,0 +1,50 @@ +/* + * mm_fee_bump_batch.pgc - mm 수수료율 일괄 인상 배치 (커서 + UPDATE + XA). + * + * 지정 card_type 의 수수료율 행을 커서로 훑어 rate_bps 를 delta 만큼 인상하고, + * 변경 이력을 mm_fee_rate_hist 에 남긴다. 전 구간 ONE global XA transaction. + * host 변수는 전용 카피북 mm_fee_bump_cpy.h 를 EXEC SQL INCLUDE. + * Usage: mm_fee_bump_batch [CARD_TYPE] [DELTA_BPS] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *card = (argc > 1) ? argv[1] : "CREDIT"; + long delta = (argc > 2) ? atol(argv[2]) : 1; + long bumped = 0; + + EXEC SQL INCLUDE mm_fee_bump_cpy; + + strncpy(b_card, card, sizeof(b_card)-1); b_card[sizeof(b_card)-1] = 0; + b_delta = delta; + + 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 mfb CURSOR FOR + SELECT merchant_id FROM mm_fee_rate WHERE card_type = :b_card ORDER BY merchant_id; + EXEC SQL OPEN mfb; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH mfb INTO :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 mfb; tpabort(0); return 1; } + EXEC SQL UPDATE mm_fee_rate SET rate_bps = rate_bps + :b_delta, updated_at = now() + WHERE merchant_id = :b_merch AND card_type = :b_card; + if (sqlca.sqlcode < 0) { fprintf(stderr, "UPDATE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE mfb; tpabort(0); return 1; } + bumped++; + } + EXEC SQL CLOSE mfb; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_fee_bump_batch COMMIT: card=%s delta=%ld 인상=%ld건\n", card, delta, bumped); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_fee_bump_cpy.h b/app/src/mm/batch/mm_fee_bump_cpy.h new file mode 100644 index 0000000..abed62f --- /dev/null +++ b/app/src/mm/batch/mm_fee_bump_cpy.h @@ -0,0 +1,6 @@ +/* mm_fee_bump_cpy.h - mm_fee_bump_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char b_card[16]; +long b_delta; +char b_merch[64]; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/mm/batch/mm_hist_archive_batch.pgc b/app/src/mm/batch/mm_hist_archive_batch.pgc new file mode 100644 index 0000000..8c6715e --- /dev/null +++ b/app/src/mm/batch/mm_hist_archive_batch.pgc @@ -0,0 +1,39 @@ +/* + * mm_hist_archive_batch.pgc - mm 현행 수수료율 이력 적재 배치 (INSERT..SELECT + XA). + * + * 현재 mm_fee_rate 스냅샷 전체를 mm_fee_rate_hist 에 한 문장으로 적재한다. + * hist_id 는 시퀀스 기준값 + row_number() 로 부여. ONE global XA transaction. + * host 변수는 전용 카피북 mm_hist_archive_cpy.h 를 EXEC SQL INCLUDE. + * Usage: mm_hist_archive_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + (void) argc; (void) argv; + + EXEC SQL INCLUDE mm_hist_archive_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 coalesce(max(hist_id),0) INTO :a_base FROM mm_fee_rate_hist; + if (sqlca.sqlcode < 0) { fprintf(stderr, "BASE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + EXEC SQL INSERT INTO mm_fee_rate_hist (hist_id, merchant_id, card_type, rate_bps, eff_date) + SELECT :a_base + row_number() OVER (ORDER BY merchant_id, card_type), + merchant_id, card_type, rate_bps, eff_date + FROM mm_fee_rate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "INSERT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + a_rows = (long) sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_hist_archive_batch COMMIT: 이력적재=%ld건\n", a_rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_hist_archive_cpy.h b/app/src/mm/batch/mm_hist_archive_cpy.h new file mode 100644 index 0000000..ff20273 --- /dev/null +++ b/app/src/mm/batch/mm_hist_archive_cpy.h @@ -0,0 +1,5 @@ +/* mm_hist_archive_cpy.h - mm_hist_archive_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +long a_base; +long a_rows; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/mm/batch/mm_limit_scale_batch.pgc b/app/src/mm/batch/mm_limit_scale_batch.pgc new file mode 100644 index 0000000..5d109b6 --- /dev/null +++ b/app/src/mm/batch/mm_limit_scale_batch.pgc @@ -0,0 +1,40 @@ +/* + * mm_limit_scale_batch.pgc - mm 등급별 한도 일괄 조정 배치 (UPDATE + IN 서브셀렉트 + XA). + * + * 지정 등급 가맹점(IN 서브셀렉트)의 mm_limit.daily_limit 을 pct 퍼센트로 스케일한다. + * 한 UPDATE 문으로 처리하고 변경행수를 sqlca.sqlerrd[2] 로 확인. ONE global XA. + * host 변수는 전용 카피북 mm_limit_scale_cpy.h 를 EXEC SQL INCLUDE. + * Usage: mm_limit_scale_batch [GRADE] [PCT] + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *grade = (argc > 1) ? argv[1] : "S"; + long pct = (argc > 2) ? atol(argv[2]) : 120; + + EXEC SQL INCLUDE mm_limit_scale_cpy; + + strncpy(s_grade, grade, sizeof(s_grade)-1); s_grade[sizeof(s_grade)-1] = 0; + s_pct = pct; + + 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_limit + SET daily_limit = daily_limit * :s_pct / 100, updated_at = now() + WHERE merchant_id IN (SELECT merchant_id FROM mm_merchant_ext + WHERE grade = :s_grade AND status = 'ACTIVE'); + if (sqlca.sqlcode < 0) { fprintf(stderr, "UPDATE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + s_scaled = (long) sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_limit_scale_batch COMMIT: grade=%s pct=%ld 조정=%ld건\n", grade, pct, s_scaled); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_limit_scale_cpy.h b/app/src/mm/batch/mm_limit_scale_cpy.h new file mode 100644 index 0000000..09e0edd --- /dev/null +++ b/app/src/mm/batch/mm_limit_scale_cpy.h @@ -0,0 +1,6 @@ +/* mm_limit_scale_cpy.h - mm_limit_scale_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char s_grade[8]; +long s_pct; +long s_scaled; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/mm/batch/mm_risk_rollup_batch.pgc b/app/src/mm/batch/mm_risk_rollup_batch.pgc new file mode 100644 index 0000000..8a8f115 --- /dev/null +++ b/app/src/mm/batch/mm_risk_rollup_batch.pgc @@ -0,0 +1,47 @@ +/* + * mm_risk_rollup_batch.pgc - mm MCC 위험도 재산정 배치 (커서 + 가맹점수 기반 UPDATE + XA). + * + * mm_mcc 를 커서로 훑어 각 MCC 를 쓰는 가맹점 수를 세고, 그 규모에 따라 risk_level 을 + * 재산정(UPDATE)한다. 규모가 클수록 높은 위험도. 전 구간 ONE global XA transaction. + * host 변수는 전용 카피북 mm_risk_rollup_cpy.h 를 EXEC SQL INCLUDE. + * Usage: mm_risk_rollup_batch + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long rolled = 0; + (void) argc; (void) argv; + + EXEC SQL INCLUDE mm_risk_rollup_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 mrr CURSOR FOR SELECT mcc FROM mm_mcc ORDER BY mcc; + EXEC SQL OPEN mrr; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH mrr INTO :k_mcc; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE mrr; tpabort(0); return 1; } + EXEC SQL SELECT count(*) INTO :k_merch FROM mm_merchant_ext WHERE mcc = :k_mcc; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE mrr; tpabort(0); return 1; } + k_level = (k_merch >= 100) ? 3 : (k_merch >= 10) ? 2 : 1; + EXEC SQL UPDATE mm_mcc SET risk_level = :k_level WHERE mcc = :k_mcc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "UPDATE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE mrr; tpabort(0); return 1; } + rolled++; + } + EXEC SQL CLOSE mrr; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> mm_risk_rollup_batch COMMIT: 재산정=%ld건\n", rolled); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/mm/batch/mm_risk_rollup_cpy.h b/app/src/mm/batch/mm_risk_rollup_cpy.h new file mode 100644 index 0000000..2a5bafd --- /dev/null +++ b/app/src/mm/batch/mm_risk_rollup_cpy.h @@ -0,0 +1,6 @@ +/* mm_risk_rollup_cpy.h - mm_risk_rollup_batch 전용 카피북 (host 변수 선언). */ +EXEC SQL BEGIN DECLARE SECTION; +char k_mcc[8]; +long k_merch; +long k_level; +EXEC SQL END DECLARE SECTION; diff --git a/app/src/mm/dbio/mm_bin_distinct_dbio.h b/app/src/mm/dbio/mm_bin_distinct_dbio.h new file mode 100644 index 0000000..9b0b6c6 --- /dev/null +++ b/app/src/mm/dbio/mm_bin_distinct_dbio.h @@ -0,0 +1,9 @@ +/* + * mm_bin_distinct_dbio.h - mm 모듈 DB 접근 계층 copybook (mm_bin_distinct_dbio, part of libmmdbio.a). + * 카드브랜드별 고유 발급사 수 (count DISTINCT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_BIN_DISTINCT_DBIO_H +#define MM_BIN_DISTINCT_DBIO_H +long mmdb_bin_distinct_issuer(const char *brand); +#endif /* MM_BIN_DISTINCT_DBIO_H */ diff --git a/app/src/mm/dbio/mm_bin_distinct_dbio.pgc b/app/src/mm/dbio/mm_bin_distinct_dbio.pgc new file mode 100644 index 0000000..544b83e --- /dev/null +++ b/app/src/mm/dbio/mm_bin_distinct_dbio.pgc @@ -0,0 +1,22 @@ +/* + * mm_bin_distinct_dbio.pgc - mm 모듈 DB 접근 함수 (mm_bin_distinct_dbio), archived into libmmdbio.a. + * 카드브랜드별 고유 발급사 수 (count DISTINCT). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_bin_distinct_dbio.h" + +/* card_brand 의 BIN 에서 고유 issuer_cd 수. */ +long mmdb_bin_distinct_issuer(const char *brand) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_brand[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_brand, brand, sizeof(h_brand)-1); h_brand[sizeof(h_brand)-1] = 0; + EXEC SQL SELECT count(DISTINCT issuer_cd) INTO :h_v FROM mm_bin + WHERE card_brand = :h_brand AND status = 'ACTIVE'; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/mm/dbio/mm_fee_in_dbio.h b/app/src/mm/dbio/mm_fee_in_dbio.h new file mode 100644 index 0000000..af80bbd --- /dev/null +++ b/app/src/mm/dbio/mm_fee_in_dbio.h @@ -0,0 +1,9 @@ +/* + * mm_fee_in_dbio.h - mm 모듈 DB 접근 계층 copybook (mm_fee_in_dbio, part of libmmdbio.a). + * 특정 등급 가맹점의 평균 수수료율 (IN 서브셀렉트). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_FEE_IN_DBIO_H +#define MM_FEE_IN_DBIO_H +long mmdb_fee_in_avg(const char *grade); +#endif /* MM_FEE_IN_DBIO_H */ diff --git a/app/src/mm/dbio/mm_fee_in_dbio.pgc b/app/src/mm/dbio/mm_fee_in_dbio.pgc new file mode 100644 index 0000000..27c3d40 --- /dev/null +++ b/app/src/mm/dbio/mm_fee_in_dbio.pgc @@ -0,0 +1,23 @@ +/* + * mm_fee_in_dbio.pgc - mm 모듈 DB 접근 함수 (mm_fee_in_dbio), archived into libmmdbio.a. + * 특정 등급 가맹점의 평균 수수료율 (IN 서브셀렉트). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_fee_in_dbio.h" + +/* grade 에 해당하는 가맹점(IN) 의 CREDIT 수수료율 평균(bps). */ +long mmdb_fee_in_avg(const char *grade) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_grade[8]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_grade, grade, sizeof(h_grade)-1); h_grade[sizeof(h_grade)-1] = 0; + EXEC SQL SELECT coalesce(avg(rate_bps),0)::bigint INTO :h_v FROM mm_fee_rate + WHERE card_type = 'CREDIT' + AND merchant_id IN (SELECT merchant_id FROM mm_merchant_ext WHERE grade = :h_grade); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/mm/dbio/mm_fee_rank_dbio.h b/app/src/mm/dbio/mm_fee_rank_dbio.h new file mode 100644 index 0000000..6866a79 --- /dev/null +++ b/app/src/mm/dbio/mm_fee_rank_dbio.h @@ -0,0 +1,9 @@ +/* + * mm_fee_rank_dbio.h - mm 모듈 DB 접근 계층 copybook (mm_fee_rank_dbio, part of libmmdbio.a). + * 카드유형 내 수수료율 순위 n번째 가맹점 (window RANK). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_FEE_RANK_DBIO_H +#define MM_FEE_RANK_DBIO_H +int mmdb_fee_rank(const char *card_type, long rank, char *merch_out); +#endif /* MM_FEE_RANK_DBIO_H */ diff --git a/app/src/mm/dbio/mm_fee_rank_dbio.pgc b/app/src/mm/dbio/mm_fee_rank_dbio.pgc new file mode 100644 index 0000000..587c928 --- /dev/null +++ b/app/src/mm/dbio/mm_fee_rank_dbio.pgc @@ -0,0 +1,26 @@ +/* + * mm_fee_rank_dbio.pgc - mm 모듈 DB 접근 함수 (mm_fee_rank_dbio), archived into libmmdbio.a. + * 카드유형 내 수수료율 순위 n번째 가맹점 (window RANK). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_fee_rank_dbio.h" + +/* card_type 파티션에서 rate_bps 내림차순 rank 번째 가맹점. */ +int mmdb_fee_rank(const char *card_type, long rank, char *merch_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_ct[16], h_merch[64]; + long h_rank = rank; + EXEC SQL END DECLARE SECTION; + strncpy(h_ct, card_type, sizeof(h_ct)-1); h_ct[sizeof(h_ct)-1] = 0; + EXEC SQL SELECT merchant_id INTO :h_merch FROM ( + SELECT merchant_id, rank() OVER (PARTITION BY card_type ORDER BY rate_bps DESC) AS rk + FROM mm_fee_rate WHERE card_type = :h_ct) t + WHERE t.rk = :h_rank LIMIT 1; + if (sqlca.sqlcode == 100) { merch_out[0] = 0; return 0; } + if (sqlca.sqlcode < 0) return -1; + strcpy(merch_out, h_merch); + return 0; +} diff --git a/app/src/mm/dbio/mm_fx_upsert_dbio.h b/app/src/mm/dbio/mm_fx_upsert_dbio.h new file mode 100644 index 0000000..0bfa11a --- /dev/null +++ b/app/src/mm/dbio/mm_fx_upsert_dbio.h @@ -0,0 +1,9 @@ +/* + * mm_fx_upsert_dbio.h - mm 모듈 DB 접근 계층 copybook (mm_fx_upsert_dbio, part of libmmdbio.a). + * 환율 마스터 upsert (ON CONFLICT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_FX_UPSERT_DBIO_H +#define MM_FX_UPSERT_DBIO_H +int mmdb_fx_upsert(const char *ccy, long rate_bps, const char *base_date); +#endif /* MM_FX_UPSERT_DBIO_H */ diff --git a/app/src/mm/dbio/mm_fx_upsert_dbio.pgc b/app/src/mm/dbio/mm_fx_upsert_dbio.pgc new file mode 100644 index 0000000..6e45edb --- /dev/null +++ b/app/src/mm/dbio/mm_fx_upsert_dbio.pgc @@ -0,0 +1,25 @@ +/* + * mm_fx_upsert_dbio.pgc - mm 모듈 DB 접근 함수 (mm_fx_upsert_dbio), archived into libmmdbio.a. + * 환율 마스터 upsert (ON CONFLICT). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_fx_upsert_dbio.h" + +/* mm_fx_rate 에 통화별 환율을 upsert. */ +int mmdb_fx_upsert(const char *ccy, long rate_bps, const char *base_date) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_ccy[8], h_date[16]; + long h_rate = rate_bps; + EXEC SQL END DECLARE SECTION; + strncpy(h_ccy, ccy, sizeof(h_ccy)-1); h_ccy[sizeof(h_ccy)-1] = 0; + strncpy(h_date, base_date, sizeof(h_date)-1); h_date[sizeof(h_date)-1] = 0; + EXEC SQL INSERT INTO mm_fx_rate (ccy, rate_bps, base_date) + VALUES (:h_ccy, :h_rate, :h_date) + ON CONFLICT (ccy) DO UPDATE + SET rate_bps = EXCLUDED.rate_bps, base_date = EXCLUDED.base_date, updated_at = now(); + if (sqlca.sqlcode < 0) { userlog("mmdb_fx_upsert FAIL [%d]", sqlca.sqlcode); return -1; } + return 0; +} diff --git a/app/src/mm/dbio/mm_grade_having_dbio.h b/app/src/mm/dbio/mm_grade_having_dbio.h new file mode 100644 index 0000000..5d9d87f --- /dev/null +++ b/app/src/mm/dbio/mm_grade_having_dbio.h @@ -0,0 +1,9 @@ +/* + * mm_grade_having_dbio.h - mm 모듈 DB 접근 계층 copybook (mm_grade_having_dbio, part of libmmdbio.a). + * 가맹점 수 임계 초과 등급 수 (GROUP BY ... HAVING). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_GRADE_HAVING_DBIO_H +#define MM_GRADE_HAVING_DBIO_H +long mmdb_grade_having(long min_cnt); +#endif /* MM_GRADE_HAVING_DBIO_H */ diff --git a/app/src/mm/dbio/mm_grade_having_dbio.pgc b/app/src/mm/dbio/mm_grade_having_dbio.pgc new file mode 100644 index 0000000..b35140d --- /dev/null +++ b/app/src/mm/dbio/mm_grade_having_dbio.pgc @@ -0,0 +1,32 @@ +/* + * mm_grade_having_dbio.pgc - mm 모듈 DB 접근 함수 (mm_grade_having_dbio), archived into libmmdbio.a. + * 가맹점 수 임계 초과 등급 수 (GROUP BY ... HAVING 커서). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_grade_having_dbio.h" + +/* 등급별 활성 가맹점 수가 min_cnt 이상인 등급 개수. */ +long mmdb_grade_having(long min_cnt) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_min = min_cnt, h_cnt; + char h_grade[8]; + EXEC SQL END DECLARE SECTION; + long grades = 0; + EXEC SQL DECLARE mgh CURSOR FOR + SELECT grade, count(*) FROM mm_merchant_ext + WHERE status = 'ACTIVE' GROUP BY grade HAVING count(*) >= :h_min + ORDER BY grade; + EXEC SQL OPEN mgh; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH mgh INTO :h_grade, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE mgh; return -1; } + grades++; + } + EXEC SQL CLOSE mgh; + return grades; +} diff --git a/app/src/mm/dbio/mm_grade_insert_sel_dbio.h b/app/src/mm/dbio/mm_grade_insert_sel_dbio.h new file mode 100644 index 0000000..5143b60 --- /dev/null +++ b/app/src/mm/dbio/mm_grade_insert_sel_dbio.h @@ -0,0 +1,9 @@ +/* + * mm_grade_insert_sel_dbio.h - mm 모듈 DB 접근 계층 copybook (mm_grade_insert_sel_dbio, part of libmmdbio.a). + * 등급별 가맹점 수 통계 일괄 적재 (INSERT..SELECT). + * Return convention: >=0 ok(적재건수), -1 = SQL error. + */ +#ifndef MM_GRADE_INSERT_SEL_DBIO_H +#define MM_GRADE_INSERT_SEL_DBIO_H +long mmdb_grade_insert_sel(const char *statdate); +#endif /* MM_GRADE_INSERT_SEL_DBIO_H */ diff --git a/app/src/mm/dbio/mm_grade_insert_sel_dbio.pgc b/app/src/mm/dbio/mm_grade_insert_sel_dbio.pgc new file mode 100644 index 0000000..25ea45a --- /dev/null +++ b/app/src/mm/dbio/mm_grade_insert_sel_dbio.pgc @@ -0,0 +1,24 @@ +/* + * mm_grade_insert_sel_dbio.pgc - mm 모듈 DB 접근 함수 (mm_grade_insert_sel_dbio), archived into libmmdbio.a. + * 등급별 가맹점 수 통계 일괄 적재 (INSERT..SELECT). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_grade_insert_sel_dbio.h" + +/* 활성 가맹점을 등급별로 집계해 mm_grade_stat 에 한 문장으로 적재/갱신. */ +long mmdb_grade_insert_sel(const char *statdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_date[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_date, statdate, sizeof(h_date)-1); h_date[sizeof(h_date)-1] = 0; + EXEC SQL INSERT INTO mm_grade_stat (stat_date, grade, merch_cnt) + SELECT :h_date, grade, count(*) FROM mm_merchant_ext + WHERE status = 'ACTIVE' GROUP BY grade + ON CONFLICT (stat_date, grade) DO UPDATE + SET merch_cnt = EXCLUDED.merch_cnt, updated_at = now(); + if (sqlca.sqlcode < 0) { userlog("mmdb_grade_insert_sel FAIL [%d]", sqlca.sqlcode); return -1; } + return (long) sqlca.sqlerrd[2]; +} diff --git a/app/src/mm/dbio/mm_hist_purge_dbio.h b/app/src/mm/dbio/mm_hist_purge_dbio.h new file mode 100644 index 0000000..e11b263 --- /dev/null +++ b/app/src/mm/dbio/mm_hist_purge_dbio.h @@ -0,0 +1,9 @@ +/* + * mm_hist_purge_dbio.h - mm 모듈 DB 접근 계층 copybook (mm_hist_purge_dbio, part of libmmdbio.a). + * 수수료율 이력 범위 삭제 후 삭제건수 반환 (DELETE range). + * Return convention: >=0 ok(삭제건수), -1 = SQL error. + */ +#ifndef MM_HIST_PURGE_DBIO_H +#define MM_HIST_PURGE_DBIO_H +long mmdb_hist_purge(const char *cutoff); +#endif /* MM_HIST_PURGE_DBIO_H */ diff --git a/app/src/mm/dbio/mm_hist_purge_dbio.pgc b/app/src/mm/dbio/mm_hist_purge_dbio.pgc new file mode 100644 index 0000000..d0702a9 --- /dev/null +++ b/app/src/mm/dbio/mm_hist_purge_dbio.pgc @@ -0,0 +1,20 @@ +/* + * mm_hist_purge_dbio.pgc - mm 모듈 DB 접근 함수 (mm_hist_purge_dbio), archived into libmmdbio.a. + * 수수료율 이력 범위 삭제 후 삭제건수 반환 (DELETE range). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_hist_purge_dbio.h" + +/* eff_date 이전 수수료율 이력 삭제, 삭제 행수 반환. */ +long mmdb_hist_purge(const char *cutoff) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_cutoff[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_cutoff, cutoff, sizeof(h_cutoff)-1); h_cutoff[sizeof(h_cutoff)-1] = 0; + EXEC SQL DELETE FROM mm_fee_rate_hist WHERE eff_date < :h_cutoff; + if (sqlca.sqlcode < 0) { userlog("mmdb_hist_purge FAIL [%d]", sqlca.sqlcode); return -1; } + return (long) sqlca.sqlerrd[2]; +} diff --git a/app/src/mm/dbio/mm_issuer_corr_dbio.h b/app/src/mm/dbio/mm_issuer_corr_dbio.h new file mode 100644 index 0000000..00f8dbf --- /dev/null +++ b/app/src/mm/dbio/mm_issuer_corr_dbio.h @@ -0,0 +1,9 @@ +/* + * mm_issuer_corr_dbio.h - mm 모듈 DB 접근 계층 copybook (mm_issuer_corr_dbio, part of libmmdbio.a). + * 발급사별 BIN 수 상관 서브쿼리 순회 최대치 (correlated subquery + cursor). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_ISSUER_CORR_DBIO_H +#define MM_ISSUER_CORR_DBIO_H +long mmdb_issuer_corr_max_bin(void); +#endif /* MM_ISSUER_CORR_DBIO_H */ diff --git a/app/src/mm/dbio/mm_issuer_corr_dbio.pgc b/app/src/mm/dbio/mm_issuer_corr_dbio.pgc new file mode 100644 index 0000000..e7daaf4 --- /dev/null +++ b/app/src/mm/dbio/mm_issuer_corr_dbio.pgc @@ -0,0 +1,33 @@ +/* + * mm_issuer_corr_dbio.pgc - mm 모듈 DB 접근 함수 (mm_issuer_corr_dbio), archived into libmmdbio.a. + * 발급사별 BIN 수 상관 서브쿼리 순회 최대치 (correlated subquery + cursor). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_issuer_corr_dbio.h" + +/* 각 발급사의 활성 BIN 수를 상관 서브쿼리로 뽑아 커서로 훑어 최대값 반환. */ +long mmdb_issuer_corr_max_bin(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_issuer[16]; + long h_bins; + EXEC SQL END DECLARE SECTION; + long peak = 0; + EXEC SQL DECLARE mic CURSOR FOR + SELECT i.issuer_cd, + (SELECT count(*) FROM mm_bin b WHERE b.issuer_cd = i.issuer_cd AND b.status = 'ACTIVE') + FROM mm_issuer i WHERE i.status = 'ACTIVE' + ORDER BY i.issuer_cd; + EXEC SQL OPEN mic; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH mic INTO :h_issuer, :h_bins; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE mic; return -1; } + if (h_bins > peak) peak = h_bins; + } + EXEC SQL CLOSE mic; + return peak; +} diff --git a/app/src/mm/dbio/mm_mcc_risk_cursor_dbio.h b/app/src/mm/dbio/mm_mcc_risk_cursor_dbio.h new file mode 100644 index 0000000..5c0d202 --- /dev/null +++ b/app/src/mm/dbio/mm_mcc_risk_cursor_dbio.h @@ -0,0 +1,9 @@ +/* + * mm_mcc_risk_cursor_dbio.h - mm 모듈 DB 접근 계층 copybook (mm_mcc_risk_cursor_dbio, part of libmmdbio.a). + * MCC 위험도 가맹점수 가중 합 (cursor + 누적). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_MCC_RISK_CURSOR_DBIO_H +#define MM_MCC_RISK_CURSOR_DBIO_H +long mmdb_mcc_weighted_risk(void); +#endif /* MM_MCC_RISK_CURSOR_DBIO_H */ diff --git a/app/src/mm/dbio/mm_mcc_risk_cursor_dbio.pgc b/app/src/mm/dbio/mm_mcc_risk_cursor_dbio.pgc new file mode 100644 index 0000000..4336a0f --- /dev/null +++ b/app/src/mm/dbio/mm_mcc_risk_cursor_dbio.pgc @@ -0,0 +1,32 @@ +/* + * mm_mcc_risk_cursor_dbio.pgc - mm 모듈 DB 접근 함수 (mm_mcc_risk_cursor_dbio), archived into libmmdbio.a. + * MCC 위험도 가맹점수 가중 합 (cursor + 누적). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_mcc_risk_cursor_dbio.h" + +/* MCC 를 커서로 훑어 각 MCC 의 가맹점 수 x risk_level 을 누적. */ +long mmdb_mcc_weighted_risk(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_mcc[8]; + long h_risk, h_merch; + EXEC SQL END DECLARE SECTION; + long total = 0; + EXEC SQL DECLARE mrc CURSOR FOR + SELECT mcc, risk_level FROM mm_mcc ORDER BY mcc; + EXEC SQL OPEN mrc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH mrc INTO :h_mcc, :h_risk; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE mrc; return -1; } + EXEC SQL SELECT count(*) INTO :h_merch FROM mm_merchant_ext WHERE mcc = :h_mcc; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE mrc; return -1; } + total += h_risk * h_merch; + } + EXEC SQL CLOSE mrc; + return total; +} diff --git a/app/src/mm/dbio/mm_noterm_dbio.h b/app/src/mm/dbio/mm_noterm_dbio.h new file mode 100644 index 0000000..0dd79fb --- /dev/null +++ b/app/src/mm/dbio/mm_noterm_dbio.h @@ -0,0 +1,9 @@ +/* + * mm_noterm_dbio.h - mm 모듈 DB 접근 계층 copybook (mm_noterm_dbio, part of libmmdbio.a). + * 단말 미보유 활성 가맹점 수 (NOT EXISTS). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_NOTERM_DBIO_H +#define MM_NOTERM_DBIO_H +long mmdb_noterm_count(void); +#endif /* MM_NOTERM_DBIO_H */ diff --git a/app/src/mm/dbio/mm_noterm_dbio.pgc b/app/src/mm/dbio/mm_noterm_dbio.pgc new file mode 100644 index 0000000..3fc8984 --- /dev/null +++ b/app/src/mm/dbio/mm_noterm_dbio.pgc @@ -0,0 +1,22 @@ +/* + * mm_noterm_dbio.pgc - mm 모듈 DB 접근 함수 (mm_noterm_dbio), archived into libmmdbio.a. + * 단말 미보유 활성 가맹점 수 (NOT EXISTS). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_noterm_dbio.h" + +/* mm_terminal 에 단말이 하나도 없는 활성 가맹점 수. */ +long mmdb_noterm_count(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_v FROM mm_merchant_ext m + WHERE m.status = 'ACTIVE' + AND NOT EXISTS (SELECT 1 FROM mm_terminal t + WHERE t.merchant_id = m.merchant_id AND t.status = 'ACTIVE'); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/mm/dbio/mm_rate_minmax_dbio.h b/app/src/mm/dbio/mm_rate_minmax_dbio.h new file mode 100644 index 0000000..c2060f4 --- /dev/null +++ b/app/src/mm/dbio/mm_rate_minmax_dbio.h @@ -0,0 +1,9 @@ +/* + * mm_rate_minmax_dbio.h - mm 모듈 DB 접근 계층 copybook (mm_rate_minmax_dbio, part of libmmdbio.a). + * 카드유형 수수료율 최소/최대 (min/max). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_RATE_MINMAX_DBIO_H +#define MM_RATE_MINMAX_DBIO_H +int mmdb_rate_minmax(const char *card_type, long *lo, long *hi); +#endif /* MM_RATE_MINMAX_DBIO_H */ diff --git a/app/src/mm/dbio/mm_rate_minmax_dbio.pgc b/app/src/mm/dbio/mm_rate_minmax_dbio.pgc new file mode 100644 index 0000000..a8a920c --- /dev/null +++ b/app/src/mm/dbio/mm_rate_minmax_dbio.pgc @@ -0,0 +1,23 @@ +/* + * mm_rate_minmax_dbio.pgc - mm 모듈 DB 접근 함수 (mm_rate_minmax_dbio), archived into libmmdbio.a. + * 카드유형 수수료율 최소/최대 (min/max). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_rate_minmax_dbio.h" + +/* card_type 수수료율의 최소/최대 bps 를 반환. */ +int mmdb_rate_minmax(const char *card_type, long *lo, long *hi) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_ct[16]; + long h_lo = 0, h_hi = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_ct, card_type, sizeof(h_ct)-1); h_ct[sizeof(h_ct)-1] = 0; + EXEC SQL SELECT coalesce(min(rate_bps),0), coalesce(max(rate_bps),0) + INTO :h_lo, :h_hi FROM mm_fee_rate WHERE card_type = :h_ct; + if (sqlca.sqlcode < 0) return -1; + *lo = h_lo; *hi = h_hi; + return 0; +} diff --git a/app/src/mm/dbio/mm_risk_case_dbio.h b/app/src/mm/dbio/mm_risk_case_dbio.h new file mode 100644 index 0000000..854b65d --- /dev/null +++ b/app/src/mm/dbio/mm_risk_case_dbio.h @@ -0,0 +1,9 @@ +/* + * mm_risk_case_dbio.h - mm 모듈 DB 접근 계층 copybook (mm_risk_case_dbio, part of libmmdbio.a). + * MCC 위험도 구간별 건수 (CASE 집계). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_RISK_CASE_DBIO_H +#define MM_RISK_CASE_DBIO_H +int mmdb_risk_case(long *lo, long *mid, long *hi); +#endif /* MM_RISK_CASE_DBIO_H */ diff --git a/app/src/mm/dbio/mm_risk_case_dbio.pgc b/app/src/mm/dbio/mm_risk_case_dbio.pgc new file mode 100644 index 0000000..2e8bec7 --- /dev/null +++ b/app/src/mm/dbio/mm_risk_case_dbio.pgc @@ -0,0 +1,24 @@ +/* + * mm_risk_case_dbio.pgc - mm 모듈 DB 접근 함수 (mm_risk_case_dbio), archived into libmmdbio.a. + * MCC 위험도 구간별 건수 (CASE 집계). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_risk_case_dbio.h" + +/* risk_level 을 저/중/고 구간으로 CASE 분류해 각 건수 반환. */ +int mmdb_risk_case(long *lo, long *mid, long *hi) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_lo = 0, h_mid = 0, h_hi = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT + coalesce(sum(CASE WHEN risk_level <= 1 THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN risk_level = 2 THEN 1 ELSE 0 END),0), + coalesce(sum(CASE WHEN risk_level >= 3 THEN 1 ELSE 0 END),0) + INTO :h_lo, :h_mid, :h_hi FROM mm_mcc; + if (sqlca.sqlcode < 0) return -1; + *lo = h_lo; *mid = h_mid; *hi = h_hi; + return 0; +} diff --git a/app/src/mm/dbio/mm_status_filter_dbio.h b/app/src/mm/dbio/mm_status_filter_dbio.h new file mode 100644 index 0000000..5559eb6 --- /dev/null +++ b/app/src/mm/dbio/mm_status_filter_dbio.h @@ -0,0 +1,9 @@ +/* + * mm_status_filter_dbio.h - mm 모듈 DB 접근 계층 copybook (mm_status_filter_dbio, part of libmmdbio.a). + * 가맹점 상태별 건수 한 행 분리집계 (FILTER). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_STATUS_FILTER_DBIO_H +#define MM_STATUS_FILTER_DBIO_H +int mmdb_status_filter(long *active, long *hold, long *deleted); +#endif /* MM_STATUS_FILTER_DBIO_H */ diff --git a/app/src/mm/dbio/mm_status_filter_dbio.pgc b/app/src/mm/dbio/mm_status_filter_dbio.pgc new file mode 100644 index 0000000..61aa77e --- /dev/null +++ b/app/src/mm/dbio/mm_status_filter_dbio.pgc @@ -0,0 +1,24 @@ +/* + * mm_status_filter_dbio.pgc - mm 모듈 DB 접근 함수 (mm_status_filter_dbio), archived into libmmdbio.a. + * 가맹점 상태별 건수 한 행 분리집계 (FILTER). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_status_filter_dbio.h" + +/* ACTIVE/HOLD/DELETED 건수를 FILTER 로 한 번에 반환. */ +int mmdb_status_filter(long *active, long *hold, long *deleted) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_a = 0, h_h = 0, h_d = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT + count(*) FILTER (WHERE status = 'ACTIVE'), + count(*) FILTER (WHERE status = 'HOLD'), + count(*) FILTER (WHERE status = 'DELETED') + INTO :h_a, :h_h, :h_d FROM mm_merchant_ext; + if (sqlca.sqlcode < 0) return -1; + *active = h_a; *hold = h_h; *deleted = h_d; + return 0; +} diff --git a/app/src/mm/dbio/mm_term_join_dbio.h b/app/src/mm/dbio/mm_term_join_dbio.h new file mode 100644 index 0000000..575638a --- /dev/null +++ b/app/src/mm/dbio/mm_term_join_dbio.h @@ -0,0 +1,9 @@ +/* + * mm_term_join_dbio.h - mm 모듈 DB 접근 계층 copybook (mm_term_join_dbio, part of libmmdbio.a). + * 활성 가맹점의 활성 단말 수 (JOIN). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_TERM_JOIN_DBIO_H +#define MM_TERM_JOIN_DBIO_H +long mmdb_active_terminals(void); +#endif /* MM_TERM_JOIN_DBIO_H */ diff --git a/app/src/mm/dbio/mm_term_join_dbio.pgc b/app/src/mm/dbio/mm_term_join_dbio.pgc new file mode 100644 index 0000000..22ff23d --- /dev/null +++ b/app/src/mm/dbio/mm_term_join_dbio.pgc @@ -0,0 +1,21 @@ +/* + * mm_term_join_dbio.pgc - mm 모듈 DB 접근 함수 (mm_term_join_dbio), archived into libmmdbio.a. + * 활성 가맹점의 활성 단말 수 (JOIN). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_term_join_dbio.h" + +/* mm_terminal 을 mm_merchant_ext 와 조인해 활성 가맹점의 활성 단말 수. */ +long mmdb_active_terminals(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_v + FROM mm_terminal t JOIN mm_merchant_ext m ON t.merchant_id = m.merchant_id + WHERE t.status = 'ACTIVE' AND m.status = 'ACTIVE'; + if (sqlca.sqlcode < 0) { userlog("mmdb_active_terminals FAIL [%d]", sqlca.sqlcode); return -1; } + return h_v; +} diff --git a/app/src/mm/dbio/mm_wavg_limit_dbio.h b/app/src/mm/dbio/mm_wavg_limit_dbio.h new file mode 100644 index 0000000..2d8adcf --- /dev/null +++ b/app/src/mm/dbio/mm_wavg_limit_dbio.h @@ -0,0 +1,9 @@ +/* + * mm_wavg_limit_dbio.h - mm 모듈 DB 접근 계층 copybook (mm_wavg_limit_dbio, part of libmmdbio.a). + * 한도 가중 수수료율 평균 (weighted average + JOIN). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef MM_WAVG_LIMIT_DBIO_H +#define MM_WAVG_LIMIT_DBIO_H +long mmdb_wavg_rate(void); +#endif /* MM_WAVG_LIMIT_DBIO_H */ diff --git a/app/src/mm/dbio/mm_wavg_limit_dbio.pgc b/app/src/mm/dbio/mm_wavg_limit_dbio.pgc new file mode 100644 index 0000000..db54216 --- /dev/null +++ b/app/src/mm/dbio/mm_wavg_limit_dbio.pgc @@ -0,0 +1,23 @@ +/* + * mm_wavg_limit_dbio.pgc - mm 모듈 DB 접근 함수 (mm_wavg_limit_dbio), archived into libmmdbio.a. + * 한도 가중 수수료율 평균 (weighted average + JOIN). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "mm_wavg_limit_dbio.h" + +/* 일한도로 가중한 CREDIT 수수료율 평균 = sum(rate*limit)/sum(limit). */ +long mmdb_wavg_rate(void) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_num = 0, h_den = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT coalesce(sum(f.rate_bps * l.daily_limit),0), coalesce(sum(l.daily_limit),0) + INTO :h_num, :h_den + FROM mm_fee_rate f JOIN mm_limit l ON f.merchant_id = l.merchant_id + WHERE f.card_type = 'CREDIT'; + if (sqlca.sqlcode < 0) return -1; + if (h_den <= 0) return 0; + return h_num / h_den; +} diff --git a/app/src/py/batch/py_cancelpurge_batch.pgc b/app/src/py/batch/py_cancelpurge_batch.pgc new file mode 100644 index 0000000..03b35d5 --- /dev/null +++ b/app/src/py/batch/py_cancelpurge_batch.pgc @@ -0,0 +1,46 @@ +/* + * py_cancelpurge_batch.pgc - py 취소 지급 정리 (기간 DELETE 배치, XA). + * + * Deletes cancelled(C) payouts across a [from,to] date range in ONE XA + * transaction and reports how many rows were purged. + * Usage: py_cancelpurge_batch FROM_DATE TO_DATE + */ +#include +#include +#include +#include +#include "py_cancelpurge_batch.h" + +int main(int argc, char **argv) +{ + const char *from_date = (argc > 1) ? argv[1] : "2026-01-01"; + const char *to_date = (argc > 2) ? argv[2] : "2026-06-30"; + py_cancelpurge_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_from[16], h_to[16]; + long h_purged; + EXEC SQL END DECLARE SECTION; + + strncpy(h_from, from_date, sizeof(h_from)-1); h_from[sizeof(h_from)-1] = 0; + strncpy(h_to, to_date, 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 DELETE FROM py_payout + WHERE status = 'C' AND biz_date >= :h_from AND biz_date <= :h_to; + if (sqlca.sqlcode < 0) { fprintf(stderr, "py_cancelpurge_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_purged = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + + strncpy(rec.from_date, from_date, sizeof(rec.from_date)-1); rec.from_date[sizeof(rec.from_date)-1] = 0; + strncpy(rec.to_date, to_date, sizeof(rec.to_date)-1); rec.to_date[sizeof(rec.to_date)-1] = 0; + rec.purged = h_purged; + printf(">>> py_cancelpurge_batch COMMIT: range=%s..%s purged=%ld\n", rec.from_date, rec.to_date, rec.purged); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_chanwalk_batch.pgc b/app/src/py/batch/py_chanwalk_batch.pgc new file mode 100644 index 0000000..90f4090 --- /dev/null +++ b/app/src/py/batch/py_chanwalk_batch.pgc @@ -0,0 +1,52 @@ +/* + * py_chanwalk_batch.pgc - py 채널별 집계 순회 (커서 배치, XA). + * + * Walks a channel-grouped cursor over the day's payouts, accumulating channel + * count and grand total, inside ONE XA transaction. + * Usage: py_chanwalk_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "py_chanwalk_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + py_chanwalk_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_ch[16]; + long h_cnt, h_amt; + EXEC SQL END DECLARE SECTION; + + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + rec.channels = 0; rec.grand_total = 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 cwcur CURSOR FOR + SELECT channel, count(*), coalesce(sum(amount),0) + FROM py_payout WHERE biz_date = :h_bizdate GROUP BY channel ORDER BY channel; + EXEC SQL OPEN cwcur; + if (sqlca.sqlcode < 0) { fprintf(stderr, "py_chanwalk_batch OPEN FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH cwcur INTO :h_ch, :h_cnt, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE cwcur; fprintf(stderr, "py_chanwalk_batch FETCH FAIL [%d]\n", sqlca.sqlcode); tpabort(0); return 1; } + rec.channels++; rec.grand_total += h_amt; + } + EXEC SQL CLOSE cwcur; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + + strncpy(rec.biz_date, bizdate, sizeof(rec.biz_date)-1); rec.biz_date[sizeof(rec.biz_date)-1] = 0; + printf(">>> py_chanwalk_batch COMMIT: date=%s channels=%ld grand_total=%ld\n", + rec.biz_date, rec.channels, rec.grand_total); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_ledgerpost_batch.pgc b/app/src/py/batch/py_ledgerpost_batch.pgc new file mode 100644 index 0000000..08f156c --- /dev/null +++ b/app/src/py/batch/py_ledgerpost_batch.pgc @@ -0,0 +1,44 @@ +/* + * py_ledgerpost_batch.pgc - py 지급완료 원장 전기 (INSERT..SELECT 배치, XA). + * + * Posts every paid(P) payout of the day into py_ledger as an OUT entry with a + * single INSERT..SELECT, in ONE XA transaction. + * Usage: py_ledgerpost_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "py_ledgerpost_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + py_ledgerpost_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_posted; + 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 py_ledger (ledger_id, payout_id, entry_type, amount, biz_date) + SELECT nextval('py_ledger_seq'), payout_id, 'OUT', net, biz_date + FROM py_payout WHERE biz_date = :h_bizdate AND status = 'P'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "py_ledgerpost_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + h_posted = sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + + strncpy(rec.biz_date, bizdate, sizeof(rec.biz_date)-1); rec.biz_date[sizeof(rec.biz_date)-1] = 0; + rec.posted = h_posted; + printf(">>> py_ledgerpost_batch COMMIT: date=%s posted=%ld\n", rec.biz_date, rec.posted); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_recon_batch.pgc b/app/src/py/batch/py_recon_batch.pgc new file mode 100644 index 0000000..9618f5f --- /dev/null +++ b/app/src/py/batch/py_recon_batch.pgc @@ -0,0 +1,49 @@ +/* + * py_recon_batch.pgc - py 지급집계 대사 (집계 vs 실집계 2-쿼리 대조 배치, XA). + * + * Compares the stored py_summary amount against the live py_payout sum for one + * merchant/day and reports the gap, all inside ONE XA transaction. + * Usage: py_recon_batch MERCHANT [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "py_recon_batch.h" + +int main(int argc, char **argv) +{ + const char *merch = (argc > 1) ? argv[1] : "M0001"; + const char *bizdate = (argc > 2) ? argv[2] : "2026-07-19"; + py_recon_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16]; + long h_rec, h_live; + EXEC SQL END DECLARE SECTION; + + 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; + + 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(payout_amount,0) INTO :h_rec FROM py_summary + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "py_recon_batch sum FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + if (sqlca.sqlcode == 100) h_rec = 0; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_live FROM py_payout + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "py_recon_batch live 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; } + + strncpy(rec.merchant_id, merch, sizeof(rec.merchant_id)-1); rec.merchant_id[sizeof(rec.merchant_id)-1] = 0; + rec.recorded_amt = h_rec; rec.live_amt = h_live; rec.gap = h_live - h_rec; + printf(">>> py_recon_batch COMMIT: merch=%s date=%s recorded=%ld live=%ld gap=%ld\n", + rec.merchant_id, bizdate, rec.recorded_amt, rec.live_amt, rec.gap); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/batch/py_winrank_batch.pgc b/app/src/py/batch/py_winrank_batch.pgc new file mode 100644 index 0000000..bd2b231 --- /dev/null +++ b/app/src/py/batch/py_winrank_batch.pgc @@ -0,0 +1,43 @@ +/* + * py_winrank_batch.pgc - py 누계 최고점 (윈도우 함수 배치, XA). + * + * Computes the peak per-merchant running total of net payouts for the day using + * a SUM() OVER window, inside ONE XA transaction. + * Usage: py_winrank_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "py_winrank_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + py_winrank_rec_t rec; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_peak; + 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(max(run_tot),0) INTO :h_peak FROM ( + SELECT sum(net) OVER (PARTITION BY merchant_id ORDER BY payout_id) AS run_tot + FROM py_payout WHERE biz_date = :h_bizdate) w; + if (sqlca.sqlcode < 0) { fprintf(stderr, "py_winrank_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; } + + strncpy(rec.biz_date, bizdate, sizeof(rec.biz_date)-1); rec.biz_date[sizeof(rec.biz_date)-1] = 0; + rec.peak_running = h_peak; + printf(">>> py_winrank_batch COMMIT: date=%s peak_running=%ld\n", rec.biz_date, rec.peak_running); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/py/dbio/py_account_having_dbio.h b/app/src/py/dbio/py_account_having_dbio.h new file mode 100644 index 0000000..f7cc9dc --- /dev/null +++ b/app/src/py/dbio/py_account_having_dbio.h @@ -0,0 +1,11 @@ +/* + * py_account_having_dbio.h - py 모듈 DB 접근 계층 copybook (py_account_having_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_ACCOUNT_HAVING_DBIO_H +#define PY_ACCOUNT_HAVING_DBIO_H + +long pydb_account_low_balance_merchants(long floor_bal); + +#endif /* PY_ACCOUNT_HAVING_DBIO_H */ diff --git a/app/src/py/dbio/py_account_having_dbio.pgc b/app/src/py/dbio/py_account_having_dbio.pgc new file mode 100644 index 0000000..9394a3a --- /dev/null +++ b/app/src/py/dbio/py_account_having_dbio.pgc @@ -0,0 +1,21 @@ +/* + * py_account_having_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_account_having_dbio), archived into libpydbio.a. + * 가맹점 계좌 합산잔액이 기준 미만인 가맹점 수를 GROUP BY HAVING 으로 센다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_account_having_dbio.h" + +/* GROUP BY HAVING: 합산잔액 < floor 인 가맹점 수. */ +long pydb_account_low_balance_merchants(long floor_bal) +{ + EXEC SQL BEGIN DECLARE SECTION; + long h_floor = floor_bal, h_cnt = 0; + EXEC SQL END DECLARE SECTION; + EXEC SQL SELECT count(*) INTO :h_cnt FROM ( + SELECT merchant_id FROM py_account + GROUP BY merchant_id HAVING coalesce(sum(balance),0) < :h_floor) t; + if (sqlca.sqlcode < 0) { userlog("pydb_account_low_balance_merchants FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_cnt; +} diff --git a/app/src/py/dbio/py_cancelpurge_batch.h b/app/src/py/dbio/py_cancelpurge_batch.h new file mode 100644 index 0000000..e12a015 --- /dev/null +++ b/app/src/py/dbio/py_cancelpurge_batch.h @@ -0,0 +1,14 @@ +/* + * py_cancelpurge_batch.h - py 취소건 정리 배치 전용 copybook. + * 삭제 범위/건수 레이아웃. + */ +#ifndef PY_CANCELPURGE_BATCH_H +#define PY_CANCELPURGE_BATCH_H + +typedef struct { + char from_date[16]; + char to_date[16]; + long purged; +} py_cancelpurge_rec_t; + +#endif /* PY_CANCELPURGE_BATCH_H */ diff --git a/app/src/py/dbio/py_channel_walk_dbio.h b/app/src/py/dbio/py_channel_walk_dbio.h new file mode 100644 index 0000000..1ac1c32 --- /dev/null +++ b/app/src/py/dbio/py_channel_walk_dbio.h @@ -0,0 +1,11 @@ +/* + * py_channel_walk_dbio.h - py 모듈 DB 접근 계층 copybook (py_channel_walk_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_CHANNEL_WALK_DBIO_H +#define PY_CHANNEL_WALK_DBIO_H + +long pydb_channel_active(const char *bizdate, long min_txn); + +#endif /* PY_CHANNEL_WALK_DBIO_H */ diff --git a/app/src/py/dbio/py_channel_walk_dbio.pgc b/app/src/py/dbio/py_channel_walk_dbio.pgc new file mode 100644 index 0000000..ecbe178 --- /dev/null +++ b/app/src/py/dbio/py_channel_walk_dbio.pgc @@ -0,0 +1,32 @@ +/* + * py_channel_walk_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_channel_walk_dbio), archived into libpydbio.a. + * 채널별 집계를 커서로 순회하며 활성 채널(기준 건수 이상)을 센다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_channel_walk_dbio.h" + +/* 커서 순회: 채널별 (건수, 금액) 을 훑어 min_txn 이상인 채널 수 반환. */ +long pydb_channel_active(const char *bizdate, long min_txn) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_ch[16]; + long h_min = min_txn, h_cnt, h_amt; + EXEC SQL END DECLARE SECTION; + long active = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE pych CURSOR FOR + SELECT channel, count(*), coalesce(sum(amount),0) + FROM py_payout WHERE biz_date = :h_bizdate GROUP BY channel ORDER BY channel; + EXEC SQL OPEN pych; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH pych INTO :h_ch, :h_cnt, :h_amt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE pych; return -1; } + if (h_cnt >= h_min) active++; + } + EXEC SQL CLOSE pych; + return active; +} diff --git a/app/src/py/dbio/py_chanwalk_batch.h b/app/src/py/dbio/py_chanwalk_batch.h new file mode 100644 index 0000000..14e662c --- /dev/null +++ b/app/src/py/dbio/py_chanwalk_batch.h @@ -0,0 +1,14 @@ +/* + * py_chanwalk_batch.h - py 채널 순회 집계 배치 전용 copybook. + * 채널별 (건수,금액) 누적 레이아웃. + */ +#ifndef PY_CHANWALK_BATCH_H +#define PY_CHANWALK_BATCH_H + +typedef struct { + char biz_date[16]; + long channels; + long grand_total; +} py_chanwalk_rec_t; + +#endif /* PY_CHANWALK_BATCH_H */ diff --git a/app/src/py/dbio/py_file_span_dbio.h b/app/src/py/dbio/py_file_span_dbio.h new file mode 100644 index 0000000..039879c --- /dev/null +++ b/app/src/py/dbio/py_file_span_dbio.h @@ -0,0 +1,11 @@ +/* + * py_file_span_dbio.h - py 모듈 DB 접근 계층 copybook (py_file_span_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_FILE_SPAN_DBIO_H +#define PY_FILE_SPAN_DBIO_H + +int pydb_file_record_span(const char *bizdate, long *min_out, long *max_out); + +#endif /* PY_FILE_SPAN_DBIO_H */ diff --git a/app/src/py/dbio/py_file_span_dbio.pgc b/app/src/py/dbio/py_file_span_dbio.pgc new file mode 100644 index 0000000..50a846e --- /dev/null +++ b/app/src/py/dbio/py_file_span_dbio.pgc @@ -0,0 +1,23 @@ +/* + * py_file_span_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_file_span_dbio), archived into libpydbio.a. + * 당일 지급파일의 최소/최대 레코드건수를 한 번에 조회한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_file_span_dbio.h" + +/* min/max: 당일 파일 레코드건수 범위를 out 파라미터로 반환. */ +int pydb_file_record_span(const char *bizdate, long *min_out, long *max_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_min = 0, h_max = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(min(record_count),0), coalesce(max(record_count),0) + INTO :h_min, :h_max FROM py_file WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("pydb_file_record_span FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + *min_out = h_min; *max_out = h_max; + return 0; +} diff --git a/app/src/py/dbio/py_ledger_mix_dbio.h b/app/src/py/dbio/py_ledger_mix_dbio.h new file mode 100644 index 0000000..4951dbd --- /dev/null +++ b/app/src/py/dbio/py_ledger_mix_dbio.h @@ -0,0 +1,11 @@ +/* + * py_ledger_mix_dbio.h - py 모듈 DB 접근 계층 copybook (py_ledger_mix_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_LEDGER_MIX_DBIO_H +#define PY_LEDGER_MIX_DBIO_H + +int pydb_ledger_type_mix(const char *bizdate, long *kinds_out, long *out_amt_out); + +#endif /* PY_LEDGER_MIX_DBIO_H */ diff --git a/app/src/py/dbio/py_ledger_mix_dbio.pgc b/app/src/py/dbio/py_ledger_mix_dbio.pgc new file mode 100644 index 0000000..199486a --- /dev/null +++ b/app/src/py/dbio/py_ledger_mix_dbio.pgc @@ -0,0 +1,24 @@ +/* + * py_ledger_mix_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_ledger_mix_dbio), archived into libpydbio.a. + * 지급원장 상세의 엔트리 종류 수와 OUT 금액을 count DISTINCT + FILTER 로 함께 구한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_ledger_mix_dbio.h" + +/* count DISTINCT + FILTER: 엔트리 종류 수와 OUT 합계 동시 산출. */ +int pydb_ledger_type_mix(const char *bizdate, long *kinds_out, long *out_amt_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_kinds = 0, h_out = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(DISTINCT entry_type), + coalesce(sum(amount) FILTER (WHERE entry_type = 'OUT'), 0) + INTO :h_kinds, :h_out FROM py_ledger WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("pydb_ledger_type_mix FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + *kinds_out = h_kinds; *out_amt_out = h_out; + return 0; +} diff --git a/app/src/py/dbio/py_ledger_post_dbio.h b/app/src/py/dbio/py_ledger_post_dbio.h new file mode 100644 index 0000000..5514c81 --- /dev/null +++ b/app/src/py/dbio/py_ledger_post_dbio.h @@ -0,0 +1,11 @@ +/* + * py_ledger_post_dbio.h - py 모듈 DB 접근 계층 copybook (py_ledger_post_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_LEDGER_POST_DBIO_H +#define PY_LEDGER_POST_DBIO_H + +long pydb_ledger_post_paid(const char *bizdate); + +#endif /* PY_LEDGER_POST_DBIO_H */ diff --git a/app/src/py/dbio/py_ledger_post_dbio.pgc b/app/src/py/dbio/py_ledger_post_dbio.pgc new file mode 100644 index 0000000..f4d19b0 --- /dev/null +++ b/app/src/py/dbio/py_ledger_post_dbio.pgc @@ -0,0 +1,22 @@ +/* + * py_ledger_post_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_ledger_post_dbio), archived into libpydbio.a. + * 지급완료(P) 건을 지급원장 상세로 INSERT..SELECT 전기한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_ledger_post_dbio.h" + +/* INSERT..SELECT: 지급완료 건을 OUT 엔트리로 원장상세에 적재, 적재 건수 반환. */ +long pydb_ledger_post_paid(const char *bizdate) +{ + 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; + EXEC SQL INSERT INTO py_ledger (ledger_id, payout_id, entry_type, amount, biz_date) + SELECT nextval('py_ledger_seq'), payout_id, 'OUT', net, biz_date + FROM py_payout WHERE biz_date = :h_bizdate AND status = 'P'; + if (sqlca.sqlcode < 0) { userlog("pydb_ledger_post_paid FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return sqlca.sqlerrd[2]; +} diff --git a/app/src/py/dbio/py_ledgerpost_batch.h b/app/src/py/dbio/py_ledgerpost_batch.h new file mode 100644 index 0000000..fbf640a --- /dev/null +++ b/app/src/py/dbio/py_ledgerpost_batch.h @@ -0,0 +1,13 @@ +/* + * py_ledgerpost_batch.h - py 지급완료 원장전기 배치 전용 copybook. + * INSERT..SELECT 전기 건수 레이아웃. + */ +#ifndef PY_LEDGERPOST_BATCH_H +#define PY_LEDGERPOST_BATCH_H + +typedef struct { + char biz_date[16]; + long posted; +} py_ledgerpost_rec_t; + +#endif /* PY_LEDGERPOST_BATCH_H */ diff --git a/app/src/py/dbio/py_payout_corr_dbio.h b/app/src/py/dbio/py_payout_corr_dbio.h new file mode 100644 index 0000000..abafbf4 --- /dev/null +++ b/app/src/py/dbio/py_payout_corr_dbio.h @@ -0,0 +1,11 @@ +/* + * py_payout_corr_dbio.h - py 모듈 DB 접근 계층 copybook (py_payout_corr_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_PAYOUT_CORR_DBIO_H +#define PY_PAYOUT_CORR_DBIO_H + +long pydb_payout_above_own_avg(const char *bizdate); + +#endif /* PY_PAYOUT_CORR_DBIO_H */ diff --git a/app/src/py/dbio/py_payout_corr_dbio.pgc b/app/src/py/dbio/py_payout_corr_dbio.pgc new file mode 100644 index 0000000..d629517 --- /dev/null +++ b/app/src/py/dbio/py_payout_corr_dbio.pgc @@ -0,0 +1,24 @@ +/* + * py_payout_corr_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_corr_dbio), archived into libpydbio.a. + * 같은 가맹점 평균 금액을 상회하는 지급건 수를 상관 서브쿼리로 센다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_payout_corr_dbio.h" + +/* 상관 서브쿼리: 자기 가맹점 평균금액 초과 지급건 수. */ +long pydb_payout_above_own_avg(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_cnt FROM py_payout p1 + WHERE p1.biz_date = :h_bizdate + AND p1.amount > (SELECT avg(p2.amount) FROM py_payout p2 + WHERE p2.merchant_id = p1.merchant_id); + if (sqlca.sqlcode < 0) { userlog("pydb_payout_above_own_avg FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_cnt; +} diff --git a/app/src/py/dbio/py_payout_delrange_dbio.h b/app/src/py/dbio/py_payout_delrange_dbio.h new file mode 100644 index 0000000..39d35fa --- /dev/null +++ b/app/src/py/dbio/py_payout_delrange_dbio.h @@ -0,0 +1,11 @@ +/* + * py_payout_delrange_dbio.h - py 모듈 DB 접근 계층 copybook (py_payout_delrange_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_PAYOUT_DELRANGE_DBIO_H +#define PY_PAYOUT_DELRANGE_DBIO_H + +long pydb_payout_purge_cancelled(const char *from_date, const char *to_date); + +#endif /* PY_PAYOUT_DELRANGE_DBIO_H */ diff --git a/app/src/py/dbio/py_payout_delrange_dbio.pgc b/app/src/py/dbio/py_payout_delrange_dbio.pgc new file mode 100644 index 0000000..f2de2b9 --- /dev/null +++ b/app/src/py/dbio/py_payout_delrange_dbio.pgc @@ -0,0 +1,22 @@ +/* + * py_payout_delrange_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_delrange_dbio), archived into libpydbio.a. + * 기간 범위의 취소(C) 지급건을 정리 삭제한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_payout_delrange_dbio.h" + +/* DELETE 범위: [from,to] 기간의 취소 지급건 삭제, 삭제 건수 반환. */ +long pydb_payout_purge_cancelled(const char *from_date, const char *to_date) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_from[16], h_to[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_from, from_date, sizeof(h_from)-1); h_from[sizeof(h_from)-1] = 0; + strncpy(h_to, to_date, sizeof(h_to)-1); h_to[sizeof(h_to)-1] = 0; + EXEC SQL DELETE FROM py_payout + WHERE status = 'C' AND biz_date >= :h_from AND biz_date <= :h_to; + if (sqlca.sqlcode < 0) { userlog("pydb_payout_purge_cancelled FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return sqlca.sqlerrd[2]; +} diff --git a/app/src/py/dbio/py_payout_notexists_dbio.h b/app/src/py/dbio/py_payout_notexists_dbio.h new file mode 100644 index 0000000..cf5750a --- /dev/null +++ b/app/src/py/dbio/py_payout_notexists_dbio.h @@ -0,0 +1,11 @@ +/* + * py_payout_notexists_dbio.h - py 모듈 DB 접근 계층 copybook (py_payout_notexists_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_PAYOUT_NOTEXISTS_DBIO_H +#define PY_PAYOUT_NOTEXISTS_DBIO_H + +long pydb_payout_unscheduled(const char *bizdate); + +#endif /* PY_PAYOUT_NOTEXISTS_DBIO_H */ diff --git a/app/src/py/dbio/py_payout_notexists_dbio.pgc b/app/src/py/dbio/py_payout_notexists_dbio.pgc new file mode 100644 index 0000000..007def5 --- /dev/null +++ b/app/src/py/dbio/py_payout_notexists_dbio.pgc @@ -0,0 +1,23 @@ +/* + * py_payout_notexists_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_notexists_dbio), archived into libpydbio.a. + * 예약 테이블에 대응 행이 없는 지급건(미예약)을 NOT EXISTS 로 센다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_payout_notexists_dbio.h" + +/* NOT EXISTS: 예약행이 없는 당일 지급건 수. */ +long pydb_payout_unscheduled(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_cnt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_cnt FROM py_payout pp + WHERE pp.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM py_schedule ps WHERE ps.payout_id = pp.payout_id); + if (sqlca.sqlcode < 0) { userlog("pydb_payout_unscheduled FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_cnt; +} diff --git a/app/src/py/dbio/py_payout_rejrate_dbio.h b/app/src/py/dbio/py_payout_rejrate_dbio.h new file mode 100644 index 0000000..38a4eb5 --- /dev/null +++ b/app/src/py/dbio/py_payout_rejrate_dbio.h @@ -0,0 +1,11 @@ +/* + * py_payout_rejrate_dbio.h - py 모듈 DB 접근 계층 copybook (py_payout_rejrate_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_PAYOUT_REJRATE_DBIO_H +#define PY_PAYOUT_REJRATE_DBIO_H + +long pydb_payout_reject_rate(const char *bizdate); + +#endif /* PY_PAYOUT_REJRATE_DBIO_H */ diff --git a/app/src/py/dbio/py_payout_rejrate_dbio.pgc b/app/src/py/dbio/py_payout_rejrate_dbio.pgc new file mode 100644 index 0000000..ed4ea52 --- /dev/null +++ b/app/src/py/dbio/py_payout_rejrate_dbio.pgc @@ -0,0 +1,24 @@ +/* + * py_payout_rejrate_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_rejrate_dbio), archived into libpydbio.a. + * 반려(J) 지급건 비율(퍼센트)을 CASE 분기 나눗셈으로 산출한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_payout_rejrate_dbio.h" + +/* 비율(CASE): 반려 건수 비중(%) = count(J)*100 / count(*). */ +long pydb_payout_reject_rate(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_rate = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT CASE WHEN count(*) > 0 + THEN count(*) FILTER (WHERE status = 'J') * 100 / count(*) + ELSE 0 END + INTO :h_rate FROM py_payout WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("pydb_payout_reject_rate FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_rate; +} diff --git a/app/src/py/dbio/py_payout_retryhist_dbio.h b/app/src/py/dbio/py_payout_retryhist_dbio.h new file mode 100644 index 0000000..e7ebde9 --- /dev/null +++ b/app/src/py/dbio/py_payout_retryhist_dbio.h @@ -0,0 +1,11 @@ +/* + * py_payout_retryhist_dbio.h - py 모듈 DB 접근 계층 copybook (py_payout_retryhist_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_PAYOUT_RETRYHIST_DBIO_H +#define PY_PAYOUT_RETRYHIST_DBIO_H + +long pydb_payout_retry_buckets(const char *bizdate, long min_share); + +#endif /* PY_PAYOUT_RETRYHIST_DBIO_H */ diff --git a/app/src/py/dbio/py_payout_retryhist_dbio.pgc b/app/src/py/dbio/py_payout_retryhist_dbio.pgc new file mode 100644 index 0000000..29d6d7b --- /dev/null +++ b/app/src/py/dbio/py_payout_retryhist_dbio.pgc @@ -0,0 +1,23 @@ +/* + * py_payout_retryhist_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_retryhist_dbio), archived into libpydbio.a. + * 재시도 횟수별 분포에서 일정 건수 이상 나타난 버킷 수를 구한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_payout_retryhist_dbio.h" + +/* GROUP BY retry_count HAVING: 건수 >= min_share 인 재시도 버킷 수. */ +long pydb_payout_retry_buckets(const char *bizdate, long min_share) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_min = min_share, h_cnt = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_cnt FROM ( + SELECT retry_count FROM py_payout WHERE biz_date = :h_bizdate + GROUP BY retry_count HAVING count(*) >= :h_min) b; + if (sqlca.sqlcode < 0) { userlog("pydb_payout_retry_buckets FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_cnt; +} diff --git a/app/src/py/dbio/py_payout_weighted_dbio.h b/app/src/py/dbio/py_payout_weighted_dbio.h new file mode 100644 index 0000000..38b4075 --- /dev/null +++ b/app/src/py/dbio/py_payout_weighted_dbio.h @@ -0,0 +1,11 @@ +/* + * py_payout_weighted_dbio.h - py 모듈 DB 접근 계층 copybook (py_payout_weighted_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_PAYOUT_WEIGHTED_DBIO_H +#define PY_PAYOUT_WEIGHTED_DBIO_H + +long pydb_payout_weighted_bps(const char *bizdate); + +#endif /* PY_PAYOUT_WEIGHTED_DBIO_H */ diff --git a/app/src/py/dbio/py_payout_weighted_dbio.pgc b/app/src/py/dbio/py_payout_weighted_dbio.pgc new file mode 100644 index 0000000..0a8553a --- /dev/null +++ b/app/src/py/dbio/py_payout_weighted_dbio.pgc @@ -0,0 +1,23 @@ +/* + * py_payout_weighted_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_weighted_dbio), archived into libpydbio.a. + * 금액 가중 수수료율(bps) = sum(fee)*10000 / sum(amount). + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_payout_weighted_dbio.h" + +/* 가중평균 수수료율(bps): 총수수료를 총금액으로 나눈 값. */ +long pydb_payout_weighted_bps(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_bps = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT CASE WHEN coalesce(sum(amount),0) > 0 + THEN sum(fee) * 10000 / sum(amount) ELSE 0 END + INTO :h_bps FROM py_payout WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("pydb_payout_weighted_bps FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_bps; +} diff --git a/app/src/py/dbio/py_payout_window_dbio.h b/app/src/py/dbio/py_payout_window_dbio.h new file mode 100644 index 0000000..7465c1a --- /dev/null +++ b/app/src/py/dbio/py_payout_window_dbio.h @@ -0,0 +1,11 @@ +/* + * py_payout_window_dbio.h - py 모듈 DB 접근 계층 copybook (py_payout_window_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_PAYOUT_WINDOW_DBIO_H +#define PY_PAYOUT_WINDOW_DBIO_H + +long pydb_payout_peak_running(const char *bizdate); + +#endif /* PY_PAYOUT_WINDOW_DBIO_H */ diff --git a/app/src/py/dbio/py_payout_window_dbio.pgc b/app/src/py/dbio/py_payout_window_dbio.pgc new file mode 100644 index 0000000..8f6a1a3 --- /dev/null +++ b/app/src/py/dbio/py_payout_window_dbio.pgc @@ -0,0 +1,23 @@ +/* + * py_payout_window_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_payout_window_dbio), archived into libpydbio.a. + * 가맹점별 지급 순액을 윈도우 누계로 계산해 최대 누계선을 구한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_payout_window_dbio.h" + +/* 윈도우 함수: 가맹점별 payout_id 순 누계(net) 중 최대값. */ +long pydb_payout_peak_running(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_peak = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(max(run_tot),0) INTO :h_peak FROM ( + SELECT sum(net) OVER (PARTITION BY merchant_id ORDER BY payout_id) AS run_tot + FROM py_payout WHERE biz_date = :h_bizdate) w; + if (sqlca.sqlcode < 0) { userlog("pydb_payout_peak_running FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_peak; +} diff --git a/app/src/py/dbio/py_recon_batch.h b/app/src/py/dbio/py_recon_batch.h new file mode 100644 index 0000000..6d35097 --- /dev/null +++ b/app/src/py/dbio/py_recon_batch.h @@ -0,0 +1,15 @@ +/* + * py_recon_batch.h - py 대사 배치 전용 copybook (레코드 레이아웃). + * 집계 테이블 vs 원장 실집계 대사 결과를 담는 구조체. + */ +#ifndef PY_RECON_BATCH_H +#define PY_RECON_BATCH_H + +typedef struct { + char merchant_id[64]; + long recorded_amt; + long live_amt; + long gap; +} py_recon_rec_t; + +#endif /* PY_RECON_BATCH_H */ diff --git a/app/src/py/dbio/py_schedule_due_dbio.h b/app/src/py/dbio/py_schedule_due_dbio.h new file mode 100644 index 0000000..53b0de3 --- /dev/null +++ b/app/src/py/dbio/py_schedule_due_dbio.h @@ -0,0 +1,11 @@ +/* + * py_schedule_due_dbio.h - py 모듈 DB 접근 계층 copybook (py_schedule_due_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_SCHEDULE_DUE_DBIO_H +#define PY_SCHEDULE_DUE_DBIO_H + +long pydb_schedule_due_net(const char *rundate); + +#endif /* PY_SCHEDULE_DUE_DBIO_H */ diff --git a/app/src/py/dbio/py_schedule_due_dbio.pgc b/app/src/py/dbio/py_schedule_due_dbio.pgc new file mode 100644 index 0000000..5c72b6d --- /dev/null +++ b/app/src/py/dbio/py_schedule_due_dbio.pgc @@ -0,0 +1,23 @@ +/* + * py_schedule_due_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_schedule_due_dbio), archived into libpydbio.a. + * 실행일에 예약(S)되고 승인(A)된 지급건의 순액을 예약-지급 조인으로 합산한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_schedule_due_dbio.h" + +/* JOIN: 예약(S) x 승인(A) 지급건의 실행 대상 순액 합. */ +long pydb_schedule_due_net(const char *rundate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_rundate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_rundate, rundate, sizeof(h_rundate)-1); h_rundate[sizeof(h_rundate)-1] = 0; + EXEC SQL SELECT coalesce(sum(pp.net),0) INTO :h_v + FROM py_schedule ps JOIN py_payout pp ON ps.payout_id = pp.payout_id + WHERE ps.run_date = :h_rundate AND ps.status = 'S' AND pp.status = 'A'; + if (sqlca.sqlcode < 0) { userlog("pydb_schedule_due_net FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/py/dbio/py_summary_accum_dbio.h b/app/src/py/dbio/py_summary_accum_dbio.h new file mode 100644 index 0000000..8ee9445 --- /dev/null +++ b/app/src/py/dbio/py_summary_accum_dbio.h @@ -0,0 +1,11 @@ +/* + * py_summary_accum_dbio.h - py 모듈 DB 접근 계층 copybook (py_summary_accum_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_SUMMARY_ACCUM_DBIO_H +#define PY_SUMMARY_ACCUM_DBIO_H + +int pydb_summary_accumulate(const char *merch, const char *bizdate, long cnt, long amount); + +#endif /* PY_SUMMARY_ACCUM_DBIO_H */ diff --git a/app/src/py/dbio/py_summary_accum_dbio.pgc b/app/src/py/dbio/py_summary_accum_dbio.pgc new file mode 100644 index 0000000..80f00f8 --- /dev/null +++ b/app/src/py/dbio/py_summary_accum_dbio.pgc @@ -0,0 +1,27 @@ +/* + * py_summary_accum_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_summary_accum_dbio), archived into libpydbio.a. + * 가맹점 일별 집계에 건수/금액을 누적 가산하는 upsert. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_summary_accum_dbio.h" + +/* ON CONFLICT 누적 upsert: 기존 집계에 delta 를 더한다. */ +int pydb_summary_accumulate(const char *merch, const char *bizdate, long cnt, long amount) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16]; + long h_cnt = cnt, h_amt = amount; + EXEC SQL END DECLARE SECTION; + 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 INSERT INTO py_summary (merchant_id, biz_date, payout_count, payout_amount) + VALUES (:h_merch, :h_bizdate, :h_cnt, :h_amt) + ON CONFLICT (merchant_id, biz_date) DO UPDATE + SET payout_count = py_summary.payout_count + EXCLUDED.payout_count, + payout_amount = py_summary.payout_amount + EXCLUDED.payout_amount, + updated_at = now(); + if (sqlca.sqlcode < 0) { userlog("pydb_summary_accumulate FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return 0; +} diff --git a/app/src/py/dbio/py_summary_gap_dbio.h b/app/src/py/dbio/py_summary_gap_dbio.h new file mode 100644 index 0000000..00c664b --- /dev/null +++ b/app/src/py/dbio/py_summary_gap_dbio.h @@ -0,0 +1,12 @@ +/* + * py_summary_gap_dbio.h - py 모듈 DB 접근 계층 copybook (py_summary_gap_dbio, part of libpydbio.a). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef PY_SUMMARY_GAP_DBIO_H +#define PY_SUMMARY_GAP_DBIO_H + +int pydb_summary_gap(const char *merch, const char *bizdate, + long *recorded, long *live, long *gap); + +#endif /* PY_SUMMARY_GAP_DBIO_H */ diff --git a/app/src/py/dbio/py_summary_gap_dbio.pgc b/app/src/py/dbio/py_summary_gap_dbio.pgc new file mode 100644 index 0000000..9ffc22e --- /dev/null +++ b/app/src/py/dbio/py_summary_gap_dbio.pgc @@ -0,0 +1,29 @@ +/* + * py_summary_gap_dbio.pgc - py 모듈 DB 접근 함수 세트 (py_summary_gap_dbio), archived into libpydbio.a. + * 집계 테이블에 적재된 금액과 원장 실집계 금액의 차이를 대조한다. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + */ +#include +#include +#include "py_summary_gap_dbio.h" + +/* 집계 vs 실집계 대조: 적재금액, 실집계금액, 차액을 반환. */ +int pydb_summary_gap(const char *merch, const char *bizdate, + long *recorded, long *live, long *gap) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_merch[64], h_bizdate[16]; + long h_rec = 0, h_live = 0; + EXEC SQL END DECLARE SECTION; + 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(payout_amount,0) INTO :h_rec FROM py_summary + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0 && sqlca.sqlcode != 100) return -1; + if (sqlca.sqlcode == 100) h_rec = 0; + EXEC SQL SELECT coalesce(sum(amount),0) INTO :h_live FROM py_payout + WHERE merchant_id = :h_merch AND biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("pydb_summary_gap FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + *recorded = h_rec; *live = h_live; *gap = h_live - h_rec; + return 0; +} diff --git a/app/src/py/dbio/py_winrank_batch.h b/app/src/py/dbio/py_winrank_batch.h new file mode 100644 index 0000000..507298e --- /dev/null +++ b/app/src/py/dbio/py_winrank_batch.h @@ -0,0 +1,13 @@ +/* + * py_winrank_batch.h - py 윈도우 누계 배치 전용 copybook. + * 가맹점별 누계선 최고점 산출 결과 레이아웃. + */ +#ifndef PY_WINRANK_BATCH_H +#define PY_WINRANK_BATCH_H + +typedef struct { + char biz_date[16]; + long peak_running; +} py_winrank_rec_t; + +#endif /* PY_WINRANK_BATCH_H */ diff --git a/app/src/rc/batch/rc_audit_batch.pgc b/app/src/rc/batch/rc_audit_batch.pgc new file mode 100644 index 0000000..e478b94 --- /dev/null +++ b/app/src/rc/batch/rc_audit_batch.pgc @@ -0,0 +1,55 @@ +/* + * rc_audit_batch.pgc - rc 미해결 예외 감사 -> 경보 생성 (커서 + XA). + * + * 당일 미해결 예외를 커서로 순회하며 각 건에 대해 rc_alert 에 WARN 경보를 남긴다. + * 하나의 글로벌 트랜잭션에서 반복 INSERT 후 2PC 커밋. 사용법: rc_audit_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" +#include "rc_audit_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + rc_audit_rec_t rec; + long alerts = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_reason[64], h_msg[80]; + 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 adc CURSOR FOR + SELECT purchase_id, reason FROM rc_exception + WHERE biz_date = :h_bizdate AND resolved = false ORDER BY exc_id; + EXEC SQL OPEN adc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH adc INTO :h_pid, :h_reason; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE adc; tpabort(0); return 1; } + rec.pid = h_pid; + strncpy(rec.reason, h_reason, sizeof(rec.reason)-1); rec.reason[sizeof(rec.reason)-1] = 0; + snprintf(h_msg, sizeof(h_msg), "unresolved: %s", rec.reason); + EXEC SQL INSERT INTO rc_alert (alert_id, purchase_id, lvl, message, biz_date) + VALUES (nextval('rc_alert_seq'), :h_pid, 'WARN', :h_msg, :h_bizdate); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE adc; tpabort(0); return 1; } + alerts++; + } + EXEC SQL CLOSE adc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s alerts=%ld\n", RC_AUDIT_TAG, bizdate, alerts); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_backfill_batch.pgc b/app/src/rc/batch/rc_backfill_batch.pgc new file mode 100644 index 0000000..7bb4c67 --- /dev/null +++ b/app/src/rc/batch/rc_backfill_batch.pgc @@ -0,0 +1,45 @@ +/* + * rc_backfill_batch.pgc - rc 당일 요약 백필 (집계 + upsert + XA). + * + * rc_match_log 를 집계해 rc_summary 를 ON CONFLICT upsert 로 재구성. 두 문장을 하나의 + * 글로벌 트랜잭션에 묶어 2PC 로 커밋. 사용법: rc_backfill_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" +#include "rc_backfill_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + rc_backfill_rec_t rec; rec.matched = 0; rec.gross = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_matched, 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 SELECT count(*) FILTER (WHERE status = 'MATCHED'), coalesce(sum(amount), 0) + INTO :h_matched, :h_gross FROM rc_match_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { fprintf(stderr, "AGG FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.matched = h_matched; rec.gross = h_gross; + + EXEC SQL INSERT INTO rc_summary (biz_date, matched_cnt, unmatched_cnt, gross_amount) + VALUES (:h_bizdate, :h_matched, 0, :h_gross) + ON CONFLICT (biz_date) DO UPDATE + SET matched_cnt = EXCLUDED.matched_cnt, gross_amount = EXCLUDED.gross_amount, updated_at = now(); + if (sqlca.sqlcode < 0) { fprintf(stderr, "UPSERT 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 matched=%ld gross=%ld\n", RC_BACKFILL_TAG, bizdate, rec.matched, rec.gross); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_purgeold_batch.pgc b/app/src/rc/batch/rc_purgeold_batch.pgc new file mode 100644 index 0000000..3402dd1 --- /dev/null +++ b/app/src/rc/batch/rc_purgeold_batch.pgc @@ -0,0 +1,39 @@ +/* + * rc_purgeold_batch.pgc - rc 컷오프 이전 경보 일괄 삭제 (범위 DELETE + XA). + * + * 단일 범위 DELETE 를 하나의 글로벌 트랜잭션에서 수행하고 2PC 로 커밋. + * 사용법: rc_purgeold_batch [YYYY-MM-DD(컷오프)] + */ +#include +#include +#include +#include +#include "rc_dbio.h" +#include "rc_purgeold_batch.h" + +int main(int argc, char **argv) +{ + rc_purgeold_rec_t rec; + strncpy(rec.cutoff, (argc > 1) ? argv[1] : "2026-07-01", sizeof(rec.cutoff)-1); + rec.cutoff[sizeof(rec.cutoff)-1] = 0; + rec.removed = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_cut[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_cut, rec.cutoff, sizeof(h_cut)-1); h_cut[sizeof(h_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 DELETE FROM rc_alert WHERE biz_date < :h_cut AND lvl = 'INFO'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "DELETE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.removed = (long) sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: cutoff=%s removed=%ld\n", RC_PURGEOLD_TAG, rec.cutoff, rec.removed); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_reclass_batch.pgc b/app/src/rc/batch/rc_reclass_batch.pgc new file mode 100644 index 0000000..b2de6fc --- /dev/null +++ b/app/src/rc/batch/rc_reclass_batch.pgc @@ -0,0 +1,40 @@ +/* + * rc_reclass_batch.pgc - rc HOLD 대사 CASE 재분류 (집합 UPDATE + XA). + * + * 금액 구간에 따라 HOLD 대사를 CASE 로 EXCEPTION/RELEASED 로 재분류하는 단일 집합 UPDATE. + * 사용법: rc_reclass_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" +#include "rc_reclass_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + rc_reclass_rec_t rec; rec.touched = 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 UPDATE rc_match_log + SET status = CASE WHEN amount >= 1000000 THEN 'EXCEPTION' ELSE 'RELEASED' END, + updated_at = now() + WHERE biz_date = :h_bizdate AND status = 'HOLD'; + if (sqlca.sqlcode < 0) { fprintf(stderr, "UPDATE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.touched = (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 reclassified=%ld\n", RC_RECLASS_TAG, bizdate, rec.touched); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/batch/rc_windowload_batch.pgc b/app/src/rc/batch/rc_windowload_batch.pgc new file mode 100644 index 0000000..4da6a6c --- /dev/null +++ b/app/src/rc/batch/rc_windowload_batch.pgc @@ -0,0 +1,58 @@ +/* + * rc_windowload_batch.pgc - rc 유형 1순위 대사 -> rc_diff 적재 (윈도우 커서 + XA). + * + * ATMI client: tpinit/tpopen 로 ECPG XA RM 를 열고 tpbegin 으로 글로벌 tx 시작. + * rank() OVER 커서로 유형별 최고금액(1순위) 대사를 골라 rc_diff 에 기준선으로 적재한 뒤 + * tpcommit 이 XA 2PC 를 구동. 사용법: rc_windowload_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "rc_dbio.h" +#include "rc_windowload_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + rc_windowload_rec_t rec; + long loaded = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_pid, h_amt, h_rank; + 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 wlc CURSOR FOR + SELECT purchase_id, amount, + rank() OVER (PARTITION BY match_type ORDER BY amount DESC) + FROM rc_match_log WHERE biz_date = :h_bizdate; + EXEC SQL OPEN wlc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH wlc INTO :h_pid, :h_amt, :h_rank; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE wlc; tpabort(0); return 1; } + rec.pid = h_pid; rec.amt = h_amt; rec.rnk = h_rank; + if (rec.rnk == 1) { + 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_amt, 0, 'AMT', :h_bizdate); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE wlc; tpabort(0); return 1; } + loaded++; + } + } + EXEC SQL CLOSE wlc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s loaded=%ld\n", RC_WINDOWLOAD_TAG, bizdate, loaded); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/rc/dbio/rc_alertpurge_dbio.h b/app/src/rc/dbio/rc_alertpurge_dbio.h new file mode 100644 index 0000000..1a3a12d --- /dev/null +++ b/app/src/rc/dbio/rc_alertpurge_dbio.h @@ -0,0 +1,5 @@ +/* rc_alertpurge_dbio.h - 오래된 경보 삭제 copybook (librcdbio.a). */ +#ifndef RC_ALERTPURGE_DBIO_H +#define RC_ALERTPURGE_DBIO_H +long rcdb_purge_alerts_before(const char *cutoff); +#endif diff --git a/app/src/rc/dbio/rc_alertpurge_dbio.pgc b/app/src/rc/dbio/rc_alertpurge_dbio.pgc new file mode 100644 index 0000000..3d695a6 --- /dev/null +++ b/app/src/rc/dbio/rc_alertpurge_dbio.pgc @@ -0,0 +1,18 @@ +/* + * rc_alertpurge_dbio.pgc - 컷오프 이전 경보 정리 (rc_alertpurge_dbio), librcdbio.a. + * ECPG; DELETE 범위 삭제. INFO 레벨 경보만 컷오프 이전 것을 제거하고 삭제 행수를 반환. + */ +#include +#include +#include "rc_alertpurge_dbio.h" + +long rcdb_purge_alerts_before(const char *cutoff) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_cut[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_cut, cutoff, sizeof(h_cut)-1); h_cut[sizeof(h_cut)-1] = 0; + EXEC SQL DELETE FROM rc_alert WHERE biz_date < :h_cut AND lvl = 'INFO'; + if (sqlca.sqlcode < 0) { userlog("rcdb_purge_alerts_before FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return (long) sqlca.sqlerrd[2]; +} diff --git a/app/src/rc/dbio/rc_amtcase_dbio.h b/app/src/rc/dbio/rc_amtcase_dbio.h new file mode 100644 index 0000000..0428a6b --- /dev/null +++ b/app/src/rc/dbio/rc_amtcase_dbio.h @@ -0,0 +1,5 @@ +/* rc_amtcase_dbio.h - rc 금액 구간 CASE 버킷 copybook (librcdbio.a). */ +#ifndef RC_AMTCASE_DBIO_H +#define RC_AMTCASE_DBIO_H +long rcdb_amount_buckets(const char *bizdate, long *large_out); +#endif diff --git a/app/src/rc/dbio/rc_amtcase_dbio.pgc b/app/src/rc/dbio/rc_amtcase_dbio.pgc new file mode 100644 index 0000000..cb18610 --- /dev/null +++ b/app/src/rc/dbio/rc_amtcase_dbio.pgc @@ -0,0 +1,32 @@ +/* + * rc_amtcase_dbio.pgc - rc 금액 구간 분류 (rc_amtcase_dbio), librcdbio.a. + * ECPG; 호출자 XA 브랜치에서 수행. CASE 식으로 소/중/대 버킷을 만들어 GROUP BY 로 집계. + */ +#include +#include +#include "rc_amtcase_dbio.h" + +long rcdb_amount_buckets(const char *bizdate, long *large_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_bucket[8]; + long h_cnt, buckets = 0, large = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE acc CURSOR FOR + SELECT CASE WHEN amount < 10000 THEN 'S' + WHEN amount < 1000000 THEN 'M' ELSE 'L' END AS bkt, count(*) + FROM rc_match_log WHERE biz_date = :h_bizdate GROUP BY bkt; + EXEC SQL OPEN acc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH acc INTO :h_bucket, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE acc; return -1; } + buckets++; + if (h_bucket[0] == 'L') large += h_cnt; + } + EXEC SQL CLOSE acc; + if (large_out) *large_out = large; + return buckets; +} diff --git a/app/src/rc/dbio/rc_amtminmax_dbio.h b/app/src/rc/dbio/rc_amtminmax_dbio.h new file mode 100644 index 0000000..9750f4e --- /dev/null +++ b/app/src/rc/dbio/rc_amtminmax_dbio.h @@ -0,0 +1,5 @@ +/* rc_amtminmax_dbio.h - 대사 금액 최소/최대/평균 copybook (librcdbio.a). */ +#ifndef RC_AMTMINMAX_DBIO_H +#define RC_AMTMINMAX_DBIO_H +long rcdb_amount_span(const char *bizdate, long *min_out, long *max_out); +#endif diff --git a/app/src/rc/dbio/rc_amtminmax_dbio.pgc b/app/src/rc/dbio/rc_amtminmax_dbio.pgc new file mode 100644 index 0000000..05b923d --- /dev/null +++ b/app/src/rc/dbio/rc_amtminmax_dbio.pgc @@ -0,0 +1,23 @@ +/* + * rc_amtminmax_dbio.pgc - 대사 금액 스팬 (rc_amtminmax_dbio), librcdbio.a. + * ECPG; min(amount)/max(amount)/avg(amount) 을 한 번에 구해 평균을 반환, 최소/최대는 출력인자. + */ +#include +#include +#include "rc_amtminmax_dbio.h" + +long rcdb_amount_span(const char *bizdate, long *min_out, long *max_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_min = 0, h_max = 0; + double h_avg = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(min(amount),0), coalesce(max(amount),0), coalesce(avg(amount),0) + INTO :h_min, :h_max, :h_avg FROM rc_match_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("rcdb_amount_span FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + if (min_out) *min_out = h_min; + if (max_out) *max_out = h_max; + return (long) h_avg; +} diff --git a/app/src/rc/dbio/rc_amtwindow_dbio.h b/app/src/rc/dbio/rc_amtwindow_dbio.h new file mode 100644 index 0000000..313615c --- /dev/null +++ b/app/src/rc/dbio/rc_amtwindow_dbio.h @@ -0,0 +1,5 @@ +/* rc_amtwindow_dbio.h - rc 대사 금액 윈도우 순위 copybook (librcdbio.a). */ +#ifndef RC_AMTWINDOW_DBIO_H +#define RC_AMTWINDOW_DBIO_H +long rcdb_top_amount_per_type(const char *bizdate); +#endif diff --git a/app/src/rc/dbio/rc_amtwindow_dbio.pgc b/app/src/rc/dbio/rc_amtwindow_dbio.pgc new file mode 100644 index 0000000..2329af9 --- /dev/null +++ b/app/src/rc/dbio/rc_amtwindow_dbio.pgc @@ -0,0 +1,31 @@ +/* + * rc_amtwindow_dbio.pgc - rc 유형별 최고금액 순위 (rc_amtwindow_dbio), librcdbio.a. + * ECPG; 호출자 XA 브랜치에서 수행, EXEC SQL CONNECT 없음. + * 윈도우 rank() OVER (PARTITION BY match_type ORDER BY amount DESC) 로 유형별 1순위 건수. + */ +#include +#include +#include "rc_amtwindow_dbio.h" + +long rcdb_top_amount_per_type(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_type[24]; + long h_rank, h_amt, tops = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE awc CURSOR FOR + SELECT match_type, amount, + rank() OVER (PARTITION BY match_type ORDER BY amount DESC) + FROM rc_match_log WHERE biz_date = :h_bizdate; + EXEC SQL OPEN awc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH awc INTO :h_type, :h_amt, :h_rank; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE awc; return -1; } + if (h_rank == 1) tops++; + } + EXEC SQL CLOSE awc; + return tops; +} diff --git a/app/src/rc/dbio/rc_audit_batch.h b/app/src/rc/dbio/rc_audit_batch.h new file mode 100644 index 0000000..62bd062 --- /dev/null +++ b/app/src/rc/dbio/rc_audit_batch.h @@ -0,0 +1,6 @@ +/* rc_audit_batch.h - rc 예외 감사 경보 배치 copybook. */ +#ifndef RC_AUDIT_BATCH_H +#define RC_AUDIT_BATCH_H +#define RC_AUDIT_TAG "rc_audit_batch" +typedef struct { long pid; char reason[64]; } rc_audit_rec_t; +#endif diff --git a/app/src/rc/dbio/rc_backfill_batch.h b/app/src/rc/dbio/rc_backfill_batch.h new file mode 100644 index 0000000..55a2a86 --- /dev/null +++ b/app/src/rc/dbio/rc_backfill_batch.h @@ -0,0 +1,6 @@ +/* rc_backfill_batch.h - rc 요약 백필 배치 copybook. */ +#ifndef RC_BACKFILL_BATCH_H +#define RC_BACKFILL_BATCH_H +#define RC_BACKFILL_TAG "rc_backfill_batch" +typedef struct { long matched; long gross; } rc_backfill_rec_t; +#endif diff --git a/app/src/rc/dbio/rc_diffhaving_dbio.h b/app/src/rc/dbio/rc_diffhaving_dbio.h new file mode 100644 index 0000000..3389a8b --- /dev/null +++ b/app/src/rc/dbio/rc_diffhaving_dbio.h @@ -0,0 +1,5 @@ +/* rc_diffhaving_dbio.h - 매입별 차이합 HAVING copybook (librcdbio.a). */ +#ifndef RC_DIFFHAVING_DBIO_H +#define RC_DIFFHAVING_DBIO_H +long rcdb_big_diff_pids(const char *bizdate, long threshold); +#endif diff --git a/app/src/rc/dbio/rc_diffhaving_dbio.pgc b/app/src/rc/dbio/rc_diffhaving_dbio.pgc new file mode 100644 index 0000000..216907d --- /dev/null +++ b/app/src/rc/dbio/rc_diffhaving_dbio.pgc @@ -0,0 +1,29 @@ +/* + * rc_diffhaving_dbio.pgc - 임계 초과 차이 매입 (rc_diffhaving_dbio), librcdbio.a. + * ECPG; GROUP BY purchase_id HAVING sum(diff_amt) > 임계 커서로 큰 차이 매입 수를 센다. + */ +#include +#include +#include "rc_diffhaving_dbio.h" + +long rcdb_big_diff_pids(const char *bizdate, long threshold) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_thr = threshold, h_pid, h_sum, pids = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE dhc CURSOR FOR + SELECT purchase_id, sum(diff_amt) FROM rc_diff + WHERE biz_date = :h_bizdate GROUP BY purchase_id HAVING sum(diff_amt) > :h_thr; + EXEC SQL OPEN dhc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH dhc INTO :h_pid, :h_sum; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE dhc; return -1; } + pids++; + } + EXEC SQL CLOSE dhc; + return pids; +} diff --git a/app/src/rc/dbio/rc_diffinsert_dbio.h b/app/src/rc/dbio/rc_diffinsert_dbio.h new file mode 100644 index 0000000..cd05688 --- /dev/null +++ b/app/src/rc/dbio/rc_diffinsert_dbio.h @@ -0,0 +1,5 @@ +/* rc_diffinsert_dbio.h - 예외 대사 -> rc_diff INSERT..SELECT copybook (librcdbio.a). */ +#ifndef RC_DIFFINSERT_DBIO_H +#define RC_DIFFINSERT_DBIO_H +long rcdb_seed_diff_from_exception(const char *bizdate); +#endif diff --git a/app/src/rc/dbio/rc_diffinsert_dbio.pgc b/app/src/rc/dbio/rc_diffinsert_dbio.pgc new file mode 100644 index 0000000..002f203 --- /dev/null +++ b/app/src/rc/dbio/rc_diffinsert_dbio.pgc @@ -0,0 +1,20 @@ +/* + * rc_diffinsert_dbio.pgc - 예외 대사로부터 차이 라인 생성 (rc_diffinsert_dbio), librcdbio.a. + * ECPG; INSERT .. SELECT 로 EXCEPTION 상태 대사를 rc_diff 로 적재. 반환=적재 행수. + */ +#include +#include +#include "rc_diffinsert_dbio.h" + +long rcdb_seed_diff_from_exception(const char *bizdate) +{ + 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; + EXEC SQL INSERT INTO rc_diff (diff_id, purchase_id, expected_amt, actual_amt, diff_amt, diff_kind, biz_date) + SELECT nextval('rc_diff_seq'), purchase_id, amount, 0, amount, 'AMT', biz_date + FROM rc_match_log WHERE biz_date = :h_bizdate AND status = 'EXCEPTION'; + if (sqlca.sqlcode < 0) { userlog("rcdb_seed_diff_from_exception FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return (long) sqlca.sqlerrd[2]; +} diff --git a/app/src/rc/dbio/rc_diffweight_dbio.h b/app/src/rc/dbio/rc_diffweight_dbio.h new file mode 100644 index 0000000..c821f2a --- /dev/null +++ b/app/src/rc/dbio/rc_diffweight_dbio.h @@ -0,0 +1,5 @@ +/* rc_diffweight_dbio.h - 기대금액 가중 차이 평균 copybook (librcdbio.a). */ +#ifndef RC_DIFFWEIGHT_DBIO_H +#define RC_DIFFWEIGHT_DBIO_H +long rcdb_weighted_diff(const char *bizdate); +#endif diff --git a/app/src/rc/dbio/rc_diffweight_dbio.pgc b/app/src/rc/dbio/rc_diffweight_dbio.pgc new file mode 100644 index 0000000..b42d63f --- /dev/null +++ b/app/src/rc/dbio/rc_diffweight_dbio.pgc @@ -0,0 +1,20 @@ +/* + * rc_diffweight_dbio.pgc - 기대금액으로 가중한 차이 평균 (rc_diffweight_dbio), librcdbio.a. + * ECPG; sum(diff_amt*expected_amt)/nullif(sum(expected_amt),0) 가중평균을 반환. + */ +#include +#include +#include "rc_diffweight_dbio.h" + +long rcdb_weighted_diff(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + double h_w = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(diff_amt * expected_amt)::float8 / nullif(sum(expected_amt), 0), 0) + INTO :h_w FROM rc_diff WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("rcdb_weighted_diff FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return (long) h_w; +} diff --git a/app/src/rc/dbio/rc_excinsub_dbio.h b/app/src/rc/dbio/rc_excinsub_dbio.h new file mode 100644 index 0000000..8714c94 --- /dev/null +++ b/app/src/rc/dbio/rc_excinsub_dbio.h @@ -0,0 +1,5 @@ +/* rc_excinsub_dbio.h - 미해결 예외 대상 대사 건수 (IN subselect) copybook (librcdbio.a). */ +#ifndef RC_EXCINSUB_DBIO_H +#define RC_EXCINSUB_DBIO_H +long rcdb_matches_in_open_exc(const char *bizdate); +#endif diff --git a/app/src/rc/dbio/rc_excinsub_dbio.pgc b/app/src/rc/dbio/rc_excinsub_dbio.pgc new file mode 100644 index 0000000..735e395 --- /dev/null +++ b/app/src/rc/dbio/rc_excinsub_dbio.pgc @@ -0,0 +1,21 @@ +/* + * rc_excinsub_dbio.pgc - 미해결 예외 매입에 대한 대사 건수 (rc_excinsub_dbio), librcdbio.a. + * ECPG; purchase_id IN (SELECT ... FROM rc_exception WHERE resolved=false) 형태의 IN 서브셀렉트. + */ +#include +#include +#include "rc_excinsub_dbio.h" + +long rcdb_matches_in_open_exc(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM rc_match_log + WHERE biz_date = :h_bizdate + AND purchase_id IN (SELECT purchase_id FROM rc_exception WHERE resolved = false); + if (sqlca.sqlcode < 0) { userlog("rcdb_matches_in_open_exc FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/rc/dbio/rc_excnotexists_dbio.h b/app/src/rc/dbio/rc_excnotexists_dbio.h new file mode 100644 index 0000000..c7c8157 --- /dev/null +++ b/app/src/rc/dbio/rc_excnotexists_dbio.h @@ -0,0 +1,5 @@ +/* rc_excnotexists_dbio.h - 예외 없는 대사 건수 (NOT EXISTS) copybook (librcdbio.a). */ +#ifndef RC_EXCNOTEXISTS_DBIO_H +#define RC_EXCNOTEXISTS_DBIO_H +long rcdb_clean_matches(const char *bizdate); +#endif diff --git a/app/src/rc/dbio/rc_excnotexists_dbio.pgc b/app/src/rc/dbio/rc_excnotexists_dbio.pgc new file mode 100644 index 0000000..1dacf3c --- /dev/null +++ b/app/src/rc/dbio/rc_excnotexists_dbio.pgc @@ -0,0 +1,21 @@ +/* + * rc_excnotexists_dbio.pgc - 예외가 없는 대사 건수 (rc_excnotexists_dbio), librcdbio.a. + * ECPG; 상관 NOT EXISTS 로 rc_exception 에 매칭 purchase_id 가 없는 rc_match_log 를 센다. + */ +#include +#include +#include "rc_excnotexists_dbio.h" + +long rcdb_clean_matches(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM rc_match_log m + WHERE m.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM rc_exception e WHERE e.purchase_id = m.purchase_id); + if (sqlca.sqlcode < 0) { userlog("rcdb_clean_matches FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/rc/dbio/rc_excupdate_dbio.h b/app/src/rc/dbio/rc_excupdate_dbio.h new file mode 100644 index 0000000..4429b5e --- /dev/null +++ b/app/src/rc/dbio/rc_excupdate_dbio.h @@ -0,0 +1,5 @@ +/* rc_excupdate_dbio.h - 오래된 예외 일괄 해결 (positioned UPDATE) copybook (librcdbio.a). */ +#ifndef RC_EXCUPDATE_DBIO_H +#define RC_EXCUPDATE_DBIO_H +long rcdb_resolve_stale_exc(const char *bizdate); +#endif diff --git a/app/src/rc/dbio/rc_excupdate_dbio.pgc b/app/src/rc/dbio/rc_excupdate_dbio.pgc new file mode 100644 index 0000000..6024e9f --- /dev/null +++ b/app/src/rc/dbio/rc_excupdate_dbio.pgc @@ -0,0 +1,31 @@ +/* + * rc_excupdate_dbio.pgc - 당일 미해결 예외 일괄 해결 (rc_excupdate_dbio), librcdbio.a. + * ECPG; FOR UPDATE 커서를 순회하며 WHERE CURRENT OF 로 각 행을 resolved=true 로 갱신. + */ +#include +#include +#include "rc_excupdate_dbio.h" + +long rcdb_resolve_stale_exc(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_id, fixed = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE euc CURSOR FOR + SELECT exc_id FROM rc_exception + WHERE biz_date = :h_bizdate AND resolved = false FOR UPDATE; + EXEC SQL OPEN euc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH euc INTO :h_id; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE euc; return -1; } + EXEC SQL UPDATE rc_exception SET resolved = true WHERE CURRENT OF euc; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE euc; return -1; } + fixed++; + } + EXEC SQL CLOSE euc; + return fixed; +} diff --git a/app/src/rc/dbio/rc_lvlfilter_dbio.h b/app/src/rc/dbio/rc_lvlfilter_dbio.h new file mode 100644 index 0000000..7c0999c --- /dev/null +++ b/app/src/rc/dbio/rc_lvlfilter_dbio.h @@ -0,0 +1,5 @@ +/* rc_lvlfilter_dbio.h - 경보 레벨별 FILTER 집계 copybook (librcdbio.a). */ +#ifndef RC_LVLFILTER_DBIO_H +#define RC_LVLFILTER_DBIO_H +long rcdb_alert_levels(const char *bizdate, long *crit_out); +#endif diff --git a/app/src/rc/dbio/rc_lvlfilter_dbio.pgc b/app/src/rc/dbio/rc_lvlfilter_dbio.pgc new file mode 100644 index 0000000..a09bd4c --- /dev/null +++ b/app/src/rc/dbio/rc_lvlfilter_dbio.pgc @@ -0,0 +1,21 @@ +/* + * rc_lvlfilter_dbio.pgc - 경보 레벨 FILTER 집계 (rc_lvlfilter_dbio), librcdbio.a. + * ECPG; count(*) FILTER (WHERE lvl=...) 로 WARN/CRIT 경보 수를 한 번에 집계. + */ +#include +#include +#include "rc_lvlfilter_dbio.h" + +long rcdb_alert_levels(const char *bizdate, long *crit_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_warn = 0, h_crit = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) FILTER (WHERE lvl = 'WARN'), count(*) FILTER (WHERE lvl = 'CRIT') + INTO :h_warn, :h_crit FROM rc_alert WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("rcdb_alert_levels FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + if (crit_out) *crit_out = h_crit; + return h_warn; +} diff --git a/app/src/rc/dbio/rc_matchcorr_dbio.h b/app/src/rc/dbio/rc_matchcorr_dbio.h new file mode 100644 index 0000000..f17b687 --- /dev/null +++ b/app/src/rc/dbio/rc_matchcorr_dbio.h @@ -0,0 +1,5 @@ +/* rc_matchcorr_dbio.h - 대사별 예외건수 상관 서브쿼리 copybook (librcdbio.a). */ +#ifndef RC_MATCHCORR_DBIO_H +#define RC_MATCHCORR_DBIO_H +long rcdb_matches_with_exc(const char *bizdate); +#endif diff --git a/app/src/rc/dbio/rc_matchcorr_dbio.pgc b/app/src/rc/dbio/rc_matchcorr_dbio.pgc new file mode 100644 index 0000000..1a9543f --- /dev/null +++ b/app/src/rc/dbio/rc_matchcorr_dbio.pgc @@ -0,0 +1,30 @@ +/* + * rc_matchcorr_dbio.pgc - 대사별 예외 동반 여부 (rc_matchcorr_dbio), librcdbio.a. + * ECPG; SELECT 목록의 상관 서브쿼리로 각 대사의 동일 매입 예외수를 구해 >0 인 건수를 센다. + */ +#include +#include +#include "rc_matchcorr_dbio.h" + +long rcdb_matches_with_exc(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_pid, h_ecnt, withexc = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE mcc CURSOR FOR + SELECT m.purchase_id, + (SELECT count(*) FROM rc_exception e WHERE e.purchase_id = m.purchase_id) + FROM rc_match_log m WHERE m.biz_date = :h_bizdate; + EXEC SQL OPEN mcc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH mcc INTO :h_pid, :h_ecnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE mcc; return -1; } + if (h_ecnt > 0) withexc++; + } + EXEC SQL CLOSE mcc; + return withexc; +} diff --git a/app/src/rc/dbio/rc_matchsummaryjoin_dbio.h b/app/src/rc/dbio/rc_matchsummaryjoin_dbio.h new file mode 100644 index 0000000..897fcbf --- /dev/null +++ b/app/src/rc/dbio/rc_matchsummaryjoin_dbio.h @@ -0,0 +1,5 @@ +/* rc_matchsummaryjoin_dbio.h - 대사x요약 JOIN copybook (librcdbio.a). */ +#ifndef RC_MATCHSUMMARYJOIN_DBIO_H +#define RC_MATCHSUMMARYJOIN_DBIO_H +long rcdb_join_summary_gross(const char *bizdate); +#endif diff --git a/app/src/rc/dbio/rc_matchsummaryjoin_dbio.pgc b/app/src/rc/dbio/rc_matchsummaryjoin_dbio.pgc new file mode 100644 index 0000000..7a2647f --- /dev/null +++ b/app/src/rc/dbio/rc_matchsummaryjoin_dbio.pgc @@ -0,0 +1,23 @@ +/* + * rc_matchsummaryjoin_dbio.pgc - 대사와 요약 조인 (rc_matchsummaryjoin_dbio), librcdbio.a. + * ECPG; rc_match_log 를 rc_summary 와 biz_date INNER JOIN 하여 요약행이 존재하는 날의 + * MATCHED 금액 합을 구한다. + */ +#include +#include +#include "rc_matchsummaryjoin_dbio.h" + +long rcdb_join_summary_gross(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(m.amount), 0) INTO :h_v + FROM rc_match_log m + JOIN rc_summary s ON s.biz_date = m.biz_date + WHERE m.biz_date = :h_bizdate AND m.status = 'MATCHED'; + if (sqlca.sqlcode < 0) { userlog("rcdb_join_summary_gross FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/rc/dbio/rc_pidcount_dbio.h b/app/src/rc/dbio/rc_pidcount_dbio.h new file mode 100644 index 0000000..118e3b8 --- /dev/null +++ b/app/src/rc/dbio/rc_pidcount_dbio.h @@ -0,0 +1,5 @@ +/* rc_pidcount_dbio.h - 고유 매입/유형 수 copybook (librcdbio.a). */ +#ifndef RC_PIDCOUNT_DBIO_H +#define RC_PIDCOUNT_DBIO_H +long rcdb_distinct_pids(const char *bizdate, long *types_out); +#endif diff --git a/app/src/rc/dbio/rc_pidcount_dbio.pgc b/app/src/rc/dbio/rc_pidcount_dbio.pgc new file mode 100644 index 0000000..2d5fa37 --- /dev/null +++ b/app/src/rc/dbio/rc_pidcount_dbio.pgc @@ -0,0 +1,21 @@ +/* + * rc_pidcount_dbio.pgc - 고유 매입/대사유형 수 (rc_pidcount_dbio), librcdbio.a. + * ECPG; count(DISTINCT purchase_id) 와 count(DISTINCT match_type) 를 한 SELECT 로 반환. + */ +#include +#include +#include "rc_pidcount_dbio.h" + +long rcdb_distinct_pids(const char *bizdate, long *types_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_pids = 0, h_types = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(DISTINCT purchase_id), count(DISTINCT match_type) + INTO :h_pids, :h_types FROM rc_match_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("rcdb_distinct_pids FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + if (types_out) *types_out = h_types; + return h_pids; +} diff --git a/app/src/rc/dbio/rc_purgeold_batch.h b/app/src/rc/dbio/rc_purgeold_batch.h new file mode 100644 index 0000000..26c58f3 --- /dev/null +++ b/app/src/rc/dbio/rc_purgeold_batch.h @@ -0,0 +1,6 @@ +/* rc_purgeold_batch.h - rc 경보 정리 배치 copybook. */ +#ifndef RC_PURGEOLD_BATCH_H +#define RC_PURGEOLD_BATCH_H +#define RC_PURGEOLD_TAG "rc_purgeold_batch" +typedef struct { char cutoff[16]; long removed; } rc_purgeold_rec_t; +#endif diff --git a/app/src/rc/dbio/rc_reclass_batch.h b/app/src/rc/dbio/rc_reclass_batch.h new file mode 100644 index 0000000..654ee03 --- /dev/null +++ b/app/src/rc/dbio/rc_reclass_batch.h @@ -0,0 +1,6 @@ +/* rc_reclass_batch.h - rc 대사 상태 재분류 배치 copybook. */ +#ifndef RC_RECLASS_BATCH_H +#define RC_RECLASS_BATCH_H +#define RC_RECLASS_TAG "rc_reclass_batch" +typedef struct { long touched; } rc_reclass_rec_t; +#endif diff --git a/app/src/rc/dbio/rc_sumupsert_dbio.h b/app/src/rc/dbio/rc_sumupsert_dbio.h new file mode 100644 index 0000000..1b4f205 --- /dev/null +++ b/app/src/rc/dbio/rc_sumupsert_dbio.h @@ -0,0 +1,5 @@ +/* rc_sumupsert_dbio.h - rc_summary 증분 upsert copybook (librcdbio.a). */ +#ifndef RC_SUMUPSERT_DBIO_H +#define RC_SUMUPSERT_DBIO_H +int rcdb_bump_summary(const char *bizdate, long matched, long gross); +#endif diff --git a/app/src/rc/dbio/rc_sumupsert_dbio.pgc b/app/src/rc/dbio/rc_sumupsert_dbio.pgc new file mode 100644 index 0000000..2d23b63 --- /dev/null +++ b/app/src/rc/dbio/rc_sumupsert_dbio.pgc @@ -0,0 +1,24 @@ +/* + * rc_sumupsert_dbio.pgc - 요약 증분 upsert (rc_sumupsert_dbio), librcdbio.a. + * ECPG; ON CONFLICT (biz_date) DO UPDATE 로 matched_cnt / gross_amount 를 누적한다. + */ +#include +#include +#include "rc_sumupsert_dbio.h" + +int rcdb_bump_summary(const char *bizdate, long matched, long gross) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_m = matched, h_g = gross; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL INSERT INTO rc_summary (biz_date, matched_cnt, unmatched_cnt, gross_amount) + VALUES (:h_bizdate, :h_m, 0, :h_g) + ON CONFLICT (biz_date) DO UPDATE + SET matched_cnt = rc_summary.matched_cnt + EXCLUDED.matched_cnt, + gross_amount = rc_summary.gross_amount + EXCLUDED.gross_amount, + updated_at = now(); + if (sqlca.sqlcode < 0) { userlog("rcdb_bump_summary FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return 0; +} diff --git a/app/src/rc/dbio/rc_windowload_batch.h b/app/src/rc/dbio/rc_windowload_batch.h new file mode 100644 index 0000000..3775e40 --- /dev/null +++ b/app/src/rc/dbio/rc_windowload_batch.h @@ -0,0 +1,6 @@ +/* rc_windowload_batch.h - rc 윈도우 적재 배치 copybook (rank-1 라인 레코드). */ +#ifndef RC_WINDOWLOAD_BATCH_H +#define RC_WINDOWLOAD_BATCH_H +#define RC_WINDOWLOAD_TAG "rc_windowload_batch" +typedef struct { long pid; long amt; long rnk; } rc_windowload_rec_t; +#endif diff --git a/app/src/st/batch/st_adjnet_batch.pgc b/app/src/st/batch/st_adjnet_batch.pgc new file mode 100644 index 0000000..3d1aa7c --- /dev/null +++ b/app/src/st/batch/st_adjnet_batch.pgc @@ -0,0 +1,42 @@ +/* + * st_adjnet_batch.pgc - st 가맹점 조정 순액 백필 (INSERT..SELECT upsert + XA). + * + * st_adjust 를 가맹점 기준이 아닌 정산건 기준으로 집계하기 어려우므로, 지급(st_payment) + * 가맹점별 조정 총액을 st_merch_settle 순액에 반영하는 INSERT..SELECT upsert 를 수행. + * 사용법: st_adjnet_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" +#include "st_adjnet_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + st_adjnet_rec_t rec; rec.rows = 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_merch_settle (merchant_id, biz_date, txn_count, gross_amount, fee_amount, net_amount) + SELECT merchant_id, biz_date, count(*), coalesce(sum(net_amount), 0), 0, coalesce(sum(net_amount), 0) + FROM st_payment WHERE biz_date = :h_bizdate GROUP BY merchant_id, biz_date + ON CONFLICT (merchant_id, biz_date) DO UPDATE + SET net_amount = st_merch_settle.net_amount + EXCLUDED.net_amount, updated_at = now(); + if (sqlca.sqlcode < 0) { fprintf(stderr, "INSERT..SELECT FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.rows = (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 upserted=%ld\n", ST_ADJNET_TAG, bizdate, rec.rows); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_paysweep_batch.pgc b/app/src/st/batch/st_paysweep_batch.pgc new file mode 100644 index 0000000..fa1020e --- /dev/null +++ b/app/src/st/batch/st_paysweep_batch.pgc @@ -0,0 +1,41 @@ +/* + * st_paysweep_batch.pgc - st 소액 예정지급 일괄 보류 (집합 UPDATE + XA). + * + * 최소액 미만 SCHEDULED 지급을 하나의 집합 UPDATE 로 HELD 전환. 사용법: + * st_paysweep_batch [YYYY-MM-DD] [floor] + */ +#include +#include +#include +#include +#include "st_dbio.h" +#include "st_paysweep_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + st_paysweep_rec_t rec; + rec.floor_amt = (argc > 2) ? atol(argv[2]) : 10000; + rec.held = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_floor = rec.floor_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 UPDATE st_payment SET status = 'HELD' + WHERE biz_date = :h_bizdate AND status = 'SCHEDULED' AND net_amount < :h_floor; + if (sqlca.sqlcode < 0) { fprintf(stderr, "UPDATE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.held = (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 floor=%ld held=%ld\n", ST_PAYSWEEP_TAG, bizdate, rec.floor_amt, rec.held); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_purgeadj_batch.pgc b/app/src/st/batch/st_purgeadj_batch.pgc new file mode 100644 index 0000000..a056c4e --- /dev/null +++ b/app/src/st/batch/st_purgeadj_batch.pgc @@ -0,0 +1,40 @@ +/* + * st_purgeadj_batch.pgc - st 무효 조정 범위 정리 (BETWEEN DELETE + XA). + * + * 두 영업일 사이의 delta=0 조정을 하나의 범위 DELETE 로 제거하고 2PC 커밋. + * 사용법: st_purgeadj_batch [FROM] [TO] + */ +#include +#include +#include +#include +#include "st_dbio.h" +#include "st_purgeadj_batch.h" + +int main(int argc, char **argv) +{ + st_purgeadj_rec_t rec; + strncpy(rec.from_date, (argc > 1) ? argv[1] : "2026-07-01", sizeof(rec.from_date)-1); rec.from_date[sizeof(rec.from_date)-1] = 0; + strncpy(rec.to_date, (argc > 2) ? argv[2] : "2026-07-15", sizeof(rec.to_date)-1); rec.to_date[sizeof(rec.to_date)-1] = 0; + rec.removed = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_from[16], h_to[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_from, rec.from_date, sizeof(h_from)-1); h_from[sizeof(h_from)-1] = 0; + strncpy(h_to, rec.to_date, 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 DELETE FROM st_adjust WHERE biz_date BETWEEN :h_from AND :h_to AND delta = 0; + if (sqlca.sqlcode < 0) { fprintf(stderr, "DELETE FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.removed = (long) sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: from=%s to=%s removed=%ld\n", ST_PURGEADJ_TAG, rec.from_date, rec.to_date, rec.removed); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_rankpay_batch.pgc b/app/src/st/batch/st_rankpay_batch.pgc new file mode 100644 index 0000000..e0173ab --- /dev/null +++ b/app/src/st/batch/st_rankpay_batch.pgc @@ -0,0 +1,57 @@ +/* + * st_rankpay_batch.pgc - st 가맹점별 최상위 지급 -> 정산상세 적재 (윈도우 커서 + XA). + * + * row_number() OVER (PARTITION BY merchant_id ORDER BY net_amount DESC) 커서로 가맹점별 + * 1순위 지급을 골라 st_settle_dtl 에 RATE 라인으로 기록. 사용법: st_rankpay_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" +#include "st_rankpay_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + st_rankpay_rec_t rec; + long loaded = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_mid[32]; + long h_net, h_rn; + 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 rpc CURSOR FOR + SELECT merchant_id, net_amount, + row_number() OVER (PARTITION BY merchant_id ORDER BY net_amount DESC) + FROM st_payment WHERE biz_date = :h_bizdate; + EXEC SQL OPEN rpc; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH rpc INTO :h_mid, :h_net, :h_rn; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE rpc; tpabort(0); return 1; } + strncpy(rec.merchant, h_mid, sizeof(rec.merchant)-1); rec.merchant[sizeof(rec.merchant)-1] = 0; + rec.net = h_net; rec.rnk = h_rn; + if (rec.rnk == 1) { + 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, 0, :h_mid, 'RATE', :h_net, :h_bizdate); + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE rpc; tpabort(0); return 1; } + loaded++; + } + } + EXEC SQL CLOSE rpc; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s loaded=%ld\n", ST_RANKPAY_TAG, bizdate, loaded); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/batch/st_rateapply_batch.pgc b/app/src/st/batch/st_rateapply_batch.pgc new file mode 100644 index 0000000..328c5d4 --- /dev/null +++ b/app/src/st/batch/st_rateapply_batch.pgc @@ -0,0 +1,54 @@ +/* + * st_rateapply_batch.pgc - st 구간요율 적용 산출 (조인 커서 + XA). + * + * st_settle_dtl 을 st_fee_rate 와 구간(band_min) 조인 커서로 순회하며 요율 적용액을 + * 누계한다. 하나의 글로벌 트랜잭션에서 조회 후 2PC 커밋. 사용법: st_rateapply_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "st_dbio.h" +#include "st_rateapply_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + st_rateapply_rec_t rec; + long total = 0, lines = 0; + + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_ft[16]; + long h_amt, 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 DECLARE rac CURSOR FOR + SELECT d.fee_type, d.amount, r.rate_bps + FROM st_settle_dtl d + JOIN st_fee_rate r ON r.fee_type = d.fee_type AND d.amount >= r.band_min + WHERE d.biz_date = :h_bizdate ORDER BY d.dtl_id; + EXEC SQL OPEN rac; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + + for (;;) { + EXEC SQL FETCH rac INTO :h_ft, :h_amt, :h_bps; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { fprintf(stderr, "FETCH FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); EXEC SQL CLOSE rac; tpabort(0); return 1; } + strncpy(rec.fee_type, h_ft, sizeof(rec.fee_type)-1); rec.fee_type[sizeof(rec.fee_type)-1] = 0; + rec.amt = h_amt; rec.bps = h_bps; + total += rec.amt * rec.bps / 10000; + lines++; + } + EXEC SQL CLOSE rac; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> %s COMMIT: bizdate=%s lines=%ld fee=%ld\n", ST_RATEAPPLY_TAG, bizdate, lines, total); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/st/dbio/st_adjcorr_dbio.h b/app/src/st/dbio/st_adjcorr_dbio.h new file mode 100644 index 0000000..d2ae62e --- /dev/null +++ b/app/src/st/dbio/st_adjcorr_dbio.h @@ -0,0 +1,5 @@ +/* st_adjcorr_dbio.h - 정산건별 조정합 상관 서브쿼리 copybook (libstdbio.a). */ +#ifndef ST_ADJCORR_DBIO_H +#define ST_ADJCORR_DBIO_H +long stdb_settlements_net_positive(const char *bizdate); +#endif diff --git a/app/src/st/dbio/st_adjcorr_dbio.pgc b/app/src/st/dbio/st_adjcorr_dbio.pgc new file mode 100644 index 0000000..7795dfa --- /dev/null +++ b/app/src/st/dbio/st_adjcorr_dbio.pgc @@ -0,0 +1,30 @@ +/* + * st_adjcorr_dbio.pgc - 조정합이 양수인 정산건 (st_adjcorr_dbio), libstdbio.a. + * ECPG; 상관 서브쿼리로 각 정산건의 delta 합을 구해 양수인 distinct 정산건 수를 센다. + */ +#include +#include +#include "st_adjcorr_dbio.h" + +long stdb_settlements_net_positive(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_sid, h_delta, positive = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE acc2 CURSOR FOR + SELECT DISTINCT a.settlement_id, + (SELECT coalesce(sum(a2.delta), 0) FROM st_adjust a2 WHERE a2.settlement_id = a.settlement_id) + FROM st_adjust a WHERE a.biz_date = :h_bizdate; + EXEC SQL OPEN acc2; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH acc2 INTO :h_sid, :h_delta; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE acc2; return -1; } + if (h_delta > 0) positive++; + } + EXEC SQL CLOSE acc2; + return positive; +} diff --git a/app/src/st/dbio/st_adjnet_batch.h b/app/src/st/dbio/st_adjnet_batch.h new file mode 100644 index 0000000..1211d30 --- /dev/null +++ b/app/src/st/dbio/st_adjnet_batch.h @@ -0,0 +1,6 @@ +/* st_adjnet_batch.h - st 조정 순액 집계 배치 copybook. */ +#ifndef ST_ADJNET_BATCH_H +#define ST_ADJNET_BATCH_H +#define ST_ADJNET_TAG "st_adjnet_batch" +typedef struct { long rows; } st_adjnet_rec_t; +#endif diff --git a/app/src/st/dbio/st_adjpurge_dbio.h b/app/src/st/dbio/st_adjpurge_dbio.h new file mode 100644 index 0000000..fdb466f --- /dev/null +++ b/app/src/st/dbio/st_adjpurge_dbio.h @@ -0,0 +1,5 @@ +/* st_adjpurge_dbio.h - 0-델타 조정 범위 삭제 copybook (libstdbio.a). */ +#ifndef ST_ADJPURGE_DBIO_H +#define ST_ADJPURGE_DBIO_H +long stdb_purge_zero_adj(const char *from_date, const char *to_date); +#endif diff --git a/app/src/st/dbio/st_adjpurge_dbio.pgc b/app/src/st/dbio/st_adjpurge_dbio.pgc new file mode 100644 index 0000000..2330cc7 --- /dev/null +++ b/app/src/st/dbio/st_adjpurge_dbio.pgc @@ -0,0 +1,19 @@ +/* + * st_adjpurge_dbio.pgc - 영향 없는 조정 정리 (st_adjpurge_dbio), libstdbio.a. + * ECPG; DELETE .. WHERE biz_date BETWEEN a AND b AND delta=0 범위 삭제. 반환=삭제 행수. + */ +#include +#include +#include "st_adjpurge_dbio.h" + +long stdb_purge_zero_adj(const char *from_date, const char *to_date) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_from[16], h_to[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_from, from_date, sizeof(h_from)-1); h_from[sizeof(h_from)-1] = 0; + strncpy(h_to, to_date, sizeof(h_to)-1); h_to[sizeof(h_to)-1] = 0; + EXEC SQL DELETE FROM st_adjust WHERE biz_date BETWEEN :h_from AND :h_to AND delta = 0; + if (sqlca.sqlcode < 0) { userlog("stdb_purge_zero_adj FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return (long) sqlca.sqlerrd[2]; +} diff --git a/app/src/st/dbio/st_dtlinsert_dbio.h b/app/src/st/dbio/st_dtlinsert_dbio.h new file mode 100644 index 0000000..23417f0 --- /dev/null +++ b/app/src/st/dbio/st_dtlinsert_dbio.h @@ -0,0 +1,5 @@ +/* st_dtlinsert_dbio.h - 지급 -> 정산상세 INSERT..SELECT copybook (libstdbio.a). */ +#ifndef ST_DTLINSERT_DBIO_H +#define ST_DTLINSERT_DBIO_H +long stdb_seed_cap_lines(const char *bizdate); +#endif diff --git a/app/src/st/dbio/st_dtlinsert_dbio.pgc b/app/src/st/dbio/st_dtlinsert_dbio.pgc new file mode 100644 index 0000000..b5330b8 --- /dev/null +++ b/app/src/st/dbio/st_dtlinsert_dbio.pgc @@ -0,0 +1,20 @@ +/* + * st_dtlinsert_dbio.pgc - 지급액으로부터 CAP 라인 생성 (st_dtlinsert_dbio), libstdbio.a. + * ECPG; INSERT .. SELECT 로 st_payment 를 CAP 수수료 라인으로 st_settle_dtl 에 적재. 반환=행수. + */ +#include +#include +#include "st_dtlinsert_dbio.h" + +long stdb_seed_cap_lines(const char *bizdate) +{ + 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; + 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'), 0, 0, merchant_id, 'CAP', net_amount / 100, biz_date + FROM st_payment WHERE biz_date = :h_bizdate AND status = 'PAID'; + if (sqlca.sqlcode < 0) { userlog("stdb_seed_cap_lines FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return (long) sqlca.sqlerrd[2]; +} diff --git a/app/src/st/dbio/st_feecase_dbio.h b/app/src/st/dbio/st_feecase_dbio.h new file mode 100644 index 0000000..54c3fe9 --- /dev/null +++ b/app/src/st/dbio/st_feecase_dbio.h @@ -0,0 +1,5 @@ +/* st_feecase_dbio.h - 수수료 종류별 CASE 합계 copybook (libstdbio.a). */ +#ifndef ST_FEECASE_DBIO_H +#define ST_FEECASE_DBIO_H +long stdb_mdr_vs_van(const char *bizdate, long *van_out); +#endif diff --git a/app/src/st/dbio/st_feecase_dbio.pgc b/app/src/st/dbio/st_feecase_dbio.pgc new file mode 100644 index 0000000..505ff4b --- /dev/null +++ b/app/src/st/dbio/st_feecase_dbio.pgc @@ -0,0 +1,22 @@ +/* + * st_feecase_dbio.pgc - MDR/VAN 수수료 분리 합계 (st_feecase_dbio), libstdbio.a. + * ECPG; sum(CASE WHEN fee_type=... THEN amount ELSE 0 END) 형태로 한 SELECT 에서 두 합계. + */ +#include +#include +#include "st_feecase_dbio.h" + +long stdb_mdr_vs_van(const char *bizdate, long *van_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_mdr = 0, h_van = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(CASE WHEN fee_type = 'MDR' THEN amount ELSE 0 END), 0), + coalesce(sum(CASE WHEN fee_type = 'VAN' THEN amount ELSE 0 END), 0) + INTO :h_mdr, :h_van FROM st_settle_dtl WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("stdb_mdr_vs_van FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + if (van_out) *van_out = h_van; + return h_mdr; +} diff --git a/app/src/st/dbio/st_feehaving_dbio.h b/app/src/st/dbio/st_feehaving_dbio.h new file mode 100644 index 0000000..4cbeeff --- /dev/null +++ b/app/src/st/dbio/st_feehaving_dbio.h @@ -0,0 +1,5 @@ +/* st_feehaving_dbio.h - 고수수료 가맹점 HAVING copybook (libstdbio.a). */ +#ifndef ST_FEEHAVING_DBIO_H +#define ST_FEEHAVING_DBIO_H +long stdb_high_fee_merchants(const char *bizdate, long amt_thr, long cnt_thr); +#endif diff --git a/app/src/st/dbio/st_feehaving_dbio.pgc b/app/src/st/dbio/st_feehaving_dbio.pgc new file mode 100644 index 0000000..c61addf --- /dev/null +++ b/app/src/st/dbio/st_feehaving_dbio.pgc @@ -0,0 +1,30 @@ +/* + * st_feehaving_dbio.pgc - 고수수료 다건 가맹점 (st_feehaving_dbio), libstdbio.a. + * ECPG; GROUP BY merchant_id HAVING sum(amount) > a AND count(*) > c 커서로 대상 가맹점 수. + */ +#include +#include +#include "st_feehaving_dbio.h" + +long stdb_high_fee_merchants(const char *bizdate, long amt_thr, long cnt_thr) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_mid[32]; + long h_amt = amt_thr, h_cnt = cnt_thr, h_sum, h_n, merchants = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE fhc CURSOR FOR + SELECT merchant_id, sum(amount), count(*) FROM st_settle_dtl + WHERE biz_date = :h_bizdate GROUP BY merchant_id + HAVING sum(amount) > :h_amt AND count(*) > :h_cnt; + EXEC SQL OPEN fhc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH fhc INTO :h_mid, :h_sum, :h_n; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE fhc; return -1; } + merchants++; + } + EXEC SQL CLOSE fhc; + return merchants; +} diff --git a/app/src/st/dbio/st_feeweight_dbio.h b/app/src/st/dbio/st_feeweight_dbio.h new file mode 100644 index 0000000..7ac8d60 --- /dev/null +++ b/app/src/st/dbio/st_feeweight_dbio.h @@ -0,0 +1,5 @@ +/* st_feeweight_dbio.h - 요율 가중 평균 (JOIN) copybook (libstdbio.a). */ +#ifndef ST_FEEWEIGHT_DBIO_H +#define ST_FEEWEIGHT_DBIO_H +long stdb_weighted_rate(const char *bizdate); +#endif diff --git a/app/src/st/dbio/st_feeweight_dbio.pgc b/app/src/st/dbio/st_feeweight_dbio.pgc new file mode 100644 index 0000000..5185a12 --- /dev/null +++ b/app/src/st/dbio/st_feeweight_dbio.pgc @@ -0,0 +1,22 @@ +/* + * st_feeweight_dbio.pgc - 금액 가중 평균 요율 (st_feeweight_dbio), libstdbio.a. + * ECPG; st_settle_dtl 를 st_fee_rate 와 조인해 sum(amount*rate_bps)/nullif(sum(amount),0) 가중평균. + */ +#include +#include +#include "st_feeweight_dbio.h" + +long stdb_weighted_rate(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + double h_w = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(d.amount * r.rate_bps)::float8 / nullif(sum(d.amount), 0), 0) + INTO :h_w FROM st_settle_dtl d + JOIN st_fee_rate r ON r.fee_type = d.fee_type AND d.amount >= r.band_min + WHERE d.biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("stdb_weighted_rate FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return (long) h_w; +} diff --git a/app/src/st/dbio/st_feewindow_dbio.h b/app/src/st/dbio/st_feewindow_dbio.h new file mode 100644 index 0000000..0b85a3b --- /dev/null +++ b/app/src/st/dbio/st_feewindow_dbio.h @@ -0,0 +1,5 @@ +/* st_feewindow_dbio.h - 가맹점별 수수료 순위 copybook (libstdbio.a). */ +#ifndef ST_FEEWINDOW_DBIO_H +#define ST_FEEWINDOW_DBIO_H +long stdb_top3_fee_lines(const char *bizdate); +#endif diff --git a/app/src/st/dbio/st_feewindow_dbio.pgc b/app/src/st/dbio/st_feewindow_dbio.pgc new file mode 100644 index 0000000..1658d41 --- /dev/null +++ b/app/src/st/dbio/st_feewindow_dbio.pgc @@ -0,0 +1,31 @@ +/* + * st_feewindow_dbio.pgc - 가맹점별 수수료 상위 라인 (st_feewindow_dbio), libstdbio.a. + * ECPG; 호출자 XA 브랜치에서 수행, EXEC SQL CONNECT 없음. + * dense_rank() OVER (PARTITION BY merchant_id ORDER BY amount DESC) 로 가맹점별 상위 3 라인 수. + */ +#include +#include +#include "st_feewindow_dbio.h" + +long stdb_top3_fee_lines(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_mid[32]; + long h_dr, h_amt, tops = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE fwc CURSOR FOR + SELECT merchant_id, amount, + dense_rank() OVER (PARTITION BY merchant_id ORDER BY amount DESC) + FROM st_settle_dtl WHERE biz_date = :h_bizdate; + EXEC SQL OPEN fwc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH fwc INTO :h_mid, :h_amt, :h_dr; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE fwc; return -1; } + if (h_dr <= 3) tops++; + } + EXEC SQL CLOSE fwc; + return tops; +} diff --git a/app/src/st/dbio/st_merchdistinct_dbio.h b/app/src/st/dbio/st_merchdistinct_dbio.h new file mode 100644 index 0000000..becdc24 --- /dev/null +++ b/app/src/st/dbio/st_merchdistinct_dbio.h @@ -0,0 +1,5 @@ +/* st_merchdistinct_dbio.h - 고유 가맹점/수수료종류 수 copybook (libstdbio.a). */ +#ifndef ST_MERCHDISTINCT_DBIO_H +#define ST_MERCHDISTINCT_DBIO_H +long stdb_distinct_merch(const char *bizdate, long *feetypes_out); +#endif diff --git a/app/src/st/dbio/st_merchdistinct_dbio.pgc b/app/src/st/dbio/st_merchdistinct_dbio.pgc new file mode 100644 index 0000000..a880ec7 --- /dev/null +++ b/app/src/st/dbio/st_merchdistinct_dbio.pgc @@ -0,0 +1,21 @@ +/* + * st_merchdistinct_dbio.pgc - 고유 가맹점/수수료종류 수 (st_merchdistinct_dbio), libstdbio.a. + * ECPG; count(DISTINCT merchant_id) 와 count(DISTINCT fee_type) 를 한 SELECT 로 반환. + */ +#include +#include +#include "st_merchdistinct_dbio.h" + +long stdb_distinct_merch(const char *bizdate, long *feetypes_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_m = 0, h_ft = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(DISTINCT merchant_id), count(DISTINCT fee_type) + INTO :h_m, :h_ft FROM st_settle_dtl WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("stdb_distinct_merch FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + if (feetypes_out) *feetypes_out = h_ft; + return h_m; +} diff --git a/app/src/st/dbio/st_merchupsert_dbio.h b/app/src/st/dbio/st_merchupsert_dbio.h new file mode 100644 index 0000000..221a8e5 --- /dev/null +++ b/app/src/st/dbio/st_merchupsert_dbio.h @@ -0,0 +1,5 @@ +/* st_merchupsert_dbio.h - 가맹점 정산 집계 upsert copybook (libstdbio.a). */ +#ifndef ST_MERCHUPSERT_DBIO_H +#define ST_MERCHUPSERT_DBIO_H +int stdb_bump_merch(const char *merchant, const char *bizdate, long gross, long fee, long net); +#endif diff --git a/app/src/st/dbio/st_merchupsert_dbio.pgc b/app/src/st/dbio/st_merchupsert_dbio.pgc new file mode 100644 index 0000000..5097fdd --- /dev/null +++ b/app/src/st/dbio/st_merchupsert_dbio.pgc @@ -0,0 +1,27 @@ +/* + * st_merchupsert_dbio.pgc - 가맹점 정산 집계 증분 upsert (st_merchupsert_dbio), libstdbio.a. + * ECPG; 복합키 (merchant_id, biz_date) ON CONFLICT DO UPDATE 로 건수/총액/수수료/순액을 누적. + */ +#include +#include +#include "st_merchupsert_dbio.h" + +int stdb_bump_merch(const char *merchant, const char *bizdate, long gross, long fee, long net) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_mid[32], h_bizdate[16]; + long h_g = gross, h_f = fee, h_n = net; + EXEC SQL END DECLARE SECTION; + strncpy(h_mid, merchant, sizeof(h_mid)-1); h_mid[sizeof(h_mid)-1] = 0; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL INSERT INTO st_merch_settle (merchant_id, biz_date, txn_count, gross_amount, fee_amount, net_amount) + VALUES (:h_mid, :h_bizdate, 1, :h_g, :h_f, :h_n) + ON CONFLICT (merchant_id, biz_date) DO UPDATE + SET txn_count = st_merch_settle.txn_count + 1, + gross_amount = st_merch_settle.gross_amount + EXCLUDED.gross_amount, + fee_amount = st_merch_settle.fee_amount + EXCLUDED.fee_amount, + net_amount = st_merch_settle.net_amount + EXCLUDED.net_amount, + updated_at = now(); + if (sqlca.sqlcode < 0) { userlog("stdb_bump_merch FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return 0; +} diff --git a/app/src/st/dbio/st_netminmax_dbio.h b/app/src/st/dbio/st_netminmax_dbio.h new file mode 100644 index 0000000..27cc7f5 --- /dev/null +++ b/app/src/st/dbio/st_netminmax_dbio.h @@ -0,0 +1,5 @@ +/* st_netminmax_dbio.h - 지급 순액 최소/최대/합 copybook (libstdbio.a). */ +#ifndef ST_NETMINMAX_DBIO_H +#define ST_NETMINMAX_DBIO_H +long stdb_net_span(const char *bizdate, const char *status, long *min_out, long *max_out); +#endif diff --git a/app/src/st/dbio/st_netminmax_dbio.pgc b/app/src/st/dbio/st_netminmax_dbio.pgc new file mode 100644 index 0000000..233af14 --- /dev/null +++ b/app/src/st/dbio/st_netminmax_dbio.pgc @@ -0,0 +1,24 @@ +/* + * st_netminmax_dbio.pgc - 상태별 순액 스팬 (st_netminmax_dbio), libstdbio.a. + * ECPG; 특정 status 지급의 min/max/sum(net_amount) 을 한 번에 구해 합계를 반환. + */ +#include +#include +#include "st_netminmax_dbio.h" + +long stdb_net_span(const char *bizdate, const char *status, long *min_out, long *max_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_status[16]; + long h_min = 0, h_max = 0, h_sum = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + strncpy(h_status, status, sizeof(h_status)-1); h_status[sizeof(h_status)-1] = 0; + EXEC SQL SELECT coalesce(min(net_amount),0), coalesce(max(net_amount),0), coalesce(sum(net_amount),0) + INTO :h_min, :h_max, :h_sum FROM st_payment + WHERE biz_date = :h_bizdate AND status = :h_status; + if (sqlca.sqlcode < 0) { userlog("stdb_net_span FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + if (min_out) *min_out = h_min; + if (max_out) *max_out = h_max; + return h_sum; +} diff --git a/app/src/st/dbio/st_paycursor2_dbio.h b/app/src/st/dbio/st_paycursor2_dbio.h new file mode 100644 index 0000000..f46c3c5 --- /dev/null +++ b/app/src/st/dbio/st_paycursor2_dbio.h @@ -0,0 +1,5 @@ +/* st_paycursor2_dbio.h - 예정 지급 보류 전환 (positioned UPDATE) copybook (libstdbio.a). */ +#ifndef ST_PAYCURSOR2_DBIO_H +#define ST_PAYCURSOR2_DBIO_H +long stdb_hold_small_payments(const char *bizdate, long floor_amt); +#endif diff --git a/app/src/st/dbio/st_paycursor2_dbio.pgc b/app/src/st/dbio/st_paycursor2_dbio.pgc new file mode 100644 index 0000000..58eaba8 --- /dev/null +++ b/app/src/st/dbio/st_paycursor2_dbio.pgc @@ -0,0 +1,33 @@ +/* + * st_paycursor2_dbio.pgc - 소액 예정지급 보류 전환 (st_paycursor2_dbio), libstdbio.a. + * ECPG; FOR UPDATE 커서로 SCHEDULED 지급을 순회하며 최소액 미만이면 WHERE CURRENT OF 로 HELD 전환. + */ +#include +#include +#include "st_paycursor2_dbio.h" + +long stdb_hold_small_payments(const char *bizdate, long floor_amt) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_floor = floor_amt, h_id, h_net, held = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE p2c CURSOR FOR + SELECT pay_id, net_amount FROM st_payment + WHERE biz_date = :h_bizdate AND status = 'SCHEDULED' FOR UPDATE; + EXEC SQL OPEN p2c; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH p2c INTO :h_id, :h_net; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE p2c; return -1; } + if (h_net < h_floor) { + EXEC SQL UPDATE st_payment SET status = 'HELD' WHERE CURRENT OF p2c; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE p2c; return -1; } + held++; + } + } + EXEC SQL CLOSE p2c; + return held; +} diff --git a/app/src/st/dbio/st_payinsub_dbio.h b/app/src/st/dbio/st_payinsub_dbio.h new file mode 100644 index 0000000..e6ad2b7 --- /dev/null +++ b/app/src/st/dbio/st_payinsub_dbio.h @@ -0,0 +1,5 @@ +/* st_payinsub_dbio.h - 활성 가맹점 지급합 (IN subselect) copybook (libstdbio.a). */ +#ifndef ST_PAYINSUB_DBIO_H +#define ST_PAYINSUB_DBIO_H +long stdb_pay_for_active_merch(const char *bizdate, long min_txn); +#endif diff --git a/app/src/st/dbio/st_payinsub_dbio.pgc b/app/src/st/dbio/st_payinsub_dbio.pgc new file mode 100644 index 0000000..5ad2e16 --- /dev/null +++ b/app/src/st/dbio/st_payinsub_dbio.pgc @@ -0,0 +1,22 @@ +/* + * st_payinsub_dbio.pgc - 활성 가맹점 지급 합계 (st_payinsub_dbio), libstdbio.a. + * ECPG; merchant_id IN (SELECT ... FROM st_merch_settle WHERE txn_count > n) IN 서브셀렉트로 지급합. + */ +#include +#include +#include "st_payinsub_dbio.h" + +long stdb_pay_for_active_merch(const char *bizdate, long min_txn) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_min = min_txn, h_v = 0; + EXEC SQL END DECLARE SECTION; + 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_v FROM st_payment + WHERE biz_date = :h_bizdate + AND merchant_id IN (SELECT merchant_id FROM st_merch_settle + WHERE biz_date = :h_bizdate AND txn_count > :h_min); + if (sqlca.sqlcode < 0) { userlog("stdb_pay_for_active_merch FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/st/dbio/st_paynotexists_dbio.h b/app/src/st/dbio/st_paynotexists_dbio.h new file mode 100644 index 0000000..b26e25b --- /dev/null +++ b/app/src/st/dbio/st_paynotexists_dbio.h @@ -0,0 +1,5 @@ +/* st_paynotexists_dbio.h - 지급 누락 가맹점 (NOT EXISTS) copybook (libstdbio.a). */ +#ifndef ST_PAYNOTEXISTS_DBIO_H +#define ST_PAYNOTEXISTS_DBIO_H +long stdb_merch_without_payment(const char *bizdate); +#endif diff --git a/app/src/st/dbio/st_paynotexists_dbio.pgc b/app/src/st/dbio/st_paynotexists_dbio.pgc new file mode 100644 index 0000000..86f5dd9 --- /dev/null +++ b/app/src/st/dbio/st_paynotexists_dbio.pgc @@ -0,0 +1,22 @@ +/* + * st_paynotexists_dbio.pgc - 지급이 없는 정산 가맹점 (st_paynotexists_dbio), libstdbio.a. + * ECPG; merchant_id+biz_date 2컬럼 상관 NOT EXISTS 로 st_payment 가 없는 st_merch_settle 를 센다. + */ +#include +#include +#include "st_paynotexists_dbio.h" + +long stdb_merch_without_payment(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM st_merch_settle ms + WHERE ms.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM st_payment p + WHERE p.merchant_id = ms.merchant_id AND p.biz_date = ms.biz_date); + if (sqlca.sqlcode < 0) { userlog("stdb_merch_without_payment FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + return h_v; +} diff --git a/app/src/st/dbio/st_paysweep_batch.h b/app/src/st/dbio/st_paysweep_batch.h new file mode 100644 index 0000000..77f4d15 --- /dev/null +++ b/app/src/st/dbio/st_paysweep_batch.h @@ -0,0 +1,6 @@ +/* st_paysweep_batch.h - st 예정지급 보류 스윕 배치 copybook. */ +#ifndef ST_PAYSWEEP_BATCH_H +#define ST_PAYSWEEP_BATCH_H +#define ST_PAYSWEEP_TAG "st_paysweep_batch" +typedef struct { long floor_amt; long held; } st_paysweep_rec_t; +#endif diff --git a/app/src/st/dbio/st_purgeadj_batch.h b/app/src/st/dbio/st_purgeadj_batch.h new file mode 100644 index 0000000..fa1e9af --- /dev/null +++ b/app/src/st/dbio/st_purgeadj_batch.h @@ -0,0 +1,6 @@ +/* st_purgeadj_batch.h - st 조정 범위 삭제 배치 copybook. */ +#ifndef ST_PURGEADJ_BATCH_H +#define ST_PURGEADJ_BATCH_H +#define ST_PURGEADJ_TAG "st_purgeadj_batch" +typedef struct { char from_date[16]; char to_date[16]; long removed; } st_purgeadj_rec_t; +#endif diff --git a/app/src/st/dbio/st_rankpay_batch.h b/app/src/st/dbio/st_rankpay_batch.h new file mode 100644 index 0000000..e627948 --- /dev/null +++ b/app/src/st/dbio/st_rankpay_batch.h @@ -0,0 +1,6 @@ +/* st_rankpay_batch.h - st 가맹점 상위지급 적재 배치 copybook. */ +#ifndef ST_RANKPAY_BATCH_H +#define ST_RANKPAY_BATCH_H +#define ST_RANKPAY_TAG "st_rankpay_batch" +typedef struct { char merchant[32]; long net; long rnk; } st_rankpay_rec_t; +#endif diff --git a/app/src/st/dbio/st_rateapply_batch.h b/app/src/st/dbio/st_rateapply_batch.h new file mode 100644 index 0000000..4b1a89c --- /dev/null +++ b/app/src/st/dbio/st_rateapply_batch.h @@ -0,0 +1,6 @@ +/* st_rateapply_batch.h - st 구간요율 적용 배치 copybook. */ +#ifndef ST_RATEAPPLY_BATCH_H +#define ST_RATEAPPLY_BATCH_H +#define ST_RATEAPPLY_TAG "st_rateapply_batch" +typedef struct { char fee_type[16]; long amt; long bps; } st_rateapply_rec_t; +#endif diff --git a/app/src/st/dbio/st_ratejoin_dbio.h b/app/src/st/dbio/st_ratejoin_dbio.h new file mode 100644 index 0000000..96f47db --- /dev/null +++ b/app/src/st/dbio/st_ratejoin_dbio.h @@ -0,0 +1,5 @@ +/* st_ratejoin_dbio.h - 상세x요율 조인 커서 copybook (libstdbio.a). */ +#ifndef ST_RATEJOIN_DBIO_H +#define ST_RATEJOIN_DBIO_H +long stdb_apply_band_rate(const char *bizdate); +#endif diff --git a/app/src/st/dbio/st_ratejoin_dbio.pgc b/app/src/st/dbio/st_ratejoin_dbio.pgc new file mode 100644 index 0000000..54e1992 --- /dev/null +++ b/app/src/st/dbio/st_ratejoin_dbio.pgc @@ -0,0 +1,32 @@ +/* + * st_ratejoin_dbio.pgc - 구간 요율 적용 (st_ratejoin_dbio), libstdbio.a. + * ECPG; st_settle_dtl 를 st_fee_rate 와 fee_type + band_min 조건으로 조인, 커서로 요율 적용액 합. + */ +#include +#include +#include "st_ratejoin_dbio.h" + +long stdb_apply_band_rate(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16], h_ft[16]; + long h_amt, h_bps, total = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE rjc CURSOR FOR + SELECT d.fee_type, d.amount, r.rate_bps + FROM st_settle_dtl d + JOIN st_fee_rate r ON r.fee_type = d.fee_type AND d.amount >= r.band_min + WHERE d.biz_date = :h_bizdate + ORDER BY d.dtl_id; + EXEC SQL OPEN rjc; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH rjc INTO :h_ft, :h_amt, :h_bps; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE rjc; return -1; } + total += h_amt * h_bps / 10000; + } + EXEC SQL CLOSE rjc; + return total; +} diff --git a/app/src/st/dbio/st_statusfilter_dbio.h b/app/src/st/dbio/st_statusfilter_dbio.h new file mode 100644 index 0000000..ab1da9a --- /dev/null +++ b/app/src/st/dbio/st_statusfilter_dbio.h @@ -0,0 +1,5 @@ +/* st_statusfilter_dbio.h - 지급 상태별 FILTER 집계 copybook (libstdbio.a). */ +#ifndef ST_STATUSFILTER_DBIO_H +#define ST_STATUSFILTER_DBIO_H +long stdb_pay_status_mix(const char *bizdate, long *paid_out, long *cancelled_out); +#endif diff --git a/app/src/st/dbio/st_statusfilter_dbio.pgc b/app/src/st/dbio/st_statusfilter_dbio.pgc new file mode 100644 index 0000000..548b80d --- /dev/null +++ b/app/src/st/dbio/st_statusfilter_dbio.pgc @@ -0,0 +1,24 @@ +/* + * st_statusfilter_dbio.pgc - 지급 상태 구성 (st_statusfilter_dbio), libstdbio.a. + * ECPG; count(*) FILTER (WHERE status=...) 3종으로 HELD/PAID/CANCELLED 를 동시에 집계. + */ +#include +#include +#include "st_statusfilter_dbio.h" + +long stdb_pay_status_mix(const char *bizdate, long *paid_out, long *cancelled_out) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_held = 0, h_paid = 0, h_cancel = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) FILTER (WHERE status = 'HELD'), + count(*) FILTER (WHERE status = 'PAID'), + count(*) FILTER (WHERE status = 'CANCELLED') + INTO :h_held, :h_paid, :h_cancel FROM st_payment WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("stdb_pay_status_mix FAIL [%d] %s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); return -1; } + if (paid_out) *paid_out = h_paid; + if (cancelled_out) *cancelled_out = h_cancel; + return h_held; +} diff --git a/app/src/vl/batch/vl_errgrp_batch.pgc b/app/src/vl/batch/vl_errgrp_batch.pgc new file mode 100644 index 0000000..088c70f --- /dev/null +++ b/app/src/vl/batch/vl_errgrp_batch.pgc @@ -0,0 +1,41 @@ +/* + * vl_errgrp_batch.pgc - vl 반복 에러코드 그룹 집계 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, EXEC SQL runs on the biz_date, then tpcommit drives XA 2PC. + * Usage: vl_errgrp_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_errgrp_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_min = 2, h_groups = 0; + EXEC SQL END DECLARE SECTION; + vl_errgrp_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_groups FROM ( + SELECT result_code FROM vl_check_log + WHERE biz_date = :h_bizdate AND result_code > 0 + GROUP BY result_code HAVING count(*) >= :h_min) t; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_errgrp_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.threshold = h_min; rec.groups = h_groups; + printf(">>> vl_errgrp_batch COMMIT: bizdate=%s 임계=%ld 반복에러그룹=%ld\n", bizdate, rec.threshold, rec.groups); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_logpurge_batch.pgc b/app/src/vl/batch/vl_logpurge_batch.pgc new file mode 100644 index 0000000..51d7d78 --- /dev/null +++ b/app/src/vl/batch/vl_logpurge_batch.pgc @@ -0,0 +1,40 @@ +/* + * vl_logpurge_batch.pgc - vl 정상 검증로그 보존기간 삭제 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, EXEC SQL runs on the biz_date, then tpcommit drives XA 2PC. + * Usage: vl_logpurge_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_logpurge_batch.h" + +int main(int argc, char **argv) +{ + const char *bizdate = (argc > 1) ? argv[1] : "2026-07-19"; + const char *cutoff = (argc > 2) ? argv[2] : VL_LOGPURGE_CUTOFF; + EXEC SQL BEGIN DECLARE SECTION; + char h_cut[16]; + EXEC SQL END DECLARE SECTION; + vl_logpurge_rec_t rec; + (void) bizdate; + + strncpy(h_cut, cutoff, sizeof(h_cut)-1); h_cut[sizeof(h_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 DELETE FROM vl_check_log + WHERE biz_date < :h_cut AND result_code = 0; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_logpurge_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.deleted = (long) sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_logpurge_batch COMMIT: cutoff=%s 삭제=%ld\n", cutoff, rec.deleted); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_orphan_batch.pgc b/app/src/vl/batch/vl_orphan_batch.pgc new file mode 100644 index 0000000..17ad5c2 --- /dev/null +++ b/app/src/vl/batch/vl_orphan_batch.pgc @@ -0,0 +1,40 @@ +/* + * vl_orphan_batch.pgc - vl 고아 의심거래 점검 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, EXEC SQL runs on the biz_date, then tpcommit drives XA 2PC. + * Usage: vl_orphan_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_orphan_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_orphans = 0; + EXEC SQL END DECLARE SECTION; + vl_orphan_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_orphans FROM vl_suspect s + WHERE s.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM vl_check_log l WHERE l.target_id = s.purchase_id); + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_orphan_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.orphans = h_orphans; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_orphan_batch COMMIT: bizdate=%s 고아의심=%ld\n", bizdate, rec.orphans); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_promote_batch.pgc b/app/src/vl/batch/vl_promote_batch.pgc new file mode 100644 index 0000000..c5b3a36 --- /dev/null +++ b/app/src/vl/batch/vl_promote_batch.pgc @@ -0,0 +1,41 @@ +/* + * vl_promote_batch.pgc - vl 실패 검증로그 의심거래 승격 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, EXEC SQL runs on the biz_date, then tpcommit drives XA 2PC. + * Usage: vl_promote_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_promote_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; + vl_promote_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 INSERT INTO vl_suspect + (suspect_id, purchase_id, rule_code, severity, detail, biz_date) + SELECT nextval('vl_suspect_seq'), l.target_id, 'CHK_FAIL', 'WARN', l.detail, l.biz_date + FROM vl_check_log l + WHERE l.biz_date = :h_bizdate AND l.result_code > 0; + if (sqlca.sqlcode < 0) { fprintf(stderr, "vl_promote_batch FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + rec.promoted = (long) sqlca.sqlerrd[2]; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_promote_batch COMMIT: bizdate=%s 승격=%ld\n", bizdate, rec.promoted); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/batch/vl_rankscan_batch.pgc b/app/src/vl/batch/vl_rankscan_batch.pgc new file mode 100644 index 0000000..2065a5b --- /dev/null +++ b/app/src/vl/batch/vl_rankscan_batch.pgc @@ -0,0 +1,49 @@ +/* + * vl_rankscan_batch.pgc - vl 심각도별 최초 의심거래 랭크 스캔 배치 (XA 단일 글로벌 트랜잭션 배치). + * + * ATMI client: tpinit/tpopen open the ECPG XA RM, tpbegin starts ONE global + * transaction, EXEC SQL runs on the biz_date, then tpcommit drives XA 2PC. + * Usage: vl_rankscan_batch [YYYY-MM-DD] + */ +#include +#include +#include +#include +#include "vl_rankscan_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_sid, h_rn; + EXEC SQL END DECLARE SECTION; + vl_rankscan_rec_t rec; rec.scanned = 0; rec.firsts = 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 vrs CURSOR FOR + SELECT suspect_id, + ROW_NUMBER() OVER (PARTITION BY severity ORDER BY suspect_id) + FROM vl_suspect WHERE biz_date = :h_bizdate; + EXEC SQL OPEN vrs; + if (sqlca.sqlcode < 0) { fprintf(stderr, "OPEN FAIL [%d] %s\n", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); tpabort(0); return 1; } + for (;;) { + EXEC SQL FETCH vrs INTO :h_sid, :h_rn; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE vrs; tpabort(0); return 1; } + rec.scanned++; + if (h_rn == 1) rec.firsts++; + } + EXEC SQL CLOSE vrs; + + if (tpcommit(0) < 0) { fprintf(stderr, "tpcommit FAIL: %s\n", tpstrerror(tperrno)); tpabort(0); return 1; } + printf(">>> vl_rankscan_batch COMMIT: bizdate=%s 스캔=%ld 최초=%ld\n", bizdate, rec.scanned, rec.firsts); + + tpclose(); tpterm(); + return 0; +} diff --git a/app/src/vl/dbio/vl_corr_dbio.h b/app/src/vl/dbio/vl_corr_dbio.h new file mode 100644 index 0000000..86937ef --- /dev/null +++ b/app/src/vl/dbio/vl_corr_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_corr_dbio.h - vl 모듈 DB 접근 copybook (vl_corr_dbio). 규칙 기본심각도와 일치하는 의심거래 건수 (상관 서브쿼리). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_CORR_DBIO_H +#define VL_CORR_DBIO_H + +long vldb_rule_consistent(const char *bizdate); + +#endif /* VL_CORR_DBIO_H */ diff --git a/app/src/vl/dbio/vl_corr_dbio.pgc b/app/src/vl/dbio/vl_corr_dbio.pgc new file mode 100644 index 0000000..4ca903d --- /dev/null +++ b/app/src/vl/dbio/vl_corr_dbio.pgc @@ -0,0 +1,22 @@ +/* + * vl_corr_dbio.pgc - vl 모듈 DB 접근 함수 (vl_corr_dbio), archived into libvldbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 규칙 기본심각도와 일치하는 의심거래 건수 (상관 서브쿼리). + */ +#include +#include +#include "vl_corr_dbio.h" + +long vldb_rule_consistent(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM vl_suspect s + WHERE s.biz_date = :h_bizdate + AND s.severity = (SELECT r.severity FROM vl_rule r WHERE r.rule_code = s.rule_code); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/vl/dbio/vl_errdistinct_dbio.h b/app/src/vl/dbio/vl_errdistinct_dbio.h new file mode 100644 index 0000000..0fa25e6 --- /dev/null +++ b/app/src/vl/dbio/vl_errdistinct_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_errdistinct_dbio.h - vl 모듈 DB 접근 copybook (vl_errdistinct_dbio). 당일 의심거래에서 발화한 고유 규칙 수 (count DISTINCT). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_ERRDISTINCT_DBIO_H +#define VL_ERRDISTINCT_DBIO_H + +long vldb_distinct_rules(const char *bizdate); + +#endif /* VL_ERRDISTINCT_DBIO_H */ diff --git a/app/src/vl/dbio/vl_errdistinct_dbio.pgc b/app/src/vl/dbio/vl_errdistinct_dbio.pgc new file mode 100644 index 0000000..7c784e2 --- /dev/null +++ b/app/src/vl/dbio/vl_errdistinct_dbio.pgc @@ -0,0 +1,21 @@ +/* + * vl_errdistinct_dbio.pgc - vl 모듈 DB 접근 함수 (vl_errdistinct_dbio), archived into libvldbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 당일 의심거래에서 발화한 고유 규칙 수 (count DISTINCT). + */ +#include +#include +#include "vl_errdistinct_dbio.h" + +long vldb_distinct_rules(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(DISTINCT rule_code) INTO :h_v + FROM vl_suspect WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) { userlog("vldb_distinct_rules FAIL [%d]", sqlca.sqlcode); return -1; } + return h_v; +} diff --git a/app/src/vl/dbio/vl_errfilter_dbio.h b/app/src/vl/dbio/vl_errfilter_dbio.h new file mode 100644 index 0000000..5ea6884 --- /dev/null +++ b/app/src/vl/dbio/vl_errfilter_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_errfilter_dbio.h - vl 모듈 DB 접근 copybook (vl_errfilter_dbio). 전체 대비 실패 검증 비율(bps) - FILTER 절 집계. + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_ERRFILTER_DBIO_H +#define VL_ERRFILTER_DBIO_H + +long vldb_error_filter(const char *bizdate); + +#endif /* VL_ERRFILTER_DBIO_H */ diff --git a/app/src/vl/dbio/vl_errfilter_dbio.pgc b/app/src/vl/dbio/vl_errfilter_dbio.pgc new file mode 100644 index 0000000..ebe3347 --- /dev/null +++ b/app/src/vl/dbio/vl_errfilter_dbio.pgc @@ -0,0 +1,22 @@ +/* + * vl_errfilter_dbio.pgc - vl 모듈 DB 접근 함수 (vl_errfilter_dbio), archived into libvldbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 전체 대비 실패 검증 비율(bps) - FILTER 절 집계. + */ +#include +#include +#include "vl_errfilter_dbio.h" + +long vldb_error_filter(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_err = 0, h_tot = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) FILTER (WHERE result_code > 0), count(*) + INTO :h_err, :h_tot FROM vl_check_log WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + if (h_tot <= 0) return 0; + return h_err * 10000 / h_tot; +} diff --git a/app/src/vl/dbio/vl_errgroup_dbio.h b/app/src/vl/dbio/vl_errgroup_dbio.h new file mode 100644 index 0000000..0e79e2a --- /dev/null +++ b/app/src/vl/dbio/vl_errgroup_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_errgroup_dbio.h - vl 모듈 DB 접근 copybook (vl_errgroup_dbio). 임계 이상 반복된 검증 에러코드 그룹 수 (GROUP BY HAVING 커서). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_ERRGROUP_DBIO_H +#define VL_ERRGROUP_DBIO_H + +long vldb_err_group_over(const char *bizdate, long minc); + +#endif /* VL_ERRGROUP_DBIO_H */ diff --git a/app/src/vl/dbio/vl_errgroup_dbio.pgc b/app/src/vl/dbio/vl_errgroup_dbio.pgc new file mode 100644 index 0000000..cc929a6 --- /dev/null +++ b/app/src/vl/dbio/vl_errgroup_dbio.pgc @@ -0,0 +1,33 @@ +/* + * vl_errgroup_dbio.pgc - vl 모듈 DB 접근 함수 (vl_errgroup_dbio), archived into libvldbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 임계 이상 반복된 검증 에러코드 그룹 수 (GROUP BY HAVING 커서). + */ +#include +#include +#include "vl_errgroup_dbio.h" + +/* 반복 임계(minc) 이상 발생한 에러코드(result_code) 그룹 수를 커서로 집계. */ +long vldb_err_group_over(const char *bizdate, long minc) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_min, h_rc, h_cnt, groups = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + h_min = minc; + EXEC SQL DECLARE veg CURSOR FOR + SELECT result_code, count(*) FROM vl_check_log + WHERE biz_date = :h_bizdate AND result_code > 0 + GROUP BY result_code HAVING count(*) >= :h_min; + EXEC SQL OPEN veg; + if (sqlca.sqlcode < 0) { userlog("vldb_err_group_over OPEN [%d]", sqlca.sqlcode); return -1; } + for (;;) { + EXEC SQL FETCH veg INTO :h_rc, :h_cnt; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE veg; return -1; } + groups++; + } + EXEC SQL CLOSE veg; + return groups; +} diff --git a/app/src/vl/dbio/vl_errgrp_batch.h b/app/src/vl/dbio/vl_errgrp_batch.h new file mode 100644 index 0000000..86beea9 --- /dev/null +++ b/app/src/vl/dbio/vl_errgrp_batch.h @@ -0,0 +1,10 @@ +/* + * vl_errgrp_batch.h - vl vl_errgrp_batch 배치 전용 카피북 (copybook). vl 반복 에러코드 그룹 집계 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by vl_errgrp_batch.pgc. + */ +#ifndef VL_ERRGRP_BATCH_H +#define VL_ERRGRP_BATCH_H + +typedef struct { long threshold; long groups; } vl_errgrp_rec_t; + +#endif /* VL_ERRGRP_BATCH_H */ diff --git a/app/src/vl/dbio/vl_errminmax_dbio.h b/app/src/vl/dbio/vl_errminmax_dbio.h new file mode 100644 index 0000000..4a172be --- /dev/null +++ b/app/src/vl/dbio/vl_errminmax_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_errminmax_dbio.h - vl 모듈 DB 접근 copybook (vl_errminmax_dbio). 당일 검증 에러코드 최대-최소 스프레드 (min/max). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_ERRMINMAX_DBIO_H +#define VL_ERRMINMAX_DBIO_H + +long vldb_err_spread(const char *bizdate); + +#endif /* VL_ERRMINMAX_DBIO_H */ diff --git a/app/src/vl/dbio/vl_errminmax_dbio.pgc b/app/src/vl/dbio/vl_errminmax_dbio.pgc new file mode 100644 index 0000000..42eb3bb --- /dev/null +++ b/app/src/vl/dbio/vl_errminmax_dbio.pgc @@ -0,0 +1,22 @@ +/* + * vl_errminmax_dbio.pgc - vl 모듈 DB 접근 함수 (vl_errminmax_dbio), archived into libvldbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 당일 검증 에러코드 최대-최소 스프레드 (min/max). + */ +#include +#include +#include "vl_errminmax_dbio.h" + +long vldb_err_spread(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_mn = 0, h_mx = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(min(result_code),0), coalesce(max(result_code),0) + INTO :h_mn, :h_mx FROM vl_check_log + WHERE biz_date = :h_bizdate AND result_code > 0; + if (sqlca.sqlcode < 0) return -1; + return h_mx - h_mn; +} diff --git a/app/src/vl/dbio/vl_inset_dbio.h b/app/src/vl/dbio/vl_inset_dbio.h new file mode 100644 index 0000000..ef51f34 --- /dev/null +++ b/app/src/vl/dbio/vl_inset_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_inset_dbio.h - vl 모듈 DB 접근 copybook (vl_inset_dbio). 미결 의심거래를 대상으로 남긴 검증로그 건수 (IN 서브셀렉트). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_INSET_DBIO_H +#define VL_INSET_DBIO_H + +long vldb_logged_targets(const char *bizdate); + +#endif /* VL_INSET_DBIO_H */ diff --git a/app/src/vl/dbio/vl_inset_dbio.pgc b/app/src/vl/dbio/vl_inset_dbio.pgc new file mode 100644 index 0000000..442d75c --- /dev/null +++ b/app/src/vl/dbio/vl_inset_dbio.pgc @@ -0,0 +1,22 @@ +/* + * vl_inset_dbio.pgc - vl 모듈 DB 접근 함수 (vl_inset_dbio), archived into libvldbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 미결 의심거래를 대상으로 남긴 검증로그 건수 (IN 서브셀렉트). + */ +#include +#include +#include "vl_inset_dbio.h" + +long vldb_logged_targets(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM vl_check_log + WHERE biz_date = :h_bizdate + AND target_id IN (SELECT purchase_id FROM vl_suspect WHERE status = 'OPEN'); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/vl/dbio/vl_logpurge_batch.h b/app/src/vl/dbio/vl_logpurge_batch.h new file mode 100644 index 0000000..ca8c561 --- /dev/null +++ b/app/src/vl/dbio/vl_logpurge_batch.h @@ -0,0 +1,11 @@ +/* + * vl_logpurge_batch.h - vl vl_logpurge_batch 배치 전용 카피북 (copybook). vl 정상 검증로그 보존기간 삭제 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by vl_logpurge_batch.pgc. + */ +#ifndef VL_LOGPURGE_BATCH_H +#define VL_LOGPURGE_BATCH_H + +typedef struct { long deleted; } vl_logpurge_rec_t; +#define VL_LOGPURGE_CUTOFF "2026-07-01" + +#endif /* VL_LOGPURGE_BATCH_H */ diff --git a/app/src/vl/dbio/vl_orphan_batch.h b/app/src/vl/dbio/vl_orphan_batch.h new file mode 100644 index 0000000..dedea44 --- /dev/null +++ b/app/src/vl/dbio/vl_orphan_batch.h @@ -0,0 +1,10 @@ +/* + * vl_orphan_batch.h - vl vl_orphan_batch 배치 전용 카피북 (copybook). vl 고아 의심거래 점검 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by vl_orphan_batch.pgc. + */ +#ifndef VL_ORPHAN_BATCH_H +#define VL_ORPHAN_BATCH_H + +typedef struct { long orphans; } vl_orphan_rec_t; + +#endif /* VL_ORPHAN_BATCH_H */ diff --git a/app/src/vl/dbio/vl_orphan_dbio.h b/app/src/vl/dbio/vl_orphan_dbio.h new file mode 100644 index 0000000..8ff6219 --- /dev/null +++ b/app/src/vl/dbio/vl_orphan_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_orphan_dbio.h - vl 모듈 DB 접근 copybook (vl_orphan_dbio). 검증 로그가 없는 고아 의심거래 건수 (NOT EXISTS). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_ORPHAN_DBIO_H +#define VL_ORPHAN_DBIO_H + +long vldb_orphan_suspect(const char *bizdate); + +#endif /* VL_ORPHAN_DBIO_H */ diff --git a/app/src/vl/dbio/vl_orphan_dbio.pgc b/app/src/vl/dbio/vl_orphan_dbio.pgc new file mode 100644 index 0000000..1b2e033 --- /dev/null +++ b/app/src/vl/dbio/vl_orphan_dbio.pgc @@ -0,0 +1,22 @@ +/* + * vl_orphan_dbio.pgc - vl 모듈 DB 접근 함수 (vl_orphan_dbio), archived into libvldbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 검증 로그가 없는 고아 의심거래 건수 (NOT EXISTS). + */ +#include +#include +#include "vl_orphan_dbio.h" + +long vldb_orphan_suspect(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT count(*) INTO :h_v FROM vl_suspect s + WHERE s.biz_date = :h_bizdate + AND NOT EXISTS (SELECT 1 FROM vl_check_log l WHERE l.target_id = s.purchase_id); + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/vl/dbio/vl_promote_batch.h b/app/src/vl/dbio/vl_promote_batch.h new file mode 100644 index 0000000..cf4703d --- /dev/null +++ b/app/src/vl/dbio/vl_promote_batch.h @@ -0,0 +1,10 @@ +/* + * vl_promote_batch.h - vl vl_promote_batch 배치 전용 카피북 (copybook). vl 실패 검증로그 의심거래 승격 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by vl_promote_batch.pgc. + */ +#ifndef VL_PROMOTE_BATCH_H +#define VL_PROMOTE_BATCH_H + +typedef struct { long promoted; } vl_promote_rec_t; + +#endif /* VL_PROMOTE_BATCH_H */ diff --git a/app/src/vl/dbio/vl_promote_dbio.h b/app/src/vl/dbio/vl_promote_dbio.h new file mode 100644 index 0000000..b1bd462 --- /dev/null +++ b/app/src/vl/dbio/vl_promote_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_promote_dbio.h - vl 모듈 DB 접근 copybook (vl_promote_dbio). 실패 검증로그(result_code>0)를 의심거래로 승격 (INSERT..SELECT). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_PROMOTE_DBIO_H +#define VL_PROMOTE_DBIO_H + +long vldb_promote_failed(const char *bizdate); + +#endif /* VL_PROMOTE_DBIO_H */ diff --git a/app/src/vl/dbio/vl_promote_dbio.pgc b/app/src/vl/dbio/vl_promote_dbio.pgc new file mode 100644 index 0000000..0f0ed75 --- /dev/null +++ b/app/src/vl/dbio/vl_promote_dbio.pgc @@ -0,0 +1,23 @@ +/* + * vl_promote_dbio.pgc - vl 모듈 DB 접근 함수 (vl_promote_dbio), archived into libvldbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 실패 검증로그(result_code>0)를 의심거래로 승격 (INSERT..SELECT). + */ +#include +#include +#include "vl_promote_dbio.h" + +long vldb_promote_failed(const char *bizdate) +{ + 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; + EXEC SQL INSERT INTO vl_suspect + (suspect_id, purchase_id, rule_code, severity, detail, biz_date) + SELECT nextval('vl_suspect_seq'), l.target_id, 'CHK_FAIL', 'WARN', l.detail, l.biz_date + FROM vl_check_log l + WHERE l.biz_date = :h_bizdate AND l.result_code > 0; + if (sqlca.sqlcode < 0) { userlog("vldb_promote_failed FAIL [%d]", sqlca.sqlcode); return -1; } + return (long) sqlca.sqlerrd[2]; +} diff --git a/app/src/vl/dbio/vl_purgelog_dbio.h b/app/src/vl/dbio/vl_purgelog_dbio.h new file mode 100644 index 0000000..e298479 --- /dev/null +++ b/app/src/vl/dbio/vl_purgelog_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_purgelog_dbio.h - vl 모듈 DB 접근 copybook (vl_purgelog_dbio). 기준일 이전 정상(result_code=0) 검증로그 삭제 후 삭제건수 반환 (DELETE 범위). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_PURGELOG_DBIO_H +#define VL_PURGELOG_DBIO_H + +long vldb_purge_checklog(const char *cutoff); + +#endif /* VL_PURGELOG_DBIO_H */ diff --git a/app/src/vl/dbio/vl_purgelog_dbio.pgc b/app/src/vl/dbio/vl_purgelog_dbio.pgc new file mode 100644 index 0000000..9bd6fc7 --- /dev/null +++ b/app/src/vl/dbio/vl_purgelog_dbio.pgc @@ -0,0 +1,20 @@ +/* + * vl_purgelog_dbio.pgc - vl 모듈 DB 접근 함수 (vl_purgelog_dbio), archived into libvldbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 기준일 이전 정상(result_code=0) 검증로그 삭제 후 삭제건수 반환 (DELETE 범위). + */ +#include +#include +#include "vl_purgelog_dbio.h" + +long vldb_purge_checklog(const char *cutoff) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_cut[16]; + EXEC SQL END DECLARE SECTION; + strncpy(h_cut, cutoff, sizeof(h_cut)-1); h_cut[sizeof(h_cut)-1] = 0; + EXEC SQL DELETE FROM vl_check_log + WHERE biz_date < :h_cut AND result_code = 0; + if (sqlca.sqlcode < 0) { userlog("vldb_purge_checklog FAIL [%d]", sqlca.sqlcode); return -1; } + return (long) sqlca.sqlerrd[2]; +} diff --git a/app/src/vl/dbio/vl_rank_dbio.h b/app/src/vl/dbio/vl_rank_dbio.h new file mode 100644 index 0000000..fb899d2 --- /dev/null +++ b/app/src/vl/dbio/vl_rank_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_rank_dbio.h - vl 모듈 DB 접근 copybook (vl_rank_dbio). 심각도별 최초(랭크1) 의심거래 수 (ROW_NUMBER 윈도우 커서). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_RANK_DBIO_H +#define VL_RANK_DBIO_H + +long vldb_rank_suspects(const char *bizdate); + +#endif /* VL_RANK_DBIO_H */ diff --git a/app/src/vl/dbio/vl_rank_dbio.pgc b/app/src/vl/dbio/vl_rank_dbio.pgc new file mode 100644 index 0000000..16c834b --- /dev/null +++ b/app/src/vl/dbio/vl_rank_dbio.pgc @@ -0,0 +1,31 @@ +/* + * vl_rank_dbio.pgc - vl 모듈 DB 접근 함수 (vl_rank_dbio), archived into libvldbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 심각도별 최초(랭크1) 의심거래 수 (ROW_NUMBER 윈도우 커서). + */ +#include +#include +#include "vl_rank_dbio.h" + +long vldb_rank_suspects(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_sid, h_rn, firsts = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL DECLARE vrk CURSOR FOR + SELECT suspect_id, + ROW_NUMBER() OVER (PARTITION BY severity ORDER BY suspect_id) + FROM vl_suspect WHERE biz_date = :h_bizdate; + EXEC SQL OPEN vrk; + if (sqlca.sqlcode < 0) return -1; + for (;;) { + EXEC SQL FETCH vrk INTO :h_sid, :h_rn; + if (sqlca.sqlcode == 100) break; + if (sqlca.sqlcode < 0) { EXEC SQL CLOSE vrk; return -1; } + if (h_rn == 1) firsts++; + } + EXEC SQL CLOSE vrk; + return firsts; +} diff --git a/app/src/vl/dbio/vl_rankscan_batch.h b/app/src/vl/dbio/vl_rankscan_batch.h new file mode 100644 index 0000000..741283d --- /dev/null +++ b/app/src/vl/dbio/vl_rankscan_batch.h @@ -0,0 +1,10 @@ +/* + * vl_rankscan_batch.h - vl vl_rankscan_batch 배치 전용 카피북 (copybook). vl 심각도별 최초 의심거래 랭크 스캔 배치 + * 배치 집계 결과 레코드 + 기본 영업일 상수. Included by vl_rankscan_batch.pgc. + */ +#ifndef VL_RANKSCAN_BATCH_H +#define VL_RANKSCAN_BATCH_H + +typedef struct { long scanned; long firsts; } vl_rankscan_rec_t; + +#endif /* VL_RANKSCAN_BATCH_H */ diff --git a/app/src/vl/dbio/vl_rulejoin_dbio.h b/app/src/vl/dbio/vl_rulejoin_dbio.h new file mode 100644 index 0000000..39567e4 --- /dev/null +++ b/app/src/vl/dbio/vl_rulejoin_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_rulejoin_dbio.h - vl 모듈 DB 접근 copybook (vl_rulejoin_dbio). 미결 의심거래에 걸린 규칙 임계값 합 (vl_suspect JOIN vl_rule). + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_RULEJOIN_DBIO_H +#define VL_RULEJOIN_DBIO_H + +long vldb_open_threshold_sum(const char *bizdate); + +#endif /* VL_RULEJOIN_DBIO_H */ diff --git a/app/src/vl/dbio/vl_rulejoin_dbio.pgc b/app/src/vl/dbio/vl_rulejoin_dbio.pgc new file mode 100644 index 0000000..80c0781 --- /dev/null +++ b/app/src/vl/dbio/vl_rulejoin_dbio.pgc @@ -0,0 +1,22 @@ +/* + * vl_rulejoin_dbio.pgc - vl 모듈 DB 접근 함수 (vl_rulejoin_dbio), archived into libvldbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 미결 의심거래에 걸린 규칙 임계값 합 (vl_suspect JOIN vl_rule). + */ +#include +#include +#include "vl_rulejoin_dbio.h" + +long vldb_open_threshold_sum(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(r.threshold),0) INTO :h_v + FROM vl_suspect s JOIN vl_rule r ON r.rule_code = s.rule_code + WHERE s.biz_date = :h_bizdate AND s.status = 'OPEN'; + if (sqlca.sqlcode < 0) return -1; + return h_v; +} diff --git a/app/src/vl/dbio/vl_sevcase_dbio.h b/app/src/vl/dbio/vl_sevcase_dbio.h new file mode 100644 index 0000000..84be897 --- /dev/null +++ b/app/src/vl/dbio/vl_sevcase_dbio.h @@ -0,0 +1,11 @@ +/* + * vl_sevcase_dbio.h - vl 모듈 DB 접근 copybook (vl_sevcase_dbio). 심각도 가중합 (CRIT8/ERROR4/WARN2/그외1) - CASE 식. + * ECPG functions; run on the caller's XA branch (no EXEC SQL CONNECT). + * Return convention: >=0 ok, -1 = SQL error. + */ +#ifndef VL_SEVCASE_DBIO_H +#define VL_SEVCASE_DBIO_H + +long vldb_severity_weighted(const char *bizdate); + +#endif /* VL_SEVCASE_DBIO_H */ diff --git a/app/src/vl/dbio/vl_sevcase_dbio.pgc b/app/src/vl/dbio/vl_sevcase_dbio.pgc new file mode 100644 index 0000000..3f296fa --- /dev/null +++ b/app/src/vl/dbio/vl_sevcase_dbio.pgc @@ -0,0 +1,25 @@ +/* + * vl_sevcase_dbio.pgc - vl 모듈 DB 접근 함수 (vl_sevcase_dbio), archived into libvldbio.a. + * ECPG (EXEC SQL) functions; run on the caller's XA branch. No EXEC SQL CONNECT. + * 심각도 가중합 (CRIT8/ERROR4/WARN2/그외1) - CASE 식. + */ +#include +#include +#include "vl_sevcase_dbio.h" + +long vldb_severity_weighted(const char *bizdate) +{ + EXEC SQL BEGIN DECLARE SECTION; + char h_bizdate[16]; + long h_v = 0; + EXEC SQL END DECLARE SECTION; + strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0; + EXEC SQL SELECT coalesce(sum(CASE severity + WHEN 'CRIT' THEN 8 + WHEN 'ERROR' THEN 4 + WHEN 'WARN' THEN 2 + ELSE 1 END), 0) + INTO :h_v FROM vl_suspect WHERE biz_date = :h_bizdate; + if (sqlca.sqlcode < 0) return -1; + return h_v; +}