From 6c73c57a91eda8debe706cb010d6691502d76a88 Mon Sep 17 00:00:00 2001 From: OoO Date: Fri, 1 May 2026 00:04:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=E6=96=B0=E5=A2=9E=E5=BB=A0?= =?UTF-8?q?=E5=95=86=E7=BC=BA=E8=B2=A8=20V2=20=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 4 +- routes/vendor_routes.py | 68 ++++ templates/vendor_stockout_index_v2.html | 497 ++++++++++++++++++++++++ 3 files changed, 567 insertions(+), 2 deletions(-) create mode 100644 templates/vendor_stockout_index_v2.html diff --git a/app.py b/app.py index 29e8747..3149bd7 100644 --- a/app.py +++ b/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 防護函數 diff --git a/routes/vendor_routes.py b/routes/vendor_routes.py index 0c107f5..8528499 100644 --- a/routes/vendor_routes.py +++ b/routes/vendor_routes.py @@ -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') diff --git a/templates/vendor_stockout_index_v2.html b/templates/vendor_stockout_index_v2.html new file mode 100644 index 0000000..917f32b --- /dev/null +++ b/templates/vendor_stockout_index_v2.html @@ -0,0 +1,497 @@ +{% extends 'ewoooc_base.html' %} + +{% block title %}EwoooC 廠商缺貨{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block ewooo_content %} +
+
+
+
+
+ + VENDOR STOCKOUT +
+

廠商缺貨

+

+ 以匯入的缺貨清單、廠商聯絡資料與郵件發送紀錄為準,追蹤待處理缺貨、已發送通知與聯絡資料完整度。 +

+
+ +
+ + +
+ +
+ +
+
+
缺貨總筆數
+
{{ stats.total_stockouts | number_format }}
+
涵蓋 {{ stats.distinct_vendor_count | number_format }} 個來源廠商
+
+
+
待發送
+
{{ stats.pending_stockouts | number_format }}
+
失敗 {{ stats.failed_stockouts | number_format }} 筆
+
+
+
已發送
+
{{ stats.sent_stockouts | number_format }}
+
重複 {{ stats.duplicate_stockouts | number_format }} 筆
+
+
+
啟用廠商
+
{{ stats.active_vendors | number_format }}
+
啟用郵件 {{ stats.active_emails | number_format }}
+
+
+
+ +
+ + +
+ +
+
+ 資料狀態摘要 + 來源:vendor_stockout / vendor_list / vendor_emails / email_send_log +
+
+
+
最新匯入
+ {% if stats.latest_import_time %} +
{{ stats.latest_import_vendor or '未標記廠商' }}
+
+ {{ stats.latest_import_time.strftime('%Y-%m-%d %H:%M') }}
+ {{ stats.latest_import_product or '未標記商品' }} +
+ {% else %} +
尚無匯入紀錄
+
資料庫目前沒有缺貨資料
+ {% endif %} +
+
+
郵件成功率
+
{{ stats.email_success_rate }}%
+
+ 成功 {{ stats.email_success | number_format }} / 總計 {{ stats.email_total | number_format }}
+ 失敗 {{ stats.email_failed | number_format }},待發送 {{ stats.email_pending | number_format }} +
+
+
+
最新郵件
+ {% if stats.latest_email_time %} +
{{ stats.latest_email_status or '未標記狀態' }}
+
+ {{ stats.latest_email_time.strftime('%Y-%m-%d %H:%M') }}
+ vendor_id {{ stats.latest_email_vendor_id }} +
+ {% else %} +
尚無郵件紀錄
+
email_send_log 目前沒有資料
+ {% endif %} +
+
+ {% if stats.total_stockouts == 0 %} +
+ 目前沒有匯入的缺貨資料;請先使用既有 Excel 匯入流程建立真實批次。 +
+ {% endif %} +
+
+{% endblock %}