--==========================================================================
--  PKG_SETTLE.sql  -  정산 처리 패키지 (As-Is 원본, Oracle PL/SQL)
----------------------------------------------------------------------------
--  대상 : BC 매입시스템 / 정산 도메인 공용 패키지 PKG_SETTLE
--  DBMS : Oracle 19c
--  ※ 이 파일은 As-Is 역사 원본이다. ACQUIRE.pc 의
--     "pkg_settle.acquire_post(:h_pid,:h_merch,:h_amount,:h_fee,:h_net)"
--     호출 대상이며, 현행 오픈스택 이식본에서는 SETTLE/ST_MDR 서비스
--     (app/src/st/svc/*) 로 인라인 전개되었다. 컴파일 대상 아님(빌드 제외).
----------------------------------------------------------------------------
--  변경이력
--   1998.11.20  KIM K.H   최초작성 (매입시점 정산예정 적재)
--   2005.03.14  PARK J.S  가맹점 일단위 묶음정산 settle_merchant_day 추가
--   2012.09.01  LEE S.W   정산확정/취소 프로시저 분리, 원장연계 pkg_ledger
--   2019.09.01  CHOI H.W  Oracle 11g -> 19c, 예외코드 -20500 대역 정리
--==========================================================================

CREATE OR REPLACE PACKAGE pkg_settle AS

    -- 정산 상태 상수
    C_PENDING   CONSTANT VARCHAR2(1) := 'P';   -- 정산예정
    C_SETTLED   CONSTANT VARCHAR2(1) := 'S';   -- 정산확정
    C_CANCELED  CONSTANT VARCHAR2(1) := 'X';   -- 정산취소

    -- 사용자정의 예외 (-20500 대역)
    e_bad_amount    EXCEPTION;
    e_no_purchase   EXCEPTION;
    e_already_set   EXCEPTION;
    PRAGMA EXCEPTION_INIT(e_bad_amount,  -20501);
    PRAGMA EXCEPTION_INIT(e_no_purchase, -20502);
    PRAGMA EXCEPTION_INIT(e_already_set, -20503);

    -- ACQUIRE.pc 가 매입확정 직후 호출: 정산예정행 + 원장기표
    PROCEDURE acquire_post(p_pid    IN NUMBER,
                           p_merch  IN VARCHAR2,
                           p_amount IN NUMBER,
                           p_fee    IN NUMBER,
                           p_net    IN NUMBER);

    -- 가맹점 일단위 묶음정산 (커서 집계 -> settlement 확정)
    PROCEDURE settle_merchant_day(p_merch   IN  VARCHAR2,
                                  p_bizdate IN  DATE,
                                  o_sid     OUT NUMBER,
                                  o_net     OUT NUMBER);

    -- 정산 확정 (P -> S) : 원장 정산기표 연계
    PROCEDURE confirm_settlement(p_sid IN NUMBER);

    -- 정산 취소 (S/P -> X) : 역기표
    PROCEDURE cancel_settlement(p_sid IN NUMBER, p_reason IN VARCHAR2);

END pkg_settle;
/

CREATE OR REPLACE PACKAGE BODY pkg_settle AS

    ----------------------------------------------------------------------
    -- acquire_post : 매입 1건 정산예정 적재 + MDR/정산 원장기표
    ----------------------------------------------------------------------
    PROCEDURE acquire_post(p_pid    IN NUMBER,
                           p_merch  IN VARCHAR2,
                           p_amount IN NUMBER,
                           p_fee    IN NUMBER,
                           p_net    IN NUMBER)
    IS
        v_sid       settlement.settlement_id%TYPE;
        v_bizdate   purchase.biz_date%TYPE;
        v_cnt       PLS_INTEGER;
    BEGIN
        IF p_amount <= 0 OR p_net < 0 THEN
            RAISE_APPLICATION_ERROR(-20501,
                '정산 금액 오류 amount='||p_amount||' net='||p_net);
        END IF;

        -- 매입행 존재/영업일 확인
        BEGIN
            SELECT biz_date INTO v_bizdate
              FROM purchase
             WHERE purchase_id = p_pid;
        EXCEPTION
            WHEN NO_DATA_FOUND THEN
                RAISE_APPLICATION_ERROR(-20502,
                    '매입원장 없음 pid='||p_pid);
        END;

        -- 중복 정산예정 방지
        SELECT COUNT(*) INTO v_cnt
          FROM settlement
         WHERE purchase_id = p_pid
           AND status     <> C_CANCELED;
        IF v_cnt > 0 THEN
            RAISE_APPLICATION_ERROR(-20503,
                '이미 정산예정 존재 pid='||p_pid);
        END IF;

        v_sid := settlement_seq.NEXTVAL;
        INSERT INTO settlement
               (settlement_id, purchase_id, merchant_id,
                net, fee, biz_date, status, created_at)
        VALUES (v_sid, p_pid, p_merch,
                p_net, p_fee, v_bizdate, C_PENDING, SYSDATE);

        -- 매입시점 원장기표 : 수수료(MDR) + 정산부채
        pkg_ledger.post_voucher(p_pid, 'MDR',    p_fee, v_bizdate);
        pkg_ledger.post_voucher(p_pid, 'SETTLE', p_net, v_bizdate);
    END acquire_post;

    ----------------------------------------------------------------------
    -- settle_merchant_day : 가맹점 당일 정산예정 묶음 -> 확정
    ----------------------------------------------------------------------
    PROCEDURE settle_merchant_day(p_merch   IN  VARCHAR2,
                                  p_bizdate IN  DATE,
                                  o_sid     OUT NUMBER,
                                  o_net     OUT NUMBER)
    IS
        CURSOR c_pend IS
            SELECT s.settlement_id, s.purchase_id, s.net
              FROM settlement s
             WHERE s.merchant_id = p_merch
               AND s.biz_date    = p_bizdate
               AND s.status      = C_PENDING
             FOR UPDATE OF s.status;
        r_pend  c_pend%ROWTYPE;
        v_sum   NUMBER := 0;
        v_cnt   PLS_INTEGER := 0;
    BEGIN
        OPEN c_pend;
        LOOP
            FETCH c_pend INTO r_pend;
            EXIT WHEN c_pend%NOTFOUND;
            v_sum := v_sum + NVL(r_pend.net, 0);
            v_cnt := v_cnt + 1;
            UPDATE settlement
               SET status = C_SETTLED, settled_at = SYSDATE
             WHERE CURRENT OF c_pend;
        END LOOP;
        CLOSE c_pend;

        IF v_cnt = 0 THEN
            RAISE_APPLICATION_ERROR(-20502,
                '정산예정 없음 merch='||p_merch||' date='||TO_CHAR(p_bizdate,'YYYYMMDD'));
        END IF;

        o_sid := settle_batch_seq.NEXTVAL;
        o_net := v_sum;
        INSERT INTO settle_batch
               (batch_id, merchant_id, biz_date, txn_count, net_amount, status, created_at)
        VALUES (o_sid, p_merch, p_bizdate, v_cnt, v_sum, C_SETTLED, SYSDATE);
    END settle_merchant_day;

    ----------------------------------------------------------------------
    -- confirm_settlement : 개별 정산예정 확정
    ----------------------------------------------------------------------
    PROCEDURE confirm_settlement(p_sid IN NUMBER)
    IS
        r_set   settlement%ROWTYPE;
    BEGIN
        SELECT * INTO r_set
          FROM settlement
         WHERE settlement_id = p_sid
           FOR UPDATE OF status;

        IF r_set.status = C_SETTLED THEN
            RAISE_APPLICATION_ERROR(-20503, '이미 확정 sid='||p_sid);
        ELSIF r_set.status = C_CANCELED THEN
            RAISE_APPLICATION_ERROR(-20503, '취소건 확정불가 sid='||p_sid);
        END IF;

        UPDATE settlement
           SET status = C_SETTLED, settled_at = SYSDATE
         WHERE settlement_id = p_sid;

        pkg_ledger.post_voucher(r_set.purchase_id, 'PAYOUT',
                                r_set.net, r_set.biz_date);
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            RAISE_APPLICATION_ERROR(-20502, '정산건 없음 sid='||p_sid);
    END confirm_settlement;

    ----------------------------------------------------------------------
    -- cancel_settlement : 정산 취소 + 역기표
    ----------------------------------------------------------------------
    PROCEDURE cancel_settlement(p_sid IN NUMBER, p_reason IN VARCHAR2)
    IS
        v_pid   settlement.purchase_id%TYPE;
        v_net   settlement.net%TYPE;
        v_bd    settlement.biz_date%TYPE;
        v_st    settlement.status%TYPE;
    BEGIN
        SELECT purchase_id, net, biz_date, status
          INTO v_pid, v_net, v_bd, v_st
          FROM settlement
         WHERE settlement_id = p_sid
           FOR UPDATE OF status;

        IF v_st = C_CANCELED THEN
            RAISE_APPLICATION_ERROR(-20503, '이미 취소 sid='||p_sid);
        END IF;

        UPDATE settlement
           SET status      = C_CANCELED,
               cancel_rsn  = p_reason,
               canceled_at = SYSDATE
         WHERE settlement_id = p_sid;

        -- 역기표 (음수 정산)
        pkg_ledger.post_voucher(v_pid, 'SETTLE_REV', -v_net, v_bd);
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            RAISE_APPLICATION_ERROR(-20502, '정산건 없음 sid='||p_sid);
    END cancel_settlement;

END pkg_settle;
/
