feat(cupang): 출고방식 파렛트, 상태/문서번호 폼 제거, 폼·상세 레이아웃 개편

- 출고방식 선택지에 "파렛트" 추가
- 신규/수정 폼에서 상태·문서번호 입력 제거 (status는 작성중 기본 + 취소처리로만 변경)
- document_no DB 컬럼 제거: init.sql 반영 + 기존 DB용 002 드롭 마이그레이션(승인 후 실행)
- 폼: 공통 헤더(좌) / 품목 라인(우) 2열 배치
- 상세: 엑셀·상태변경 버튼 삭제, ◀◀ 달력(검정) 좌측, 수정 우측 정렬, 문서번호 표시 제거

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-05-30 05:07:21 +09:00
parent 4113a034f4
commit d9ea7550b0
8 changed files with 48 additions and 42 deletions
+3 -6
View File
@@ -323,14 +323,13 @@ class CupangDBStore:
row = conn.execute( row = conn.execute(
""" """
INSERT INTO cupang_shipments INSERT INTO cupang_shipments
(document_no, created_by, document_date, ship_date, (created_by, document_date, ship_date,
center_arrival_date, center_id, center_name_snapshot, center_arrival_date, center_id, center_name_snapshot,
ship_method, outbound_summary, worker, status, memo) ship_method, outbound_summary, worker, status, memo)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
RETURNING * RETURNING *
""", """,
( (
h["document_no"] or None,
created_by.lower().strip(), created_by.lower().strip(),
h["document_date"], h["document_date"],
h["ship_date"], h["ship_date"],
@@ -360,7 +359,7 @@ class CupangDBStore:
row = conn.execute( row = conn.execute(
""" """
UPDATE cupang_shipments UPDATE cupang_shipments
SET document_no = %s, document_date = %s, ship_date = %s, SET document_date = %s, ship_date = %s,
center_arrival_date = %s, center_id = %s, center_arrival_date = %s, center_id = %s,
center_name_snapshot = %s, ship_method = %s, center_name_snapshot = %s, ship_method = %s,
outbound_summary = %s, worker = %s, memo = %s outbound_summary = %s, worker = %s, memo = %s
@@ -368,7 +367,6 @@ class CupangDBStore:
RETURNING * RETURNING *
""", """,
( (
h["document_no"] or None,
h["document_date"], h["document_date"],
h["ship_date"], h["ship_date"],
h["center_arrival_date"], h["center_arrival_date"],
@@ -526,7 +524,6 @@ class CupangDBStore:
center_id = None center_id = None
return { return {
"document_no": str(header.get("document_no") or "").strip(),
"document_date": document_date, "document_date": document_date,
"ship_date": ship_date, "ship_date": ship_date,
"center_arrival_date": center_arrival_date, "center_arrival_date": center_arrival_date,
-6
View File
@@ -248,7 +248,6 @@ async def new_form(request: Request) -> HTMLResponse:
async def create( async def create(
request: Request, request: Request,
lines_json: str = Form("[]"), lines_json: str = Form("[]"),
document_no: str = Form(""),
document_date: str = Form(...), document_date: str = Form(...),
ship_date: str = Form(...), ship_date: str = Form(...),
center_arrival_date: str = Form(...), center_arrival_date: str = Form(...),
@@ -257,7 +256,6 @@ async def create(
ship_method: str = Form("택배"), ship_method: str = Form("택배"),
outbound_summary: str = Form(""), outbound_summary: str = Form(""),
worker: str = Form(""), worker: str = Form(""),
status: str = Form("작성중"),
memo: str = Form(""), memo: str = Form(""),
user: dict[str, Any] = Depends(_require_user), user: dict[str, Any] = Depends(_require_user),
) -> RedirectResponse: ) -> RedirectResponse:
@@ -265,7 +263,6 @@ async def create(
if store is None: if store is None:
raise HTTPException(status_code=503, detail="cupang_db 미설정") raise HTTPException(status_code=503, detail="cupang_db 미설정")
header = { header = {
"document_no": document_no,
"document_date": document_date, "document_date": document_date,
"ship_date": ship_date, "ship_date": ship_date,
"center_arrival_date": center_arrival_date, "center_arrival_date": center_arrival_date,
@@ -274,7 +271,6 @@ async def create(
"ship_method": ship_method, "ship_method": ship_method,
"outbound_summary": outbound_summary, "outbound_summary": outbound_summary,
"worker": worker, "worker": worker,
"status": status,
"memo": memo, "memo": memo,
} }
try: try:
@@ -351,7 +347,6 @@ async def update(
request: Request, request: Request,
shipment_id: int, shipment_id: int,
lines_json: str = Form("[]"), lines_json: str = Form("[]"),
document_no: str = Form(""),
document_date: str = Form(...), document_date: str = Form(...),
ship_date: str = Form(...), ship_date: str = Form(...),
center_arrival_date: str = Form(...), center_arrival_date: str = Form(...),
@@ -367,7 +362,6 @@ async def update(
if store is None: if store is None:
raise HTTPException(status_code=503, detail="cupang_db 미설정") raise HTTPException(status_code=503, detail="cupang_db 미설정")
header = { header = {
"document_no": document_no,
"document_date": document_date, "document_date": document_date,
"ship_date": ship_date, "ship_date": ship_date,
"center_arrival_date": center_arrival_date, "center_arrival_date": center_arrival_date,
+1 -1
View File
@@ -21,7 +21,7 @@ STATUSES: tuple[str, ...] = (
) )
# 출고방식 기본 후보 (자유 입력 허용, 아래는 select 기본값) # 출고방식 기본 후보 (자유 입력 허용, 아래는 select 기본값)
SHIP_METHODS: tuple[str, ...] = ("택배", "직접배송", "화물", "기타") SHIP_METHODS: tuple[str, ...] = ("택배", "직접배송", "화물", "파렛트", "기타")
# 엑셀/시트 컬럼 순서 — 기존 구글시트와 동일하게 유지 # 엑셀/시트 컬럼 순서 — 기존 구글시트와 동일하게 유지
SHEET_COLUMNS: tuple[str, ...] = ( SHEET_COLUMNS: tuple[str, ...] = (
@@ -6,18 +6,7 @@
<section class="cpg"> <section class="cpg">
<div class="erp-page-actions cpg-actions"> <div class="erp-page-actions cpg-actions">
<a class="erp-btn erp-btn-primary" href="/cupang/{{ shipment.id }}/edit">수정</a> <a class="erp-btn erp-btn-primary" href="/cupang/?date={{ shipment.ship_date }}">◀◀ 달력</a>
<a class="erp-btn erp-btn-outline" href="/cupang/{{ shipment.id }}/export">엑셀</a>
<a class="erp-btn erp-btn-outline" href="/cupang/?date={{ shipment.ship_date }}">달력으로</a>
<form method="post" action="/cupang/{{ shipment.id }}/status" class="cpg-inline-form">
<select class="erp-select" name="status">
{% for s in statuses %}
<option value="{{ s }}" {% if shipment.status == s %}selected{% endif %}>{{ s }}</option>
{% endfor %}
</select>
<button type="submit" class="erp-btn erp-btn-outline">상태 변경</button>
</form>
<form method="post" action="/cupang/{{ shipment.id }}/delete" class="cpg-inline-form" <form method="post" action="/cupang/{{ shipment.id }}/delete" class="cpg-inline-form"
onsubmit="return confirm('이 출고 묶음을 취소 처리합니다(상태=취소). 계속할까요?');"> onsubmit="return confirm('이 출고 묶음을 취소 처리합니다(상태=취소). 계속할까요?');">
@@ -28,6 +17,8 @@
onsubmit="return confirm('이 출고를 완전 삭제합니다(복구 불가, 품목 포함). 계속할까요?');"> onsubmit="return confirm('이 출고를 완전 삭제합니다(복구 불가, 품목 포함). 계속할까요?');">
<button type="submit" class="erp-btn erp-btn-danger">삭제</button> <button type="submit" class="erp-btn erp-btn-danger">삭제</button>
</form> </form>
<a class="erp-btn erp-btn-primary cpg-push-right" href="/cupang/{{ shipment.id }}/edit">수정</a>
</div> </div>
<!-- 헤더 --> <!-- 헤더 -->
@@ -48,7 +39,6 @@
<div><dt>입고센터</dt><dd>{{ shipment.center_name_snapshot or '—' }}</dd></div> <div><dt>입고센터</dt><dd>{{ shipment.center_name_snapshot or '—' }}</dd></div>
<div><dt>출고방식</dt><dd>{{ shipment.ship_method }}</dd></div> <div><dt>출고방식</dt><dd>{{ shipment.ship_method }}</dd></div>
<div><dt>작업자</dt><dd>{{ shipment.worker or '—' }}</dd></div> <div><dt>작업자</dt><dd>{{ shipment.worker or '—' }}</dd></div>
<div><dt>문서번호</dt><dd>{{ shipment.document_no or '—' }}</dd></div>
<div class="cpg-full"><dt>출고/박스 요약</dt><dd>{{ shipment.outbound_summary or '—' }}</dd></div> <div class="cpg-full"><dt>출고/박스 요약</dt><dd>{{ shipment.outbound_summary or '—' }}</dd></div>
<div class="cpg-full"><dt>메모</dt><dd>{{ shipment.memo or '—' }}</dd></div> <div class="cpg-full"><dt>메모</dt><dd>{{ shipment.memo or '—' }}</dd></div>
</dl> </dl>
+8 -15
View File
@@ -9,8 +9,10 @@
<form id="cpg-form" method="post" action="{{ action }}"> <form id="cpg-form" method="post" action="{{ action }}">
<input type="hidden" name="lines_json" id="cpg-lines-json" value="[]" /> <input type="hidden" name="lines_json" id="cpg-lines-json" value="[]" />
<!-- ── 공통 헤더 ── --> <div class="cpg-form-2col">
<div class="erp-card cpg-form-card">
<!-- ── 공통 헤더 (왼쪽) ── -->
<div class="erp-card cpg-form-card cpg-form-head">
<div class="cpg-card-head"><h2>공통 헤더</h2></div> <div class="cpg-card-head"><h2>공통 헤더</h2></div>
<div class="cpg-header-grid"> <div class="cpg-header-grid">
<label class="erp-field"><span>작성일 *</span> <label class="erp-field"><span>작성일 *</span>
@@ -45,17 +47,6 @@
<label class="erp-field"><span>작업자</span> <label class="erp-field"><span>작업자</span>
<input class="erp-input" type="text" name="worker" <input class="erp-input" type="text" name="worker"
value="{{ shipment.worker if shipment else '' }}" /></label> value="{{ shipment.worker if shipment else '' }}" /></label>
{% if mode == 'new' %}
<label class="erp-field"><span>상태</span>
<select class="erp-select" name="status">
{% for s in statuses %}<option value="{{ s }}">{{ s }}</option>{% endfor %}
</select></label>
{% endif %}
<label class="erp-field"><span>문서번호(선택)</span>
<input class="erp-input" type="text" name="document_no"
value="{{ shipment.document_no or '' if shipment else '' }}" /></label>
</div> </div>
<label class="erp-field cpg-full"><span>출고/박스 요약 (수동 보정 메모)</span> <label class="erp-field cpg-full"><span>출고/박스 요약 (수동 보정 메모)</span>
@@ -66,8 +57,8 @@
<textarea class="erp-input" name="memo" rows="2">{{ shipment.memo if shipment else '' }}</textarea></label> <textarea class="erp-input" name="memo" rows="2">{{ shipment.memo if shipment else '' }}</textarea></label>
</div> </div>
<!-- ── 품목 라인 ── --> <!-- ── 품목 라인 (오른쪽) ── -->
<div class="erp-card cpg-form-card"> <div class="erp-card cpg-form-card cpg-form-lines">
<div class="cpg-card-head"> <div class="cpg-card-head">
<h2>품목 라인</h2> <h2>품목 라인</h2>
<span class="erp-muted"> <span class="erp-muted">
@@ -90,6 +81,8 @@
<button type="button" class="erp-btn erp-btn-outline" id="cpg-add-line">+ 라인 추가</button> <button type="button" class="erp-btn erp-btn-outline" id="cpg-add-line">+ 라인 추가</button>
</div> </div>
</div><!-- /cpg-form-2col -->
<div class="erp-page-actions"> <div class="erp-page-actions">
<button type="submit" class="erp-btn erp-btn-primary">저장</button> <button type="submit" class="erp-btn erp-btn-primary">저장</button>
{% if mode == 'edit' %} {% if mode == 'edit' %}
+12
View File
@@ -2,6 +2,8 @@
.cpg { display: flex; flex-direction: column; gap: 16px; } .cpg { display: flex; flex-direction: column; gap: 16px; }
.cpg-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; } .cpg-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
/* 맨 오른쪽으로 밀기 (수정 버튼) */
.cpg-push-right { margin-left: auto; }
/* ── 레이아웃: 왼쪽 큰 달력 + 오른쪽 리스트 ── */ /* ── 레이아웃: 왼쪽 큰 달력 + 오른쪽 리스트 ── */
.cpg-layout { .cpg-layout {
@@ -68,6 +70,16 @@
/* ── 폼 ── */ /* ── 폼 ── */
.cpg-form-card { padding: 16px; margin-bottom: 16px; } .cpg-form-card { padding: 16px; margin-bottom: 16px; }
/* 공통 헤더(좌) + 품목 라인(우) 2열 */
.cpg-form-2col {
display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start;
}
.cpg-form-head { flex: 1 1 380px; min-width: 0; margin-bottom: 0; }
.cpg-form-lines { flex: 1 1 520px; min-width: 0; margin-bottom: 0; }
@media (max-width: 900px) {
.cpg-form-head, .cpg-form-lines { flex-basis: 100%; }
}
.cpg-card-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; } .cpg-card-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }
.cpg-card-head h2 { font-size: 16px; font-weight: 600; margin: 0; } .cpg-card-head h2 { font-size: 16px; font-weight: 600; margin: 0; }
@@ -0,0 +1,21 @@
-- =====================================================================
-- cupang_db 마이그레이션 002 — cupang_shipments.document_no 컬럼 제거
-- =====================================================================
-- 사유: 출고 폼에서 "문서번호" 항목 제거(미사용).
-- 멱등: IF EXISTS. 운영 적용 전 백업 권장(DROP COLUMN 은 되돌릴 수 없음).
--
-- 실행:
-- docker exec -i postgres-db psql -U postgres -d cupang_db \
-- < scripts/sql/cupang_db_002_drop_document_no.sql
--
-- 주의: 이 컬럼에 보관된 값이 있으면 함께 삭제된다. 현재 폼에서 입력받지
-- 않으므로 값이 없거나 NULL 일 가능성이 높다. 확인 후 실행:
-- SELECT count(*) FROM cupang_shipments WHERE document_no IS NOT NULL AND document_no <> '';
-- =====================================================================
\set ON_ERROR_STOP on
\connect cupang_db
ALTER TABLE cupang_shipments DROP COLUMN IF EXISTS document_no;
SELECT 'cupang_db 002 done' AS status;
-1
View File
@@ -116,7 +116,6 @@ CREATE TRIGGER trg_cupang_products_updated
-- ════════════════════════════════════════════════════════════ -- ════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS cupang_shipments ( CREATE TABLE IF NOT EXISTS cupang_shipments (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
document_no TEXT UNIQUE,
created_by TEXT NOT NULL, created_by TEXT NOT NULL,
document_date DATE NOT NULL, document_date DATE NOT NULL,
ship_date DATE NOT NULL, ship_date DATE NOT NULL,