feat(frontend): 新增廠商缺貨 V2 入口
This commit is contained in:
4
app.py
4
app.py
@@ -95,8 +95,8 @@ except Exception as e:
|
||||
sys_log.error(f"無法檢測磁碟空間: {e}")
|
||||
|
||||
# 🚩 系統版本定義 (備份與顯示用)
|
||||
# 🚩 2026-04-30 V10.36: EDM dashboard v2 feature flag
|
||||
SYSTEM_VERSION = "V10.36"
|
||||
# 🚩 2026-04-30 V10.37: Vendor stockout v2 feature flag
|
||||
SYSTEM_VERSION = "V10.37"
|
||||
|
||||
# ==========================================
|
||||
# 🔒 SQL Injection 防護函數
|
||||
|
||||
@@ -35,10 +35,78 @@ vendor_db = VendorDatabaseManager()
|
||||
# 主要頁面路由
|
||||
# ==========================================
|
||||
|
||||
|
||||
def _get_vendor_dashboard_stats():
|
||||
"""彙整廠商缺貨首頁 V2 所需的真實資料庫統計。"""
|
||||
session = vendor_db.get_session()
|
||||
try:
|
||||
total_stockouts = session.query(VendorStockout).count()
|
||||
pending_stockouts = session.query(VendorStockout).filter(or_(
|
||||
VendorStockout.status == 'pending',
|
||||
VendorStockout.status == None
|
||||
)).count()
|
||||
sent_stockouts = session.query(VendorStockout).filter(VendorStockout.status == 'sent').count()
|
||||
failed_stockouts = session.query(VendorStockout).filter(VendorStockout.status == 'failed').count()
|
||||
duplicate_stockouts = session.query(VendorStockout).filter(VendorStockout.is_duplicate == True).count()
|
||||
distinct_vendor_count = session.query(VendorStockout.vendor_code).distinct().count()
|
||||
|
||||
active_vendors = session.query(VendorList).filter(VendorList.is_active == True).count()
|
||||
inactive_vendors = session.query(VendorList).filter(VendorList.is_active == False).count()
|
||||
active_emails = session.query(VendorEmail).filter(VendorEmail.is_active == True).count()
|
||||
|
||||
email_total = session.query(EmailSendLog).count()
|
||||
email_success = session.query(EmailSendLog).filter(EmailSendLog.status == 'sent').count()
|
||||
email_failed = session.query(EmailSendLog).filter(EmailSendLog.status == 'failed').count()
|
||||
email_pending = session.query(EmailSendLog).filter(EmailSendLog.status == 'pending').count()
|
||||
email_success_rate = round(email_success / email_total * 100, 1) if email_total else 0
|
||||
|
||||
latest_import = session.query(VendorStockout).order_by(desc(VendorStockout.created_at)).first()
|
||||
latest_email = session.query(EmailSendLog).order_by(desc(EmailSendLog.created_at)).first()
|
||||
latest_batch = session.query(
|
||||
VendorStockout.batch_id,
|
||||
func.count(VendorStockout.id).label('record_count'),
|
||||
func.max(VendorStockout.created_at).label('latest_date')
|
||||
).group_by(VendorStockout.batch_id).order_by(desc('latest_date')).first()
|
||||
|
||||
return {
|
||||
'total_stockouts': total_stockouts,
|
||||
'pending_stockouts': pending_stockouts,
|
||||
'sent_stockouts': sent_stockouts,
|
||||
'failed_stockouts': failed_stockouts,
|
||||
'duplicate_stockouts': duplicate_stockouts,
|
||||
'distinct_vendor_count': distinct_vendor_count,
|
||||
'active_vendors': active_vendors,
|
||||
'inactive_vendors': inactive_vendors,
|
||||
'active_emails': active_emails,
|
||||
'email_total': email_total,
|
||||
'email_success': email_success,
|
||||
'email_failed': email_failed,
|
||||
'email_pending': email_pending,
|
||||
'email_success_rate': email_success_rate,
|
||||
'latest_import_time': latest_import.created_at if latest_import else None,
|
||||
'latest_import_vendor': latest_import.vendor_name if latest_import else None,
|
||||
'latest_import_product': latest_import.product_name if latest_import else None,
|
||||
'latest_email_time': latest_email.created_at if latest_email else None,
|
||||
'latest_email_status': latest_email.status if latest_email else None,
|
||||
'latest_email_vendor_id': latest_email.vendor_id if latest_email else None,
|
||||
'latest_batch_id': latest_batch.batch_id if latest_batch else None,
|
||||
'latest_batch_count': latest_batch.record_count if latest_batch else 0,
|
||||
'latest_batch_time': latest_batch.latest_date if latest_batch else None,
|
||||
}
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@vendor_bp.route('/')
|
||||
def index():
|
||||
"""廠商缺貨系統主頁"""
|
||||
sys_log.info("[VendorStockout] 進入廠商缺貨系統主頁")
|
||||
if request.args.get('ui') == 'v2':
|
||||
return render_template(
|
||||
'vendor_stockout_index_v2.html',
|
||||
active_page='vendor_stockout',
|
||||
stats=_get_vendor_dashboard_stats()
|
||||
)
|
||||
return render_template('vendor_stockout/index.html')
|
||||
|
||||
|
||||
|
||||
497
templates/vendor_stockout_index_v2.html
Normal file
497
templates/vendor_stockout_index_v2.html
Normal file
@@ -0,0 +1,497 @@
|
||||
{% extends 'ewoooc_base.html' %}
|
||||
|
||||
{% block title %}EwoooC 廠商缺貨{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.vendor-v2-stack {
|
||||
display: grid;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.vendor-hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.6fr) minmax(280px, 0.8fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.vendor-hero-panel,
|
||||
.vendor-card,
|
||||
.vendor-table-card {
|
||||
background: var(--momo-bg-surface);
|
||||
border: 1px solid var(--momo-border-light);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.vendor-hero-panel {
|
||||
display: flex;
|
||||
min-height: 228px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.vendor-eyebrow {
|
||||
display: inline-flex;
|
||||
width: fit-content;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
padding: 4px 10px;
|
||||
color: var(--momo-accent-strong);
|
||||
background: var(--momo-accent-soft);
|
||||
border-radius: var(--momo-radius-pill);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.vendor-title {
|
||||
margin: 0;
|
||||
color: var(--momo-text-primary);
|
||||
font-size: 34px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.12;
|
||||
}
|
||||
|
||||
.vendor-subtitle {
|
||||
max-width: 680px;
|
||||
margin: 10px 0 0;
|
||||
color: var(--momo-text-secondary);
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.vendor-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 22px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.vendor-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 36px;
|
||||
padding: 8px 14px;
|
||||
color: var(--momo-text-primary);
|
||||
background: var(--momo-bg-paper);
|
||||
border: 1px solid var(--momo-border-light);
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
text-decoration: none;
|
||||
transition: var(--momo-transition-base);
|
||||
}
|
||||
|
||||
.vendor-action:hover {
|
||||
color: var(--momo-text-primary);
|
||||
border-color: var(--momo-border-strong);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.vendor-action.is-primary {
|
||||
color: var(--momo-text-inverse);
|
||||
background: var(--momo-ink);
|
||||
border-color: var(--momo-ink);
|
||||
}
|
||||
|
||||
.vendor-pulse {
|
||||
display: grid;
|
||||
align-content: stretch;
|
||||
padding: 18px;
|
||||
background: var(--momo-ink);
|
||||
border: 1px solid var(--momo-ink);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.vendor-pulse-label {
|
||||
color: rgba(250, 247, 240, 0.62);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.vendor-pulse-value {
|
||||
margin-top: 8px;
|
||||
color: var(--momo-text-inverse);
|
||||
font-size: 34px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.vendor-pulse-meta {
|
||||
margin-top: 12px;
|
||||
color: rgba(250, 247, 240, 0.7);
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.vendor-kpi-grid,
|
||||
.vendor-flow-grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.vendor-kpi-grid {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.vendor-flow-grid {
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.vendor-card {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.vendor-kpi-label,
|
||||
.vendor-card-label {
|
||||
color: var(--momo-text-secondary);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.vendor-kpi-value {
|
||||
margin-top: 8px;
|
||||
color: var(--momo-text-primary);
|
||||
font-size: 30px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.vendor-kpi-sub {
|
||||
margin-top: 8px;
|
||||
color: var(--momo-text-tertiary);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.vendor-kpi-value.is-danger {
|
||||
color: var(--momo-danger);
|
||||
}
|
||||
|
||||
.vendor-kpi-value.is-success {
|
||||
color: var(--momo-success);
|
||||
}
|
||||
|
||||
.vendor-section-label {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.vendor-section-label .num {
|
||||
color: var(--momo-text-tertiary);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.vendor-section-label .title {
|
||||
color: var(--momo-text-primary);
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.vendor-flow-card {
|
||||
display: flex;
|
||||
min-height: 138px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
color: var(--momo-text-primary);
|
||||
background: var(--momo-bg-surface);
|
||||
border: 1px solid var(--momo-border-light);
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
transition: var(--momo-transition-base);
|
||||
}
|
||||
|
||||
.vendor-flow-card:hover {
|
||||
color: var(--momo-text-primary);
|
||||
border-color: var(--momo-border-strong);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.vendor-flow-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
color: var(--momo-accent-strong);
|
||||
background: var(--momo-accent-soft);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.vendor-flow-title {
|
||||
margin-top: 14px;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.vendor-flow-meta {
|
||||
margin-top: 4px;
|
||||
color: var(--momo-text-tertiary);
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.vendor-table-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vendor-table-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid var(--momo-border-light);
|
||||
}
|
||||
|
||||
.vendor-table-title {
|
||||
color: var(--momo-text-primary);
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.vendor-summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.vendor-summary-item {
|
||||
min-height: 120px;
|
||||
padding: 18px;
|
||||
border-right: 1px solid var(--momo-border-light);
|
||||
}
|
||||
|
||||
.vendor-summary-item:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.vendor-summary-title {
|
||||
margin-top: 8px;
|
||||
color: var(--momo-text-primary);
|
||||
font-size: 16px;
|
||||
font-weight: 800;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.vendor-summary-meta {
|
||||
margin-top: 8px;
|
||||
color: var(--momo-text-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.vendor-empty {
|
||||
padding: 28px;
|
||||
color: var(--momo-text-secondary);
|
||||
background: var(--momo-bg-paper);
|
||||
border-top: 1px solid var(--momo-border-light);
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.vendor-hero,
|
||||
.vendor-kpi-grid,
|
||||
.vendor-flow-grid,
|
||||
.vendor-summary-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.vendor-hero,
|
||||
.vendor-kpi-grid,
|
||||
.vendor-flow-grid,
|
||||
.vendor-summary-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.vendor-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.vendor-summary-item {
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid var(--momo-border-light);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block ewooo_content %}
|
||||
<div class="vendor-v2-stack">
|
||||
<section class="vendor-hero" aria-label="廠商缺貨概況">
|
||||
<div class="vendor-hero-panel">
|
||||
<div>
|
||||
<div class="vendor-eyebrow momo-mono">
|
||||
<i class="fas fa-box-open"></i>
|
||||
VENDOR STOCKOUT
|
||||
</div>
|
||||
<h1 class="vendor-title">廠商缺貨</h1>
|
||||
<p class="vendor-subtitle">
|
||||
以匯入的缺貨清單、廠商聯絡資料與郵件發送紀錄為準,追蹤待處理缺貨、已發送通知與聯絡資料完整度。
|
||||
</p>
|
||||
</div>
|
||||
<div class="vendor-actions">
|
||||
<a class="vendor-action is-primary" href="{{ url_for('vendor.import_page') }}">
|
||||
<i class="fas fa-file-import"></i>
|
||||
匯入 Excel
|
||||
</a>
|
||||
<a class="vendor-action" href="{{ url_for('vendor.list_page') }}">
|
||||
<i class="fas fa-list-check"></i>
|
||||
查看清單
|
||||
</a>
|
||||
<a class="vendor-action" href="{{ url_for('vendor.vendor_management_page') }}">
|
||||
<i class="fas fa-address-book"></i>
|
||||
廠商資料
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="vendor-pulse">
|
||||
<div>
|
||||
<div class="vendor-pulse-label momo-mono">最新批次</div>
|
||||
{% if stats.latest_batch_id %}
|
||||
<div class="vendor-pulse-value momo-mono">{{ stats.latest_batch_count | number_format }}</div>
|
||||
<div class="vendor-pulse-meta momo-mono">
|
||||
批次 {{ stats.latest_batch_id }}<br>
|
||||
{{ stats.latest_batch_time.strftime('%Y-%m-%d %H:%M') if stats.latest_batch_time else '沒有時間紀錄' }}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="vendor-pulse-value momo-mono">0</div>
|
||||
<div class="vendor-pulse-meta momo-mono">目前沒有匯入批次紀錄</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<section aria-label="廠商缺貨指標">
|
||||
<div class="vendor-section-label">
|
||||
<span class="num momo-mono">01</span>
|
||||
<span class="title">即時指標</span>
|
||||
</div>
|
||||
<div class="vendor-kpi-grid">
|
||||
<div class="vendor-card">
|
||||
<div class="vendor-kpi-label momo-mono">缺貨總筆數</div>
|
||||
<div class="vendor-kpi-value momo-mono">{{ stats.total_stockouts | number_format }}</div>
|
||||
<div class="vendor-kpi-sub momo-mono">涵蓋 {{ stats.distinct_vendor_count | number_format }} 個來源廠商</div>
|
||||
</div>
|
||||
<div class="vendor-card">
|
||||
<div class="vendor-kpi-label momo-mono">待發送</div>
|
||||
<div class="vendor-kpi-value momo-mono is-danger">{{ stats.pending_stockouts | number_format }}</div>
|
||||
<div class="vendor-kpi-sub momo-mono">失敗 {{ stats.failed_stockouts | number_format }} 筆</div>
|
||||
</div>
|
||||
<div class="vendor-card">
|
||||
<div class="vendor-kpi-label momo-mono">已發送</div>
|
||||
<div class="vendor-kpi-value momo-mono is-success">{{ stats.sent_stockouts | number_format }}</div>
|
||||
<div class="vendor-kpi-sub momo-mono">重複 {{ stats.duplicate_stockouts | number_format }} 筆</div>
|
||||
</div>
|
||||
<div class="vendor-card">
|
||||
<div class="vendor-kpi-label momo-mono">啟用廠商</div>
|
||||
<div class="vendor-kpi-value momo-mono">{{ stats.active_vendors | number_format }}</div>
|
||||
<div class="vendor-kpi-sub momo-mono">啟用郵件 {{ stats.active_emails | number_format }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section aria-label="廠商缺貨工作流程">
|
||||
<div class="vendor-section-label">
|
||||
<span class="num momo-mono">02</span>
|
||||
<span class="title">工作入口</span>
|
||||
</div>
|
||||
<div class="vendor-flow-grid">
|
||||
<a class="vendor-flow-card" href="{{ url_for('vendor.import_page') }}">
|
||||
<span class="vendor-flow-icon"><i class="fas fa-file-import"></i></span>
|
||||
<span>
|
||||
<span class="vendor-flow-title">Excel 匯入</span>
|
||||
<span class="vendor-flow-meta momo-mono">建立缺貨批次</span>
|
||||
</span>
|
||||
</a>
|
||||
<a class="vendor-flow-card" href="{{ url_for('vendor.list_page') }}">
|
||||
<span class="vendor-flow-icon"><i class="fas fa-table-list"></i></span>
|
||||
<span>
|
||||
<span class="vendor-flow-title">缺貨清單</span>
|
||||
<span class="vendor-flow-meta momo-mono">{{ stats.total_stockouts | number_format }} 筆記錄</span>
|
||||
</span>
|
||||
</a>
|
||||
<a class="vendor-flow-card" href="{{ url_for('vendor.vendor_management_page') }}">
|
||||
<span class="vendor-flow-icon"><i class="fas fa-address-card"></i></span>
|
||||
<span>
|
||||
<span class="vendor-flow-title">廠商管理</span>
|
||||
<span class="vendor-flow-meta momo-mono">{{ stats.active_vendors | number_format }} 個啟用廠商</span>
|
||||
</span>
|
||||
</a>
|
||||
<a class="vendor-flow-card" href="{{ url_for('vendor.send_email_page') }}">
|
||||
<span class="vendor-flow-icon"><i class="fas fa-paper-plane"></i></span>
|
||||
<span>
|
||||
<span class="vendor-flow-title">郵件發送</span>
|
||||
<span class="vendor-flow-meta momo-mono">{{ stats.pending_stockouts | number_format }} 筆待處理</span>
|
||||
</span>
|
||||
</a>
|
||||
<a class="vendor-flow-card" href="{{ url_for('vendor.history_page') }}">
|
||||
<span class="vendor-flow-icon"><i class="fas fa-clock-rotate-left"></i></span>
|
||||
<span>
|
||||
<span class="vendor-flow-title">發送歷史</span>
|
||||
<span class="vendor-flow-meta momo-mono">{{ stats.email_total | number_format }} 筆郵件紀錄</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="vendor-table-card" aria-label="資料狀態摘要">
|
||||
<div class="vendor-table-head">
|
||||
<span class="vendor-table-title">資料狀態摘要</span>
|
||||
<span class="vendor-kpi-sub momo-mono">來源:vendor_stockout / vendor_list / vendor_emails / email_send_log</span>
|
||||
</div>
|
||||
<div class="vendor-summary-grid">
|
||||
<div class="vendor-summary-item">
|
||||
<div class="vendor-card-label momo-mono">最新匯入</div>
|
||||
{% if stats.latest_import_time %}
|
||||
<div class="vendor-summary-title">{{ stats.latest_import_vendor or '未標記廠商' }}</div>
|
||||
<div class="vendor-summary-meta momo-mono">
|
||||
{{ stats.latest_import_time.strftime('%Y-%m-%d %H:%M') }}<br>
|
||||
{{ stats.latest_import_product or '未標記商品' }}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="vendor-summary-title">尚無匯入紀錄</div>
|
||||
<div class="vendor-summary-meta momo-mono">資料庫目前沒有缺貨資料</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="vendor-summary-item">
|
||||
<div class="vendor-card-label momo-mono">郵件成功率</div>
|
||||
<div class="vendor-summary-title momo-mono">{{ stats.email_success_rate }}%</div>
|
||||
<div class="vendor-summary-meta momo-mono">
|
||||
成功 {{ stats.email_success | number_format }} / 總計 {{ stats.email_total | number_format }}<br>
|
||||
失敗 {{ stats.email_failed | number_format }},待發送 {{ stats.email_pending | number_format }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="vendor-summary-item">
|
||||
<div class="vendor-card-label momo-mono">最新郵件</div>
|
||||
{% if stats.latest_email_time %}
|
||||
<div class="vendor-summary-title momo-mono">{{ stats.latest_email_status or '未標記狀態' }}</div>
|
||||
<div class="vendor-summary-meta momo-mono">
|
||||
{{ stats.latest_email_time.strftime('%Y-%m-%d %H:%M') }}<br>
|
||||
vendor_id {{ stats.latest_email_vendor_id }}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="vendor-summary-title">尚無郵件紀錄</div>
|
||||
<div class="vendor-summary-meta momo-mono">email_send_log 目前沒有資料</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if stats.total_stockouts == 0 %}
|
||||
<div class="vendor-empty momo-mono">
|
||||
目前沒有匯入的缺貨資料;請先使用既有 Excel 匯入流程建立真實批次。
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user