3rd
This commit is contained in:
31
.env.example
31
.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
|
||||
|
92
MAIL_OPTIMIZATION_GUIDE.md
Normal file
92
MAIL_OPTIMIZATION_GUIDE.md
Normal file
@@ -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` - 資料庫遷移腳本
|
||||
|
||||
### 相容性
|
||||
- 完全向後相容,不影響現有功能
|
||||
- 舊資料可正常使用,新功能會在首次使用時生效
|
152
SMTP_CONFIGURATION_UPDATE.md
Normal file
152
SMTP_CONFIGURATION_UPDATE.md
Normal file
@@ -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 並提供正確的密碼
|
@@ -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']
|
||||
|
80
migrate_add_email_column.py
Normal file
80
migrate_add_email_column.py
Normal file
@@ -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)
|
@@ -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')
|
||||
|
@@ -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/<int:spec_id>')
|
||||
@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/<int:spec_id>')
|
||||
@login_required # 補上登入驗證
|
||||
|
Binary file not shown.
@@ -30,6 +30,11 @@
|
||||
<!-- 郵件通知對象選擇 -->
|
||||
<div class="mb-3">
|
||||
<label for="recipients" class="form-label"><strong>郵件通知對象</strong></label>
|
||||
{% if saved_emails %}
|
||||
<div class="alert alert-info mb-2">
|
||||
<small>以下為生效時設定的通知對象,您可以直接使用或進行編輯。如果修改,展延後將更新為新的通知對象。</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
<select id="recipients" name="recipients" multiple placeholder="請輸入姓名或 Email 來搜尋...">
|
||||
</select>
|
||||
<div class="form-text">可搜尋姓名或 Email 地址,支援多人選擇</div>
|
||||
@@ -45,6 +50,13 @@
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 預先載入已儲存的郵件清單
|
||||
{% if saved_emails %}
|
||||
const savedEmails = "{{ saved_emails }}".split(';').filter(email => email.trim());
|
||||
{% else %}
|
||||
const savedEmails = [];
|
||||
{% endif %}
|
||||
|
||||
const recipientSelect = new TomSelect('#recipients', {
|
||||
valueField: 'value',
|
||||
labelField: 'text',
|
||||
@@ -89,6 +101,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 預填已儲存的郵件
|
||||
if (savedEmails.length > 0) {
|
||||
savedEmails.forEach(email => {
|
||||
const trimmedEmail = email.trim();
|
||||
if (trimmedEmail) {
|
||||
recipientSelect.addOption({value: trimmedEmail, text: trimmedEmail});
|
||||
recipientSelect.addItem(trimmedEmail);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@@ -23,6 +23,11 @@
|
||||
<!-- 郵件通知對象選擇 -->
|
||||
<div class="mb-3">
|
||||
<label for="recipients" class="form-label"><strong>郵件通知對象</strong></label>
|
||||
{% if saved_emails %}
|
||||
<div class="alert alert-info mb-2">
|
||||
<small>以下為生效時設定的通知對象,您可以直接使用或進行編輯。</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
<select id="recipients" name="recipients" multiple placeholder="請輸入姓名或 Email 來搜尋...">
|
||||
</select>
|
||||
<div class="form-text">可搜尋姓名或 Email 地址,支援多人選擇</div>
|
||||
@@ -38,6 +43,13 @@
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 預先載入已儲存的郵件清單
|
||||
{% if saved_emails %}
|
||||
const savedEmails = "{{ saved_emails }}".split(';').filter(email => email.trim());
|
||||
{% else %}
|
||||
const savedEmails = [];
|
||||
{% endif %}
|
||||
|
||||
const recipientSelect = new TomSelect('#recipients', {
|
||||
valueField: 'value',
|
||||
labelField: 'text',
|
||||
@@ -82,6 +94,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 預填已儲存的郵件
|
||||
if (savedEmails.length > 0) {
|
||||
savedEmails.forEach(email => {
|
||||
const trimmedEmail = email.trim();
|
||||
if (trimmedEmail) {
|
||||
recipientSelect.addOption({value: trimmedEmail, text: trimmedEmail});
|
||||
recipientSelect.addItem(trimmedEmail);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
28
utils.py
28
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] 發送郵件...")
|
||||
|
204
功能升級規劃.txt
204
功能升級規劃.txt
@@ -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:
|
||||
|
||||
新增一個 <select multiple> 輸入框,用於選擇收件人。
|
||||
|
||||
編寫 JavaScript,初始化 Tom Select 元件,並設定其 load 事件去呼叫後端的 /api/ldap-search API,實現使用者輸入時的動態搜尋與載入功能。
|
||||
|
||||
A.3. 邏輯:更新啟用流程
|
||||
修改 routes/temp_spec.py 中的 activate_spec 函式:
|
||||
|
||||
移除原先寫死的 get_ldap_group_members('TempSpec_Approvers') 邏輯。
|
||||
|
||||
改為從 request.form.get('recipients') 獲取由前端 Tom Select 傳來、以逗號分隔的 Email 字串。
|
||||
|
||||
解析字串為 Email 列表,並傳遞給 send_email 函式。
|
||||
|
||||
4. 模組 B:全流程事件通知 (全流程覆蓋)
|
||||
此模組的目標是將 模組 A 的成果,擴展應用到「展延」與「終止」這兩個操作上。
|
||||
|
||||
B.1. 修改 templates/extend_spec.html
|
||||
複製 activate_spec.html 中新增的「郵件通知對象」HTML 區塊。
|
||||
|
||||
將其貼到 extend_spec.html 的表單中,<button type="submit"> 之前。
|
||||
|
||||
複製 activate_spec.html 中 {% block scripts %} 內的 Tom Select 初始化腳本,並貼到 extend_spec.html 的 scripts 區塊中。
|
||||
|
||||
B.2. 修改 routes/temp_spec.py 中的 extend_spec 函式
|
||||
Python
|
||||
|
||||
# In: routes/temp_spec.py
|
||||
@temp_spec_bp.route('/extend/<int:spec_id>', methods=['GET', 'POST'])
|
||||
@editor_or_admin_required
|
||||
def extend_spec(spec_id):
|
||||
spec = TempSpec.query.get_or_404(spec_id)
|
||||
if request.method == 'POST':
|
||||
# ... (原有的檔案上傳、日期更新等邏輯不變) ...
|
||||
|
||||
# ===== START: 新增郵件通知邏輯 =====
|
||||
recipients_str = request.form.get('recipients')
|
||||
if recipients_str:
|
||||
recipients = [email.strip() for email in recipients_str.split(',')]
|
||||
subject = f"[暫規通知] 規範 '{spec.spec_code}' 已展延"
|
||||
body = f"""
|
||||
<html><body>
|
||||
<p>您好,</p>
|
||||
<p>暫時規範 <b>{spec.spec_code} - {spec.title}</b> 已成功展延。</p>
|
||||
<p>新的結束日期為: <b>{spec.end_date.strftime('%Y-%m-%d')}</b></p>
|
||||
<p>詳細資訊請登入系統查看。</p>
|
||||
</body></html>
|
||||
"""
|
||||
send_email(recipients, subject, body)
|
||||
# ===== END: 新增郵件通知邏輯 =====
|
||||
|
||||
db.session.commit()
|
||||
flash(f"規範 '{spec.spec_code}' 已成功展延!", 'success')
|
||||
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)
|
||||
B.3. 針對 terminate_spec 重複 B.1 與 B.2 的步驟
|
||||
對 templates/terminate_spec.html 和 routes/temp_spec.py 中的 terminate_spec 函式執行相同的修改,調整郵件的 subject 和 body 以符合「終止」的情境。
|
||||
|
||||
5. 模組 C:自動化排程提醒 (前瞻性提醒)
|
||||
此模組將為系統引入大腦,使其能夠主動進行管理。
|
||||
|
||||
C.1. 安裝與設定排程器
|
||||
安裝套件:
|
||||
|
||||
Bash
|
||||
|
||||
pip install Flask-APScheduler
|
||||
並將 Flask-APScheduler 新增到 requirements.txt 檔案中。
|
||||
|
||||
在 app.py 中初始化排程器:
|
||||
|
||||
Python
|
||||
|
||||
# In: app.py
|
||||
from flask_apscheduler import APScheduler # <-- 新增
|
||||
|
||||
# ...
|
||||
app = Flask(__name__)
|
||||
app.config.from_object('config.Config')
|
||||
|
||||
# ===== START: 初始化排程器 =====
|
||||
scheduler = APScheduler()
|
||||
scheduler.init_app(app)
|
||||
scheduler.start()
|
||||
# ===== END: 初始化排程器 =====
|
||||
|
||||
db.init_app(app)
|
||||
# ...
|
||||
C.2. 建立排程任務 (tasks.py)
|
||||
為了保持程式碼的模組化,我們建立一個新檔案來存放排程任務。
|
||||
|
||||
建立新檔案 tasks.py:
|
||||
|
||||
Python
|
||||
|
||||
# 檔案位置: beabigegg/temp_spec_system_v3/TEMP_spec_system_V3-b9557250a410cf778a51ece25ffe28543f494ffb/tasks.py (新檔案)
|
||||
|
||||
from datetime import date, timedelta
|
||||
from models import TempSpec
|
||||
from utils import send_email
|
||||
from ldap_utils import get_ldap_group_members
|
||||
|
||||
def check_expiring_specs(app):
|
||||
"""
|
||||
每日執行的排程任務:檢查即將到期的暫規並發送提醒郵件。
|
||||
"""
|
||||
with app.app_context():
|
||||
print("Running scheduled task: Checking for expiring specs...")
|
||||
today = date.today()
|
||||
seven_days_later = today + timedelta(days=7)
|
||||
three_days_later = today + timedelta(days=3)
|
||||
|
||||
# 找出 7 天後 和 3 天後到期的暫規
|
||||
expiring_soon = TempSpec.query.filter(
|
||||
TempSpec.status == 'active',
|
||||
TempSpec.end_date.in_([seven_days_later, three_days_later])
|
||||
).all()
|
||||
|
||||
if not expiring_soon:
|
||||
print("No specs expiring in 3 or 7 days.")
|
||||
return
|
||||
|
||||
# **重要**: 定義預設的通知對象,例如某個管理群組
|
||||
# 您需要將 'TempSpec_Admins' 替換為實際的 AD 群組名稱
|
||||
default_recipients = get_ldap_group_members('TempSpec_Admins')
|
||||
if not default_recipients:
|
||||
print("Warning: Could not find default recipients in AD group 'TempSpec_Admins'.")
|
||||
return
|
||||
|
||||
for spec in expiring_soon:
|
||||
remaining_days = (spec.end_date - today).days
|
||||
|
||||
# 組合通知郵件
|
||||
subject = f"[暫規到期提醒] 規範 '{spec.spec_code}' 將於 {remaining_days} 天後到期"
|
||||
body = f"""
|
||||
<html><body>
|
||||
<p>您好,</p>
|
||||
<p>此為自動提醒郵件。</p>
|
||||
<p>暫時規範 <b>{spec.spec_code} - {spec.title}</b> 即將到期。</p>
|
||||
<p><b>結束日期: {spec.end_date.strftime('%Y-%m-%d')} (剩餘 {remaining_days} 天)</b></p>
|
||||
<p>申請人: {spec.applicant}</p>
|
||||
<p>請及時處理,如需展延請登入系統操作。</p>
|
||||
</body></html>
|
||||
"""
|
||||
|
||||
# 發送郵件給預設群組
|
||||
send_email(default_recipients, subject, body)
|
||||
print(f"Sent expiry reminder for spec {spec.spec_code} to {len(default_recipients)} recipients.")
|
||||
|
||||
C.3. 在 app.py 中註冊與啟動任務
|
||||
最後,我們需要告訴排程器有這個任務,並設定它每天執行。
|
||||
|
||||
Python
|
||||
|
||||
# In: app.py
|
||||
|
||||
# ... (在 from ... import ... 之後)
|
||||
from tasks import check_expiring_specs
|
||||
|
||||
# ... (在 scheduler.start() 之後)
|
||||
|
||||
# 註冊排程任務:每天凌晨 2:00 執行一次
|
||||
@scheduler.task('cron', id='check_expiring_specs_job', hour=2, minute=0)
|
||||
def scheduled_job():
|
||||
check_expiring_specs(app)
|
||||
|
||||
# ... (Flask app 的其他部分)
|
Reference in New Issue
Block a user