f1097b85dc
- 플랫폼 TikTok/Shopee 분리: 업로드 화면이 선택에 따라 파일 안내 변경 (TikTok=Order Export xlsx, Shopee=Packing List xlsx / 라벨 PDF 선택) - TikTok Picking List.pdf 업로드 제거(피킹 요약은 데이터 엑셀로 생성) - 파서 공용화 + 헤더 행 자동탐지(Shopee 제목행 대응), 두 포맷 헤더 별칭 통합 - 받는 사람 이름/전화/주소를 박스 단위로 dispatch_db 에 저장(개인정보) · init SQL 갱신 + 기존 DB용 멱등 마이그레이션 dispatch_add_recipient_columns.sql - 출고 작업 카드: Package 제거, 받는 사람 이름(크게)+주소(여러 줄)+전화 표시 - 배치 다운로드 zip 에 취합 출고 엑셀 포함 파일명 YYYY.MM.DD(Ddd)_tictoc.xlsx / _shopee.xlsx (export.py) - 문서(CLAUDE.md, DISPATCH_MODULE.md) PII 보관·Shopee 반영 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
158 lines
8.7 KiB
PL/PgSQL
158 lines
8.7 KiB
PL/PgSQL
-- =====================================================================
|
|
-- dispatch_db 초기화 스크립트 (PostgreSQL) — 말레이시아 TikTok 출고관리
|
|
-- =====================================================================
|
|
-- 멱등(idempotent): 여러 번 실행해도 안전. 기존 데이터를 삭제하지 않는다.
|
|
--
|
|
-- ⚠️ 실행 전 사용자 승인 + 백업 확인 필수. DROP/TRUNCATE 없음.
|
|
--
|
|
-- 실행 방법 (운영 PostgreSQL 컨테이너명: postgres-db):
|
|
--
|
|
-- 1) DB / 역할 / 스키마 생성 (superuser 로 1회)
|
|
-- read -s -p "dispatch_app password: " APP_PWD; echo
|
|
-- docker exec -i postgres-db psql -U postgres \
|
|
-- -v app_password="$APP_PWD" \
|
|
-- < scripts/sql/dispatch_db_init.sql
|
|
--
|
|
-- 2) main-app .env 에 연결 정보 등록
|
|
-- DISPATCH_DB_URL=postgresql://dispatch_app:<APP_PWD>@postgres-db:5432/dispatch_db
|
|
--
|
|
-- 3) main-app 재기동
|
|
-- cd /opt/www/main && docker compose up -d --build web
|
|
--
|
|
-- 주의:
|
|
-- - 기존 DB 가 있으면 DROP 하지 않는다(CREATE DATABASE 는 미존재 시에만).
|
|
-- - 비밀번호는 절대 Git 에 커밋하지 않는다. psql -v 로만 전달.
|
|
-- - 받는 사람 정보(이름/전화/주소)는 출고 작업 카드 표시 + 출고 엑셀 생성을
|
|
-- 위해 박스 단위로 보관한다(개인정보 — 접근/보관 정책 검토 대상).
|
|
-- - 작업 기준 키: order_id / package_id / tracking_id / seller_sku / quantity.
|
|
-- =====================================================================
|
|
|
|
\set ON_ERROR_STOP on
|
|
|
|
-- DB 가 없을 때만 생성
|
|
SELECT 'CREATE DATABASE dispatch_db ENCODING ''UTF8'' TEMPLATE template0'
|
|
WHERE NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = 'dispatch_db')
|
|
\gexec
|
|
|
|
-- 앱 전용 로그인 역할
|
|
SELECT 'CREATE ROLE dispatch_app LOGIN PASSWORD ' || quote_literal(:'app_password')
|
|
WHERE NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'dispatch_app')
|
|
\gexec
|
|
|
|
-- 항상 최신 비밀번호로 동기화
|
|
SELECT 'ALTER ROLE dispatch_app WITH LOGIN PASSWORD ' || quote_literal(:'app_password')
|
|
\gexec
|
|
|
|
GRANT CONNECT ON DATABASE dispatch_db TO dispatch_app;
|
|
|
|
-- dispatch_db 컨텍스트로 전환
|
|
\connect dispatch_db
|
|
|
|
-- ── updated_at 자동 갱신 트리거 함수 (멱등: CREATE OR REPLACE) ──
|
|
CREATE OR REPLACE FUNCTION dispatch_set_updated_at() RETURNS trigger AS $$
|
|
BEGIN
|
|
NEW.updated_at := now();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- ════════════════════════════════════════════════════════════
|
|
-- 1) 출고 배치 (하루 1회~여러 회 업로드 단위)
|
|
-- 같은 날짜/플랫폼이라도 batch_name 으로 구분해 여러 배치 생성 가능.
|
|
-- ════════════════════════════════════════════════════════════
|
|
CREATE TABLE IF NOT EXISTS dispatch_batches (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
platform TEXT NOT NULL DEFAULT 'TikTok',
|
|
dispatch_date DATE NOT NULL,
|
|
batch_name TEXT NOT NULL DEFAULT '',
|
|
picking_pdf_path TEXT NOT NULL DEFAULT '',
|
|
label_pdf_path TEXT NOT NULL DEFAULT '',
|
|
order_export_path TEXT NOT NULL DEFAULT '',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_dispatch_batches_date ON dispatch_batches (dispatch_date DESC);
|
|
|
|
DROP TRIGGER IF EXISTS trg_dispatch_batches_updated ON dispatch_batches;
|
|
CREATE TRIGGER trg_dispatch_batches_updated
|
|
BEFORE UPDATE ON dispatch_batches
|
|
FOR EACH ROW EXECUTE FUNCTION dispatch_set_updated_at();
|
|
|
|
-- ════════════════════════════════════════════════════════════
|
|
-- 2) 출고 박스 (1박스 = 1행 = 1작업카드)
|
|
-- 묶음 기준: package_id > tracking_id > order_id (파서가 결정).
|
|
-- 받는 사람(recipient_*) 정보 보관 — 작업 카드 표시 + 출고 엑셀 생성용.
|
|
-- seq 는 배치 내 박스 표시 순번(No).
|
|
-- ════════════════════════════════════════════════════════════
|
|
CREATE TABLE IF NOT EXISTS dispatch_parcels (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
batch_id BIGINT NOT NULL REFERENCES dispatch_batches(id) ON DELETE CASCADE,
|
|
seq INTEGER NOT NULL DEFAULT 0,
|
|
order_id TEXT NOT NULL DEFAULT '',
|
|
package_id TEXT NOT NULL DEFAULT '',
|
|
tracking_id TEXT NOT NULL DEFAULT '',
|
|
shipping_provider TEXT NOT NULL DEFAULT '',
|
|
recipient_name TEXT NOT NULL DEFAULT '',
|
|
recipient_phone TEXT NOT NULL DEFAULT '',
|
|
recipient_address TEXT NOT NULL DEFAULT '',
|
|
product_ready BOOLEAN NOT NULL DEFAULT FALSE,
|
|
packed BOOLEAN NOT NULL DEFAULT FALSE,
|
|
label_attached BOOLEAN NOT NULL DEFAULT FALSE,
|
|
handed_to_kagayaku BOOLEAN NOT NULL DEFAULT FALSE,
|
|
courier_scanned BOOLEAN NOT NULL DEFAULT FALSE,
|
|
memo TEXT NOT NULL DEFAULT '',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_dispatch_parcels_batch ON dispatch_parcels (batch_id, seq);
|
|
CREATE INDEX IF NOT EXISTS idx_dispatch_parcels_order ON dispatch_parcels (order_id);
|
|
CREATE INDEX IF NOT EXISTS idx_dispatch_parcels_tracking ON dispatch_parcels (tracking_id);
|
|
CREATE INDEX IF NOT EXISTS idx_dispatch_parcels_package ON dispatch_parcels (package_id);
|
|
|
|
DROP TRIGGER IF EXISTS trg_dispatch_parcels_updated ON dispatch_parcels;
|
|
CREATE TRIGGER trg_dispatch_parcels_updated
|
|
BEFORE UPDATE ON dispatch_parcels
|
|
FOR EACH ROW EXECUTE FUNCTION dispatch_set_updated_at();
|
|
|
|
-- ════════════════════════════════════════════════════════════
|
|
-- 3) 박스 안 상품 (1박스 N상품, 같은 SKU 는 수량 합산되어 1행)
|
|
-- ════════════════════════════════════════════════════════════
|
|
CREATE TABLE IF NOT EXISTS dispatch_items (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
parcel_id BIGINT NOT NULL REFERENCES dispatch_parcels(id) ON DELETE CASCADE,
|
|
seller_sku TEXT NOT NULL DEFAULT '',
|
|
product_name TEXT NOT NULL DEFAULT '',
|
|
quantity INTEGER NOT NULL DEFAULT 1
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_dispatch_items_parcel ON dispatch_items (parcel_id);
|
|
CREATE INDEX IF NOT EXISTS idx_dispatch_items_sku ON dispatch_items (seller_sku);
|
|
|
|
-- ════════════════════════════════════════════════════════════
|
|
-- 4) 작업 상태 변경 로그 (감사/추적용)
|
|
-- ════════════════════════════════════════════════════════════
|
|
CREATE TABLE IF NOT EXISTS dispatch_logs (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
parcel_id BIGINT NOT NULL REFERENCES dispatch_parcels(id) ON DELETE CASCADE,
|
|
action TEXT NOT NULL DEFAULT '',
|
|
old_value TEXT NOT NULL DEFAULT '',
|
|
new_value TEXT NOT NULL DEFAULT '',
|
|
worker_name TEXT NOT NULL DEFAULT '',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_dispatch_logs_parcel ON dispatch_logs (parcel_id);
|
|
|
|
-- ════════════════════════════════════════════════════════════
|
|
-- 5) 권한 (dispatch_app: CRUD only)
|
|
-- ════════════════════════════════════════════════════════════
|
|
GRANT USAGE ON SCHEMA public TO dispatch_app;
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON
|
|
dispatch_batches, dispatch_parcels, dispatch_items, dispatch_logs
|
|
TO dispatch_app;
|
|
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO dispatch_app;
|
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO dispatch_app;
|
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
|
GRANT USAGE, SELECT ON SEQUENCES TO dispatch_app;
|
|
|
|
SELECT 'dispatch_db ready' AS status;
|