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

-- ════════════════════════════════════════════════════════════
-- 4-b) 콤보(세트) 출고 원장 (세트 단위)
--    set_code 는 MY- 만. 세트 출고 시 stock_movement(낱개 분해)와 함께
--    여기에 콤보 단위 OUT 을 1행 기록한다(같은 트랜잭션).
--    콤보 재고현황 = 최근 재고조사 콤보수량 − 재고조사일 이후 OUT 합.
--    콤보 입고는 업무상 불가(낱개로만 입고) → OUT/ADJUST 만 허용.
-- ════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS set_movement (
    id             BIGSERIAL   PRIMARY KEY,
    movement_date  DATE        NOT NULL,
    warehouse_code TEXT        NOT NULL REFERENCES warehouses(warehouse_code),
    set_code       TEXT        NOT NULL CHECK (set_code LIKE 'MY-%'),
    movement_type  TEXT        NOT NULL DEFAULT 'OUT' CHECK (movement_type IN ('OUT', 'ADJUST')),
    qty            INTEGER     NOT NULL,
    ref_type       TEXT        NOT NULL DEFAULT '',
    ref_no         TEXT        NOT NULL DEFAULT '',
    memo           TEXT        NOT NULL DEFAULT '',
    created_by     TEXT        NOT NULL DEFAULT '',
    created_at     TIMESTAMPTZ NOT NULL DEFAULT now(),
    -- OUT 은 양수만. ADJUST 만 음수 허용.
    CHECK ((movement_type = 'OUT' AND qty > 0) OR movement_type = 'ADJUST')
);
CREATE INDEX IF NOT EXISTS idx_set_mv_wh_set ON set_movement (warehouse_code, set_code);
CREATE INDEX IF NOT EXISTS idx_set_mv_date   ON set_movement (movement_date);

-- ════════════════════════════════════════════════════════════
-- 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();

-- ════════════════════════════════════════════════════════════
-- 6.5) 일일 재고조사 랙(rack) 입력
--    창고 랙(A/B면, 행/열/분할 칸)별로 "어떤 SKU 가 한 박스당 몇 개씩
--    몇 박스 있는지"를 기록한다. 칸 단위 입력 → 서버가 SKU 별
--    qty = SUM(units_per_box * box_count) 로 집계해 daily_stocktake_line 을
--    재생성한다(랙이 라인의 단일 출처). 한 칸(파렛트)에 여러 SKU 가능.
--    cell_code 예: 'A3-1-1' (면 A, 행 3, 열 1, 분할 1).
--    sku_code: 낱개(MT-/MX-/MZ-) 또는 세트(MY-) 허용. MD- 금지.
-- ════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS daily_stocktake_rack (
    id            BIGSERIAL   PRIMARY KEY,
    stocktake_id  BIGINT      NOT NULL REFERENCES daily_stocktake(id) ON DELETE CASCADE,
    cell_code     TEXT        NOT NULL,            -- 랙 위치(예: A3-1-1)
    line_no       INTEGER     NOT NULL DEFAULT 0,  -- 칸 안 입력 순서
    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-%'),
    units_per_box INTEGER     NOT NULL CHECK (units_per_box > 0),
    box_count     INTEGER     NOT NULL CHECK (box_count > 0),
    created_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_stocktake_rack_st   ON daily_stocktake_rack (stocktake_id);
CREATE INDEX IF NOT EXISTS idx_stocktake_rack_cell ON daily_stocktake_rack (stocktake_id, cell_code);

-- ════════════════════════════════════════════════════════════
-- 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, set_movement,
  daily_stocktake, daily_stocktake_line, daily_stocktake_rack
  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;
