--========================================================================== -- 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; /