chore(cupang): 박스 입수량 박스명을 '쿠팡박스' 로 통일

운영 DB 일괄 변경 스크립트(cupang_box_rules_unify_box_name.sql) 추가.
기존 호수(2호/3호/6호)는 사라지지 않게 memo 뒤로 옮긴다
(예: "피킹비 750원 · 2호").

시드 SQL 2개도 같은 규칙으로 맞춰, 재실행해도 호수 박스명이
되살아나지 않게 했다.

주의: 박스명은 박스 계산의 "자투리 혼합" 그룹 기준이라, 통일 후에는
2호/3호/6호 자투리가 한 박스에 섞여 계산된다(사용자 확인됨).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 02:22:52 +09:00
parent 07f71d1df0
commit 354eebeaac
4 changed files with 145 additions and 38 deletions
+14 -14
View File
@@ -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,
+25 -24
View File
@@ -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
@@ -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;
@@ -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 등록 확인