From 15947e73ed8eaa9c96c03928415720637531feeb Mon Sep 17 00:00:00 2001 From: king Date: Tue, 1 Sep 2026 19:49:42 +0900 Subject: [PATCH] =?UTF-8?q?feat(cupang):=20=EB=8B=AC=EB=A0=A5=20=EC=9B=94?= =?UTF-8?q?=20=ED=91=9C=EA=B8=B0=20=ED=99=95=EB=8C=80,=20=EC=B6=9C?= =?UTF-8?q?=EA=B3=A0=20=EB=B3=B4=EA=B8=B0=20=E2=91=A1=EB=A5=BC=20=EC=83=81?= =?UTF-8?q?=EC=9E=90=20=EB=B2=88=ED=98=B8=EB=B3=84=20=EB=82=B4=EC=9A=A9?= =?UTF-8?q?=EB=AC=BC=20=EB=AA=A9=EB=A1=9D=EC=9C=BC=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 달력 월 이름/연도 글자 크기 2배·굵게 - 출고 상세 ② 상자 요약 → ② 상자 목록: 전체 상자에 1번부터 번호를 붙이고 상자마다 담긴 제품·수량을 표시. box_plan 스냅샷 우선, 없으면 계산 결과로 대체 Co-Authored-By: Claude Opus 5 --- app/modules/cupang/router.py | 76 +++++++++++++++++++ .../cupang/templates/cupang/box_calc.html | 2 +- .../cupang/templates/cupang/box_rules.html | 2 +- .../cupang/templates/cupang/centers.html | 2 +- .../cupang/templates/cupang/index.html | 2 +- .../cupang/templates/cupang/products.html | 2 +- app/modules/cupang/templates/cupang/view.html | 71 ++++++----------- app/static/cupang.css | 42 +++++++++- 8 files changed, 143 insertions(+), 56 deletions(-) diff --git a/app/modules/cupang/router.py b/app/modules/cupang/router.py index 77bcda6..73a836b 100644 --- a/app/modules/cupang/router.py +++ b/app/modules/cupang/router.py @@ -425,6 +425,7 @@ async def detail(request: Request, shipment_id: int) -> HTMLResponse: "shipment": ship, "items": items, "calc": calc, + "box_list": _expand_box_list(ship, calc, items), "total_qty": total_qty, }, ) @@ -1052,6 +1053,81 @@ def _compute_boxes(store: Any, raw_items: list[Any]) -> dict[str, Any]: } +def _expand_box_list( + shipment: dict[str, Any], calc: dict[str, Any], items: list[dict[str, Any]] +) -> list[dict[str, Any]]: + """전체 상자를 1번부터 번호 붙여 펼친다 — 상자마다 무엇이 몇 개 들었는지. + + 확정 당시 스냅샷(`box_plan`)이 있으면 그것을 그대로 펼치고, + 없는 예전 데이터는 계산 결과(`calc`)로 대신 만든다. + """ + box_name_by_code = { + r["product_code"]: (r.get("box_name") or "") for r in calc.get("results", []) + } + out: list[dict[str, Any]] = [] + + plan = shipment.get("box_plan") or [] + if plan: + for entry in plan: + count = int(entry.get("count") or 0) + units = int(entry.get("units") or 0) + if count <= 0: + continue + if entry.get("kind") == "mix": + contents = [ + { + "product_name": it.get("product_name") or it.get("product_code") or "", + "quantity": int(it.get("quantity") or 0), + } + for it in (entry.get("items") or []) + ] + box_name = entry.get("box_type") or "" + for _ in range(count): + out.append({"kind": "mix", "box_name": box_name, "items": contents}) + else: + code = str(entry.get("product_code") or "") + contents = [ + {"product_name": entry.get("name") or code, "quantity": units} + ] + box_name = box_name_by_code.get(code, "") + for _ in range(count): + out.append({"kind": "product", "box_name": box_name, "items": contents}) + else: + for r in calc.get("results", []): + if not r.get("configured"): + continue + for _ in range(int(r.get("full_boxes") or 0)): + out.append( + { + "kind": "product", + "box_name": r.get("box_name") or "", + "items": [ + {"product_name": r["product_name"], "quantity": r["units_per_box"]} + ], + } + ) + for mix in calc.get("mixes", []): + for box in mix.get("boxes", []): + out.append( + { + "kind": "mix", + "box_name": mix.get("box_name") or "", + "items": [ + { + "product_name": it.get("product_name") or "", + "quantity": int(it.get("quantity") or 0), + } + for it in (box.get("items") or []) + ], + } + ) + + for idx, box in enumerate(out, start=1): + box["no"] = idx + box["quantity"] = sum(int(it["quantity"]) for it in box["items"]) + return out + + def _pack_leftovers(results: list[dict[str, Any]]) -> list[dict[str, Any]]: """제품별 자투리(remainder_units)를 같은 상자명끼리 모아 혼합 상자에 담는다. diff --git a/app/modules/cupang/templates/cupang/box_calc.html b/app/modules/cupang/templates/cupang/box_calc.html index dc007dc..ff3ea8a 100644 --- a/app/modules/cupang/templates/cupang/box_calc.html +++ b/app/modules/cupang/templates/cupang/box_calc.html @@ -1,6 +1,6 @@ {% extends "erp_base.html" %} -{% block head_extra %}{% endblock %} +{% block head_extra %}{% endblock %} {% block content %}
diff --git a/app/modules/cupang/templates/cupang/box_rules.html b/app/modules/cupang/templates/cupang/box_rules.html index 4b23e84..c238572 100644 --- a/app/modules/cupang/templates/cupang/box_rules.html +++ b/app/modules/cupang/templates/cupang/box_rules.html @@ -1,6 +1,6 @@ {% extends "erp_base.html" %} -{% block head_extra %}{% endblock %} +{% block head_extra %}{% endblock %} {% block content %}
diff --git a/app/modules/cupang/templates/cupang/centers.html b/app/modules/cupang/templates/cupang/centers.html index de5f936..0d4f267 100644 --- a/app/modules/cupang/templates/cupang/centers.html +++ b/app/modules/cupang/templates/cupang/centers.html @@ -1,6 +1,6 @@ {% extends "erp_base.html" %} -{% block head_extra %}{% endblock %} +{% block head_extra %}{% endblock %} {% block content %}
diff --git a/app/modules/cupang/templates/cupang/index.html b/app/modules/cupang/templates/cupang/index.html index 7059eb4..4b7fe14 100644 --- a/app/modules/cupang/templates/cupang/index.html +++ b/app/modules/cupang/templates/cupang/index.html @@ -1,6 +1,6 @@ {% extends "erp_base.html" %} -{% block head_extra %}{% endblock %} +{% block head_extra %}{% endblock %} {% block content %}
diff --git a/app/modules/cupang/templates/cupang/products.html b/app/modules/cupang/templates/cupang/products.html index ca28da1..164bb12 100644 --- a/app/modules/cupang/templates/cupang/products.html +++ b/app/modules/cupang/templates/cupang/products.html @@ -1,6 +1,6 @@ {% extends "erp_base.html" %} -{% block head_extra %}{% endblock %} +{% block head_extra %}{% endblock %} {% block content %}
diff --git a/app/modules/cupang/templates/cupang/view.html b/app/modules/cupang/templates/cupang/view.html index 86bbeba..f9cfed0 100644 --- a/app/modules/cupang/templates/cupang/view.html +++ b/app/modules/cupang/templates/cupang/view.html @@ -1,6 +1,6 @@ {% extends "erp_base.html" %} -{% block head_extra %}{% endblock %} +{% block head_extra %}{% endblock %} {% block content %} {# 출고 묶음 보기 — 상자 계산 화면과 같은 3열 구성. 읽기 전용(수정 없음). #} @@ -44,18 +44,18 @@ - +
-

② 상자 요약

- 확정 당시 수량 기준으로 다시 계산한 값 +

② 상자 목록

+ 상자마다 담긴 제품과 수량
총 상자 - {{ calc.grand_total_boxes }}상자 + {{ box_list|length }}상자 제품별 + 혼합
@@ -71,53 +71,26 @@
-

제품별 상자

-
- {% for r in calc.results if r.configured %} -
-
- {{ r.product_name }} +
    + {% for b in box_list %} +
  • +
    + {{ b.no }} + {{ b.box_name or '상자' }} + {% if b.kind == 'mix' %}혼합{% endif %} + {{ b.quantity }}개
    -
    - {{ r.full_boxes }}상자 - {{ r.quantity }}개 · 입수량 {{ r.units_per_box }} - {% if r.remainder_units %} - 자투리 {{ r.remainder_units }}개 - {% else %} - 딱 맞음 - {% endif %} -
    -
+
    + {% for it in b['items'] %} +
  • {{ it.product_name }}{{ it.quantity }}개
  • + {% endfor %} +
+ {% endfor %} - {% set unconfigured = calc.results | rejectattr('configured') | list %} - {% if unconfigured %} -

상자 입수량 미설정: - {% for r in unconfigured %}{{ r.product_name }}{% if not loop.last %}, {% endif %}{% endfor %} -

+ {% if not box_list %} +
  • 상자 정보가 없습니다.
  • {% endif %} -
    - -

    자투리 혼합 상자 - {% if not calc.mixes %}— 자투리 없음{% else %}— 1장 = 1상자{% endif %} -

    -
    - {% for m in calc.mixes %} - {% for b in m.boxes %} -
    -
    -
    {{ m.box_name }} 혼합 #{{ loop.index }}
    -
      - {% for it in b['items'] %} -
    • {{ it.product_name }}{{ it.quantity }}개
    • - {% endfor %} -
    -
    -
    {{ b.fill_percent }}% 채움
    -
    -
    - {% endfor %} - {% endfor %} -
    +
    diff --git a/app/static/cupang.css b/app/static/cupang.css index b432a06..8b67500 100644 --- a/app/static/cupang.css +++ b/app/static/cupang.css @@ -1181,8 +1181,8 @@ body.cpg-dragging { cursor: grabbing; user-select: none; } display: flex; align-items: baseline; gap: 6px; padding: 0 2px 8px; } -.cpg-mcal-mname { font-size: 15px; font-weight: 700; letter-spacing: -0.2px; } -.cpg-mcal-myear { font-size: 12px; color: var(--color-midtone-gray); font-family: var(--font-geist-mono); } +.cpg-mcal-mname { font-size: 30px; font-weight: 800; letter-spacing: -0.5px; } +.cpg-mcal-myear { font-size: 24px; font-weight: 800; color: var(--color-midtone-gray); font-family: var(--font-geist-mono); } .cpg-mcal-total { margin-left: auto; font-size: 11px; padding: 2px 8px; border-radius: 999px; @@ -1399,6 +1399,44 @@ body.cpg-dragging { cursor: grabbing; user-select: none; } .cpg-calc-card .cpg-calc-table .cpg-calc-qty { width: 70px; max-width: 70px; margin-left: auto; } /* 출고 보기(읽기 전용) — ③ 하단 메타 정보 */ +/* ── 출고 보기 ② 상자 목록 — 상자마다 번호 + 내용물 ── */ +.cpg-boxno-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; } +.cpg-boxno { + border: 1px solid var(--color-cloud-gray); + border-radius: 10px; + padding: 8px 10px; + background: #fff; +} +.cpg-boxno.is-mix { border-color: var(--color-deep-black); } +.cpg-boxno-head { display: flex; align-items: center; gap: 6px; } +.cpg-boxno-no { + display: inline-flex; align-items: center; justify-content: center; + min-width: 24px; height: 24px; padding: 0 6px; + border-radius: 999px; + background: var(--color-deep-black); color: #fff; + font-size: 12px; font-weight: 700; font-family: var(--font-geist-mono); +} +.cpg-boxno-name { font-size: 13px; font-weight: 600; } +.cpg-boxno-tag { + font-size: 10px; padding: 1px 6px; border-radius: 999px; + background: var(--color-ghost-gray); color: var(--color-midtone-gray); +} +.cpg-boxno-qty { + margin-left: auto; + font-size: 12px; color: var(--color-midtone-gray); + font-family: var(--font-geist-mono); +} +.cpg-boxno-items { list-style: none; margin: 6px 0 0; padding: 0 0 0 30px; } +.cpg-boxno-items li { + display: flex; align-items: baseline; gap: 8px; + padding: 3px 0; + font-size: 12px; + border-bottom: 1px dashed var(--color-cloud-gray); +} +.cpg-boxno-items li:last-child { border-bottom: 0; } +.cpg-boxno-items li span { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.cpg-boxno-items li b { flex: 0 0 auto; font-family: var(--font-geist-mono); } + .cpg-view-meta { margin: 12px 0 0; padding: 10px 12px; border: 1px solid var(--color-subtle-ash); border-radius: 10px;