# Phase 1 DB 스키마 (상품등록/전시 + SKU + 판매채널 + 결제수단 + 자사핀재고 + 이미지) - 신규 테이블은 모두 `pfy_` 접두사 사용 - MariaDB (InnoDB, `utf8mb4`) 기준 - 레거시/기존 회원/관리자 테이블과 FK는 **1단계에서는 걸지 않음(컬럼만 준비)** → 2단계(주문/결제)부터 FK/연동 강화 --- ## 1) `pfy_categories` : 1차/2차 카테고리 트리 - `parent_id` 로 1차/2차 구성 - `sort` / `is_active` 로 전시 제어 ```sql CREATE TABLE IF NOT EXISTS pfy_categories ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'PK', parent_id BIGINT UNSIGNED NULL COMMENT '상위 카테고리 ID (1차는 NULL, 2차는 1차의 id)', name VARCHAR(100) NOT NULL COMMENT '카테고리명', slug VARCHAR(120) NOT NULL COMMENT 'URL/식별용 슬러그(유니크 권장)', sort INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '정렬값(작을수록 먼저)', is_active TINYINT(1) NOT NULL DEFAULT 1 COMMENT '노출 여부(1=노출,0=숨김)', icon_path VARCHAR(255) NULL COMMENT '아이콘 경로(선택)', banner_path VARCHAR(255) NULL COMMENT '배너 경로(선택)', desc_short VARCHAR(255) NULL COMMENT '카테고리 짧은 설명(선택)', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성일', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일', PRIMARY KEY (id), UNIQUE KEY uk_pfy_categories_slug (slug), KEY idx_pfy_categories_parent_sort (parent_id, sort) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='[PFY] 카테고리(1차/2차 트리)'; ``` --- ## 2) `pfy_media_files` : 업로드된 파일(상품 이미지 등) 메타데이터 - 업로드된 이미지를 “선택”해서 상품에 연결하는 용도 ```sql CREATE TABLE IF NOT EXISTS pfy_media_files ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'PK', kind ENUM('image','file') NOT NULL DEFAULT 'image' COMMENT '파일 종류', disk VARCHAR(40) NOT NULL DEFAULT 'public' COMMENT '스토리지 디스크명(Laravel disks)', path VARCHAR(500) NOT NULL COMMENT '스토리지 상대 경로', original_name VARCHAR(255) NULL COMMENT '원본 파일명', mime VARCHAR(120) NULL COMMENT 'MIME 타입', size_bytes BIGINT UNSIGNED NULL COMMENT '파일 크기(bytes)', width INT UNSIGNED NULL COMMENT '이미지 폭(px, 이미지인 경우)', height INT UNSIGNED NULL COMMENT '이미지 높이(px, 이미지인 경우)', checksum_sha256 CHAR(64) NULL COMMENT '파일 무결성 체크섬(선택)', uploaded_by_admin_id BIGINT UNSIGNED NULL COMMENT '업로드 관리자 ID(레거시/기존 admin_users 연동 예정)', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성일', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일', PRIMARY KEY (id), KEY idx_pfy_media_files_kind (kind), KEY idx_pfy_media_files_created (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='[PFY] 업로드 파일 메타(상품이미지 선택/재사용)'; ``` --- ## 3) `pfy_products` : 상품(전시 단위) - 상품명/타입/판매기간/상태/대표이미지/매입가능 여부 ```sql CREATE TABLE IF NOT EXISTS pfy_products ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'PK', category_id BIGINT UNSIGNED NOT NULL COMMENT '2차 카테고리 ID(pfy_categories.id)', name VARCHAR(160) NOT NULL COMMENT '상품명(수기)', type ENUM('online','delivery') NOT NULL DEFAULT 'online' COMMENT '상품타입(온라인/배송)', is_buyback_allowed ENUM('Y','N') NOT NULL DEFAULT 'Y' COMMENT '매입가능여부(Y=가능,N=불가)', sale_period_type ENUM('always','ranged') NOT NULL DEFAULT 'always' COMMENT '판매기간 타입(상시/기간설정)', sale_start_at DATETIME NULL COMMENT '판매 시작일(기간설정일 때)', sale_end_at DATETIME NULL COMMENT '판매 종료일(기간설정일 때)', status ENUM('active','hidden','soldout') NOT NULL DEFAULT 'active' COMMENT '노출상태(노출/숨김/품절)', main_image_id BIGINT UNSIGNED NULL COMMENT '대표 이미지(pfy_media_files.id)', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성일', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일', PRIMARY KEY (id), KEY idx_pfy_products_category (category_id), KEY idx_pfy_products_status (status), KEY idx_pfy_products_sale_period (sale_period_type, sale_start_at, sale_end_at), CONSTRAINT fk_pfy_products_category FOREIGN KEY (category_id) REFERENCES pfy_categories(id) ON UPDATE CASCADE ON DELETE RESTRICT, CONSTRAINT fk_pfy_products_main_image FOREIGN KEY (main_image_id) REFERENCES pfy_media_files(id) ON UPDATE CASCADE ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='[PFY] 상품(전시 단위)'; ``` --- ## 4) `pfy_product_contents` : 상품 상세 에디터 3종(1:1) - 상세설명/이용안내/주의사항 HTML 저장 ```sql CREATE TABLE IF NOT EXISTS pfy_product_contents ( product_id BIGINT UNSIGNED NOT NULL COMMENT 'PK & FK(pfy_products.id)', detail_html LONGTEXT NULL COMMENT '상세설명(HTML)', guide_html LONGTEXT NULL COMMENT '이용안내(HTML)', caution_html LONGTEXT NULL COMMENT '주의사항(HTML)', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성일', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일', PRIMARY KEY (product_id), CONSTRAINT fk_pfy_product_contents_product FOREIGN KEY (product_id) REFERENCES pfy_products(id) ON UPDATE CASCADE ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='[PFY] 상품 에디터 컨텐츠(상세/안내/주의)'; ``` --- ## 5) `pfy_product_skus` : 권종/가격 단위(SKU) - 정상가/할인율/판매가(스냅샷 계산 저장 추천) - 재고는 `stock_mode` 로 (연동판매는 `infinite`, 자사핀은 `limited`) ```sql CREATE TABLE `pfy_product_skus` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'PK', `product_id` bigint(20) unsigned NOT NULL COMMENT '상품 ID (pfy_products.id)', `sku_code` varchar(60) DEFAULT NULL COMMENT '내부 SKU 코드(선택). 운영상 필요 시 사용', `face_value` int(10) unsigned NOT NULL COMMENT '권면가(원) 예: 10000', `normal_price` int(10) unsigned NOT NULL COMMENT '정상가(원)', `discount_rate` decimal(5,2) NOT NULL DEFAULT 0.00 COMMENT '할인율(%) 예: 3.50', `sale_price` int(10) unsigned NOT NULL COMMENT '판매가(원) = floor(normal_price*(100-discount_rate)/100)', `status` enum('active','hidden') NOT NULL DEFAULT 'active' COMMENT '노출상태(active=노출, hidden=숨김)', `sort` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '정렬(작을수록 우선)', `created_admin_id` bigint(20) unsigned DEFAULT NULL COMMENT '등록 관리자 ID', `updated_admin_id` bigint(20) unsigned DEFAULT NULL COMMENT '수정 관리자 ID', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성일시', `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일시', PRIMARY KEY (`id`), UNIQUE KEY `uniq_product_face` (`product_id`, `face_value`), KEY `idx_product` (`product_id`), KEY `idx_status_sort` (`status`, `sort`, `id`) /* FK를 쓰는 운영정책이면 아래 주석 해제 (기존 DB가 FK 거의 없으면 주석 유지 추천) , CONSTRAINT `fk_pfy_skus_product` FOREIGN KEY (`product_id`) REFERENCES `pfy_products` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE */ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='PFY 상품 SKU(금액권/가격)'; ``` --- ## 6) `pfy_sku_sale_channels` : SKU 판매방식/연동채널 (기본: 1 SKU = 1 판매채널) ### `sale_mode` - `SELF_PIN` : 자사핀 재고 판매 - `VENDOR_API_SHOW` : 연동발행 후 우리 사이트에서 핀 표시 - `VENDOR_SMS` : 업체가 SMS로 즉시 발송(핀 저장 최소화 가능) ### `vendor` - `DANAL` / `KORCULTURE` / `KPREPAID` (필요 시 확장) ```sql CREATE TABLE IF NOT EXISTS pfy_sku_sale_channels ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'PK', sku_id BIGINT UNSIGNED NOT NULL COMMENT 'SKU ID(pfy_product_skus.id)', sale_mode ENUM('SELF_PIN','VENDOR_API_SHOW','VENDOR_SMS') NOT NULL COMMENT '판매모드', vendor ENUM('NONE','DANAL','KORCULTURE','KPREPAID') NOT NULL DEFAULT 'NONE' COMMENT '연동업체(없으면 NONE)', vendor_product_code VARCHAR(40) NULL COMMENT '업체 상품코드(예: DANAL CULTURE, KPREPAID 1231 등)', pg ENUM('NONE','DANAL') NOT NULL DEFAULT 'DANAL' COMMENT '결제 PG(현재 DANAL, 추후 확장)', is_active TINYINT(1) NOT NULL DEFAULT 1 COMMENT '사용여부(1=사용,0=중지)', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성일', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일', PRIMARY KEY (id), UNIQUE KEY uk_pfy_sale_channels_sku (sku_id), KEY idx_pfy_sale_channels_vendor (vendor, vendor_product_code), CONSTRAINT fk_pfy_sale_channels_sku FOREIGN KEY (sku_id) REFERENCES pfy_product_skus(id) ON UPDATE CASCADE ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='[PFY] SKU 판매채널/연동 설정'; ``` --- ## 7) `pfy_payment_methods` : 결제수단 마스터 - `code` 를 PK로 사용(안정적, pivot에 쓰기 편함) ```sql CREATE TABLE IF NOT EXISTS pfy_payment_methods ( code VARCHAR(30) NOT NULL COMMENT '결제수단 코드(PK)', label VARCHAR(60) NOT NULL COMMENT '표시명(관리자/회원)', is_active TINYINT(1) NOT NULL DEFAULT 1 COMMENT '사용여부', sort INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '노출 정렬', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성일', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일', PRIMARY KEY (code), KEY idx_pfy_payment_methods_active (is_active, sort) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='[PFY] 결제수단 마스터'; ``` --- ## 8) `pfy_sku_payment_methods` : SKU별 허용 결제수단(pivot) - SKU마다 가능한 결제수단 체크박스 용도 ```sql CREATE TABLE IF NOT EXISTS pfy_sku_payment_methods ( sku_id BIGINT UNSIGNED NOT NULL COMMENT 'SKU ID(pfy_product_skus.id)', payment_method_code VARCHAR(30) NOT NULL COMMENT '결제수단 코드(pfy_payment_methods.code)', is_enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '허용 여부(1=허용,0=비허용)', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성일', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일', PRIMARY KEY (sku_id, payment_method_code), KEY idx_pfy_sku_pm_method (payment_method_code, is_enabled), CONSTRAINT fk_pfy_sku_pm_sku FOREIGN KEY (sku_id) REFERENCES pfy_product_skus(id) ON UPDATE CASCADE ON DELETE CASCADE, CONSTRAINT fk_pfy_sku_pm_method FOREIGN KEY (payment_method_code) REFERENCES pfy_payment_methods(code) ON UPDATE CASCADE ON DELETE RESTRICT ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='[PFY] SKU별 허용 결제수단 매핑'; ``` --- ## 9) `pfy_pin_batches` : 자사핀 입력 작업 단위(업로드/수기 등록) ```sql CREATE TABLE IF NOT EXISTS pfy_pin_batches ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'PK', sku_id BIGINT UNSIGNED NOT NULL COMMENT 'SKU ID(pfy_product_skus.id)', source_type ENUM('upload','manual') NOT NULL COMMENT '핀 등록 방식(파일/수기)', original_filename VARCHAR(255) NULL COMMENT '업로드 파일명(업로드일 때)', total_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '배치 총 핀 개수', uploaded_by_admin_id BIGINT UNSIGNED NULL COMMENT '업로드 관리자 ID(레거시/기존 admin_users 연동 예정)', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성일', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일', PRIMARY KEY (id), KEY idx_pfy_pin_batches_sku (sku_id, created_at), CONSTRAINT fk_pfy_pin_batches_sku FOREIGN KEY (sku_id) REFERENCES pfy_product_skus(id) ON UPDATE CASCADE ON DELETE RESTRICT ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='[PFY] 자사핀 입력 배치(업로드/수기)'; ``` --- ## 10) `pfy_pin_items` : 핀 1건 단위 재고(암호화 저장) - `pin_enc` : 쌍방향 암호화 저장값(복호화는 권한 통과한 구매자만) - `pin_fingerprint` : 중복핀 방지(유니크) ### `status` - `available` : 사용가능(재고) - `reserved` : 결제 대기/재고 예약 - `sold` : 판매 완료(주문 라인에 귀속될 예정) - `revoked` : 폐기/무효 - `recalled` : 회수(미사용 회수 프로세스에서 사용) > 1단계에서는 주문 테이블이 없으므로 `reserved_order_id`, `sold_order_item_id` 는 FK 없이 컬럼만 준비 ```sql CREATE TABLE IF NOT EXISTS pfy_pin_items ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'PK', batch_id BIGINT UNSIGNED NOT NULL COMMENT '배치 ID(pfy_pin_batches.id)', sku_id BIGINT UNSIGNED NOT NULL COMMENT 'SKU ID(pfy_product_skus.id)', pin_enc TEXT NOT NULL COMMENT '핀 암호문(쌍방향 암호화 결과)', pin_fingerprint CHAR(64) NOT NULL COMMENT '중복 방지 해시(sha256 등) - UNIQUE', status ENUM('available','reserved','sold','revoked','recalled') NOT NULL DEFAULT 'available' COMMENT '재고 상태', reserved_order_id BIGINT UNSIGNED NULL COMMENT '예약된 주문 ID(2단계에서 orders FK 예정)', reserved_until DATETIME NULL COMMENT '예약 만료 시각(스케줄러로 자동 해제)', sold_order_item_id BIGINT UNSIGNED NULL COMMENT '판매된 주문아이템 ID(2단계에서 order_items FK 예정)', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성일', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일', PRIMARY KEY (id), UNIQUE KEY uk_pfy_pin_items_fingerprint (pin_fingerprint), KEY idx_pfy_pin_items_sku_status (sku_id, status), KEY idx_pfy_pin_items_batch (batch_id), KEY idx_pfy_pin_items_reserved_until (status, reserved_until), CONSTRAINT fk_pfy_pin_items_batch FOREIGN KEY (batch_id) REFERENCES pfy_pin_batches(id) ON UPDATE CASCADE ON DELETE RESTRICT, CONSTRAINT fk_pfy_pin_items_sku FOREIGN KEY (sku_id) REFERENCES pfy_product_skus(id) ON UPDATE CASCADE ON DELETE RESTRICT ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='[PFY] 자사핀 재고(암호화 저장)'; ``` --- ## (선택) 결제수단 초기 데이터 예시 ```sql INSERT INTO pfy_payment_methods(code,label,sort) VALUES ('CARD_GENERAL','신용카드(일반)',10), ('CARD_CASHBACK','신용카드(환급성)',11), ('MOBILE','휴대폰',20), ('VBANK','무통장입금',30), ('KBANKPAY','케이뱅크페이',40), ('PAYCOIN','페이코인',50); ```