Files
dbx-main/scripts/sql/malaysia_stock_db_init.sql
T
king 6f4a62013a feat(malaysia): 콤보 상품 MY-9991 Combo 2.5L+6L seed 추가
malaysia_items 콤보 목록에 MY-9991 추가(BOM은 itemcode_db set_components 참조)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 17:11:29 +09:00

266 lines
14 KiB
PL/PgSQL

-- =====================================================================
-- malaysia_stock_db 초기화 스크립트 (PostgreSQL) — 말레이시아 창고 재고관리
-- =====================================================================
-- 멱등(idempotent): 여러 번 실행해도 안전. 기존 데이터를 삭제하지 않는다.
--
-- ⚠️ 실행 전 사용자 승인 + 백업 확인 필수. DROP/TRUNCATE 없음.
--
-- 실행 방법 (운영 PostgreSQL 컨테이너명: postgres-db):
--
-- 1) DB / 역할 / 스키마 생성 (superuser 로 1회)
-- read -s -p "malaysia_app password: " APP_PWD; echo
-- docker exec -i postgres-db psql -U postgres \
-- -v app_password="$APP_PWD" \
-- < scripts/sql/malaysia_stock_db_init.sql
--
-- 2) main-app .env 에 연결 정보 등록
-- MALAYSIA_STOCK_DB_URL=postgresql://malaysia_app:<APP_PWD>@postgres-db:5432/malaysia_stock_db
--
-- 3) main-app 재기동
-- cd /opt/www/main && docker compose up -d --build
--
-- 주의:
-- - 기존 DB 가 있으면 DROP 하지 않는다(CREATE DATABASE 는 미존재 시에만).
-- - 비밀번호는 절대 Git 에 커밋하지 않는다. psql -v 로만 전달.
-- - itemcode_db 는 이 스크립트가 건드리지 않는다(상품명은 읽기 전용 참조).
-- - 재고 계산 원칙:
-- 현재고 = SUM(IN) - SUM(OUT) + SUM(ADJUST 부호) + SUM(STOCKTAKE 부호)
-- ADJUST/STOCKTAKE 는 음수 qty 허용(±). IN/OUT 은 양수만.
-- =====================================================================
\set ON_ERROR_STOP on
-- DB 가 없을 때만 생성
SELECT 'CREATE DATABASE malaysia_stock_db ENCODING ''UTF8'' TEMPLATE template0'
WHERE NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = 'malaysia_stock_db')
\gexec
-- 앱 전용 로그인 역할 (cupang_app 패턴과 동일)
SELECT 'CREATE ROLE malaysia_app LOGIN PASSWORD ' || quote_literal(:'app_password')
WHERE NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'malaysia_app')
\gexec
-- 항상 최신 비밀번호로 동기화
SELECT 'ALTER ROLE malaysia_app WITH LOGIN PASSWORD ' || quote_literal(:'app_password')
\gexec
GRANT CONNECT ON DATABASE malaysia_stock_db TO malaysia_app;
-- malaysia_stock_db 컨텍스트로 전환
\connect malaysia_stock_db
-- ── updated_at 자동 갱신 트리거 함수 (멱등: CREATE OR REPLACE) ──
CREATE OR REPLACE FUNCTION malaysia_set_updated_at() RETURNS trigger AS $$
BEGIN
NEW.updated_at := now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- ════════════════════════════════════════════════════════════
-- 1) 창고
-- ════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS warehouses (
id BIGSERIAL PRIMARY KEY,
warehouse_code TEXT NOT NULL UNIQUE,
warehouse_name TEXT NOT NULL,
country TEXT NOT NULL DEFAULT 'MY',
active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
DROP TRIGGER IF EXISTS trg_warehouses_updated ON warehouses;
CREATE TRIGGER trg_warehouses_updated
BEFORE UPDATE ON warehouses
FOR EACH ROW EXECUTE FUNCTION malaysia_set_updated_at();
-- ════════════════════════════════════════════════════════════
-- 2) 재고관리 대상 아이템(스코프 카탈로그)
-- 상품 마스터를 복제하는 것이 아니라, "말레이시아 창고에서 관리하는
-- 코드 목록과 종류(낱개/세트)"만 보관한다. item_name 은 스냅샷이며
-- itemcode_db(읽기 전용)에서 최신 명칭을 덮어쓸 수 있다.
-- kind: 'individual'(MT/MX/MZ) | 'set'(MY). MD- 뚜껑은 제외(미등록).
-- ════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS malaysia_items (
id BIGSERIAL PRIMARY KEY,
item_code TEXT NOT NULL UNIQUE,
item_name TEXT NOT NULL DEFAULT '',
kind TEXT NOT NULL CHECK (kind IN ('individual', 'set')),
active BOOLEAN NOT NULL DEFAULT TRUE,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
-- prefix 규칙: 낱개는 MT-/MX-/MZ-, 세트는 MY-. MD- 금지.
CHECK (
(kind = 'individual' AND (item_code LIKE 'MT-%' OR item_code LIKE 'MX-%' OR item_code LIKE 'MZ-%'))
OR
(kind = 'set' AND item_code LIKE 'MY-%')
)
);
CREATE INDEX IF NOT EXISTS idx_malaysia_items_kind ON malaysia_items (kind);
DROP TRIGGER IF EXISTS trg_malaysia_items_updated ON malaysia_items;
CREATE TRIGGER trg_malaysia_items_updated
BEFORE UPDATE ON malaysia_items
FOR EACH ROW EXECUTE FUNCTION malaysia_set_updated_at();
-- ════════════════════════════════════════════════════════════
-- 3) 세트 구성표 (BOM)
-- set_code(MY-) 안에 component_code(MT-/MX-/MZ-) 가 몇 개 들어가는지.
-- MY- 가 MY- 를 포함하는 중첩은 불가(component 는 MY- 금지). MD- 금지.
-- ════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS set_bom (
id BIGSERIAL PRIMARY KEY,
set_code TEXT NOT NULL CHECK (set_code LIKE 'MY-%'),
component_code TEXT NOT NULL CHECK (
component_code LIKE 'MT-%'
OR component_code LIKE 'MX-%'
OR component_code LIKE 'MZ-%'),
component_qty INTEGER NOT NULL CHECK (component_qty > 0),
active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (set_code, component_code)
);
CREATE INDEX IF NOT EXISTS idx_set_bom_set ON set_bom (set_code);
DROP TRIGGER IF EXISTS trg_set_bom_updated ON set_bom;
CREATE TRIGGER trg_set_bom_updated
BEFORE UPDATE ON set_bom
FOR EACH ROW EXECUTE FUNCTION malaysia_set_updated_at();
-- ════════════════════════════════════════════════════════════
-- 4) 재고 이동 이력 (입고/출고/조정/재고조사 반영)
-- item_code 는 낱개(MT-/MX-/MZ-)만 저장한다. MY-/MD- 는 movement 금지.
-- qty 부호 규칙:
-- IN/OUT → 양수만(방향은 movement_type 으로 구분)
-- ADJUST → 음수 허용(±)
-- STOCKTAKE → 음수 허용(±, 재고조사 차이 반영분)
-- 현재고 = SUM(IN) - SUM(OUT) + SUM(ADJUST) + SUM(STOCKTAKE)
-- ════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS stock_movement (
id BIGSERIAL PRIMARY KEY,
movement_date DATE NOT NULL,
warehouse_code TEXT NOT NULL REFERENCES warehouses(warehouse_code),
item_code TEXT NOT NULL CHECK (
item_code LIKE 'MT-%'
OR item_code LIKE 'MX-%'
OR item_code LIKE 'MZ-%'),
movement_type TEXT NOT NULL CHECK (movement_type IN ('IN', 'OUT', 'ADJUST', 'STOCKTAKE')),
qty INTEGER NOT NULL,
ref_type TEXT NOT NULL DEFAULT '', -- 예: shopee/lazada/stocktake/manual
ref_no TEXT NOT NULL DEFAULT '', -- 외부 주문번호/조사 id 등
memo TEXT NOT NULL DEFAULT '',
created_by TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
-- IN/OUT 은 양수만. ADJUST/STOCKTAKE 만 음수 허용.
CHECK (
(movement_type IN ('IN', 'OUT') AND qty > 0)
OR movement_type IN ('ADJUST', 'STOCKTAKE')
)
);
CREATE INDEX IF NOT EXISTS idx_stock_mv_wh_item ON stock_movement (warehouse_code, item_code);
CREATE INDEX IF NOT EXISTS idx_stock_mv_date ON stock_movement (movement_date);
CREATE INDEX IF NOT EXISTS idx_stock_mv_type ON stock_movement (movement_type);
CREATE INDEX IF NOT EXISTS idx_stock_mv_ref ON stock_movement (ref_type, ref_no);
-- ════════════════════════════════════════════════════════════
-- 5) 일일 재고조사 (헤더)
-- 같은 날짜 + 같은 창고에 finalized 는 1건만 허용(부분 유니크 인덱스).
-- ════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS daily_stocktake (
id BIGSERIAL PRIMARY KEY,
stocktake_date DATE NOT NULL,
warehouse_code TEXT NOT NULL REFERENCES warehouses(warehouse_code),
status TEXT NOT NULL DEFAULT 'draft'
CHECK (status IN ('draft', 'finalized', 'cancelled')),
memo TEXT NOT NULL DEFAULT '',
created_by TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
finalized_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_stocktake_date ON daily_stocktake (stocktake_date);
CREATE INDEX IF NOT EXISTS idx_stocktake_wh ON daily_stocktake (warehouse_code);
-- 같은 날짜+창고 finalized 중복 방지
CREATE UNIQUE INDEX IF NOT EXISTS uq_stocktake_finalized
ON daily_stocktake (stocktake_date, warehouse_code)
WHERE status = 'finalized';
DROP TRIGGER IF EXISTS trg_stocktake_updated ON daily_stocktake;
CREATE TRIGGER trg_stocktake_updated
BEFORE UPDATE ON daily_stocktake
FOR EACH ROW EXECUTE FUNCTION malaysia_set_updated_at();
-- ════════════════════════════════════════════════════════════
-- 6) 일일 재고조사 라인
-- sku_code: 낱개(MT-/MX-/MZ-) 또는 세트(MY-) 허용. MD- 금지.
-- 같은 조사 안에서 같은 sku_code 중복 금지.
-- ════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS daily_stocktake_line (
id BIGSERIAL PRIMARY KEY,
stocktake_id BIGINT NOT NULL REFERENCES daily_stocktake(id) ON DELETE CASCADE,
sku_code TEXT NOT NULL CHECK (
sku_code LIKE 'MT-%'
OR sku_code LIKE 'MX-%'
OR sku_code LIKE 'MZ-%'
OR sku_code LIKE 'MY-%'),
qty INTEGER NOT NULL CHECK (qty >= 0),
memo TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (stocktake_id, sku_code)
);
CREATE INDEX IF NOT EXISTS idx_stocktake_line_st ON daily_stocktake_line (stocktake_id);
DROP TRIGGER IF EXISTS trg_stocktake_line_updated ON daily_stocktake_line;
CREATE TRIGGER trg_stocktake_line_updated
BEFORE UPDATE ON daily_stocktake_line
FOR EACH ROW EXECUTE FUNCTION malaysia_set_updated_at();
-- ════════════════════════════════════════════════════════════
-- 7) seed — 기본 창고 + 재고관리 대상 아이템
-- ════════════════════════════════════════════════════════════
INSERT INTO warehouses (warehouse_code, warehouse_name, country) VALUES
('MY-WH-01', 'Malaysia Warehouse', 'MY')
ON CONFLICT (warehouse_code) DO NOTHING;
-- 낱개 아이템 (MT-/MX-/MZ-). MD- 뚜껑은 의도적으로 제외.
INSERT INTO malaysia_items (item_code, item_name, kind, sort_order) VALUES
('MT-0320', 'Miracle Container 320ml', 'individual', 1),
('MT-0550', 'Miracle Container 550ml', 'individual', 2),
('MT-0750', 'Miracle Container 750ml', 'individual', 3),
('MT-0950', 'Miracle Container 950ml', 'individual', 4),
('MT-1800', 'Miracle Container 1800ml', 'individual', 5),
('MT-2200', 'Miracle Container 2200ml', 'individual', 6),
('MT-3200', 'Miracle Container 3200ml', 'individual', 7),
('MT-5000', 'Miracle Container 5000ml', 'individual', 8),
('MX-0001', 'Miracle Bag 3 Types', 'individual', 9),
('MX-0003', 'Miracle Bag 5 pcs', 'individual', 10),
('MZ-0001', 'Miracle Zipperbag 2.5L', 'individual', 11),
('MZ-0002', 'Miracle Zipperbag 6.0L', 'individual', 12),
('MY-0001', 'Launching Special', 'set', 21),
('MY-0002', 'Starter Set', 'set', 22),
('MY-0003', 'Essential Set', 'set', 23),
('MY-0004', 'Premium Set', 'set', 24),
('MY-0005', 'Ultimate Set', 'set', 25),
('MY-9991', 'Combo 2.5L+6L', 'set', 26)
ON CONFLICT (item_code) DO NOTHING;
-- ════════════════════════════════════════════════════════════
-- 8) 권한 (malaysia_app: CRUD only)
-- ════════════════════════════════════════════════════════════
GRANT USAGE ON SCHEMA public TO malaysia_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON
warehouses, malaysia_items, set_bom, stock_movement,
daily_stocktake, daily_stocktake_line
TO malaysia_app;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO malaysia_app;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO malaysia_app;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO malaysia_app;
SELECT 'malaysia_stock_db ready' AS status;