From bdfda30ca8742459676e4d702c536b1e9105e1d2 Mon Sep 17 00:00:00 2001 From: beabigegg Date: Thu, 28 Aug 2025 11:51:04 +0800 Subject: [PATCH] 3rd --- .env.example | 31 ++++- MAIL_OPTIMIZATION_GUIDE.md | 92 ++++++++++++++ SMTP_CONFIGURATION_UPDATE.md | 152 ++++++++++++++++++++++++ config.py | 6 +- migrate_add_email_column.py | 80 +++++++++++++ models.py | 1 + routes/temp_spec.py | 23 +++- static/generated/PE1140801.docx | Bin 24502 -> 24506 bytes templates/extend_spec.html | 23 ++++ templates/terminate_spec.html | 23 ++++ utils.py | 28 +++-- 功能升級規劃.txt | 204 -------------------------------- 12 files changed, 441 insertions(+), 222 deletions(-) create mode 100644 MAIL_OPTIMIZATION_GUIDE.md create mode 100644 SMTP_CONFIGURATION_UPDATE.md create mode 100644 migrate_add_email_column.py delete mode 100644 功能升級規劃.txt diff --git a/.env.example b/.env.example index 4947560..0437064 100644 --- a/.env.example +++ b/.env.example @@ -21,11 +21,32 @@ LDAP_BIND_USER_PASSWORD=service_password LDAP_USER_LOGIN_ATTR=userPrincipalName # SMTP 郵件設定 -SMTP_SERVER=smtp.company.com -SMTP_PORT=587 -SMTP_USE_TLS=True -SMTP_SENDER_EMAIL=noreply@company.com -SMTP_SENDER_PASSWORD=smtp_password +# 方案 1: 使用 Port 25 (無需認證) - 推薦用於內部郵件伺服器 +SMTP_SERVER=mail.panjit.com.tw +SMTP_PORT=25 +SMTP_USE_TLS=false +SMTP_USE_SSL=false +SMTP_AUTH_REQUIRED=false +SMTP_SENDER_EMAIL=temp-spec-system@panjit.com.tw +SMTP_SENDER_PASSWORD= + +# 方案 2: 使用 Port 587 (需要認證) - 用於外部 SMTP 或有認證需求的伺服器 +# SMTP_SERVER=smtp.company.com +# SMTP_PORT=587 +# SMTP_USE_TLS=true +# SMTP_USE_SSL=false +# SMTP_AUTH_REQUIRED=true +# SMTP_SENDER_EMAIL=noreply@company.com +# SMTP_SENDER_PASSWORD=smtp_password + +# 方案 3: 使用 Port 465 (SSL + 認證) - 用於安全連接 +# SMTP_SERVER=smtp.gmail.com +# SMTP_PORT=465 +# SMTP_USE_TLS=false +# SMTP_USE_SSL=true +# SMTP_AUTH_REQUIRED=true +# SMTP_SENDER_EMAIL=yourapp@gmail.com +# SMTP_SENDER_PASSWORD=app_password # ONLYOFFICE Document Server 設定 ONLYOFFICE_PORT=8080 diff --git a/MAIL_OPTIMIZATION_GUIDE.md b/MAIL_OPTIMIZATION_GUIDE.md new file mode 100644 index 0000000..391d329 --- /dev/null +++ b/MAIL_OPTIMIZATION_GUIDE.md @@ -0,0 +1,92 @@ +# 郵件通知功能優化說明 + +## 更新日期:2025-01-28 + +## 一、優化內容概述 + +本次優化主要改善了郵件通知的管理邏輯,實現了郵件通知對象的自動記憶和帶出功能,讓系統更符合實際使用需求。 + +## 二、主要變更 + +### 1. 資料庫結構調整 +- 在 `ts_temp_spec` 表新增 `notification_emails` 欄位 +- 用於儲存規範生效時設定的郵件通知對象清單 +- 郵件地址以分號 (;) 分隔儲存 + +### 2. 功能邏輯改進 + +#### 規範生效時(Activate) +- 使用者輸入郵件通知對象 +- 系統自動儲存郵件清單到資料庫 +- 發送生效通知郵件 + +#### 規範停止時(Terminate) +- 系統自動從資料庫讀取之前儲存的郵件清單 +- 在介面上預先填入這些郵件地址 +- 顯示提示訊息告知使用者這是生效時使用的通知對象 +- 使用者可以: + - 直接使用預設的郵件清單 + - 修改郵件清單後再發送 + - 清空並重新選擇收件者 + +#### 規範展延時(Extend) +- 系統自動從資料庫讀取之前儲存的郵件清單 +- 在介面上預先填入這些郵件地址 +- 顯示提示訊息說明可以編輯郵件清單 +- 若使用者修改了郵件清單,系統會更新資料庫中的記錄 +- 確保後續的通知都會發送給更新後的收件者 + +## 三、實施步驟 + +### 1. 執行資料庫遷移 +```bash +python migrate_add_email_column.py +``` + +此腳本會: +- 檢查 `notification_emails` 欄位是否已存在 +- 如果不存在,自動新增該欄位 +- 驗證欄位是否成功新增 + +### 2. 重啟應用程式 +完成資料庫遷移後,重啟 Flask 應用程式以載入新的功能。 + +## 四、使用說明 + +### 對於管理員 +1. **首次生效**:在啟用規範時,輸入所有需要通知的郵件地址 +2. **後續操作**:系統會自動記憶這些地址,無需重複輸入 + +### 對於編輯者 +1. **終止規範**:可以看到並使用之前設定的通知對象 +2. **展延規範**: + - 可以保留原有的通知對象 + - 也可以根據需要調整通知名單 + - 修改後的名單會被儲存供下次使用 + +## 五、優點 + +1. **減少重複輸入**:不需要每次都重新輸入相同的郵件地址 +2. **保持一致性**:確保同一規範的相關人員都能收到通知 +3. **提供彈性**:允許使用者在需要時修改通知名單 +4. **提升效率**:加快操作流程,減少錯誤 +5. **改善使用體驗**:更符合實際業務邏輯 + +## 六、注意事項 + +1. 舊的規範資料不會有預存的郵件清單,需要在首次操作時輸入 +2. 郵件地址以分號分隔儲存,系統會自動處理格式 +3. 如果需要完全清空通知名單,可以在介面上刪除所有地址 + +## 七、技術細節 + +### 修改的檔案 +1. `models.py` - 新增資料庫欄位 +2. `routes/temp_spec.py` - 更新郵件處理邏輯 +3. `templates/terminate_spec.html` - 新增郵件預填功能 +4. `templates/extend_spec.html` - 新增郵件預填功能 +5. `migrate_add_email_column.py` - 資料庫遷移腳本 + +### 相容性 +- 完全向後相容,不影響現有功能 +- 舊資料可正常使用,新功能會在首次使用時生效 \ No newline at end of file diff --git a/SMTP_CONFIGURATION_UPDATE.md b/SMTP_CONFIGURATION_UPDATE.md new file mode 100644 index 0000000..60d42d7 --- /dev/null +++ b/SMTP_CONFIGURATION_UPDATE.md @@ -0,0 +1,152 @@ +# SMTP 配置更新說明 + +## 更新日期:2025-01-28 + +## 一、更新背景 + +根據與 IT 部門確認,郵件伺服器 `mail.panjit.com.tw` 的 Port 25 支援無密碼認證的系統派送郵件功能。因此將系統從原本的 Port 465(需要認證)改為 Port 25(無需認證)的方式發送郵件。 + +## 二、主要變更內容 + +### 1. Config 配置新增項目 +在 `config.py` 中新增以下配置選項: + +```python +SMTP_USE_SSL = os.getenv('SMTP_USE_SSL', 'false').lower() in ['true', '1', 't'] +SMTP_SENDER_EMAIL = os.getenv('SMTP_SENDER_EMAIL', 'temp-spec-system@panjit.com.tw') +SMTP_SENDER_PASSWORD = os.getenv('SMTP_SENDER_PASSWORD', '') # Port 25 不需要密碼 +SMTP_AUTH_REQUIRED = os.getenv('SMTP_AUTH_REQUIRED', 'false').lower() in ['true', '1', 't'] +``` + +### 2. 發送邏輯優化 +更新 `utils.py` 中的 `send_email` 函式: +- 支援多種 SMTP 連接方式 +- Port 465:SSL + 認證 +- Port 587:TLS + 認證 +- Port 25:無加密 + 無認證(系統派送) + +### 3. 環境變數配置 +更新 `.env.example` 提供三種配置方案的範例。 + +## 三、新的配置方案 + +### 方案 1:Port 25 無認證(推薦) + +```env +SMTP_SERVER=mail.panjit.com.tw +SMTP_PORT=25 +SMTP_USE_TLS=false +SMTP_USE_SSL=false +SMTP_AUTH_REQUIRED=false +SMTP_SENDER_EMAIL=temp-spec-system@panjit.com.tw +SMTP_SENDER_PASSWORD= +``` + +**適用場景**: +- 內部郵件伺服器 +- 系統自動發送通知郵件 +- 不需要個人帳號認證 + +### 方案 2:Port 587 + TLS + 認證 + +```env +SMTP_SERVER=smtp.company.com +SMTP_PORT=587 +SMTP_USE_TLS=true +SMTP_USE_SSL=false +SMTP_AUTH_REQUIRED=true +SMTP_SENDER_EMAIL=noreply@company.com +SMTP_SENDER_PASSWORD=smtp_password +``` + +**適用場景**: +- 外部 SMTP 服務 +- 需要認證的郵件伺服器 +- 使用 STARTTLS 加密 + +### 方案 3:Port 465 + SSL + 認證 + +```env +SMTP_SERVER=smtp.gmail.com +SMTP_PORT=465 +SMTP_USE_TLS=false +SMTP_USE_SSL=true +SMTP_AUTH_REQUIRED=true +SMTP_SENDER_EMAIL=yourapp@gmail.com +SMTP_SENDER_PASSWORD=app_password +``` + +**適用場景**: +- Gmail 等外部服務 +- 需要 SSL 加密連接 +- 高安全性要求 + +## 四、實施步驟 + +### 1. 更新環境變數 +修改您的 `.env` 檔案,使用方案 1 的配置: + +```env +SMTP_SERVER=mail.panjit.com.tw +SMTP_PORT=25 +SMTP_USE_TLS=false +SMTP_USE_SSL=false +SMTP_AUTH_REQUIRED=false +SMTP_SENDER_EMAIL=temp-spec-system@panjit.com.tw +SMTP_SENDER_PASSWORD= +``` + +### 2. 設定適當的寄件者郵件地址 +建議使用有意義的系統郵件地址,例如: +- `temp-spec-system@panjit.com.tw` +- `tempspec-notification@panjit.com.tw` +- `noreply-tempspec@panjit.com.tw` + +### 3. 重啟應用程式 +更新配置後,重啟 Flask 應用程式以載入新的 SMTP 設定。 + +### 4. 測試郵件功能 +建議在正式環境使用前,先測試郵件發送功能: +- 啟用一個測試規範 +- 驗證郵件是否正常發送 +- 檢查郵件格式和內容 + +## 五、向後相容性 + +此次更新完全向後相容: +- 原有使用 Port 465/587 + 認證的配置仍然有效 +- 系統會根據配置自動選擇適當的連接方式 +- 不會影響現有的郵件發送功能 + +## 六、除錯訊息 + +系統提供詳細的除錯訊息,包括: +- SMTP 連接方式(SSL/TLS/一般) +- 認證狀態 +- 郵件發送結果 +- 錯誤診斷訊息 + +查看應用程式日誌可以了解郵件發送的詳細過程。 + +## 七、注意事項 + +1. **寄件者地址**:使用系統郵件地址,避免使用個人郵件地址 +2. **網路安全**:Port 25 通常在防火牆中開放,但請確認網路連通性 +3. **郵件限制**:部分郵件伺服器可能有發送頻率限制 +4. **監控建議**:建議監控郵件發送成功率和錯誤日誌 + +## 八、疑難排解 + +### 常見問題 + +**Q: 郵件發送失敗,顯示連接拒絕** +A: 檢查 SMTP_SERVER 和 SMTP_PORT 設定是否正確 + +**Q: 郵件可以發送但收不到** +A: 檢查寄件者郵件地址是否為有效的公司郵件地址 + +**Q: 系統顯示認證失敗** +A: 確認 SMTP_AUTH_REQUIRED=false,Port 25 不需要認證 + +**Q: 要如何切換回認證模式** +A: 修改 .env 檔案,設定 SMTP_AUTH_REQUIRED=true 並提供正確的密碼 \ No newline at end of file diff --git a/config.py b/config.py index 68dd8fc..8d6b126 100644 --- a/config.py +++ b/config.py @@ -27,5 +27,7 @@ class Config: SMTP_SERVER = os.getenv('SMTP_SERVER', 'mail.panjit.com.tw') SMTP_PORT = int(os.getenv('SMTP_PORT', 25)) SMTP_USE_TLS = os.getenv('SMTP_USE_TLS', 'false').lower() in ['true', '1', 't'] - SMTP_SENDER_EMAIL = os.getenv('SMTP_SENDER_EMAIL') - SMTP_SENDER_PASSWORD = os.getenv('SMTP_SENDER_PASSWORD') + SMTP_USE_SSL = os.getenv('SMTP_USE_SSL', 'false').lower() in ['true', '1', 't'] + SMTP_SENDER_EMAIL = os.getenv('SMTP_SENDER_EMAIL', 'temp-spec-system@panjit.com.tw') + SMTP_SENDER_PASSWORD = os.getenv('SMTP_SENDER_PASSWORD', '') # Port 25 不需要密碼 + SMTP_AUTH_REQUIRED = os.getenv('SMTP_AUTH_REQUIRED', 'false').lower() in ['true', '1', 't'] diff --git a/migrate_add_email_column.py b/migrate_add_email_column.py new file mode 100644 index 0000000..8fbf437 --- /dev/null +++ b/migrate_add_email_column.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +""" +資料庫遷移腳本:新增 notification_emails 欄位到 ts_temp_spec 表 +執行方式:python migrate_add_email_column.py +""" + +import os +import sys +from sqlalchemy import create_engine, text +from config import Config + +def migrate_database(): + """執行資料庫遷移,新增 notification_emails 欄位""" + + # 使用 config.py 中的資料庫配置 + engine = create_engine(Config.SQLALCHEMY_DATABASE_URI) + + try: + with engine.connect() as conn: + # 檢查欄位是否已存在 + result = conn.execute(text(""" + SELECT COLUMN_NAME + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'ts_temp_spec' + AND COLUMN_NAME = 'notification_emails' + AND TABLE_SCHEMA = DATABASE() + """)) + + if result.fetchone(): + print("✓ notification_emails 欄位已存在,無需遷移") + return True + + # 新增欄位 + print("正在新增 notification_emails 欄位到 ts_temp_spec 表...") + conn.execute(text(""" + ALTER TABLE ts_temp_spec + ADD COLUMN notification_emails TEXT DEFAULT NULL + COMMENT '通知郵件清單,以分號分隔' + """)) + conn.commit() + + print("✓ 成功新增 notification_emails 欄位") + + # 驗證欄位是否成功新增 + result = conn.execute(text(""" + SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'ts_temp_spec' + AND COLUMN_NAME = 'notification_emails' + AND TABLE_SCHEMA = DATABASE() + """)) + + column_info = result.fetchone() + if column_info: + print(f"✓ 欄位資訊:") + print(f" - 欄位名稱:{column_info[0]}") + print(f" - 資料類型:{column_info[1]}") + print(f" - 可否為空:{column_info[2]}") + return True + else: + print("✗ 欄位新增後無法驗證") + return False + + except Exception as e: + print(f"✗ 遷移失敗:{str(e)}") + return False + +if __name__ == "__main__": + print("=" * 50) + print("資料庫遷移腳本") + print("=" * 50) + + success = migrate_database() + + if success: + print("\n✓ 資料庫遷移完成!") + print(" 現在可以使用新的郵件通知功能了。") + else: + print("\n✗ 資料庫遷移失敗,請檢查錯誤訊息。") + sys.exit(1) \ No newline at end of file diff --git a/models.py b/models.py index 2a3f47e..57f1c70 100644 --- a/models.py +++ b/models.py @@ -27,6 +27,7 @@ class TempSpec(db.Model): created_at = db.Column(db.DateTime) extension_count = db.Column(db.Integer, default=0) termination_reason = db.Column(db.Text, nullable=True) + notification_emails = db.Column(db.Text, nullable=True) # 儲存通知郵件清單,以分號分隔 # 關聯到 Upload 和 SpecHistory,並設定級聯刪除 uploads = db.relationship('Upload', back_populates='spec', cascade='all, delete-orphan') diff --git a/routes/temp_spec.py b/routes/temp_spec.py index d302ca7..cfe8990 100644 --- a/routes/temp_spec.py +++ b/routes/temp_spec.py @@ -265,12 +265,17 @@ def activate_spec(spec_id): db.session.add(new_upload) spec.status = 'active' + + # 儲存通知郵件清單到資料庫 + recipients_str = request.form.get('recipients') + if recipients_str: + spec.notification_emails = recipients_str.strip() + add_history_log(spec.id, '啟用', f"上傳已簽核檔案 '{filename}'") db.session.commit() flash(f"規範 '{spec.spec_code}' 已生效!", 'success') # --- Start of Dynamic Email Notification --- - recipients_str = request.form.get('recipients') if recipients_str: recipients = process_recipients(recipients_str) if recipients: @@ -312,7 +317,10 @@ def terminate_spec(spec_id): add_history_log(spec.id, '終止', f"原因: {reason}") # --- Start of Dynamic Email Notification --- + # 優先使用表單提交的收件者,如果沒有則使用資料庫中儲存的 recipients_str = request.form.get('recipients') + if not recipients_str and spec.notification_emails: + recipients_str = spec.notification_emails if recipients_str: recipients = process_recipients(recipients_str) if recipients: @@ -338,7 +346,8 @@ def terminate_spec(spec_id): flash(f"規範 '{spec.spec_code}' 已被提早終止。", 'warning') return redirect(url_for('temp_spec.spec_list')) - return render_template('terminate_spec.html', spec=spec) + # 將儲存的郵件清單傳遞給模板 + return render_template('terminate_spec.html', spec=spec, saved_emails=spec.notification_emails) @temp_spec_bp.route('/download_initial_word/') @login_required @@ -408,7 +417,14 @@ def extend_spec(spec_id): add_history_log(spec.id, '展延', details) # --- Start of Dynamic Email Notification --- + # 優先使用表單提交的收件者,如果沒有則使用資料庫中儲存的 recipients_str = request.form.get('recipients') + if not recipients_str and spec.notification_emails: + recipients_str = spec.notification_emails + + # 如果使用者有更新郵件清單,儲存回資料庫 + if recipients_str: + spec.notification_emails = recipients_str.strip() if recipients_str: recipients = process_recipients(recipients_str) if recipients: @@ -434,7 +450,8 @@ def extend_spec(spec_id): return redirect(url_for('temp_spec.spec_list')) default_new_end_date = spec.end_date + timedelta(days=30) - return render_template('extend_spec.html', spec=spec, default_new_end_date=default_new_end_date) + # 將儲存的郵件清單傳遞給模板 + return render_template('extend_spec.html', spec=spec, default_new_end_date=default_new_end_date, saved_emails=spec.notification_emails) @temp_spec_bp.route('/history/') @login_required # 補上登入驗證 diff --git a/static/generated/PE1140801.docx b/static/generated/PE1140801.docx index 1612a6cdde3a8df119cc90115be798c193551baf..60894df148ceda9e35ec119b553b0e9f400d2c94 100644 GIT binary patch delta 4944 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 z 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 b_s
+ {% if saved_emails %} +
+ 以下為生效時設定的通知對象,您可以直接使用或進行編輯。如果修改,展延後將更新為新的通知對象。 +
+ {% endif %}
可搜尋姓名或 Email 地址,支援多人選擇
@@ -45,6 +50,13 @@ {% block scripts %} {% endblock %} \ No newline at end of file diff --git a/templates/terminate_spec.html b/templates/terminate_spec.html index 712824c..1bfbb74 100644 --- a/templates/terminate_spec.html +++ b/templates/terminate_spec.html @@ -23,6 +23,11 @@
+ {% if saved_emails %} +
+ 以下為生效時設定的通知對象,您可以直接使用或進行編輯。 +
+ {% endif %}
可搜尋姓名或 Email 地址,支援多人選擇
@@ -38,6 +43,13 @@ {% block scripts %} {% endblock %} diff --git a/utils.py b/utils.py index a850fe2..f0c453d 100644 --- a/utils.py +++ b/utils.py @@ -213,7 +213,7 @@ def process_recipients(recipients_str): def send_email(to_addrs, subject, body): """ Sends an email using the SMTP settings from the config. - Enhanced with detailed debugging information. + Supports both authenticated (Port 465/587) and unauthenticated (Port 25) methods. """ print(f"[EMAIL DEBUG] 開始發送郵件...") print(f"[EMAIL DEBUG] 收件者數量: {len(to_addrs)}") @@ -224,14 +224,18 @@ def send_email(to_addrs, subject, body): # 取得 SMTP 設定 smtp_server = current_app.config['SMTP_SERVER'] smtp_port = current_app.config['SMTP_PORT'] - use_tls = current_app.config['SMTP_USE_TLS'] + use_tls = current_app.config.get('SMTP_USE_TLS', False) + use_ssl = current_app.config.get('SMTP_USE_SSL', False) sender_email = current_app.config['SMTP_SENDER_EMAIL'] - sender_password = current_app.config['SMTP_SENDER_PASSWORD'] + sender_password = current_app.config.get('SMTP_SENDER_PASSWORD', '') + auth_required = current_app.config.get('SMTP_AUTH_REQUIRED', False) print(f"[EMAIL DEBUG] SMTP 設定:") print(f"[EMAIL DEBUG] - 伺服器: {smtp_server}:{smtp_port}") print(f"[EMAIL DEBUG] - 使用 TLS: {use_tls}") + print(f"[EMAIL DEBUG] - 使用 SSL: {use_ssl}") print(f"[EMAIL DEBUG] - 寄件者: {sender_email}") + print(f"[EMAIL DEBUG] - 需要認證: {auth_required}") print(f"[EMAIL DEBUG] - 有密碼: {'是' if sender_password else '否'}") # 建立郵件內容 @@ -243,21 +247,29 @@ def send_email(to_addrs, subject, body): print(f"[EMAIL DEBUG] 郵件內容建立完成") # 連接 SMTP 伺服器 - print(f"[EMAIL DEBUG] 連接 SMTP 伺服器 {smtp_server}:{smtp_port}...") - server = smtplib.SMTP(smtp_server, smtp_port) + if use_ssl and smtp_port == 465: + # Port 465 使用 SSL + print(f"[EMAIL DEBUG] 使用 SSL 連接 SMTP 伺服器 {smtp_server}:{smtp_port}...") + server = smtplib.SMTP_SSL(smtp_server, smtp_port) + else: + # Port 25 或 587 使用一般連接 + print(f"[EMAIL DEBUG] 連接 SMTP 伺服器 {smtp_server}:{smtp_port}...") + server = smtplib.SMTP(smtp_server, smtp_port) + print(f"[EMAIL DEBUG] SMTP 伺服器連接成功") - if use_tls: + if use_tls and smtp_port == 587: print(f"[EMAIL DEBUG] 啟用 TLS...") server.starttls() print(f"[EMAIL DEBUG] TLS 啟用成功") - if sender_password: + # 只在需要認證時才登入 + if auth_required and sender_password: print(f"[EMAIL DEBUG] 登入 SMTP 伺服器...") server.login(sender_email, sender_password) print(f"[EMAIL DEBUG] SMTP 登入成功") else: - print(f"[EMAIL DEBUG] 無需密碼認證") + print(f"[EMAIL DEBUG] 使用匿名發送(Port 25 無需認證)") # 發送郵件 print(f"[EMAIL DEBUG] 發送郵件...") diff --git a/功能升級規劃.txt b/功能升級規劃.txt deleted file mode 100644 index 9b5998b..0000000 --- a/功能升級規劃.txt +++ /dev/null @@ -1,204 +0,0 @@ -功能升級計畫:智慧通知模組 -1. 專案願景 -本次升級旨在將系統的郵件通知功能從一個靜態的範例,全面進化為一個智慧化、高彈性、自動化的通知中樞。完成後,系統將具備以下三大核心能力: - -互動式通知:在所有關鍵操作(啟用、展延、終止)中,提供類似 Outlook 的動態收件人搜尋介面。 - -全流程覆蓋:確保暫規生命週期中的每一個重要節點,都能觸發對應的事件通知。 - -前瞻性提醒:從被動通知轉向主動預警,自動發送即將到期的提醒郵件,防止暫規失效。 - -2. 專案藍圖 (Roadmap) -本計畫將分為三個獨立但環環相扣的模組進行開發,建議依序實施。 - -模組 核心功能 開發重點 狀態 -模組 A 動態收件人介面 建置 LDAP 搜尋 API、前端 UI 元件整合 全新開發 -模組 B 全流程事件通知 將模組 A 的成果應用於「展延」與「終止」流程 功能擴展 -模組 C 自動化排程提醒 引入排程器、建立每日檢查任務、定義提醒規則 全新開發 - -匯出到試算表 -3. 模組 A:動態收件人介面 (互動式通知) -此模組是整個計畫的基石,目標是打造一個可複用的收件人選擇器。 - -A.1. 後端:建置 LDAP 搜尋 API -強化 ldap_utils.py:新增 search_ldap_principals 函式,使用服務帳號安全地查詢 AD 中符合條件的使用者,並回傳其姓名與 Email。 - -建立 API 路由 routes/api.py:建立一個新的 Blueprint,提供 /api/ldap-search 端點,並使用 @login_required 保護,僅供登入者呼叫。 - -註冊 API 路由:在 app.py 中註冊 api_bp,使 API 生效。 - -(詳細程式碼請參考前次對話中的計畫,此處不再贅述) - -A.2. 前端:整合 Tom Select 動態搜尋元件 -修改 templates/base.html:透過 CDN 引入 Tom Select 的 CSS 與 JavaScript 函式庫。 - -修改 templates/activate_spec.html: - -新增一個