동기화

This commit is contained in:
GA Pro
2026-05-15 00:25:18 +09:00
parent 88e14edffd
commit d5ed642c80
69 changed files with 2634 additions and 26 deletions
@@ -0,0 +1,25 @@
-- V63: 설계사 연말 사업소득 집계 테이블
CREATE TABLE agent_annual_income (
agent_id BIGINT NOT NULL REFERENCES agent(agent_id),
settle_year INT NOT NULL,
total_commission NUMERIC(18,2) NOT NULL DEFAULT 0,
total_tax_withheld NUMERIC(18,2) NOT NULL DEFAULT 0,
total_local_tax NUMERIC(18,2) NOT NULL DEFAULT 0,
business_income NUMERIC(18,2) NOT NULL DEFAULT 0,
generated_at TIMESTAMP NOT NULL DEFAULT now(),
created_at TIMESTAMP NOT NULL DEFAULT now(),
updated_at TIMESTAMP,
PRIMARY KEY (agent_id, settle_year)
);
COMMENT ON TABLE agent_annual_income IS '설계사 연말 사업소득 집계';
COMMENT ON COLUMN agent_annual_income.business_income IS 'total_commission - total_tax_withheld - total_local_tax';
CREATE INDEX idx_aai_year ON agent_annual_income(settle_year);
-- EXPLAIN ANALYZE: settle_year 단독 집계 — idx_aai_year Index Scan 확인
-- EXPLAIN ANALYZE
-- SELECT settle_year, SUM(business_income)
-- FROM agent_annual_income
-- GROUP BY settle_year;
@@ -0,0 +1,24 @@
-- V64: 계약 회계분개 엔트리 테이블
CREATE TABLE contract_accounting_entry (
entry_id BIGSERIAL PRIMARY KEY,
contract_id BIGINT NOT NULL REFERENCES contract(contract_id),
payment_id BIGINT REFERENCES payment(payment_id),
entry_date DATE NOT NULL,
debit_account VARCHAR(20) NOT NULL,
credit_account VARCHAR(20) NOT NULL,
amount NUMERIC(18,2) NOT NULL,
description VARCHAR(200),
journal_no VARCHAR(30),
created_at TIMESTAMP NOT NULL DEFAULT now()
);
COMMENT ON TABLE contract_accounting_entry IS '계약 회계 분개 엔트리';
COMMENT ON COLUMN contract_accounting_entry.debit_account IS '차변 계정코드 (예: 8350 지급수수료)';
COMMENT ON COLUMN contract_accounting_entry.credit_account IS '대변 계정코드 (예: 2530 미지급금)';
COMMENT ON COLUMN contract_accounting_entry.journal_no IS '마감 시 채번. NULL=미전기';
CREATE INDEX idx_cae_journal ON contract_accounting_entry(journal_no);
CREATE INDEX idx_cae_date ON contract_accounting_entry(entry_date);
CREATE INDEX idx_cae_contract ON contract_accounting_entry(contract_id, entry_date);
CREATE INDEX idx_cae_unposted ON contract_accounting_entry(entry_date) WHERE journal_no IS NULL;
@@ -0,0 +1,122 @@
-- V65: P5-W1 테스트 데이터 (agent_annual_income, contract_accounting_entry, 메뉴)
-- ============================================================
-- 1. agent_annual_income: 설계사 10명 / 2025년
-- ============================================================
INSERT INTO agent_annual_income (agent_id, settle_year, total_commission, total_tax_withheld, total_local_tax, business_income)
SELECT
a.agent_id,
2025,
v.total_commission,
v.total_commission * 0.033,
v.total_commission * 0.033 * 0.1,
v.total_commission - (v.total_commission * 0.033) - (v.total_commission * 0.033 * 0.1)
FROM (
SELECT agent_id, ROW_NUMBER() OVER (ORDER BY agent_id) AS rn
FROM agent
LIMIT 10
) a
JOIN (VALUES
(1, 42000000.00),
(2, 38500000.00),
(3, 55000000.00),
(4, 29800000.00),
(5, 61200000.00),
(6, 47300000.00),
(7, 33600000.00),
(8, 52100000.00),
(9, 28400000.00),
(10, 44700000.00)
) AS v(rn, total_commission) ON a.rn = v.rn
ON CONFLICT (agent_id, settle_year) DO NOTHING;
-- ============================================================
-- 2. contract_accounting_entry: payment 10건 기반
-- journal_no NULL 5건 (미전기) + 채번 5건 (전기완료)
-- ============================================================
INSERT INTO contract_accounting_entry (contract_id, payment_id, entry_date, debit_account, credit_account, amount, description, journal_no)
SELECT
c.contract_id,
p.payment_id,
COALESCE(p.pay_date, CURRENT_DATE),
'8350',
'2530',
p.pay_amount,
'지급수수료 자동분개 #' || p.payment_id,
CASE WHEN p.payment_id % 2 = 0 THEN 'JNL-2025-' || LPAD(p.payment_id::text, 5, '0') ELSE NULL END
FROM (
SELECT py.payment_id, py.pay_amount, py.pay_date, sm.agent_id
FROM payment py
JOIN settle_master sm ON sm.settle_id = py.settle_id
ORDER BY py.payment_id
LIMIT 10
) p
JOIN LATERAL (
SELECT c.contract_id
FROM contract c
WHERE c.agent_id = p.agent_id
ORDER BY c.contract_id
LIMIT 1
) c ON true;
-- ============================================================
-- 3. 메뉴: ANNUAL_INCOME + ACCOUNTING_JOURNAL (GRP_COMMISSION 하위)
-- ============================================================
INSERT INTO menu (parent_menu_id, menu_code, menu_name, menu_type, menu_path, component_path, menu_level, sort_order, is_visible, is_active)
SELECT p.menu_id, x.code, x.name, 'PAGE', x.path, x.cmpt, 2, x.sort, 'Y', 'Y'
FROM menu p
JOIN (VALUES
('ANNUAL_INCOME', '연말 사업소득 명세서', '/commission/annual-income', 'commission/AnnualIncome', 80),
('ACCOUNTING_JOURNAL', '회계 분개장', '/commission/accounting-journal', 'commission/AccountingJournal', 90)
) AS x(code, name, path, cmpt, sort) ON p.menu_code = 'GRP_COMMISSION'
WHERE NOT EXISTS (SELECT 1 FROM menu WHERE menu_code = x.code);
-- ============================================================
-- 4. 권한 항목 등록
-- ============================================================
INSERT INTO menu_permission (menu_id, perm_code, perm_name, sort_order)
SELECT m.menu_id, p.code, p.name, p.sort
FROM menu m
CROSS JOIN (VALUES
('READ', '조회', 10),
('CREATE', '등록', 20),
('UPDATE', '수정', 30),
('EXPORT', '엑셀', 60)
) AS p(code, name, sort)
WHERE m.menu_code IN ('ANNUAL_INCOME', 'ACCOUNTING_JOURNAL')
ON CONFLICT (menu_id, perm_code) DO NOTHING;
-- ============================================================
-- 5. 역할별 권한 부여
-- ============================================================
-- SUPER_ADMIN / ADMIN: 전체
INSERT INTO role_menu_permission (role_id, menu_id, perm_code, is_granted)
SELECT r.role_id, mp.menu_id, mp.perm_code, 'Y'
FROM role r
CROSS JOIN menu_permission mp
JOIN menu m ON m.menu_id = mp.menu_id
WHERE r.role_code IN ('SUPER_ADMIN', 'ADMIN')
AND m.menu_code IN ('ANNUAL_INCOME', 'ACCOUNTING_JOURNAL')
ON CONFLICT (role_id, menu_id, perm_code) DO NOTHING;
-- MANAGER: READ + EXPORT
INSERT INTO role_menu_permission (role_id, menu_id, perm_code, is_granted)
SELECT r.role_id, mp.menu_id, mp.perm_code, 'Y'
FROM role r
CROSS JOIN menu_permission mp
JOIN menu m ON m.menu_id = mp.menu_id
WHERE r.role_code = 'MANAGER'
AND mp.perm_code IN ('READ', 'EXPORT')
AND m.menu_code IN ('ANNUAL_INCOME', 'ACCOUNTING_JOURNAL')
ON CONFLICT (role_id, menu_id, perm_code) DO NOTHING;
-- AGENT: READ (본인 연말명세서)
INSERT INTO role_menu_permission (role_id, menu_id, perm_code, is_granted)
SELECT r.role_id, mp.menu_id, mp.perm_code, 'Y'
FROM role r
CROSS JOIN menu_permission mp
JOIN menu m ON m.menu_id = mp.menu_id
WHERE r.role_code = 'AGENT'
AND mp.perm_code = 'READ'
AND m.menu_code = 'ANNUAL_INCOME'
ON CONFLICT (role_id, menu_id, perm_code) DO NOTHING;
@@ -0,0 +1,26 @@
-- V66: 회계 계정코드 마스터 신설
-- contract_accounting_entry.debit_account / credit_account 참조용
-- FK 추가는 PL 별도 논의 후 진행
CREATE TABLE account_code (
account_code VARCHAR(20) PRIMARY KEY,
account_name VARCHAR(100) NOT NULL,
account_type VARCHAR(10) NOT NULL CHECK (account_type IN ('DEBIT','CREDIT','BOTH')),
is_active CHAR(1) NOT NULL DEFAULT 'Y',
sort_order INTEGER NOT NULL DEFAULT 0,
description VARCHAR(200),
created_at TIMESTAMP NOT NULL DEFAULT now(),
created_by BIGINT,
updated_at TIMESTAMP NOT NULL DEFAULT now(),
updated_by BIGINT
);
COMMENT ON TABLE account_code IS '회계 계정코드 마스터';
COMMENT ON COLUMN account_code.account_type IS 'DEBIT=차변전용, CREDIT=대변전용, BOTH=겸용';
INSERT INTO account_code (account_code, account_name, account_type, sort_order) VALUES
('8350', '지급수수료', 'DEBIT', 10),
('2530', '미지급금', 'CREDIT', 20),
('1130', '미수수수료', 'DEBIT', 30),
('4110', '수수료수익', 'CREDIT', 40)
ON CONFLICT (account_code) DO NOTHING;
@@ -0,0 +1,23 @@
-- V67: 원천세 분기신고 테이블
CREATE TABLE withholding_tax_report (
report_id BIGSERIAL PRIMARY KEY,
settle_quarter VARCHAR(7) NOT NULL,
agent_id BIGINT NOT NULL REFERENCES agent(agent_id),
total_income NUMERIC(18,2),
total_withheld NUMERIC(18,2),
total_local_tax NUMERIC(18,2),
generated_at TIMESTAMP,
filed_at TIMESTAMP,
file_status VARCHAR(20) NOT NULL DEFAULT 'DRAFT',
created_at TIMESTAMP NOT NULL DEFAULT now(),
updated_at TIMESTAMP,
UNIQUE (agent_id, settle_quarter),
CONSTRAINT chk_wtr_status CHECK (file_status IN ('DRAFT','FILED','AMENDED'))
);
COMMENT ON TABLE withholding_tax_report IS '원천세 분기신고';
COMMENT ON COLUMN withholding_tax_report.settle_quarter IS '정산 분기 (예: 2025-Q1)';
COMMENT ON COLUMN withholding_tax_report.file_status IS 'DRAFT=작성중, FILED=신고완료, AMENDED=수정신고';
CREATE INDEX idx_wtr_quarter ON withholding_tax_report(settle_quarter);
@@ -0,0 +1,22 @@
-- V68: 부가세 분기신고 테이블
CREATE TABLE vat_report (
vat_id BIGSERIAL PRIMARY KEY,
settle_quarter VARCHAR(7) NOT NULL,
total_sales NUMERIC(18,2),
total_purchase NUMERIC(18,2),
total_vat_payable NUMERIC(18,2),
tax_invoice_count INT,
generated_at TIMESTAMP,
filed_at TIMESTAMP,
file_status VARCHAR(20) NOT NULL DEFAULT 'DRAFT',
created_at TIMESTAMP NOT NULL DEFAULT now(),
updated_at TIMESTAMP,
UNIQUE (settle_quarter),
CONSTRAINT chk_vat_status CHECK (file_status IN ('DRAFT','FILED','AMENDED'))
);
COMMENT ON TABLE vat_report IS '부가세 분기신고';
COMMENT ON COLUMN vat_report.settle_quarter IS '정산 분기 (예: 2025-Q1)';
COMMENT ON COLUMN vat_report.total_vat_payable IS '납부 부가세 = total_sales * 0.1 - 매입세액';
COMMENT ON COLUMN vat_report.file_status IS 'DRAFT=작성중, FILED=신고완료, AMENDED=수정신고';
@@ -0,0 +1,87 @@
-- V69: P5-W2 테스트 데이터 (withholding_tax_report, vat_report, 메뉴)
-- ============================================================
-- 1. withholding_tax_report: agent 5명 × 2025 Q1~Q4 = 20건
-- ============================================================
INSERT INTO withholding_tax_report (settle_quarter, agent_id, total_income, total_withheld, total_local_tax, generated_at, file_status)
SELECT
q.quarter,
a.agent_id,
q.base_income,
q.base_income * 0.033,
q.base_income * 0.033 * 0.1,
now(),
CASE WHEN q.quarter IN ('2025-Q1','2025-Q2') THEN 'FILED' ELSE 'DRAFT' END
FROM (
SELECT agent_id, ROW_NUMBER() OVER (ORDER BY agent_id) AS rn
FROM agent LIMIT 5
) a
CROSS JOIN (VALUES
('2025-Q1', 10500000.00),
('2025-Q2', 11200000.00),
('2025-Q3', 10800000.00),
('2025-Q4', 12300000.00)
) AS q(quarter, base_income)
ON CONFLICT (agent_id, settle_quarter) DO NOTHING;
-- ============================================================
-- 2. vat_report: 2025 Q1~Q4 = 4건
-- ============================================================
INSERT INTO vat_report (settle_quarter, total_sales, total_purchase, total_vat_payable, tax_invoice_count, generated_at, file_status)
VALUES
('2025-Q1', 850000000.00, 120000000.00, 73000000.00, 142, now(), 'FILED'),
('2025-Q2', 920000000.00, 135000000.00, 78500000.00, 158, now(), 'FILED'),
('2025-Q3', 880000000.00, 128000000.00, 75200000.00, 151, now(), 'DRAFT'),
('2025-Q4', 960000000.00, 140000000.00, 82000000.00, 167, now(), 'DRAFT')
ON CONFLICT (settle_quarter) DO NOTHING;
-- ============================================================
-- 3. 메뉴: WITHHOLDING_TAX + VAT_REPORT (GRP_COMMISSION 하위)
-- ============================================================
INSERT INTO menu (parent_menu_id, menu_code, menu_name, menu_type, menu_path, component_path, menu_level, sort_order, is_visible, is_active)
SELECT p.menu_id, x.code, x.name, 'PAGE', x.path, x.cmpt, 2, x.sort, 'Y', 'Y'
FROM menu p
JOIN (VALUES
('WITHHOLDING_TAX', '원천세 신고', '/commission/withholding-tax', 'commission/WithholdingTax', 100),
('VAT_REPORT', '부가세 신고', '/commission/vat-report', 'commission/VatReport', 110)
) AS x(code, name, path, cmpt, sort) ON p.menu_code = 'GRP_COMMISSION'
WHERE NOT EXISTS (SELECT 1 FROM menu WHERE menu_code = x.code);
-- ============================================================
-- 4. 권한 항목 등록
-- ============================================================
INSERT INTO menu_permission (menu_id, perm_code, perm_name, sort_order)
SELECT m.menu_id, p.code, p.name, p.sort
FROM menu m
CROSS JOIN (VALUES
('READ', '조회', 10),
('CREATE', '등록', 20),
('UPDATE', '수정', 30),
('EXPORT', '엑셀', 60)
) AS p(code, name, sort)
WHERE m.menu_code IN ('WITHHOLDING_TAX', 'VAT_REPORT')
ON CONFLICT (menu_id, perm_code) DO NOTHING;
-- ============================================================
-- 5. 역할별 권한 부여
-- ============================================================
-- SUPER_ADMIN / ADMIN: 전체
INSERT INTO role_menu_permission (role_id, menu_id, perm_code, is_granted)
SELECT r.role_id, mp.menu_id, mp.perm_code, 'Y'
FROM role r
CROSS JOIN menu_permission mp
JOIN menu m ON m.menu_id = mp.menu_id
WHERE r.role_code IN ('SUPER_ADMIN', 'ADMIN')
AND m.menu_code IN ('WITHHOLDING_TAX', 'VAT_REPORT')
ON CONFLICT (role_id, menu_id, perm_code) DO NOTHING;
-- MANAGER: READ + EXPORT
INSERT INTO role_menu_permission (role_id, menu_id, perm_code, is_granted)
SELECT r.role_id, mp.menu_id, mp.perm_code, 'Y'
FROM role r
CROSS JOIN menu_permission mp
JOIN menu m ON m.menu_id = mp.menu_id
WHERE r.role_code = 'MANAGER'
AND mp.perm_code IN ('READ', 'EXPORT')
AND m.menu_code IN ('WITHHOLDING_TAX', 'VAT_REPORT')
ON CONFLICT (role_id, menu_id, perm_code) DO NOTHING;
@@ -0,0 +1,29 @@
-- V70: rule 도메인 5개 테이블 감사 컬럼 보강
-- commission_rate / payout_rule: created_at/created_by 존재, updated_at/updated_by 누락
-- override_rule / chargeback_rule / exception_type_code: 감사 컬럼 전체 누락
ALTER TABLE commission_rate
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP,
ADD COLUMN IF NOT EXISTS updated_by BIGINT;
ALTER TABLE payout_rule
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP,
ADD COLUMN IF NOT EXISTS updated_by BIGINT;
ALTER TABLE override_rule
ADD COLUMN IF NOT EXISTS created_at TIMESTAMP NOT NULL DEFAULT now(),
ADD COLUMN IF NOT EXISTS created_by BIGINT,
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP,
ADD COLUMN IF NOT EXISTS updated_by BIGINT;
ALTER TABLE chargeback_rule
ADD COLUMN IF NOT EXISTS created_at TIMESTAMP NOT NULL DEFAULT now(),
ADD COLUMN IF NOT EXISTS created_by BIGINT,
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP,
ADD COLUMN IF NOT EXISTS updated_by BIGINT;
ALTER TABLE exception_type_code
ADD COLUMN IF NOT EXISTS created_at TIMESTAMP NOT NULL DEFAULT now(),
ADD COLUMN IF NOT EXISTS created_by BIGINT,
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP,
ADD COLUMN IF NOT EXISTS updated_by BIGINT;
@@ -0,0 +1,13 @@
-- V71: payment 테이블 세무 컬럼 누락 보강
-- V23 세무처리.sql 체크섬 미스매치로 income_tax_amount 등 미적용 → idempotent 추가
ALTER TABLE payment
ADD COLUMN IF NOT EXISTS income_tax_amount NUMERIC(15,2) NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS local_tax_amount NUMERIC(15,2) NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS vat_amount NUMERIC(15,2) NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS net_amount NUMERIC(15,2);
COMMENT ON COLUMN payment.income_tax_amount IS '원천세 (소득세 3%)';
COMMENT ON COLUMN payment.local_tax_amount IS '지방소득세 (소득세의 10%)';
COMMENT ON COLUMN payment.vat_amount IS '부가세';
COMMENT ON COLUMN payment.net_amount IS '실수령액 (pay_amount - income_tax - local_tax)';
@@ -0,0 +1,29 @@
-- V72: P5-W1 ANNUAL_INCOME 메뉴 EXECUTE 권한 누락 보강
-- V65에서 READ/CREATE/UPDATE/EXPORT만 등록되어 regenerate 엔드포인트 403 발생
-- SUPER_ADMIN / ADMIN 역할에 EXECUTE 권한 부여
-- 1. menu_permission에 EXECUTE 항목 추가
INSERT INTO menu_permission (menu_id, perm_code, perm_name, sort_order)
SELECT m.menu_id, 'EXECUTE', '재집계', 70
FROM menu m
WHERE m.menu_code = 'ANNUAL_INCOME'
AND NOT EXISTS (
SELECT 1 FROM menu_permission mp2
WHERE mp2.menu_id = m.menu_id AND mp2.perm_code = 'EXECUTE'
);
-- 2. SUPER_ADMIN / ADMIN에 EXECUTE 권한 부여
INSERT INTO role_menu_permission (role_id, menu_id, perm_code, is_granted)
SELECT r.role_id, mp.menu_id, mp.perm_code, 'Y'
FROM role r
CROSS JOIN menu_permission mp
JOIN menu m ON m.menu_id = mp.menu_id
WHERE r.role_code IN ('SUPER_ADMIN', 'ADMIN')
AND mp.perm_code = 'EXECUTE'
AND m.menu_code = 'ANNUAL_INCOME'
AND NOT EXISTS (
SELECT 1 FROM role_menu_permission rmp2
WHERE rmp2.role_id = r.role_id
AND rmp2.menu_id = mp.menu_id
AND rmp2.perm_code = 'EXECUTE'
);