--==========================================================================
--  PKG_LEDGER.sql  -  원장 복식부기 기표 패키지 (As-Is 원본, Oracle PL/SQL)
----------------------------------------------------------------------------
--  대상 : BC 매입시스템 / 회계 원장 도메인 공용 패키지 PKG_LEDGER
--  DBMS : Oracle 19c
--  ※ 이 파일은 As-Is 역사 원본이다. pkg_settle 및 ACQUIRE/SETTLE/ST_MDR
--     계열이 호출하는 복식부기(차/대변) 기표 엔진이다. 현행 오픈스택
--     이식본에서는 ledger 단식 INSERT (app/src/st/svc/*, LG_* 서비스) 로
--     단순화되었다. 컴파일 대상 아님(빌드 제외).
----------------------------------------------------------------------------
--  변경이력
--   1998.12.05  KIM K.H   최초작성 (전표 헤더/명세 복식부기 기표)
--   2007.06.18  PARK J.S  전표유형->계정 매핑 DECODE 테이블 도입
--   2014.02.27  LEE S.W   전표 역분개 reverse_voucher, 차대 균형검증 강화
--   2019.09.01  CHOI H.W  Oracle 11g -> 19c, 예외코드 -20600 대역 정리
--==========================================================================

CREATE OR REPLACE PACKAGE pkg_ledger AS

    -- 차/대변 구분
    C_DEBIT   CONSTANT VARCHAR2(1) := 'D';   -- 차변
    C_CREDIT  CONSTANT VARCHAR2(1) := 'C';   -- 대변

    -- 사용자정의 예외 (-20600 대역)
    e_unbalanced   EXCEPTION;
    e_bad_type     EXCEPTION;
    e_no_voucher   EXCEPTION;
    PRAGMA EXCEPTION_INIT(e_unbalanced, -20601);
    PRAGMA EXCEPTION_INIT(e_bad_type,   -20602);
    PRAGMA EXCEPTION_INIT(e_no_voucher, -20603);

    -- 전표유형 -> (차변계정, 대변계정) 매핑
    PROCEDURE map_accounts(p_type    IN  VARCHAR2,
                           o_dr_acct OUT VARCHAR2,
                           o_cr_acct OUT VARCHAR2);

    -- 단일 전표 복식부기 기표 (헤더 1 + 명세 2: 차변/대변)
    PROCEDURE post_voucher(p_pid     IN NUMBER,
                           p_type    IN VARCHAR2,
                           p_amount  IN NUMBER,
                           p_bizdate IN DATE);

    -- 전표 역분개 (차대 반대로 재기표)
    PROCEDURE reverse_voucher(p_voucher_id IN NUMBER, p_reason IN VARCHAR2);

    -- 전표 차대 균형 검증 (미균형시 예외)
    FUNCTION is_balanced(p_voucher_id IN NUMBER) RETURN BOOLEAN;

END pkg_ledger;
/

CREATE OR REPLACE PACKAGE BODY pkg_ledger AS

    ----------------------------------------------------------------------
    -- map_accounts : 전표유형별 차변/대변 계정 결정 (DECODE 매핑)
    ----------------------------------------------------------------------
    PROCEDURE map_accounts(p_type    IN  VARCHAR2,
                           o_dr_acct OUT VARCHAR2,
                           o_cr_acct OUT VARCHAR2)
    IS
    BEGIN
        -- 매입채권(1101)/미지급금(2101)/수수료수익(4101)/현금(1001)
        o_dr_acct := DECODE(p_type,
                        'MDR',        '1101',   -- 매입채권 / 수수료수익
                        'SETTLE',     '1101',   -- 매입채권 / 정산미지급
                        'PAYOUT',     '2101',   -- 정산미지급 / 현금
                        'SETTLE_REV', '2101',   -- (역) 정산미지급 / 매입채권
                        NULL);
        o_cr_acct := DECODE(p_type,
                        'MDR',        '4101',
                        'SETTLE',     '2101',
                        'PAYOUT',     '1001',
                        'SETTLE_REV', '1101',
                        NULL);
        IF o_dr_acct IS NULL OR o_cr_acct IS NULL THEN
            RAISE_APPLICATION_ERROR(-20602, '미정의 전표유형 type='||p_type);
        END IF;
    END map_accounts;

    ----------------------------------------------------------------------
    -- post_voucher : 복식부기 전표 1건 기표
    ----------------------------------------------------------------------
    PROCEDURE post_voucher(p_pid     IN NUMBER,
                           p_type    IN VARCHAR2,
                           p_amount  IN NUMBER,
                           p_bizdate IN DATE)
    IS
        v_vid     ledger_voucher.voucher_id%TYPE;
        v_dr      VARCHAR2(8);
        v_cr      VARCHAR2(8);
        v_abs     NUMBER := ABS(p_amount);
        v_side    VARCHAR2(1);
    BEGIN
        map_accounts(p_type, v_dr, v_cr);

        -- 음수 전표(역기표)는 차/대변을 교차
        IF p_amount < 0 THEN
            v_side := v_dr; v_dr := v_cr; v_cr := v_side;
        END IF;

        v_vid := ledger_voucher_seq.NEXTVAL;
        INSERT INTO ledger_voucher
               (voucher_id, purchase_id, entry_type, amount, biz_date, status, created_at)
        VALUES (v_vid, p_pid, p_type, p_amount, p_bizdate, 'N', SYSDATE);

        -- 명세 2행: 차변 / 대변
        INSERT INTO ledger_entry
               (entry_id, voucher_id, account_cd, dc_flag, amount, biz_date)
        VALUES (ledger_entry_seq.NEXTVAL, v_vid, v_dr, C_DEBIT,  v_abs, p_bizdate);
        INSERT INTO ledger_entry
               (entry_id, voucher_id, account_cd, dc_flag, amount, biz_date)
        VALUES (ledger_entry_seq.NEXTVAL, v_vid, v_cr, C_CREDIT, v_abs, p_bizdate);

        -- 균형 검증 (방어적)
        IF NOT is_balanced(v_vid) THEN
            RAISE_APPLICATION_ERROR(-20601, '전표 차대 불균형 vid='||v_vid);
        END IF;
    END post_voucher;

    ----------------------------------------------------------------------
    -- reverse_voucher : 기존 전표를 역분개
    ----------------------------------------------------------------------
    PROCEDURE reverse_voucher(p_voucher_id IN NUMBER, p_reason IN VARCHAR2)
    IS
        r_vou   ledger_voucher%ROWTYPE;
        CURSOR c_ent(cp_vid NUMBER) IS
            SELECT account_cd, dc_flag, amount
              FROM ledger_entry
             WHERE voucher_id = cp_vid;
        v_newvid  ledger_voucher.voucher_id%TYPE;
        v_flip    VARCHAR2(1);
    BEGIN
        SELECT * INTO r_vou
          FROM ledger_voucher
         WHERE voucher_id = p_voucher_id
           FOR UPDATE OF status;

        IF r_vou.status = 'R' THEN
            RAISE_APPLICATION_ERROR(-20603, '이미 역분개 vid='||p_voucher_id);
        END IF;

        v_newvid := ledger_voucher_seq.NEXTVAL;
        INSERT INTO ledger_voucher
               (voucher_id, purchase_id, entry_type, amount, biz_date, status, ref_voucher, created_at)
        VALUES (v_newvid, r_vou.purchase_id, r_vou.entry_type||'_REV',
                -r_vou.amount, TRUNC(SYSDATE), 'N', p_voucher_id, SYSDATE);

        -- 원전표 명세를 차대 반대로 복제
        FOR e IN c_ent(p_voucher_id) LOOP
            v_flip := DECODE(e.dc_flag, C_DEBIT, C_CREDIT, C_DEBIT);
            INSERT INTO ledger_entry
                   (entry_id, voucher_id, account_cd, dc_flag, amount, biz_date)
            VALUES (ledger_entry_seq.NEXTVAL, v_newvid, e.account_cd,
                    v_flip, e.amount, TRUNC(SYSDATE));
        END LOOP;

        UPDATE ledger_voucher
           SET status = 'R', reverse_rsn = p_reason, reversed_at = SYSDATE
         WHERE voucher_id = p_voucher_id;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            RAISE_APPLICATION_ERROR(-20603, '전표 없음 vid='||p_voucher_id);
    END reverse_voucher;

    ----------------------------------------------------------------------
    -- is_balanced : 전표 차변합 = 대변합 여부
    ----------------------------------------------------------------------
    FUNCTION is_balanced(p_voucher_id IN NUMBER) RETURN BOOLEAN
    IS
        v_dr   NUMBER := 0;
        v_cr   NUMBER := 0;
    BEGIN
        SELECT NVL(SUM(DECODE(dc_flag, C_DEBIT,  amount, 0)), 0),
               NVL(SUM(DECODE(dc_flag, C_CREDIT, amount, 0)), 0)
          INTO v_dr, v_cr
          FROM ledger_entry
         WHERE voucher_id = p_voucher_id;
        RETURN (v_dr = v_cr AND v_dr > 0);
    END is_balanced;

END pkg_ledger;
/
