diff --git a/scripts/sql/cupang_box_rules_seed.sql b/scripts/sql/cupang_box_rules_seed.sql index 5f20cd9..b08cf04 100644 --- a/scripts/sql/cupang_box_rules_seed.sql +++ b/scripts/sql/cupang_box_rules_seed.sql @@ -14,20 +14,20 @@ BEGIN; INSERT INTO cupang_box_rules (product_code, product_name_snapshot, box_name, units_per_box, memo, active) VALUES - ('MS-1001', '미라네 1호 세트', '쿠팡 2호', 8, '피킹비 750원', TRUE), - ('MS-1002', '미라네 2호 세트', '쿠팡 2호', 8, '피킹비 1100원', TRUE), - ('MS-1003', '미라네 3호 세트', '쿠팡 2호', 8, '피킹비 1200원', TRUE), - ('MS-1004', '미라네 4호 세트', '쿠팡 2호', 8, '피킹비 1000원', TRUE), - ('MS-1005', '미라네 5호 세트', '쿠팡 2호', 8, '피킹비 650원', TRUE), - ('MS-1006', '미라네 6호 세트', '쿠팡 2호', 8, '피킹비 750원', TRUE), - ('MS-1007', '미라네 7호 세트', '쿠팡 2호', 8, '피킹비 850원', TRUE), - ('MS-9812', '황토 김치통 1호세트', '쿠팡 2호', 8, '피킹비 750원', TRUE), - ('MS-9813', '황토 김치통 2호세트', '쿠팡 2호', 8, '피킹비 650원', TRUE), - ('MS-9814', '황토 김치통 3호세트', '쿠팡 2호', 8, '피킹비 750원', TRUE), - ('MS-9815', '황토 김치통 4호세트', '쿠팡 3호', 2, '피킹비 850원', TRUE), - ('MS-9816', '황토 김치통 5호세트', '쿠팡 3호', 2, '피킹비 900원', TRUE), - ('MS-9817', '황토 김치통 6호세트', '쿠팡 6호', 1, '피킹비 1000원', TRUE), - ('MS-9818', '황토 김치통 7호세트', '쿠팡 6호', 1, '피킹비 1100원', TRUE) + ('MS-1001', '미라네 1호 세트', '쿠팡박스', 8, '피킹비 750원 · 2호', TRUE), + ('MS-1002', '미라네 2호 세트', '쿠팡박스', 8, '피킹비 1100원 · 2호', TRUE), + ('MS-1003', '미라네 3호 세트', '쿠팡박스', 8, '피킹비 1200원 · 2호', TRUE), + ('MS-1004', '미라네 4호 세트', '쿠팡박스', 8, '피킹비 1000원 · 2호', TRUE), + ('MS-1005', '미라네 5호 세트', '쿠팡박스', 8, '피킹비 650원 · 2호', TRUE), + ('MS-1006', '미라네 6호 세트', '쿠팡박스', 8, '피킹비 750원 · 2호', TRUE), + ('MS-1007', '미라네 7호 세트', '쿠팡박스', 8, '피킹비 850원 · 2호', TRUE), + ('MS-9812', '황토 김치통 1호세트', '쿠팡박스', 8, '피킹비 750원 · 2호', TRUE), + ('MS-9813', '황토 김치통 2호세트', '쿠팡박스', 8, '피킹비 650원 · 2호', TRUE), + ('MS-9814', '황토 김치통 3호세트', '쿠팡박스', 8, '피킹비 750원 · 2호', TRUE), + ('MS-9815', '황토 김치통 4호세트', '쿠팡박스', 2, '피킹비 850원 · 3호', TRUE), + ('MS-9816', '황토 김치통 5호세트', '쿠팡박스', 2, '피킹비 900원 · 3호', TRUE), + ('MS-9817', '황토 김치통 6호세트', '쿠팡박스', 1, '피킹비 1000원 · 6호', TRUE), + ('MS-9818', '황토 김치통 7호세트', '쿠팡박스', 1, '피킹비 1100원 · 6호', TRUE) ON CONFLICT (product_code) DO UPDATE SET product_name_snapshot = EXCLUDED.product_name_snapshot, box_name = EXCLUDED.box_name, diff --git a/scripts/sql/cupang_box_rules_seed2.sql b/scripts/sql/cupang_box_rules_seed2.sql index 1ca5c37..02c0352 100644 --- a/scripts/sql/cupang_box_rules_seed2.sql +++ b/scripts/sql/cupang_box_rules_seed2.sql @@ -15,41 +15,42 @@ -- ════════════════════════════════════════════════════════════ BEGIN; +-- box_no: 원자료의 박스 호수. 박스명은 '쿠팡박스' 로 통일하고 호수는 memo 에 남긴다. CREATE TEMP TABLE _seed2 ( product_name TEXT, picking_fee INTEGER, units_per_box INTEGER, - box_name TEXT + box_no TEXT ) ON COMMIT DROP; -INSERT INTO _seed2 (product_name, picking_fee, units_per_box, box_name) VALUES - ('미라클통 550x4', 700, 40, '쿠팡 2호'), - ('미라클통 550x6', 700, 30, '쿠팡 2호'), - ('미라클통 750x5', 700, 30, '쿠팡 2호'), - ('미라클통 950x4', 700, 24, '쿠팡 2호'), - ('미라클통 950x6', 700, 18, '쿠팡 2호'), - ('미라클통 1800x4', 700, 16, '쿠팡 2호'), - ('미라클통 1800x6', 700, 14, '쿠팡 2호'), - ('미라클통 2200x4', 700, 14, '쿠팡 2호'), - ('미라클통 2200x6', 700, 10, '쿠팡 2호'), - ('미라클통 3200x4', 700, 10, '쿠팡 2호'), - ('빠져락 x1', 500, 35, '쿠팡 2호'), - ('빠져락 x3', 580, 8, '쿠팡 2호'), - ('빠져락 x5', 780, 8, '쿠팡 2호'), - ('미라클백 2.5L', 500, 30, '쿠팡 5호'), - ('미라클백 6.0L', 500, 23, '쿠팡 6호'), - ('멀티 5,000ml x2', 700, 8, '쿠팡 2호'), - ('멀티 5,000ml x4', 680, 2, '쿠팡 3호'), - ('점보 7,000ml x2', 700, 6, '쿠팡 2호'), - ('점보 황토 7,000ml x2', 700, 6, '쿠팡 2호'); +INSERT INTO _seed2 (product_name, picking_fee, units_per_box, box_no) VALUES + ('미라클통 550x4', 700, 40, '2호'), + ('미라클통 550x6', 700, 30, '2호'), + ('미라클통 750x5', 700, 30, '2호'), + ('미라클통 950x4', 700, 24, '2호'), + ('미라클통 950x6', 700, 18, '2호'), + ('미라클통 1800x4', 700, 16, '2호'), + ('미라클통 1800x6', 700, 14, '2호'), + ('미라클통 2200x4', 700, 14, '2호'), + ('미라클통 2200x6', 700, 10, '2호'), + ('미라클통 3200x4', 700, 10, '2호'), + ('빠져락 x1', 500, 35, '2호'), + ('빠져락 x3', 580, 8, '2호'), + ('빠져락 x5', 780, 8, '2호'), + ('미라클백 2.5L', 500, 30, '5호'), + ('미라클백 6.0L', 500, 23, '6호'), + ('멀티 5,000ml x2', 700, 8, '2호'), + ('멀티 5,000ml x4', 680, 2, '3호'), + ('점보 7,000ml x2', 700, 6, '2호'), + ('점보 황토 7,000ml x2', 700, 6, '2호'); INSERT INTO cupang_box_rules (product_code, product_name_snapshot, box_name, units_per_box, memo, active) SELECT p.product_code, p.product_name, - s.box_name, + '쿠팡박스', s.units_per_box, - '피킹비 ' || s.picking_fee || '원', + '피킹비 ' || s.picking_fee || '원 · ' || s.box_no, TRUE FROM _seed2 s JOIN cupang_products p @@ -65,7 +66,7 @@ SET product_name_snapshot = EXCLUDED.product_name_snapshot, -- 카탈로그에 없어 입력되지 않은 제품 (수기 추가 후 재실행 대상) SELECT s.product_name AS "카탈로그_미등록_제품명", s.units_per_box AS "입수량", - s.box_name AS "박스이름" + s.box_no AS "박스호수" FROM _seed2 s WHERE NOT EXISTS ( SELECT 1 FROM cupang_products p diff --git a/scripts/sql/cupang_box_rules_unify_box_name.sql b/scripts/sql/cupang_box_rules_unify_box_name.sql new file mode 100644 index 0000000..e375598 --- /dev/null +++ b/scripts/sql/cupang_box_rules_unify_box_name.sql @@ -0,0 +1,43 @@ +-- ════════════════════════════════════════════════════════════ +-- 쿠팡 밀크런 — 박스 입수량 규칙의 박스명을 '쿠팡박스' 로 통일 (cupang_db) +-- +-- 실행 전 반드시 백업: +-- docker exec postgres-db pg_dump -U postgres -d cupang_db -t cupang_box_rules \ +-- > ~/backup/cupang_box_rules_$(date +%F_%H%M).sql +-- +-- 실행: +-- docker exec -i postgres-db psql -U postgres -d cupang_db \ +-- < /opt/www/main/scripts/sql/cupang_box_rules_unify_box_name.sql +-- +-- 주의: 박스명은 "자투리 혼합 박스" 계산에서 같은 박스끼리만 섞는 기준이다. +-- 통일 후에는 2호/3호/6호 자투리가 한 박스에 섞여 계산된다. +-- 기존 호수(2호/3호/6호)는 없어지지 않게 memo 뒤에 옮겨 적는다. +-- ════════════════════════════════════════════════════════════ +BEGIN; + +-- 변경 전 상태 +SELECT box_name AS "변경전_박스명", count(*) AS "건수" +FROM cupang_box_rules +GROUP BY box_name +ORDER BY box_name; + +UPDATE cupang_box_rules +SET memo = btrim( + coalesce(memo, '') + || CASE WHEN coalesce(memo, '') = '' THEN '' ELSE ' · ' END + || regexp_replace(box_name, '^쿠팡\s*', '') + ), + box_name = '쿠팡박스' +WHERE box_name IS DISTINCT FROM '쿠팡박스'; + +COMMIT; + +-- 변경 후 확인 (박스명이 '쿠팡박스' 한 줄이어야 정상) +SELECT box_name AS "변경후_박스명", count(*) AS "건수" +FROM cupang_box_rules +GROUP BY box_name +ORDER BY box_name; + +SELECT product_code, product_name_snapshot, box_name, units_per_box, memo +FROM cupang_box_rules +ORDER BY product_code; diff --git a/scripts/sql/itemcode_db_grant_ro_autotrigger.sql b/scripts/sql/itemcode_db_grant_ro_autotrigger.sql new file mode 100644 index 0000000..ef9e59a --- /dev/null +++ b/scripts/sql/itemcode_db_grant_ro_autotrigger.sql @@ -0,0 +1,63 @@ +-- ════════════════════════════════════════════════════════════ +-- itemcode_db: itemcode_ro 읽기 권한 재발 방지 (이벤트 트리거) +-- +-- 증상: 말레이시아/쿠팡/dispatch 에서 세트 BOM "누락"·콤보 미분해· +-- 사방넷 출고 A열 빈칸. 진짜 원인은 데이터가 아니라 권한 — +-- "permission denied for table set_components(또는 single_items, +-- set_items)". itemcode_db 를 재import/재생성하면 itemcode_ro +-- GRANT 가 통째로 소실돼 반복 재발(2026-06-16/22/23/26/30, 07-01). +-- +-- main-app 은 itemcode_db 를 읽기 전용(itemcode_ro)으로만 쓴다. +-- import 절차는 다른 앱(상품코드 마스터)에 있어 이 저장소가 못 건드림. +-- 그래서 import 절차와 무관하게, 테이블이 생길 때마다 postgres 가 +-- 자동으로 itemcode_ro 에 SELECT 를 부여하도록 이벤트 트리거를 건다. +-- pg_restore 로 테이블이 drop→recreate 돼도 트리거가 즉시 재부여. +-- +-- 실행(서버, superuser): +-- docker exec -i postgres-db psql -U postgres -d itemcode_db \ +-- -f - < scripts/sql/itemcode_db_grant_ro_autotrigger.sql +-- 또는 파일 내용을 heredoc 으로 붙여넣기. +-- +-- ⚠ 이벤트 트리거는 "테이블 drop→recreate"(pg_restore --clean, TRUNCATE +-- 아님) 케이스를 자동 커버. 만약 import 가 DROP DATABASE 후 재생성이면 +-- 트리거 자체도 사라지므로, DB 재생성 직후 이 스크립트를 1회 다시 실행. +-- +-- superuser(postgres)로 실행. 멱등. +-- ════════════════════════════════════════════════════════════ + +-- 1) 지금 존재하는 테이블에 즉시 부여 (현재고 복구) +GRANT SELECT ON ALL TABLES IN SCHEMA public TO itemcode_ro; + +-- 2) 소유자(king) 기준 default privileges — king 이 만드는 신규 테이블 자동 +ALTER DEFAULT PRIVILEGES FOR ROLE king IN SCHEMA public + GRANT SELECT ON TABLES TO itemcode_ro; + +-- 3) 이벤트 트리거 — 소유자·생성 방식 무관하게 CREATE TABLE 마다 자동 GRANT +CREATE OR REPLACE FUNCTION public.grant_select_to_itemcode_ro() + RETURNS event_trigger + LANGUAGE plpgsql + SECURITY DEFINER +AS $$ +DECLARE + obj record; +BEGIN + FOR obj IN + SELECT object_identity + FROM pg_event_trigger_ddl_commands() + WHERE command_tag = 'CREATE TABLE' + AND schema_name = 'public' + LOOP + EXECUTE format('GRANT SELECT ON %s TO itemcode_ro', obj.object_identity); + END LOOP; +END; +$$; + +DROP EVENT TRIGGER IF EXISTS trg_grant_select_to_itemcode_ro; +CREATE EVENT TRIGGER trg_grant_select_to_itemcode_ro + ON ddl_command_end + WHEN TAG IN ('CREATE TABLE') + EXECUTE FUNCTION public.grant_select_to_itemcode_ro(); + +-- 확인: +-- \dp set_components → itemcode_ro=r/king 표시되면 OK +-- \dy → trg_grant_select_to_itemcode_ro 등록 확인