feat(dispatch): Order ID 강조 + 송장번호 배송조회 딥링크

- Order ID 를 카드에서 가장 크게(22px bold) 표시 — 가장 자주 보는 값
- Tracking 번호 클릭 시 배송사 조회 페이지로 송장번호 채워 새 탭 이동
  (store.courier_tracking_url + Jinja 필터 courier_track)
- Ninja Van/J&T 등 말레이시아 주요 배송사 URL 매핑(미매핑은 텍스트 폴백)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 19:02:44 +09:00
parent bd79d20eb1
commit 1c14c748dd
3 changed files with 44 additions and 4 deletions
+9 -2
View File
@@ -125,10 +125,17 @@ templates.env.loader = ChoiceLoader(
] ]
) )
# 배송사명 → 로고 URL(dispatch 모듈). 템플릿에서 {{ provider | courier_logo }}. # dispatch 모듈 템플릿 필터: 배송사 로고 URL / 송장 조회 딥링크.
from .modules.dispatch.store import courier_logo_url as _courier_logo_url # noqa: E402 # {{ provider | courier_logo }} · {{ tracking | courier_track(provider) }}
from .modules.dispatch.store import ( # noqa: E402
courier_logo_url as _courier_logo_url,
)
from .modules.dispatch.store import ( # noqa: E402
courier_tracking_url as _courier_tracking_url,
)
templates.env.filters["courier_logo"] = _courier_logo_url templates.env.filters["courier_logo"] = _courier_logo_url
templates.env.filters["courier_track"] = _courier_tracking_url
oauth = build_google_oauth() oauth = build_google_oauth()
user_store = UserStore(DATA_DIR / "users.json") user_store = UserStore(DATA_DIR / "users.json")
+25
View File
@@ -17,6 +17,7 @@ from __future__ import annotations
from functools import lru_cache from functools import lru_cache
from pathlib import Path from pathlib import Path
from typing import Any, Iterable from typing import Any, Iterable
from urllib.parse import quote
# ── 작업 상태 필드(토글 대상) ── # ── 작업 상태 필드(토글 대상) ──
# 순서 = 작업 진행 순서. label_ko/label_en 은 버튼 표기. # 순서 = 작업 진행 순서. label_ko/label_en 은 버튼 표기.
@@ -258,6 +259,30 @@ def courier_logo_url(provider: Any) -> str:
return _logo_url_for_slug(courier_slug(provider)) return _logo_url_for_slug(courier_slug(provider))
# ── 배송사별 송장 조회 딥링크 ──
# {t} 자리에 송장번호를 넣어 조회 페이지로 바로 이동(자동 입력 효과).
# ⚠️ 배송사 사이트 URL 은 바뀔 수 있다. 안 열리면 여기만 고치면 된다.
# 매핑 없는 배송사는 링크 없이 텍스트로 표시(courier_tracking_url 가 "" 반환).
_COURIER_TRACK_URL: dict[str, str] = {
"ninjavan": "https://www.ninjavan.co/en-my/tracking?id={t}",
"jnt": "https://www.jtexpress.my/tracking?billcode={t}",
"flash": "https://www.flashexpress.my/fle/tracking?se={t}",
"citylink": "https://www.citylinkexpress.com/tracking-result/?track_no={t}",
"poslaju": "https://track.pos.com.my/postal-services/quick-access/?track-trace&trackingNo03={t}",
"gdex": "https://web.gdexpress.com/official/web/ConsignmentSearch.html?capcheck=true&input_search={t}",
"spx": "https://spx.com.my/track?tracking_number={t}",
}
def courier_tracking_url(tracking: Any, provider: Any = "") -> str:
"""송장번호 + 배송사명 → 조회 딥링크. 매핑/번호 없으면 ""."""
t = clean_text(tracking)
tmpl = _COURIER_TRACK_URL.get(courier_slug(provider))
if not t or not tmpl:
return ""
return tmpl.replace("{t}", quote(t, safe=""))
def sku_summary(parcels: list[dict[str, Any]]) -> list[dict[str, Any]]: def sku_summary(parcels: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""전체 박스 기준 SKU별 총 수량(피킹 요약). seller_sku 오름차순.""" """전체 박스 기준 SKU별 총 수량(피킹 요약). seller_sku 오름차순."""
totals: dict[str, dict[str, Any]] = {} totals: dict[str, dict[str, Any]] = {}
@@ -13,6 +13,10 @@
.dsp-courier-text { position:absolute;top:12px;right:14px; } .dsp-courier-text { position:absolute;top:12px;right:14px; }
.dsp-meta { font-size:13px;line-height:1.5;color:#334155;word-break:break-all; } .dsp-meta { font-size:13px;line-height:1.5;color:#334155;word-break:break-all; }
.dsp-meta b { color:#0f172a; } .dsp-meta b { color:#0f172a; }
.dsp-order { font-size:15px;line-height:1.3;color:#334155;word-break:break-all; }
.dsp-order b { display:block;font-size:11px;font-weight:600;color:#64748b;letter-spacing:.5px; }
.dsp-order span { font-size:22px;font-weight:800;color:#0f172a; }
.dsp-track-link { color:#2563eb;font-weight:700;text-decoration:underline;text-underline-offset:2px; }
.dsp-items { list-style:none;margin:0;padding:8px 0;border-top:1px dashed #e2e8f0;border-bottom:1px dashed #e2e8f0; } .dsp-items { list-style:none;margin:0;padding:8px 0;border-top:1px dashed #e2e8f0;border-bottom:1px dashed #e2e8f0; }
.dsp-items li { font-size:16px;font-weight:600;color:#0f172a;padding:2px 0; } .dsp-items li { font-size:16px;font-weight:600;color:#0f172a;padding:2px 0; }
.dsp-items .qty { color:#2563eb; } .dsp-items .qty { color:#2563eb; }
@@ -67,10 +71,14 @@
<span class="dsp-courier-text erp-badge">{{ p.shipping_provider }}</span> <span class="dsp-courier-text erp-badge">{{ p.shipping_provider }}</span>
{% endif %} {% endif %}
<div class="dsp-no">No. {{ p.seq }}</div> <div class="dsp-no">No. {{ p.seq }}</div>
<div class="dsp-order"><b>Order ID</b><span>{{ p.order_id or '—' }}</span></div>
<div class="dsp-meta"> <div class="dsp-meta">
<div><b>Order</b> {{ p.order_id or '—' }}</div>
<div><b>Package</b> {{ p.package_id or '—' }}</div> <div><b>Package</b> {{ p.package_id or '—' }}</div>
<div><b>Tracking</b> {{ p.tracking_id or '—' }}</div> <div><b>Tracking</b>
{% set turl = p.tracking_id | courier_track(p.shipping_provider) %}
{% if turl %}<a class="dsp-track-link" href="{{ turl }}" target="_blank" rel="noopener noreferrer">{{ p.tracking_id }}</a>
{% else %}{{ p.tracking_id or '—' }}{% endif %}
</div>
</div> </div>
<ul class="dsp-items"> <ul class="dsp-items">
{% for it in p['items'] %} {% for it in p['items'] %}