/*
 * PAY_RECLAIM.pgc - py (지급) 모듈 온라인 서비스 PAY_RECLAIM.
 * 과지급 환수: 지급완료(P) 건에서 요청 환수액(T_AMOUNT, net 한도)만큼을
 * 지급계좌에서 차감(음수 account_add)하고 py_ledger 에 음수 RECLAIM 기표.
 * 처리 구성: 입력검증 -> 마스터/정책 조회 -> 업무규칙 분기 -> 본처리 ->
 *            감사이력 적재 -> 집계 갱신 -> 예외보상 -> 응답 -> 로깅 [fee].
 * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by py_svr.
 */
#include "PAY_RECLAIM.h"

void PAY_RECLAIM(TPSVCINFO *p)
{
    UBFH *b = (UBFH *)p->data;
    EXEC SQL BEGIN DECLARE SECTION;
    long h_amount;
    long h_id1;
    char h_wst[8];
    long h_wamt;
    long h_wfee;
    long h_wnet;
    char h_wmerch[64];
    char h_wacct[64];
    long h_wmdr;
    long h_wnew;
    long h_wdelta;
    long h_waff;
    long h_wadj;
    char h_wdt[16];
    char h_wdate[16];
    char h_pval[32];
    long h_thr;
    char h_lvl[4];
    char h_amsg[256];
    long h_wa1;
    long h_wa2;
    long h_wa3;
    long h_wz1;
    long h_wz2;
    long h_wz3;
    EXEC SQL END DECLARE SECTION;

    /* 1) 입력 검증부 - 모든 입력 필드의 필수/범위/형식 검사 */
    h_amount = getl(b, T_AMOUNT);
    if (h_amount < 0) {
        setl(b, T_RC, 11);
        Bchg(b, T_MSG, 0, "금액 항목은 음수가 될 수 없음", 0L);
        FAIL(b);
    }
    h_id1 = getl(b, T_ID1);
    if (h_id1 <= 0) {
        setl(b, T_RC, 12);
        Bchg(b, T_MSG, 0, "식별자(ID)는 1 이상이어야 함", 0L);
        FAIL(b);
    }
    /* 2) 마스터 조회부 - 지급건 + 가맹점 계약요율 (LEFT JOIN) */
    EXEC SQL SELECT p.status, p.amount, p.fee, p.net, p.merchant_id,
                    p.account_no, p.biz_date, coalesce(m.mdr_bps, 25)
        INTO :h_wst, :h_wamt, :h_wfee, :h_wnet, :h_wmerch,
             :h_wacct, :h_wdt, :h_wmdr
        FROM py_payout p
        LEFT JOIN merchant m ON m.merchant_id = p.merchant_id
        WHERE p.payout_id = :h_id1;
    if (sqlca.sqlcode == 100) {
        setl(b, T_RC, 40);
        Bchg(b, T_MSG, 0, "대상 지급건 없음", 0L);
        FAIL(b);
    }
    if (sqlca.sqlcode < 0) {
        setl(b, T_RC, 41);
        Bchg(b, T_MSG, 0, "지급건/가맹점 조인 조회 오류", 0L);
        FAIL(b);
    }
    /* 기 적재된 조정 합계 확인 */
    h_wadj = pydb_adjust_delta_sum(h_id1);
    if (h_wadj < 0) h_wadj = 0;
    EXEC SQL SELECT to_char(current_date, 'YYYY-MM-DD') INTO :h_wdate;
    if (sqlca.sqlcode < 0) strcpy(h_wdate, "1970-01-01");
    userlog("PAY_RECLAIM 진입: payout=%ld status=%s fee=%ld net=%ld 조정누계=%ld",
            h_id1, h_wst, h_wfee, h_wnet, h_wadj);
    /* 3) 업무규칙 분기부 - 수수료/순액 재산정 (3분기) */
    if (strcmp(h_wst, "P") == 0 || strcmp(h_wst, "X") == 0 ||
        strcmp(h_wst, "C") == 0) {
        setl(b, T_RC, 42);
        Bchg(b, T_MSG, 0, "종결된 지급건은 수수료/순액 조정 불가", 0L);
        FAIL(b);
    }
    if (h_amount >= h_wnet) {
        h_wdelta = h_wnet;
    } else if (h_amount <= 0) {
        setl(b, T_RC, 13);
        Bchg(b, T_MSG, 0, "상계/환수 금액은 1 이상이어야 함", 0L);
        FAIL(b);
    } else {
        h_wdelta = h_amount;
    }
    h_wnew = h_wfee;
    strcpy(h_pval, "1000");
    EXEC SQL SELECT coalesce(param_val, :h_pval) INTO :h_pval
        FROM cm_param WHERE param_key = 'pay_reclaim_bps_cap';
    if (sqlca.sqlcode < 0) {
        setl(b, T_RC, 91);
        Bchg(b, T_MSG, 0, "정책 파라미터(cm_param) 조회 실패", 0L);
        FAIL(b);
    }
    h_thr = atol(h_pval);
    if (h_thr <= 0) h_thr = 1000;
    if (h_wnew > acq_fee(h_wamt, h_thr)) {
        userlog("PAY_RECLAIM 수수료 상한정책 초과: fee=%ld cap_bps=%ld", h_wnew, h_thr);
    }
    /* 4) 본처리부 - fee/net 재계산 및 갱신 */
    h_wnet = h_wnet - h_wdelta;
    if (h_wnet < 0) h_wnet = 0;
    EXEC SQL UPDATE py_payout SET net = :h_wnet, updated_at = now()
        WHERE payout_id = :h_id1 AND status IN ('R','A','H');
    if (sqlca.sqlcode < 0) {
        setl(b, T_RC, 50);
        Bchg(b, T_MSG, 0, "수수료/순액 갱신 실패", 0L);
        FAIL(b);
    }
    h_waff = sqlca.sqlerrd[2];
    if (h_waff == 0) {
        setl(b, T_RC, 4);
        Bchg(b, T_MSG, 0, "갱신 대상 없음 - 처리 도중 상태 변동", 0L);
        FAIL(b);
    }
    /* 5) 이력 적재부 - 조정 원장(py_adjust) 기록 */
    if (pydb_insert_adjust(h_id1, h_wdelta, "수수료/순액 조정", h_wdt) < 0) {
        setl(b, T_RC, 55);
        Bchg(b, T_MSG, 0, "조정 원장(py_adjust) 기록 실패", 0L);
        FAIL(b);
    }
    strcpy(h_lvl, "I");
    snprintf(h_amsg, sizeof(h_amsg), "%s payout=%ld fee %ld->%ld net=%ld delta=%ld", "PAY_RECLAIM", h_id1, h_wfee, h_wnew, h_wnet, h_wdelta);
    EXEC SQL INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date)
        VALUES (nextval('cm_log_seq'), 'PAY_RECLAIM', :h_lvl, :h_amsg, :h_wdate);
    if (sqlca.sqlcode < 0) {
        setl(b, T_RC, 92);
        Bchg(b, T_MSG, 0, "감사로그(cm_log) 적재 실패 - 처리 롤백", 0L);
        FAIL(b);
    }
    /* 6) 집계/요약 갱신부 - 가맹점 당일 수수료/조정 누계 재집계 */
    EXEC SQL SELECT count(*), coalesce(sum(fee),0) INTO :h_wa1, :h_wa2
        FROM py_payout WHERE merchant_id = :h_wmerch AND biz_date = :h_wdt;
    if (sqlca.sqlcode < 0) {
        setl(b, T_RC, 61);
        Bchg(b, T_MSG, 0, "수수료/조정 누계 재집계 실패", 0L);
        FAIL(b);
    }
    EXEC SQL SELECT count(*) INTO :h_wa3 FROM py_adjust
        WHERE payout_id = :h_id1;
    if (sqlca.sqlcode < 0) h_wa3 = 0;
    if (h_wa1 > 0 && h_wa3 > h_wa1) {
        userlog("%s 조정 이력 과다: 기준=%ld 실적=%ld", "PAY_RECLAIM", h_wa1, h_wa3);
    }
    /* 8) 응답부 */
    setl(b, T_RC, 0);
    setl(b, T_AMT1, h_wnet);
    Bchg(b, T_STR1, 0, h_wacct, 0L);
    Bchg(b, T_MSG, 0, "수수료/순액 조정 완료", 0L);
    userlog("PAY_RECLAIM 완료: payout=%ld fee=%ld net=%ld", h_id1, h_wnew, h_wnet);
    /* 7) 예외/보상 점검부 - 당일 지급 원장 사후 정합 확인 */
    EXEC SQL SELECT count(*), coalesce(sum(net),0) INTO :h_wz1, :h_wz2
        FROM py_payout WHERE biz_date = :h_wdate;
    if (sqlca.sqlcode < 0) { h_wz1 = 0; h_wz2 = 0; }
    EXEC SQL SELECT count(*) INTO :h_wz3 FROM py_payout
        WHERE biz_date = :h_wdate AND amount - fee <> net;
    if (sqlca.sqlcode < 0) h_wz3 = 0;
    if (h_wz3 > 0) {
        userlog("%s 순액 정합 위반 감지: %ld건 / 당일 %ld건",
                "PAY_RECLAIM", h_wz3, h_wz1);
    }
    if (h_wz2 < 0) {
        setl(b, T_RC, 93);
        Bchg(b, T_MSG, 0, "당일 지급 순액 합계가 음수 - 원장 정합 오류", 0L);
        FAIL(b);
    }

    /* ===== 8b) 업무 체인 호출부: PAY_RECLAIM -> LG_REVERSE (지급환수-원장역분개) =====
     * XA 규율: tpcall 로 부르는 LG_REVERSE 는 별도 XA 브랜치라 PAY_RECLAIM 가 방금
     * INSERT/UPDATE 한 미커밋 행을 볼 수 없다. 그러므로 체인에 필요한 값은 모두
     * UBF 전문(T_*)에 실어 넘기고, 하위는 전문값을 우선 사용한다(owner-writes).
     * 하위 호출 실패는 같은 글로벌 트랜잭션이므로 FAIL 로 전체 롤백한다(보상 불필요). */
    {
        long cx_rlen = 0;
        long cx_pid  = getl(b, T_PURCHASE_ID);
        long cx_sid  = getl(b, T_SETTLE_ID);
        long cx_amt  = getl(b, T_AMOUNT);
        long cx_fee  = getl(b, T_FEE);
        long cx_net  = getl(b, T_NET);
        long cx_cnt  = getl(b, T_COUNT);
        long cx_grs  = getl(b, T_GROSS);
        long cx_samt, cx_sfee, cx_snet;
        char cx_bd[16], cx_mid[64], cx_sts[16];

        gets_(b, T_BIZDATE,  cx_bd,  sizeof(cx_bd));
        gets_(b, T_MERCHANT, cx_mid, sizeof(cx_mid));
        gets_(b, T_STATUS,   cx_sts, sizeof(cx_sts));
        if (strlen(cx_bd) != 10) strcpy(cx_bd, "2026-07-20");
        if (cx_pid < 0) cx_pid = 0;
        if (cx_sid < 0) cx_sid = 0;
        cx_samt = (cx_amt < 0) ? -cx_amt : cx_amt;
        cx_sfee = (cx_fee < 0) ? -cx_fee : cx_fee;
        cx_snet = (cx_net < 0) ?        0 : cx_net;

        /* 하위가 필요로 하는 값을 전문에 적재 */
        Bchg(b, T_BIZDATE, 0, cx_bd, 0L);
        if (cx_mid[0] != 0) Bchg(b, T_MERCHANT, 0, cx_mid, 0L);
        setl(b, T_PURCHASE_ID, cx_pid);
        setl(b, T_SETTLE_ID,   cx_sid);
        setl(b, T_AMOUNT,      cx_samt);
        setl(b, T_FEE,         cx_sfee);
        setl(b, T_NET,         cx_snet);
        Bchg(b, T_SVCNAME, 0, "PAY_RECLAIM", 0L);
        Bchg(b, T_REASON,  0, "지급환수-원장역분개", 0L);
        userlog("[PAY_RECLAIM] 지급환수-원장역분개: 체인 호출 -> LG_REVERSE pid=%ld sid=%ld amt=%ld biz=%s",
                cx_pid, cx_sid, cx_samt, cx_bd);
        if (tpcall("LG_REVERSE", (char *)b, 0L, (char **)&b, &cx_rlen, 0L) < 0) {
            userlog("[PAY_RECLAIM] tpcall(LG_REVERSE) 실패: %s", tpstrerror(tperrno));
            setl(b, T_RC, 8818);
            Bchg(b, T_MSG, 0, "지급환수 역분개(LG_REVERSE) 실패", 0L);
            FAIL(b);
        }
        /* 하위 응답 수신 — b 가 재할당될 수 있으므로 이후 Bget/Bchg 는 갱신된 b 사용.
         * PAY_RECLAIM 자신의 응답 계약(출력 필드)은 하위 응답에 덮이지 않도록 복원한다. */
        setl(b, T_PURCHASE_ID, cx_pid);
        setl(b, T_AMOUNT,      cx_amt);
        setl(b, T_FEE,         cx_fee);
        setl(b, T_NET,         cx_net);
        setl(b, T_COUNT,       cx_cnt);
        setl(b, T_GROSS,       cx_grs);
        if (cx_sid > 0)     setl(b, T_SETTLE_ID, cx_sid);
        if (cx_sts[0] != 0) Bchg(b, T_STATUS, 0, cx_sts, 0L);
        Bchg(b, T_SVCNAME, 0, "PAY_RECLAIM", 0L);
        setl(b, T_RC, 0);
        userlog("[PAY_RECLAIM] 지급환수-원장역분개: 체인 완료 <- LG_REVERSE pid=%ld", cx_pid);
    }

    /* ===== 8c) 업무 체인 호출부: PAY_RECLAIM -> ST_ADJUST (지급환수-정산조정) =====
     * XA 규율: tpcall 로 부르는 ST_ADJUST 는 별도 XA 브랜치라 PAY_RECLAIM 가 방금
     * INSERT/UPDATE 한 미커밋 행을 볼 수 없다. 그러므로 체인에 필요한 값은 모두
     * UBF 전문(T_*)에 실어 넘기고, 하위는 전문값을 우선 사용한다(owner-writes).
     * 하위 호출 실패는 같은 글로벌 트랜잭션이므로 FAIL 로 전체 롤백한다(보상 불필요). */
    {
        long cx_rlen = 0;
        long cx_pid  = getl(b, T_PURCHASE_ID);
        long cx_sid  = getl(b, T_SETTLE_ID);
        long cx_amt  = getl(b, T_AMOUNT);
        long cx_fee  = getl(b, T_FEE);
        long cx_net  = getl(b, T_NET);
        long cx_cnt  = getl(b, T_COUNT);
        long cx_grs  = getl(b, T_GROSS);
        long cx_samt, cx_sfee, cx_snet;
        char cx_bd[16], cx_mid[64], cx_sts[16];

        gets_(b, T_BIZDATE,  cx_bd,  sizeof(cx_bd));
        gets_(b, T_MERCHANT, cx_mid, sizeof(cx_mid));
        gets_(b, T_STATUS,   cx_sts, sizeof(cx_sts));
        if (strlen(cx_bd) != 10) strcpy(cx_bd, "2026-07-20");
        if (cx_pid < 0) cx_pid = 0;
        if (cx_sid < 0) cx_sid = 0;
        cx_samt = (cx_amt < 0) ? -cx_amt : cx_amt;
        cx_sfee = (cx_fee < 0) ? -cx_fee : cx_fee;
        cx_snet = (cx_net < 0) ?        0 : cx_net;

        /* 하위가 필요로 하는 값을 전문에 적재 */
        Bchg(b, T_BIZDATE, 0, cx_bd, 0L);
        if (cx_mid[0] != 0) Bchg(b, T_MERCHANT, 0, cx_mid, 0L);
        setl(b, T_PURCHASE_ID, cx_pid);
        setl(b, T_SETTLE_ID,   cx_sid);
        setl(b, T_AMOUNT,      cx_samt);
        setl(b, T_FEE,         cx_sfee);
        setl(b, T_NET,         cx_snet);
        Bchg(b, T_SVCNAME, 0, "PAY_RECLAIM", 0L);
        Bchg(b, T_REASON,  0, "지급환수-정산조정", 0L);

        {
            long cx_dl = getl(b, T_DELTA);
            if (cx_dl > 100000000L || cx_dl < -100000000L) setl(b, T_DELTA, 0);
        }
        userlog("[PAY_RECLAIM] 지급환수-정산조정: 체인 호출 -> ST_ADJUST pid=%ld sid=%ld amt=%ld biz=%s",
                cx_pid, cx_sid, cx_samt, cx_bd);
        if (tpcall("ST_ADJUST", (char *)b, 0L, (char **)&b, &cx_rlen, 0L) < 0) {
            userlog("[PAY_RECLAIM] tpcall(ST_ADJUST) 실패: %s", tpstrerror(tperrno));
            setl(b, T_RC, 8819);
            Bchg(b, T_MSG, 0, "지급환수 정산조정(ST_ADJUST) 실패", 0L);
            FAIL(b);
        }
        /* 하위 응답 수신 — b 가 재할당될 수 있으므로 이후 Bget/Bchg 는 갱신된 b 사용.
         * PAY_RECLAIM 자신의 응답 계약(출력 필드)은 하위 응답에 덮이지 않도록 복원한다. */
        setl(b, T_PURCHASE_ID, cx_pid);
        setl(b, T_AMOUNT,      cx_amt);
        setl(b, T_FEE,         cx_fee);
        setl(b, T_NET,         cx_net);
        setl(b, T_COUNT,       cx_cnt);
        setl(b, T_GROSS,       cx_grs);
        if (cx_sid > 0)     setl(b, T_SETTLE_ID, cx_sid);
        if (cx_sts[0] != 0) Bchg(b, T_STATUS, 0, cx_sts, 0L);
        Bchg(b, T_SVCNAME, 0, "PAY_RECLAIM", 0L);
        setl(b, T_RC, 0);
        userlog("[PAY_RECLAIM] 지급환수-정산조정: 체인 완료 <- ST_ADJUST pid=%ld", cx_pid);
    }

    OK(b);
}
