ui(cupang): 박스룰 폼 3행 배치 + 목록 컬럼 정리/하드삭제
- 폼: 1행 제품명180/제품코드100, 2행 박스이름/박스당입수량, 3행 메모(전체폭) - 목록 순서 제품명·제품코드·박스명·입수량·메모, 상태열/비활성 제거 - 삭제 버튼=완전삭제(delete_box_rule, 참조 라인 box_rule_id NULL 처리) - 캐시 버전 f Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -197,6 +197,21 @@ class CupangDBStore:
|
||||
if cur.rowcount == 0:
|
||||
raise KeyError(rule_id)
|
||||
|
||||
def delete_box_rule(self, *, rule_id: int) -> None:
|
||||
"""완전 삭제(hard). 라인의 box_rule_id 는 ON DELETE 미설정이므로
|
||||
참조 중이면 FK 위반 가능 → 참조 라인의 box_rule_id 를 먼저 NULL 처리."""
|
||||
with self._pool.connection() as conn:
|
||||
with conn.transaction():
|
||||
conn.execute(
|
||||
"UPDATE cupang_shipment_lines SET box_rule_id = NULL WHERE box_rule_id = %s",
|
||||
(rule_id,),
|
||||
)
|
||||
cur = conn.execute(
|
||||
"DELETE FROM cupang_box_rules WHERE id = %s", (rule_id,)
|
||||
)
|
||||
if cur.rowcount == 0:
|
||||
raise KeyError(rule_id)
|
||||
|
||||
def box_rule_map(self) -> dict[str, dict[str, Any]]:
|
||||
"""product_code → rule. 폼/계산에서 빠르게 조회하기 위한 dict."""
|
||||
return {r["product_code"]: r for r in self.list_box_rules()}
|
||||
|
||||
@@ -594,7 +594,7 @@ async def box_rule_delete(
|
||||
if store is None:
|
||||
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
||||
try:
|
||||
store.deactivate_box_rule(rule_id=rule_id)
|
||||
store.delete_box_rule(rule_id=rule_id)
|
||||
except KeyError:
|
||||
raise HTTPException(status_code=404, detail="규칙을 찾을 수 없습니다.")
|
||||
return RedirectResponse(url="/cupang/box-rules", status_code=303)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530e" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530f" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
@@ -20,24 +20,30 @@
|
||||
<form method="post" action="/cupang/box-rules">
|
||||
<input type="hidden" name="product_name_snapshot" id="brule-name-snap" />
|
||||
<div class="cpg-brule-fields">
|
||||
<label class="erp-field"><span>제품명 *</span>
|
||||
<select class="erp-select" id="brule-name" required>
|
||||
<option value="">— 제품명 선택 —</option>
|
||||
{% for p in products %}
|
||||
<option value="{{ p.product_code }}" data-name="{{ p.product_name }}">{{ p.product_name }}</option>
|
||||
{% endfor %}
|
||||
</select></label>
|
||||
<label class="erp-field"><span>제품코드</span>
|
||||
<input class="erp-input" type="text" name="product_code" id="brule-code" required placeholder="자동" /></label>
|
||||
<label class="erp-field"><span>박스이름</span>
|
||||
<input class="erp-input" type="text" name="box_name" value="쿠팡박스" /></label>
|
||||
<label class="erp-field"><span>박스당 입수량 *</span>
|
||||
<span class="cpg-upb-wrap">
|
||||
<input class="erp-input cpg-brule-upb" type="number" name="units_per_box" min="1" required />
|
||||
<span class="cpg-upb-unit">개</span>
|
||||
</span></label>
|
||||
<label class="erp-field cpg-full"><span>메모</span>
|
||||
<input class="erp-input cpg-brule-memo" type="text" name="memo" /></label>
|
||||
<div class="cpg-brule-row">
|
||||
<label class="erp-field"><span>제품명 *</span>
|
||||
<select class="erp-select cpg-brule-name" id="brule-name" required>
|
||||
<option value="">— 제품명 선택 —</option>
|
||||
{% for p in products %}
|
||||
<option value="{{ p.product_code }}" data-name="{{ p.product_name }}">{{ p.product_name }}</option>
|
||||
{% endfor %}
|
||||
</select></label>
|
||||
<label class="erp-field"><span>제품코드</span>
|
||||
<input class="erp-input cpg-brule-code" type="text" name="product_code" id="brule-code" required placeholder="자동" /></label>
|
||||
</div>
|
||||
<div class="cpg-brule-row">
|
||||
<label class="erp-field"><span>박스이름</span>
|
||||
<input class="erp-input cpg-brule-box" type="text" name="box_name" value="쿠팡박스" /></label>
|
||||
<label class="erp-field"><span>박스당 입수량 *</span>
|
||||
<span class="cpg-upb-wrap">
|
||||
<input class="erp-input cpg-brule-upb" type="number" name="units_per_box" min="1" required />
|
||||
<span class="cpg-upb-unit">개</span>
|
||||
</span></label>
|
||||
</div>
|
||||
<div class="cpg-brule-row">
|
||||
<label class="erp-field cpg-brule-memo-field"><span>메모</span>
|
||||
<input class="erp-input cpg-brule-memo" type="text" name="memo" /></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="erp-page-actions" style="margin-top:12px;">
|
||||
<button type="submit" class="erp-btn erp-btn-primary">저장</button>
|
||||
@@ -68,27 +74,27 @@
|
||||
<div class="erp-table-wrap">
|
||||
<table class="erp-table">
|
||||
<thead>
|
||||
<tr><th>제품코드</th><th>제품명</th><th>박스명</th><th>입수량</th><th>상태</th><th>메모</th><th></th></tr>
|
||||
<tr><th>제품명</th><th>제품코드</th><th>박스명</th><th>입수량</th><th>메모</th><th>동작</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in box_rules %}
|
||||
<tr {% if not r.active %}style="opacity:.55"{% endif %}>
|
||||
<td>{{ r.product_code }}</td>
|
||||
<tr>
|
||||
<td>{{ r.product_name_snapshot or '—' }}</td>
|
||||
<td>{{ r.product_code }}</td>
|
||||
<td>{{ r.box_name }}</td>
|
||||
<td>{{ r.units_per_box }}</td>
|
||||
<td>{% if r.active %}<span class="erp-badge erp-badge-success">활성</span>{% else %}<span class="erp-badge erp-badge-neutral">비활성</span>{% endif %}</td>
|
||||
<td>{{ r.memo or '—' }}</td>
|
||||
<td>
|
||||
{% if r.active %}
|
||||
<form method="post" action="/cupang/box-rules/{{ r.id }}/delete" class="cpg-inline-form"
|
||||
onsubmit="return confirm('비활성화합니다. 계속할까요?');">
|
||||
<button type="submit" class="erp-btn erp-btn-danger">비활성화</button>
|
||||
onsubmit="return confirm('이 규칙을 삭제합니다. 계속할까요?');">
|
||||
<button type="submit" class="erp-btn erp-btn-danger">삭제</button>
|
||||
</form>
|
||||
{% else %}—{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not box_rules %}
|
||||
<tr><td colspan="6" class="erp-muted">등록된 입수량 규칙이 없습니다.</td></tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530e" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530f" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530e" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530f" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530e" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530f" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
@@ -118,4 +118,4 @@
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}<script src="/static/cupang.js?v=20260530e" defer></script>{% endblock %}
|
||||
{% block scripts %}<script src="/static/cupang.js?v=20260530f" defer></script>{% endblock %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530e" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530f" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530e" />{% endblock %}
|
||||
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260530f" />{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="cpg">
|
||||
|
||||
@@ -246,20 +246,21 @@
|
||||
@media (max-width: 1420px) { .cpg-brule-list { flex-basis: auto; width: 100%; } }
|
||||
@media (max-width: 540px) { .cpg-brule-add { flex-basis: 100%; width: 100%; } }
|
||||
|
||||
.cpg-brule-fields { display: flex; flex-wrap: wrap; gap: 12px 16px; }
|
||||
/* 제품명·제품코드·박스이름 140px 고정 (erp.css min-width:240 오버라이드) */
|
||||
.cpg .cpg-brule-fields .erp-field > .erp-input,
|
||||
.cpg .cpg-brule-fields .erp-field > .erp-select {
|
||||
width: 140px; min-width: 140px; max-width: 140px; box-sizing: border-box;
|
||||
}
|
||||
.cpg-brule-fields { display: flex; flex-direction: column; gap: 12px; }
|
||||
.cpg-brule-row { display: flex; gap: 16px; align-items: flex-end; }
|
||||
.cpg-brule-memo-field { flex: 1 1 auto; min-width: 0; }
|
||||
/* 필드별 고정 폭 (erp.css min-width:240 오버라이드) */
|
||||
.cpg .cpg-brule-fields .cpg-brule-name { width: 180px; min-width: 180px; max-width: 180px; box-sizing: border-box; }
|
||||
.cpg .cpg-brule-fields .cpg-brule-code { width: 100px; min-width: 100px; max-width: 100px; box-sizing: border-box; }
|
||||
.cpg .cpg-brule-fields .cpg-brule-box { width: 140px; min-width: 140px; max-width: 140px; box-sizing: border-box; }
|
||||
/* 박스당 입수량 60px + "개" */
|
||||
.cpg-upb-wrap { display: inline-flex; align-items: center; gap: 4px; }
|
||||
.cpg .cpg-brule-fields .cpg-upb-wrap > .cpg-brule-upb {
|
||||
width: 60px; min-width: 60px; max-width: 60px; box-sizing: border-box;
|
||||
}
|
||||
.cpg-upb-unit { font-size: 13px; color: var(--color-midtone-gray); }
|
||||
/* 메모는 넓게 */
|
||||
.cpg .cpg-brule-fields .cpg-brule-memo { width: 300px; min-width: 300px; max-width: 300px; }
|
||||
/* 메모: 프레임 전체 너비 */
|
||||
.cpg .cpg-brule-fields .cpg-brule-memo { width: 100%; min-width: 0; max-width: 100%; box-sizing: border-box; }
|
||||
|
||||
/* ── 상품 검색 드롭다운 ── */
|
||||
.cpg-search-pop {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{{ page_title or "ERP" }} — DBX Corporation</title>
|
||||
<link rel="stylesheet" href="/static/erp.css?v=20260530e" />
|
||||
<link rel="stylesheet" href="/static/erp-shell.css?v=20260530e" />
|
||||
<link rel="stylesheet" href="/static/erp.css?v=20260530f" />
|
||||
<link rel="stylesheet" href="/static/erp-shell.css?v=20260530f" />
|
||||
<link rel="stylesheet" href="/static/erp-attach-viewer.css" />
|
||||
{% block head_extra %}{% endblock %}
|
||||
</head>
|
||||
|
||||
Reference in New Issue
Block a user