ui(cupang): 박스입수량 라벨/겹침, 입고센터 컴팩트 재디자인, 출고 완전삭제
- 박스 입수량 폼: 제품명(스냅샷)→제품명, 헤더그리드로 교체해 입력란 겹침 해소
- 입고센터: 추가 폼 슬림화 + 센터 목록 2열 그리드(한 줄/센터)로 압축
- 출고 상세: 완전삭제 버튼 추가(/{id}/hard-delete, 라인 CASCADE). 취소 처리는 outline 로
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -425,6 +425,23 @@ async def delete(
|
|||||||
return RedirectResponse(url=f"/cupang/{shipment_id}", status_code=303)
|
return RedirectResponse(url=f"/cupang/{shipment_id}", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{shipment_id:int}/hard-delete")
|
||||||
|
async def hard_delete(
|
||||||
|
request: Request,
|
||||||
|
shipment_id: int,
|
||||||
|
user: dict[str, Any] = Depends(_require_user),
|
||||||
|
) -> RedirectResponse:
|
||||||
|
"""완전 삭제(헤더+라인 CASCADE). 달력으로 복귀."""
|
||||||
|
store = _store(request)
|
||||||
|
if store is None:
|
||||||
|
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
||||||
|
try:
|
||||||
|
store.hard_delete(shipment_id=shipment_id)
|
||||||
|
except KeyError:
|
||||||
|
raise HTTPException(status_code=404, detail="출고 묶음을 찾을 수 없습니다.")
|
||||||
|
return RedirectResponse(url="/cupang/", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
# ════════════════════════════════════════════════════════════
|
# ════════════════════════════════════════════════════════════
|
||||||
# 입고센터 관리
|
# 입고센터 관리
|
||||||
# ════════════════════════════════════════════════════════════
|
# ════════════════════════════════════════════════════════════
|
||||||
|
|||||||
@@ -15,10 +15,11 @@
|
|||||||
<h2>박스 입수량 추가 / 수정</h2>
|
<h2>박스 입수량 추가 / 수정</h2>
|
||||||
<span class="erp-muted">같은 제품코드는 덮어씁니다. 쿠팡박스 1박스당 들어가는 수량.</span>
|
<span class="erp-muted">같은 제품코드는 덮어씁니다. 쿠팡박스 1박스당 들어가는 수량.</span>
|
||||||
</div>
|
</div>
|
||||||
<form method="post" action="/cupang/box-rules" class="cpg-rule-grid">
|
<form method="post" action="/cupang/box-rules">
|
||||||
|
<div class="cpg-header-grid">
|
||||||
<label class="erp-field"><span>제품코드 *</span>
|
<label class="erp-field"><span>제품코드 *</span>
|
||||||
<input class="erp-input" type="text" name="product_code" required placeholder="예: MS-1001" /></label>
|
<input class="erp-input" type="text" name="product_code" required placeholder="예: MS-1001" /></label>
|
||||||
<label class="erp-field"><span>제품명(스냅샷)</span>
|
<label class="erp-field"><span>제품명</span>
|
||||||
<input class="erp-input" type="text" name="product_name_snapshot" placeholder="예: 미라네 1호세트" /></label>
|
<input class="erp-input" type="text" name="product_name_snapshot" placeholder="예: 미라네 1호세트" /></label>
|
||||||
<label class="erp-field"><span>박스명</span>
|
<label class="erp-field"><span>박스명</span>
|
||||||
<input class="erp-input" type="text" name="box_name" value="쿠팡박스" /></label>
|
<input class="erp-input" type="text" name="box_name" value="쿠팡박스" /></label>
|
||||||
@@ -26,7 +27,10 @@
|
|||||||
<input class="erp-input" type="number" name="units_per_box" min="1" required /></label>
|
<input class="erp-input" type="number" name="units_per_box" min="1" required /></label>
|
||||||
<label class="erp-field cpg-full"><span>메모</span>
|
<label class="erp-field cpg-full"><span>메모</span>
|
||||||
<input class="erp-input" type="text" name="memo" /></label>
|
<input class="erp-input" type="text" name="memo" /></label>
|
||||||
<div><button type="submit" class="erp-btn erp-btn-primary">저장</button></div>
|
</div>
|
||||||
|
<div class="erp-page-actions" style="margin-top:12px;">
|
||||||
|
<button type="submit" class="erp-btn erp-btn-primary">저장</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -9,65 +9,56 @@
|
|||||||
<a class="erp-btn erp-btn-outline" href="/cupang/">← 달력</a>
|
<a class="erp-btn erp-btn-outline" href="/cupang/">← 달력</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 추가 -->
|
<!-- 추가 (슬림) -->
|
||||||
<div class="erp-card cpg-form-card">
|
<div class="erp-card cpg-form-card cpg-center-add">
|
||||||
<div class="cpg-card-head"><h2>입고센터 추가</h2></div>
|
<h2 class="cpg-center-add-title">입고센터 추가</h2>
|
||||||
<form method="post" action="/cupang/centers" class="cpg-inline-form">
|
<form method="post" action="/cupang/centers" class="cpg-inline-form">
|
||||||
<input class="erp-input" type="text" name="name" placeholder="센터명 (예: 대구3)" required />
|
<input class="erp-input" type="text" name="name" placeholder="센터명 (예: 대구3)" required style="min-width:180px" />
|
||||||
<input class="erp-input" type="number" name="sort_order" value="0" style="max-width:90px" title="정렬순서" />
|
<input class="erp-input" type="number" name="sort_order" value="0" style="width:70px" title="정렬순서" />
|
||||||
<button type="submit" class="erp-btn erp-btn-primary">추가</button>
|
<button type="submit" class="erp-btn erp-btn-primary">추가</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 목록 -->
|
<!-- 목록 (2열 그리드, 한 줄/센터) -->
|
||||||
<div class="erp-card cpg-form-card">
|
<div class="erp-card cpg-form-card">
|
||||||
<div class="cpg-card-head"><h2>센터 목록 ({{ centers|length }})</h2></div>
|
<div class="cpg-card-head">
|
||||||
<div class="erp-table-wrap">
|
<h2>센터 목록 ({{ centers|length }})</h2>
|
||||||
<table class="erp-table">
|
<span class="erp-muted">사용 중 센터는 삭제 시 비활성화됩니다.</span>
|
||||||
<thead>
|
</div>
|
||||||
<tr><th>센터명</th><th>정렬</th><th>상태</th><th>사용중</th><th>동작</th></tr>
|
|
||||||
</thead>
|
<div class="cpg-center-grid">
|
||||||
<tbody>
|
|
||||||
{% for c in centers %}
|
{% for c in centers %}
|
||||||
<tr {% if not c.active %}style="opacity:.55"{% endif %}>
|
<div class="cpg-center-row {% if not c.active %}is-inactive{% endif %}">
|
||||||
<td>
|
<form method="post" action="/cupang/centers/{{ c.id }}/edit" class="cpg-center-edit">
|
||||||
<form method="post" action="/cupang/centers/{{ c.id }}/edit" class="cpg-inline-form">
|
<input class="erp-input cpg-center-name" type="text" name="name" value="{{ c.name }}" />
|
||||||
<input class="erp-input" type="text" name="name" value="{{ c.name }}" style="max-width:160px" />
|
<input class="erp-input cpg-center-sort" type="number" name="sort_order" value="{{ c.sort_order }}" title="정렬" />
|
||||||
<input class="erp-input" type="number" name="sort_order" value="{{ c.sort_order }}" style="max-width:80px" />
|
<button type="submit" class="erp-btn erp-btn-outline cpg-btn-sm" title="이름/정렬 저장">저장</button>
|
||||||
<button type="submit" class="erp-btn erp-btn-outline">수정</button>
|
|
||||||
</form>
|
</form>
|
||||||
</td>
|
<span class="cpg-center-state">
|
||||||
<td>{{ c.sort_order }}</td>
|
{% if c.in_use %}<span class="erp-badge erp-badge-inverse cpg-mini">사용중</span>{% endif %}
|
||||||
<td>
|
{% if c.active %}<span class="erp-badge erp-badge-success cpg-mini">활성</span>
|
||||||
{% if c.active %}<span class="erp-badge erp-badge-success">활성</span>
|
{% else %}<span class="erp-badge erp-badge-neutral cpg-mini">비활성</span>{% endif %}
|
||||||
{% else %}<span class="erp-badge erp-badge-neutral">비활성</span>{% endif %}
|
</span>
|
||||||
</td>
|
<span class="cpg-center-act">
|
||||||
<td>{% if c.in_use %}<span class="erp-badge erp-badge-inverse">사용중</span>{% else %}—{% endif %}</td>
|
|
||||||
<td>
|
|
||||||
<div class="cpg-row-actions">
|
|
||||||
{% if c.active %}
|
{% if c.active %}
|
||||||
<form method="post" action="/cupang/centers/{{ c.id }}/edit" class="cpg-inline-form">
|
<form method="post" action="/cupang/centers/{{ c.id }}/edit" class="cpg-inline-form">
|
||||||
<input type="hidden" name="active" value="0" />
|
<input type="hidden" name="active" value="0" />
|
||||||
<button type="submit" class="erp-btn erp-btn-outline">비활성화</button>
|
<button type="submit" class="erp-btn erp-btn-outline cpg-btn-sm">비활성</button>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<form method="post" action="/cupang/centers/{{ c.id }}/edit" class="cpg-inline-form">
|
<form method="post" action="/cupang/centers/{{ c.id }}/edit" class="cpg-inline-form">
|
||||||
<input type="hidden" name="active" value="1" />
|
<input type="hidden" name="active" value="1" />
|
||||||
<button type="submit" class="erp-btn erp-btn-outline">활성화</button>
|
<button type="submit" class="erp-btn erp-btn-outline cpg-btn-sm">활성</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form method="post" action="/cupang/centers/{{ c.id }}/delete" class="cpg-inline-form"
|
<form method="post" action="/cupang/centers/{{ c.id }}/delete" class="cpg-inline-form"
|
||||||
onsubmit="return confirm('{% if c.in_use %}사용 중인 센터는 비활성화됩니다.{% else %}삭제합니다.{% endif %} 계속할까요?');">
|
onsubmit="return confirm('{% if c.in_use %}사용 중 → 비활성화됩니다.{% else %}삭제합니다.{% endif %} 계속?');">
|
||||||
<button type="submit" class="erp-btn erp-btn-danger">삭제</button>
|
<button type="submit" class="erp-btn erp-btn-danger cpg-btn-sm">삭제</button>
|
||||||
</form>
|
</form>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="erp-muted">※ 출고 데이터에서 사용 중인 센터는 삭제 시 hard delete 되지 않고 비활성화됩니다.</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -20,8 +20,13 @@
|
|||||||
</form>
|
</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('이 출고 묶음을 취소 처리합니다(상태=취소). 계속할까요?');">
|
||||||
<button type="submit" class="erp-btn erp-btn-danger">취소 처리</button>
|
<button type="submit" class="erp-btn erp-btn-outline">취소 처리</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="post" action="/cupang/{{ shipment.id }}/hard-delete" class="cpg-inline-form"
|
||||||
|
onsubmit="return confirm('이 출고를 완전 삭제합니다(복구 불가, 품목 포함). 계속할까요?');">
|
||||||
|
<button type="submit" class="erp-btn erp-btn-danger">삭제</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,31 @@
|
|||||||
.cpg-inline-form { display: inline-flex; gap: 6px; align-items: center; margin: 0; }
|
.cpg-inline-form { display: inline-flex; gap: 6px; align-items: center; margin: 0; }
|
||||||
.cpg-row-actions { display: flex; gap: 6px; flex-wrap: wrap; }
|
.cpg-row-actions { display: flex; gap: 6px; flex-wrap: wrap; }
|
||||||
|
|
||||||
|
/* ── 입고센터 관리 (컴팩트) ── */
|
||||||
|
.cpg-center-add { padding: 12px 16px; }
|
||||||
|
.cpg-center-add-title { font-size: 15px; font-weight: 600; margin: 0 0 8px; }
|
||||||
|
.cpg-btn-sm { padding: 3px 8px; font-size: 12px; }
|
||||||
|
|
||||||
|
.cpg-center-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 6px 16px;
|
||||||
|
}
|
||||||
|
@media (max-width: 820px) { .cpg-center-grid { grid-template-columns: 1fr; } }
|
||||||
|
|
||||||
|
.cpg-center-row {
|
||||||
|
display: flex; align-items: center; gap: 8px;
|
||||||
|
padding: 5px 8px;
|
||||||
|
border: 1px solid var(--color-subtle-ash); border-radius: 10px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.cpg-center-row.is-inactive { opacity: .55; }
|
||||||
|
.cpg-center-edit { display: flex; align-items: center; gap: 6px; flex: 1 1 auto; min-width: 0; }
|
||||||
|
.cpg-center-name { flex: 1 1 auto; min-width: 0; }
|
||||||
|
.cpg-center-sort { width: 56px; flex: 0 0 auto; }
|
||||||
|
.cpg-center-state { display: inline-flex; gap: 4px; flex: 0 0 auto; }
|
||||||
|
.cpg-center-act { display: inline-flex; gap: 4px; flex: 0 0 auto; }
|
||||||
|
|
||||||
/* ── 라인 테이블 ── */
|
/* ── 라인 테이블 ── */
|
||||||
.cpg-lines td { vertical-align: middle; }
|
.cpg-lines td { vertical-align: middle; }
|
||||||
.cpg-lines .cpg-name-sel { width: 100%; min-width: 160px; }
|
.cpg-lines .cpg-name-sel { width: 100%; min-width: 160px; }
|
||||||
|
|||||||
Reference in New Issue
Block a user