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:
|
if cur.rowcount == 0:
|
||||||
raise KeyError(rule_id)
|
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]]:
|
def box_rule_map(self) -> dict[str, dict[str, Any]]:
|
||||||
"""product_code → rule. 폼/계산에서 빠르게 조회하기 위한 dict."""
|
"""product_code → rule. 폼/계산에서 빠르게 조회하기 위한 dict."""
|
||||||
return {r["product_code"]: r for r in self.list_box_rules()}
|
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:
|
if store is None:
|
||||||
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
||||||
try:
|
try:
|
||||||
store.deactivate_box_rule(rule_id=rule_id)
|
store.delete_box_rule(rule_id=rule_id)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise HTTPException(status_code=404, detail="규칙을 찾을 수 없습니다.")
|
raise HTTPException(status_code=404, detail="규칙을 찾을 수 없습니다.")
|
||||||
return RedirectResponse(url="/cupang/box-rules", status_code=303)
|
return RedirectResponse(url="/cupang/box-rules", status_code=303)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% 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 %}
|
{% block content %}
|
||||||
<section class="cpg">
|
<section class="cpg">
|
||||||
@@ -20,25 +20,31 @@
|
|||||||
<form method="post" action="/cupang/box-rules">
|
<form method="post" action="/cupang/box-rules">
|
||||||
<input type="hidden" name="product_name_snapshot" id="brule-name-snap" />
|
<input type="hidden" name="product_name_snapshot" id="brule-name-snap" />
|
||||||
<div class="cpg-brule-fields">
|
<div class="cpg-brule-fields">
|
||||||
|
<div class="cpg-brule-row">
|
||||||
<label class="erp-field"><span>제품명 *</span>
|
<label class="erp-field"><span>제품명 *</span>
|
||||||
<select class="erp-select" id="brule-name" required>
|
<select class="erp-select cpg-brule-name" id="brule-name" required>
|
||||||
<option value="">— 제품명 선택 —</option>
|
<option value="">— 제품명 선택 —</option>
|
||||||
{% for p in products %}
|
{% for p in products %}
|
||||||
<option value="{{ p.product_code }}" data-name="{{ p.product_name }}">{{ p.product_name }}</option>
|
<option value="{{ p.product_code }}" data-name="{{ p.product_name }}">{{ p.product_name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select></label>
|
</select></label>
|
||||||
<label class="erp-field"><span>제품코드</span>
|
<label class="erp-field"><span>제품코드</span>
|
||||||
<input class="erp-input" type="text" name="product_code" id="brule-code" required placeholder="자동" /></label>
|
<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>
|
<label class="erp-field"><span>박스이름</span>
|
||||||
<input class="erp-input" type="text" name="box_name" value="쿠팡박스" /></label>
|
<input class="erp-input cpg-brule-box" type="text" name="box_name" value="쿠팡박스" /></label>
|
||||||
<label class="erp-field"><span>박스당 입수량 *</span>
|
<label class="erp-field"><span>박스당 입수량 *</span>
|
||||||
<span class="cpg-upb-wrap">
|
<span class="cpg-upb-wrap">
|
||||||
<input class="erp-input cpg-brule-upb" type="number" name="units_per_box" min="1" required />
|
<input class="erp-input cpg-brule-upb" type="number" name="units_per_box" min="1" required />
|
||||||
<span class="cpg-upb-unit">개</span>
|
<span class="cpg-upb-unit">개</span>
|
||||||
</span></label>
|
</span></label>
|
||||||
<label class="erp-field cpg-full"><span>메모</span>
|
</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>
|
<input class="erp-input cpg-brule-memo" type="text" name="memo" /></label>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="erp-page-actions" style="margin-top:12px;">
|
<div class="erp-page-actions" style="margin-top:12px;">
|
||||||
<button type="submit" class="erp-btn erp-btn-primary">저장</button>
|
<button type="submit" class="erp-btn erp-btn-primary">저장</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,27 +74,27 @@
|
|||||||
<div class="erp-table-wrap">
|
<div class="erp-table-wrap">
|
||||||
<table class="erp-table">
|
<table class="erp-table">
|
||||||
<thead>
|
<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>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for r in box_rules %}
|
{% for r in box_rules %}
|
||||||
<tr {% if not r.active %}style="opacity:.55"{% endif %}>
|
<tr>
|
||||||
<td>{{ r.product_code }}</td>
|
|
||||||
<td>{{ r.product_name_snapshot or '—' }}</td>
|
<td>{{ r.product_name_snapshot or '—' }}</td>
|
||||||
|
<td>{{ r.product_code }}</td>
|
||||||
<td>{{ r.box_name }}</td>
|
<td>{{ r.box_name }}</td>
|
||||||
<td>{{ r.units_per_box }}</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>{{ r.memo or '—' }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if r.active %}
|
|
||||||
<form method="post" action="/cupang/box-rules/{{ r.id }}/delete" class="cpg-inline-form"
|
<form method="post" action="/cupang/box-rules/{{ r.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-danger">삭제</button>
|
||||||
</form>
|
</form>
|
||||||
{% else %}—{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% if not box_rules %}
|
||||||
|
<tr><td colspan="6" class="erp-muted">등록된 입수량 규칙이 없습니다.</td></tr>
|
||||||
|
{% endif %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% 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 %}
|
{% block content %}
|
||||||
<section class="cpg">
|
<section class="cpg">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% 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 %}
|
{% block content %}
|
||||||
<section class="cpg">
|
<section class="cpg">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% 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 %}
|
{% block content %}
|
||||||
<section class="cpg">
|
<section class="cpg">
|
||||||
@@ -118,4 +118,4 @@
|
|||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% 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" %}
|
{% 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 %}
|
{% block content %}
|
||||||
<section class="cpg">
|
<section class="cpg">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% 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 %}
|
{% block content %}
|
||||||
<section class="cpg">
|
<section class="cpg">
|
||||||
|
|||||||
@@ -246,20 +246,21 @@
|
|||||||
@media (max-width: 1420px) { .cpg-brule-list { flex-basis: auto; width: 100%; } }
|
@media (max-width: 1420px) { .cpg-brule-list { flex-basis: auto; width: 100%; } }
|
||||||
@media (max-width: 540px) { .cpg-brule-add { flex-basis: 100%; 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; }
|
.cpg-brule-fields { display: flex; flex-direction: column; gap: 12px; }
|
||||||
/* 제품명·제품코드·박스이름 140px 고정 (erp.css min-width:240 오버라이드) */
|
.cpg-brule-row { display: flex; gap: 16px; align-items: flex-end; }
|
||||||
.cpg .cpg-brule-fields .erp-field > .erp-input,
|
.cpg-brule-memo-field { flex: 1 1 auto; min-width: 0; }
|
||||||
.cpg .cpg-brule-fields .erp-field > .erp-select {
|
/* 필드별 고정 폭 (erp.css min-width:240 오버라이드) */
|
||||||
width: 140px; min-width: 140px; max-width: 140px; box-sizing: border-box;
|
.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 + "개" */
|
/* 박스당 입수량 60px + "개" */
|
||||||
.cpg-upb-wrap { display: inline-flex; align-items: center; gap: 4px; }
|
.cpg-upb-wrap { display: inline-flex; align-items: center; gap: 4px; }
|
||||||
.cpg .cpg-brule-fields .cpg-upb-wrap > .cpg-brule-upb {
|
.cpg .cpg-brule-fields .cpg-upb-wrap > .cpg-brule-upb {
|
||||||
width: 60px; min-width: 60px; max-width: 60px; box-sizing: border-box;
|
width: 60px; min-width: 60px; max-width: 60px; box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.cpg-upb-unit { font-size: 13px; color: var(--color-midtone-gray); }
|
.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 {
|
.cpg-search-pop {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>{{ page_title or "ERP" }} — DBX Corporation</title>
|
<title>{{ page_title or "ERP" }} — DBX Corporation</title>
|
||||||
<link rel="stylesheet" href="/static/erp.css?v=20260530e" />
|
<link rel="stylesheet" href="/static/erp.css?v=20260530f" />
|
||||||
<link rel="stylesheet" href="/static/erp-shell.css?v=20260530e" />
|
<link rel="stylesheet" href="/static/erp-shell.css?v=20260530f" />
|
||||||
<link rel="stylesheet" href="/static/erp-attach-viewer.css" />
|
<link rel="stylesheet" href="/static/erp-attach-viewer.css" />
|
||||||
{% block head_extra %}{% endblock %}
|
{% block head_extra %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
Reference in New Issue
Block a user