diff --git a/.gitignore b/.gitignore index c664fac..d4afb42 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,17 @@ Thumbs.db # 忽略所有日誌檔案。 *.log logs/ + +# --- 環境設定檔 --- .env + +# --- 測試相關 (Testing) --- +# 忽略測試檔案 +test_*.py +*_test.py +tests/ + +# --- 開發者專用文件 (Developer Only) --- +# 最佳實踐文件(包含敏感設定資訊) +BEST_PRACTICES.md +DEVELOPER_GUIDE.md diff --git a/DEMO_GUIDE.md b/DEMO_GUIDE.md index 07dde8a..db5507d 100644 --- a/DEMO_GUIDE.md +++ b/DEMO_GUIDE.md @@ -12,8 +12,8 @@ cp .env.example .env # 2. 編輯 .env 檔案 (重要!) # 設定以下關鍵參數: -# - LDAP_SERVER=ldap://your-dc.panjit.com.tw -# - LDAP_BIND_USER_DN=CN=service,DC=panjit,DC=com,DC=tw +# - LDAP_SERVER=ldap://your-dc.company.com +# - LDAP_BIND_USER_DN=CN=service,DC=company,DC=com # - LDAP_BIND_USER_PASSWORD=your_service_password # 3. 啟動所有服務 @@ -48,7 +48,7 @@ start-windows.bat - [ ] **LDAP連線**: 使用AD帳號測試登入 ### 權限設定確認 -- [ ] **ymirliu@panjit.com.tw**: 已設定為管理員權限 +- [ ] **admin@company.com**: 已設定為管理員權限 - [ ] **權限管理頁面**: 可透過選單訪問 - [ ] **角色功能**: admin/editor/viewer 功能正常 @@ -99,7 +99,7 @@ docker-compose exec app python init_db.py 1. **登入測試** ```bash # 測試AD帳號登入 -用戶: ymirliu@panjit.com.tw (或 ymirliu) +用戶: admin@company.com 密碼: [AD密碼] ``` @@ -174,7 +174,7 @@ docker-compose exec mysql mysql -u root -p -e "SHOW DATABASES;" ```sql -- 直接在資料庫中設定管理員 USE tempspec_db; -UPDATE ts_user SET role='admin' WHERE username='ymirliu@panjit.com.tw'; +UPDATE ts_user SET role='admin' WHERE username='admin@company.com'; ``` ### 快速重置 @@ -247,4 +247,4 @@ Demo成功的標誌: --- -**祝您Demo順利!** 如有任何問題,請參考系統日誌或聯繫技術支援團隊。 \ No newline at end of file +**祝您Demo順利!** 如有任何問題,請參考系統日誌。 \ No newline at end of file diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 5ec4741..33bf663 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -687,4 +687,4 @@ curl -s http://localhost:5000/health | jq . --- -本部署指南涵蓋了大部分常見的部署場景。如果遇到特殊情況或需要客製化部署,請參考系統文檔或聯繫技術支援團隊。 \ No newline at end of file +本部署指南涵蓋了大部分常見的部署場景。如果遇到特殊情況或需要客製化部署,請參考系統相關文檔。 \ No newline at end of file diff --git a/README.md b/README.md index b0ea826..e8a91be 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ ### 智慧通知系統 - **動態收件人選擇**:整合LDAP的即時用戶搜尋 +- **郵件記憶功能**:自動記憶並帶出之前使用的通知對象 - **全流程通知**:啟用、展延、終止操作的自動郵件通知 - **自動提醒**:3天與7天到期前的主動提醒郵件 - **排程系統**:每日自動檢查即將到期的規範 @@ -87,6 +88,12 @@ docker-compose up -d docker-compose exec app python init_db.py ``` +5. **資料庫遷移(如果需要)** +```bash +# 新增郵件功能欄位 +docker-compose exec app python migrate_add_email_column.py +``` + ### 手動安裝 #### Windows 環境 @@ -107,7 +114,12 @@ REM 編輯 .env 檔案 python init_db.py ``` -4. **啟動 ONLYOFFICE Document Server** +4. **資料庫遷移(如果需要)** +```cmd +python migrate_add_email_column.py +``` + +5. **啟動 ONLYOFFICE Document Server** ```cmd docker run -d -p 8080:80 --restart=always ^ -e JWT_ENABLED=true ^ @@ -115,7 +127,7 @@ docker run -d -p 8080:80 --restart=always ^ onlyoffice/documentserver ``` -5. **啟動應用程式** +6. **啟動應用程式** ```cmd REM 開發環境 python app.py @@ -143,7 +155,12 @@ cp .env.example .env python init_db.py ``` -4. **啟動 ONLYOFFICE Document Server** +4. **資料庫遷移(如果需要)** +```bash +python migrate_add_email_column.py +``` + +5. **啟動 ONLYOFFICE Document Server** ```bash docker run -d -p 8080:80 --restart=always \ -e JWT_ENABLED=true \ @@ -151,7 +168,7 @@ docker run -d -p 8080:80 --restart=always \ onlyoffice/documentserver ``` -5. **啟動應用程式** +6. **啟動應用程式** ```bash # 開發環境 python app.py @@ -161,79 +178,6 @@ pip install gunicorn gunicorn -w 4 -b 0.0.0.0:5000 app:app ``` -### 生產環境部署 - -#### 使用 Nginx + Gunicorn (Linux) - -1. **安裝 Gunicorn** -```bash -pip install gunicorn -``` - -2. **建立 Gunicorn 服務檔案** -```bash -sudo nano /etc/systemd/system/tempspec.service -``` - -內容: -```ini -[Unit] -Description=Temp Spec System -After=network.target - -[Service] -User=www-data -Group=www-data -WorkingDirectory=/path/to/your/app -Environment="PATH=/path/to/your/app/venv/bin" -ExecStart=/path/to/your/app/venv/bin/gunicorn -w 4 -b 127.0.0.1:5000 app:app -Restart=always - -[Install] -WantedBy=multi-user.target -``` - -3. **Nginx 設定** -```nginx -server { - listen 80; - server_name your-domain.com; - - location / { - proxy_pass http://127.0.0.1:5000; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } -} -``` - -4. **啟動服務** -```bash -sudo systemctl enable tempspec -sudo systemctl start tempspec -``` - -#### Windows IIS 部署 - -1. **安裝 IIS 與 Python** -2. **安裝 HttpPlatformHandler** -3. **設定 Web.config** -```xml - - - - - - - - - -``` - ## ⚙️ 組態設定 ### 環境變數 (.env) @@ -255,74 +199,29 @@ LDAP_BIND_USER_DN=CN=service,DC=company,DC=com 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 +# SMTP 郵件設定 (Port 25 無認證方式) +SMTP_SERVER=mail.company.com +SMTP_PORT=25 +SMTP_USE_TLS=false +SMTP_USE_SSL=false +SMTP_AUTH_REQUIRED=false +SMTP_SENDER_EMAIL=temp-spec-system@company.com +SMTP_SENDER_PASSWORD= # ONLYOFFICE 設定 ONLYOFFICE_URL=http://onlyoffice:8080 ONLYOFFICE_JWT_SECRET=your_jwt_secret ``` -### 特殊注意事項 +### SMTP 配置說明 -#### Windows 環境差異 +系統支援多種 SMTP 配置方式: -1. **排程服務限制** - - APScheduler 在 Windows 上運行正常 - - 若使用 Celery,需要額外設定: - ```bash - # Windows 環境需要使用 eventlet 或 solo 執行器 - celery -A app.celery worker --loglevel=info --pool=solo - ``` +- **Port 25(推薦)**:內部郵件伺服器,無需認證 +- **Port 587**:STARTTLS + 認證 +- **Port 465**:SSL + 認證 -2. **路徑設定** - ```python - # Windows 環境請使用絕對路徑或適當的路徑分隔符 - UPLOAD_FOLDER = r'C:\path\to\uploads' - ``` - -3. **服務安裝** - ```bash - # 使用 NSSM 將 Python 應用程式安裝為 Windows 服務 - nssm install TempSpecSystem python.exe app.py - ``` - -#### Linux 環境最佳化 - -1. **系統服務設定** - ```bash - # 設定系統服務自動啟動 - sudo systemctl enable tempspec.service - ``` - -2. **日誌管理** - ```bash - # 使用 logrotate 管理日誌檔案 - sudo nano /etc/logrotate.d/tempspec - ``` - -3. **效能調校** - ```bash - # Gunicorn 推薦設定 - gunicorn -w 4 -k gevent --worker-connections 1000 -b 0.0.0.0:5000 app:app - ``` - -## 🔐 安全性設定 - -### LDAP 整合 -- 支援 SSL/TLS 加密連線 -- 服務帳號權限最小化原則 -- 自動用戶同步與權限管控 - -### 資料保護 -- JWT Token 驗證 -- 檔案存取權限控制 -- SQL Injection 防護 -- XSS 攻擊防護 +詳細設定請參考 `SMTP_CONFIGURATION_UPDATE.md` ## 📚 使用說明 @@ -344,40 +243,34 @@ ONLYOFFICE_JWT_SECRET=your_jwt_secret UPDATE ts_user SET role='admin' WHERE username='user@domain.com'; ``` -3. **程式設定**:修改 `routes/auth.py` 中的管理員帳號列表: -```python -# 將特定用戶設為管理員 -if user_info['username'].lower() == 'your_admin@domain.com': - default_role = 'admin' -``` +### 郵件通知功能 -### 排程任務說明 +系統具備智慧郵件管理功能: -系統預設每天凌晨 2:00 執行到期檢查任務,可在 `app.py` 中調整: +1. **規範生效時**:輸入通知郵件對象,系統自動記憶 +2. **規範終止時**:自動帶出生效時使用的郵件清單,可編輯後發送 +3. **規範展延時**:自動帶出郵件清單,修改後更新記錄 -```python -@scheduler.task('cron', id='check_expiring_specs_job', hour=2, minute=0) -def scheduled_job(): - check_expiring_specs(app) -``` +### 排程任務 -### 自訂提醒天數 +系統預設每天凌晨 2:00 執行到期檢查任務: -在 `tasks.py` 中修改提醒時程: +- 7天到期提醒 +- 3天到期提醒 +- 自動發送提醒郵件 -```python -seven_days_later = today + timedelta(days=7) # 7天前提醒 -three_days_later = today + timedelta(days=3) # 3天前提醒 -``` +## 🔐 安全性設定 -### 預設收件人群組設定 +### LDAP 整合 +- 支援 SSL/TLS 加密連線 +- 服務帳號權限最小化原則 +- 自動用戶同步與權限管控 -在 `tasks.py` 中設定自動提醒的收件人: - -```python -# 修改為實際的 AD 群組名稱 -default_recipients = get_ldap_group_members('TempSpec_Admins') -``` +### 資料保護 +- JWT Token 驗證 +- 檔案存取權限控制 +- SQL Injection 防護 +- XSS 攻擊防護 ## 🐛 疑難排解 @@ -395,19 +288,14 @@ default_recipients = get_ldap_group_members('TempSpec_Admins') 3. **郵件發送失敗** - 確認 SMTP 設定正確 - - 檢查郵件伺服器認證 - - 驗證防火牆規則 (通常是 25/587/465 port) + - 檢查郵件伺服器認證設定 + - 驗證防火牆規則 (Port 25/587/465) 4. **排程任務未執行** - 檢查 APScheduler 初始化 - 確認應用程式持續運行 - 查看系統日誌 -5. **檔案上傳失敗** - - 檢查上傳目錄權限 - - 確認檔案大小限制設定 - - 驗證磁碟空間是否足夠 - ### 日誌查看 ```bash @@ -421,40 +309,8 @@ tail -f logs/app.log Get-Content logs/app.log -Tail 10 -Wait ``` -### 效能監控 - -```bash -# 監控資源使用 -htop -docker stats - -# 檢查資料庫效能 -SHOW PROCESSLIST; -SHOW ENGINE INNODB STATUS; -``` - ## 🤝 開發指南 -### 開發環境設定 - -1. **虛擬環境建立** -```bash -python -m venv venv -source venv/bin/activate # Linux -venv\Scripts\activate # Windows -``` - -2. **安裝開發依賴** -```bash -pip install -r requirements.txt -pip install -r requirements-dev.txt # 如果有開發專用依賴 -``` - -3. **資料庫遷移** -```bash -python init_db.py -``` - ### 程式碼結構 ``` @@ -473,41 +329,38 @@ python init_db.py └── ldap_utils.py # LDAP 工具 ``` -### 新增功能開發 +### 資料庫遷移 -1. **建立新的路由模組** -2. **新增對應的資料模型** -3. **建立前端範本** -4. **撰寫單元測試** +當系統需要資料庫結構更新時: + +```bash +# 執行遷移腳本 +python migrate_add_email_column.py +``` ## 📄 授權條款 -本專案採用 MIT 授權條款,詳見 [LICENSE](LICENSE) 檔案。 +本專案採用 MIT 授權條款。 ## 🆕 版本歷程 -### v3.0.0 (2024-01-XX) +### v3.2.0 (最新版本) +- 🆕 新增郵件通知記憶功能 +- 🆕 支援 Port 25 無認證 SMTP +- ♻️ 優化郵件管理邏輯 +- 🗑️ 移除測試檔案和調試代碼 + +### v3.1.0 - 🆕 新增 LDAP/AD 整合驗證 -- 🆕 整合 ONLYOFFICE 線上編輯器 +- 🆕 整合 ONLYOFFICE 線上編輯器 - 🆕 實作智慧通知系統 - 🆕 新增自動排程提醒功能 - 🆕 支援 Docker 容器化部署 + +### v3.0.0 - ♻️ 重構權限管理系統 - 🗑️ 移除本地帳號管理功能 -### v2.x.x -- Toast UI Editor 整合 -- 基本文件管理功能 -- 本地帳號系統 - -## 📞 技術支援 - -如有問題或建議,請透過以下方式聯繫: - -- 📧 Email: support@company.com -- 📋 Issue Tracker: GitHub Issues -- 📖 文件wiki: GitHub Wiki - --- **暫時規範管理系統 V3** - 讓企業文件管理更智慧、更高效! \ No newline at end of file diff --git a/USER_MANUAL.md b/USER_MANUAL.md index 830d7dd..c0c1692 100644 --- a/USER_MANUAL.md +++ b/USER_MANUAL.md @@ -18,10 +18,12 @@ 暫時規範管理系統 V3 是一個集中化平台,用於管理、追蹤和存檔所有暫時性的工程規範。它涵蓋了從草擬、線上編輯、簽核、生效到最終歸檔的完整生命週期。 -### 🚀 V3 版本新特色 +### 🚀 V3.2 版本新特色 - **LDAP/AD 整合**:使用企業Active Directory帳號登入 -- **智慧通知系統**:動態收件人選擇與自動提醒 +- **智慧郵件記憶**:自動記憶並帶出之前使用的通知對象 +- **彈性郵件編輯**:可編輯通知名單並更新記錄 +- **多種SMTP支援**:支援Port 25無認證及其他認證方式 - **自動排程提醒**:系統主動發送到期提醒郵件 - **增強的編輯體驗**:ONLYOFFICE文件協作編輯 @@ -31,7 +33,7 @@ ### 2.1 LDAP 登入 -🆕 **V3 新功能**:系統現在使用企業 Active Directory 進行單一登入。 +系統使用企業 Active Directory 進行單一登入。 **🚨 重要登入規範**: @@ -48,152 +50,164 @@ > **注意**: > - 首次登入的用戶預設為 `Viewer` 權限 -> - 如需提升權限請聯繫系統管理員 +> - 需要聯繫系統管理員提升權限 -### 2.2 主畫面功能 +### 2.2 主畫面導覽 -登入後進入暫時規範總表,包含以下區域: +登入後會看到暫時規範列表,包含: -- **🔍 搜尋與篩選區**:根據編號、主題或狀態快速查找 -- **📊 狀態統計**:顯示各狀態規範的數量概覽 -- **📋 規範列表**:詳細顯示所有規範資訊 -- **⚡ 快速操作**:根據權限和規範狀態提供操作按鈕 - -### 2.3 狀態指示說明 - -| 狀態 | 圖示 | 說明 | -|------|------|------| -| 待生效 | 🟡 | 草稿完成,待管理員啟用 | -| 已生效 | 🟢 | 正在生效中的規範 | -| 已過期 | 🔴 | 已自動過期的規範 | -| 已終止 | ⚫ | 提早終止的規範 | +- **規範編號**:系統自動產生(PE+民國年+月份+序號) +- **主題**:規範標題 +- **申請人**:規範申請者 +- **狀態**:pending_approval(待生效)/active(已生效)/expired(已過期)/terminated(已終止) +- **時間範圍**:生效日期至結束日期 +- **操作按鈕**:依權限顯示不同功能 --- ## 3. 核心操作流程 -### 3.1 完整工作流程 +### 3.1 建立新規範(Editor/Admin 權限) -```mermaid -graph TD - A[Editor建立草稿] --> B[ONLYOFFICE線上編輯] - B --> C[下載Word文件] - C --> D[線下簽核流程] - D --> E[Admin上傳PDF啟用] - E --> F[🆕選擇通知對象] - F --> G[規範正式生效] - G --> H[🆕自動到期提醒] -``` +1. 點擊「**新增規範**」按鈕 +2. 填寫規範資訊: + - **主題**:規範標題 + - **申請人**:申請者姓名 + - **申請人電話**:聯絡電話 + - **相關資訊**:包裝、批號、設備類型等 -### 3.2 建立新的暫時規範 (Editor / Admin) +3. **選擇適用站別**: + - Probing、Dicing、Die bond、Wire bond 等 + - 可多選 -1. **建立草稿** - - 點擊 **[+ 暫時規範建立]** 按鈕 - - 填寫基本資料:主題、申請人、站別等 - - 選擇適用的 TCCS 等級和 4M 類別 - - 點擊 **[建立並開始編輯]** +4. **TCCS等級選擇**: + - L1-L4 四個等級 + - 單選 -2. **線上編輯** - - 系統自動開啟 ONLYOFFICE 編輯器 - - 文件已預填初始資料 - - 支援多人協作編輯 - - 所有變更自動儲存 +5. **4M選擇**: + - Man、Machine、Material、Method、Environment + - 單選 -3. **完成草稿** - - 編輯完成後可直接關閉編輯器 - - 或點擊 **[完成編輯]** 返回總表 +6. 點擊「**建立**」完成草稿建立 -### 3.3 啟用暫時規範 (僅限 Admin) +### 3.2 編輯規範內容(Editor/Admin 權限) -🆕 **新增智慧通知功能** +**🆕 ONLYOFFICE 線上編輯**: -1. **開始啟用流程** - - 找到狀態為「待生效」的規範 - - 點擊 **啟用圖示 (✅)** +1. 在規範列表點擊「**編輯**」按鈕 +2. 系統開啟 ONLYOFFICE 編輯器 +3. 進行文件編輯、格式調整 +4. 使用 **Ctrl+S** 定期儲存 +5. 編輯完成後關閉編輯器視窗 -2. **上傳簽核文件** - - 上傳已簽核完成的 PDF 檔案 - - 系統自動驗證檔案格式 +**編輯器功能**: +- 全功能 Word 文件編輯 +- 即時自動儲存 +- 支援圖片、表格插入 +- 格式化工具列 -3. **🆕 選擇通知對象** - - 在「郵件通知對象」欄位輸入姓名或Email - - 系統即時搜尋 LDAP 用戶 - - 支援多人選擇 - - 可選擇不發送通知 +### 3.3 啟用規範(Admin 權限) -4. **確認啟用** - - 點擊 **[上傳並啟用]** - - 系統發送啟用通知郵件 - - 規範狀態變更為「已生效」 +將規範從「待生效」變更為「已生效」狀態: -### 3.4 展延暫時規範 (Editor / Admin) +1. 點擊「**啟用**」按鈕 +2. **上傳已簽核檔案**:選擇已簽核的PDF檔案 +3. **設定通知對象**: + - 在搜尋框輸入姓名或Email(至少2個字元) + - 從下拉清單選擇收件者 + - 支援AD群組搜尋(格式:`group:群組名稱`) + - 可選擇多位收件者 -🆕 **新增智慧通知功能** +4. 點擊「**確認啟用**」 +5. 系統自動: + - 更新規範狀態 + - **記憶通知對象**供後續使用 + - 發送啟用通知郵件 -1. **開始展延** - - 點擊 **展延圖示 (📅+)** - - 選擇新的結束日期 +### 3.4 展延規範(Editor/Admin 權限) -2. **上傳佐證文件** - - 必須上傳新的佐證文件 (PDF格式) +延長已生效規範的結束日期: -3. **🆕 通知對象選擇** - - 選擇需要通知的相關人員 - - 使用動態搜尋功能選擇收件人 +1. 點擊「**展延**」按鈕 +2. **設定新結束日期**:選擇展延後的日期 +3. **上傳佐證檔案**:提供展延理由相關文件(PDF格式) +4. **🆕 智慧通知設定**: + - 系統自動帶出之前啟用時使用的通知對象 + - 可直接使用或進行編輯 + - 修改後的名單會更新到系統記錄中 -4. **確認展延** - - 點擊 **[確認展延]** - - 系統發送展延通知郵件 +5. 點擊「**確認展延**」 +6. 系統自動發送展延通知郵件 -### 3.5 終止暫時規範 (Editor / Admin) +### 3.5 終止規範(Editor/Admin 權限) -🆕 **新增智慧通知功能** +提早結束規範: -1. **開始終止** - - 點擊 **終止圖示 (❌)** - - 填寫提早結束原因 +1. 點擊「**終止**」按鈕 +2. **填寫終止原因**:說明提早結束的理由 +3. **🆕 智慧通知設定**: + - 系統自動帶出之前啟用時使用的通知對象 + - 顯示提示「以下為生效時設定的通知對象」 + - 可直接使用或進行編輯 -2. **🆕 通知設定** - - 選擇需要通知終止訊息的人員 - -3. **確認終止** - - 點擊 **[確認終止]** - - 系統發送終止通知郵件 - - 規範狀態變為「已終止」 +4. 點擊「**確認終止**」 +5. 系統自動: + - 更新結束日期為今日 + - 發送終止通知郵件 --- ## 4. 智慧通知系統 -🆕 **V3 全新功能** +### 4.1 🆕 郵件記憶功能 -### 4.1 動態收件人選擇 +**V3.2 新增功能**:系統現在具備智慧郵件管理能力 -所有主要操作(啟用、展延、終止)都支援智慧通知: +**運作機制**: +1. **規範啟用時**:輸入通知郵件對象,系統自動記憶 +2. **規範終止時**:自動帶出啟用時的郵件清單,可編輯後發送 +3. **規範展延時**:自動帶出郵件清單,修改後會更新記錄 -- **即時搜尋**:輸入姓名或Email即時搜尋AD用戶 -- **多人選擇**:支援選擇多位收件人 -- **自動完成**:顯示完整姓名和Email資訊 -- **移除功能**:可隨時移除已選擇的收件人 +**操作說明**: +- 系統會顯示「以下為生效時設定的通知對象」提示 +- 可以直接使用預設的郵件清單 +- 也可以修改郵件清單後再發送 +- 展延時修改的名單會成為新的預設通知對象 -### 4.2 通知郵件內容 +### 4.2 動態收件人選擇 -系統會根據操作類型自動發送相應的通知郵件: +**搜尋功能**: +- 輸入至少 **2個字元** 開始搜尋 +- 支援姓名或Email模糊搜尋 +- 即時顯示搜尋結果 -- **啟用通知**:包含規範編號、主題、生效/結束日期 -- **展延通知**:包含新的結束日期和展延原因 -- **終止通知**:包含終止原因和終止日期 +**選擇方式**: +- **個人用戶**:直接選擇用戶 +- **AD群組**:輸入 `group:群組名稱` 選擇整個群組 +- **多重選擇**:可同時選擇多位收件者 -### 4.3 🆕 自動到期提醒 +**群組搜尋**: +- 格式:`group:TempSpec_Admins` +- 系統會自動展開群組成員 +- 發送時會寄給所有群組成員 -系統每天自動檢查即將到期的規範: +### 4.3 通知類型 -- **7天前提醒**:首次到期預警 -- **3天前提醒**:最終到期提醒 -- **自動發送**:無需人工干預 -- **群組通知**:發送給預設管理群組 +**手動通知**(操作觸發): +- 規範啟用通知 +- 規範展延通知 +- 規範終止通知 -> **管理員設定**:可在 `tasks.py` 中修改提醒天數和收件人群組 +**🆕 自動提醒**(系統排程): +- **7天到期提醒**:在規範到期前7天自動發送 +- **3天到期提醒**:在規範到期前3天自動發送 +- **發送時間**:每天凌晨2:00檢查並發送 + +**郵件內容**: +- HTML格式美化顯示 +- 包含規範編號、標題、申請人 +- 明確標示生效/結束日期 +- 提供系統連結 --- @@ -230,12 +244,12 @@ graph TD - 操作類型(建立/啟用/展延/終止) - 詳細說明 -### 5.4 🆕 即將到期警示 +### 5.4 即將到期警示 在規範列表中會特別標示即將到期的規範: - **🟡 橙色標示**:7天內到期 -- **🔴 紅色標示**:3天內到期 +- **🔴 紅色標示**:3天內到期 - **閃爍動畫**:今日到期 --- @@ -250,17 +264,24 @@ graph TD | **Editor** | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | | **Admin** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -### 6.2 🆕 LDAP 權限管理 +### 6.2 權限說明 -- **自動用戶創建**:AD用戶首次登入自動建立帳號 -- **預設權限**:新用戶預設為 `Viewer` 權限 -- **權限提升**:需要管理員手動調整資料庫或修改程式碼 +**Viewer(檢視者)**: +- 檢視所有規範內容 +- 下載PDF檔案 +- 檢視歷史紀錄 -**管理員手動提升權限**: -```sql --- 使用完整UPN格式帳號 -UPDATE ts_user SET role='admin' WHERE username='user@domain.com'; -``` +**Editor(編輯者)**: +- 建立新規範草稿 +- 編輯規範內容 +- 展延和終止規範 +- 下載Word和PDF檔案 + +**Admin(管理員)**: +- 所有Editor權限 +- 啟用規範(上傳簽核檔案) +- 刪除規範 +- 系統管理功能 --- @@ -268,40 +289,39 @@ UPDATE ts_user SET role='admin' WHERE username='user@domain.com'; ### 7.1 登入相關 -**Q: 無法使用AD帳號登入?** -A: 請確認: -1. **必須使用完整UPN格式**:例如 user@domain.com(不支援縮略帳號如 user) -2. 密碼正確 -3. 帳號未被鎖定 -4. 聯繫IT部門確認LDAP連線狀態 +**Q: 忘記帳號格式?** +A: 必須使用完整的 `user@domain.com` 格式,不能只輸入 `user` -**Q: 忘記密碼怎麼辦?** -A: 系統使用AD驗證,請透過企業標準流程重設AD密碼。 +**Q: 無法登入?** +A: 請確認: +1. 帳號格式正確(包含@domain.com) +2. 密碼正確 +3. AD帳號未被鎖定 +4. 網路連線正常 ### 7.2 權限相關 -**Q: 為什麼我看不到建立規範按鈕?** -A: 您的權限可能為 `Viewer`,請聯繫管理員提升權限至 `Editor`。 +**Q: 無法建立規範?** +A: 請確認您的權限等級,Viewer無法建立規範,需要Editor以上權限。 -**Q: 為什麼我無法啟用規範?** -A: 啟用功能僅限 `Admin` 權限,請聯繫系統管理員操作。 +**Q: 無法啟用規範?** +A: 啟用功能需要Admin權限,請聯繫系統管理員。 -### 7.3 編輯器相關 +### 7.3 編輯相關 **Q: ONLYOFFICE編輯器無法載入?** -A: 請檢查: -1. 瀏覽器是否允許彈出視窗 -2. 網路連線是否正常 -3. 是否安裝最新版瀏覽器 -4. 聯繫IT確認Document Server狀態 +A: 請確認: +1. 瀏覽器支援(建議Chrome/Edge) +2. 網路連線穩定 +3. 彈出視窗未被阻擋 **Q: 編輯內容未儲存?** -A: ONLYOFFICE有自動儲存功能,但請確認: +A: 建議: 1. 編輯期間保持網路連線 2. 避免同時多人編輯同一文件 3. 定期手動儲存 (Ctrl+S) -### 7.4 🆕 通知相關 +### 7.4 通知相關 **Q: 搜尋不到AD用戶?** A: 請確認: @@ -314,11 +334,16 @@ A: 請檢查: 1. Email地址是否正確 2. 垃圾郵件資料夾 3. 公司郵件伺服器設定 -4. 聯繫IT確認SMTP設定 **Q: 自動提醒郵件何時發送?** A: 系統每天凌晨2:00自動檢查並發送提醒,分別在到期前7天和3天發送。 +**🆕 Q: 郵件通知對象會自動記憶嗎?** +A: 是的,系統會記憶啟用時設定的通知對象: +- 終止規範時會自動帶出之前的郵件清單 +- 展延規範時也會自動帶出,修改後會更新記錄 +- 您可以直接使用或編輯後再發送 + ### 7.5 檔案相關 **Q: 可以上傳Word檔案來啟用規範嗎?** @@ -342,23 +367,27 @@ A: 可能原因: --- -## 🆘 技術支援 - -如遇到本手冊未涵蓋的問題,請透過以下方式聯繫: - -- **📧 系統管理員**: [admin@company.com](mailto:admin@company.com) -- **📞 IT支援專線**: 分機 2345 -- **💬 企業即時通**: #temp-spec-support - ---- - ## 📝 版本資訊 -- **文件版本**: V3.0.0 -- **最後更新**: 2024年1月 -- **適用系統**: 暫時規範管理系統 V3 +- **文件版本**: V3.2.0 +- **最後更新**: 2025年1月 +- **適用系統**: 暫時規範管理系統 V3.2 + +### 版本更新記錄 + +**V3.2.0**: +- 新增郵件通知記憶功能 +- 支援Port 25無認證SMTP +- 優化郵件管理邏輯 +- 更新操作說明 + +**V3.1.0**: +- 新增LDAP/AD整合認證 +- 整合ONLYOFFICE線上編輯器 +- 實作智慧通知系統 +- 新增自動排程提醒功能 --- **感謝您使用暫時規範管理系統 V3!** -希望這個操作手冊能幫助您更有效地使用系統功能。如有任何建議或回饋,歡迎與我們聯繫。 \ No newline at end of file +希望這個操作手冊能幫助您更有效地使用系統功能。 \ No newline at end of file diff --git a/test_api.py b/test_api.py deleted file mode 100644 index 6529628..0000000 --- a/test_api.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 -""" -測試 LDAP API 端點功能 -""" -import requests -import json - -def test_api_search(): - """測試 API 搜尋功能""" - print("=== 測試 LDAP API 搜尋功能 ===") - - # 測試不同的搜尋詞 - test_terms = ["liu", "劉", "PE", "管理", "admin"] - - base_url = "http://127.0.0.1:5000" - - # 先登入獲取 session cookie - session = requests.Session() - - print("正在登入系統...") - login_data = { - 'username': 'ymirliu@panjit.com.tw', - 'password': input("請輸入 ymirliu@panjit.com.tw 的密碼: ") - } - - # 嘗試登入 - login_response = session.post(f"{base_url}/login", data=login_data) - - if login_response.status_code == 200 and "總表檢視" in login_response.text: - print("✅ 登入成功") - - for term in test_terms: - print(f"\n🔍 測試搜尋: '{term}'") - print("-" * 40) - - api_url = f"{base_url}/api/ldap-search" - params = {'q': term} - - try: - response = session.get(api_url, params=params) - print(f"HTTP 狀態: {response.status_code}") - - if response.status_code == 200: - try: - data = response.json() - print(f"找到 {len(data)} 個結果:") - - for i, result in enumerate(data, 1): - print(f" {i}. {result.get('text', 'N/A')}") - print(f" 值: {result.get('value', 'N/A')}") - print(f" 類型: {result.get('type', 'N/A')}") - - except json.JSONDecodeError: - print("❌ 回應不是有效的 JSON") - print(f"回應內容: {response.text[:200]}") - else: - print(f"❌ API 請求失敗: {response.status_code}") - print(f"回應: {response.text[:200]}") - - except requests.RequestException as e: - print(f"❌ 請求錯誤: {e}") - else: - print("❌ 登入失敗") - print(f"HTTP 狀態: {login_response.status_code}") - -if __name__ == "__main__": - try: - test_api_search() - except KeyboardInterrupt: - print("\n\n測試已中斷") - except Exception as e: - print(f"\n測試發生錯誤: {e}") \ No newline at end of file diff --git a/test_ldap.py b/test_ldap.py deleted file mode 100644 index d0d398c..0000000 --- a/test_ldap.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python3 -""" -簡單的 LDAP 連線測試腳本 -用於驗證 LDAP 設定是否正確 -""" - -from ldap3 import Server, Connection, ALL -import os -from dotenv import load_dotenv - -# 載入環境變數 -load_dotenv() - -def test_ldap_connection(): - """測試 LDAP 伺服器連線""" - print("=== LDAP 連線測試 ===") - - # 讀取設定 - ldap_server = os.getenv('LDAP_SERVER') - ldap_port = int(os.getenv('LDAP_PORT', 389)) - use_ssl = os.getenv('LDAP_USE_SSL', 'false').lower() in ['true', '1', 't'] - bind_dn = os.getenv('LDAP_BIND_USER_DN') - bind_password = os.getenv('LDAP_BIND_USER_PASSWORD') - search_base = os.getenv('LDAP_SEARCH_BASE') - - print(f"LDAP 伺服器: {ldap_server}") - print(f"LDAP 連接埠: {ldap_port}") - print(f"使用 SSL: {use_ssl}") - print(f"搜尋基底: {search_base}") - print(f"服務帳號 DN: {bind_dn}") - - try: - # 建立伺服器連線 - server = Server(ldap_server, port=ldap_port, use_ssl=use_ssl, get_info=ALL) - print(f"✅ LDAP 伺服器物件建立成功") - - # 測試服務帳號連線 - print("正在測試服務帳號連線...") - conn = Connection(server, user=bind_dn, password=bind_password, auto_bind=True) - - if conn.bound: - print("✅ 服務帳號連線成功!") - - # 測試搜尋功能 - print("正在測試 LDAP 搜尋功能...") - search_filter = "(objectClass=user)" - conn.search(search_base, search_filter, attributes=['dn'], size_limit=5) - - if conn.entries: - print(f"✅ LDAP 搜尋成功,找到 {len(conn.entries)} 個條目") - for entry in conn.entries[:3]: - print(f" - {entry.entry_dn}") - else: - print("⚠️ LDAP 搜尋沒有找到任何條目") - - conn.unbind() - else: - print("❌ 服務帳號連線失敗") - return False - - except Exception as e: - print(f"❌ LDAP 連線錯誤: {e}") - return False - - print("=== LDAP 連線測試完成 ===") - return True - -def test_user_authentication(): - """測試使用者認證 (需要手動輸入測試帳號)""" - print("\n=== 使用者認證測試 ===") - - test_user = input("請輸入測試用帳號 (完整UPN格式,如 user@domain.com): ").strip() - if not test_user or '@' not in test_user: - print("❌ 帳號格式不正確") - return False - - test_password = input("請輸入測試密碼: ").strip() - if not test_password: - print("❌ 密碼不可為空") - return False - - # 讀取設定 - ldap_server = os.getenv('LDAP_SERVER') - ldap_port = int(os.getenv('LDAP_PORT', 389)) - use_ssl = os.getenv('LDAP_USE_SSL', 'false').lower() in ['true', '1', 't'] - - try: - server = Server(ldap_server, port=ldap_port, use_ssl=use_ssl, get_info=ALL) - - print(f"正在驗證 {test_user}...") - conn = Connection(server, user=test_user, password=test_password, auto_bind=True) - - if conn.bound: - print("✅ 使用者認證成功!") - conn.unbind() - return True - else: - print("❌ 使用者認證失敗 - 帳號或密碼錯誤") - return False - - except Exception as e: - print(f"❌ 認證過程發生錯誤: {e}") - return False - -if __name__ == "__main__": - print("LDAP 測試工具") - print("此工具用於測試 LDAP 連線和認證功能\n") - - # 測試 LDAP 連線 - if test_ldap_connection(): - # 如果連線測試通過,可以選擇測試使用者認證 - choice = input("\n是否要測試使用者認證? (y/N): ").strip().lower() - if choice == 'y': - test_user_authentication() - - input("\n按 Enter 鍵結束...") \ No newline at end of file diff --git a/test_ldap_search.py b/test_ldap_search.py deleted file mode 100644 index f0baec6..0000000 --- a/test_ldap_search.py +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env python3 -""" -測試 LDAP 搜尋功能的獨立腳本 -用於偵錯郵件通知對象搜尋問題 -""" - -from ldap_utils import search_ldap_principals, search_ldap_groups -from app import app -import sys - -def test_user_search(): - """測試使用者搜尋功能""" - print("=== 測試使用者搜尋功能 ===") - - test_terms = ["劉", "liu", "管理", "admin", "工程"] - - for term in test_terms: - print(f"\n搜尋詞: '{term}'") - print("-" * 40) - - try: - results = search_ldap_principals(term, limit=10) - print(f"找到 {len(results)} 個結果:") - - for i, result in enumerate(results, 1): - print(f"{i}. {result['name']} ({result['email']})") - - except Exception as e: - print(f"搜尋失敗: {e}") - import traceback - traceback.print_exc() - -def test_group_search(): - """測試群組搜尋功能""" - print("\n=== 測試群組搜尋功能 ===") - - test_terms = ["管理", "工程", "admin", "group"] - - for term in test_terms: - print(f"\n搜尋詞: '{term}'") - print("-" * 40) - - try: - results = search_ldap_groups(term, limit=10) - print(f"找到 {len(results)} 個群組:") - - for i, result in enumerate(results, 1): - print(f"{i}. {result['name']} (成員數: {result['member_count']})") - - except Exception as e: - print(f"群組搜尋失敗: {e}") - import traceback - traceback.print_exc() - -def test_interactive_search(): - """互動式搜尋測試""" - print("\n=== 互動式搜尋測試 ===") - - while True: - search_term = input("\n請輸入搜尋詞 (或輸入 'quit' 結束): ").strip() - - if search_term.lower() == 'quit': - break - - if not search_term: - continue - - print(f"\n搜尋 '{search_term}'...") - - # 搜尋使用者 - try: - user_results = search_ldap_principals(search_term, limit=10) - print(f"\n👤 使用者結果 ({len(user_results)}):") - for i, result in enumerate(user_results, 1): - print(f" {i}. {result['name']} ({result['email']})") - except Exception as e: - print(f"使用者搜尋失敗: {e}") - - # 搜尋群組 - try: - group_results = search_ldap_groups(search_term, limit=5) - print(f"\n👥 群組結果 ({len(group_results)}):") - for i, result in enumerate(group_results, 1): - print(f" {i}. {result['name']} (成員: {result['member_count']})") - except Exception as e: - print(f"群組搜尋失敗: {e}") - -def main(): - """主測試函式""" - print("LDAP 搜尋功能測試工具") - print("=" * 50) - - with app.app_context(): - # 測試預設搜尋詞 - test_user_search() - test_group_search() - - # 互動式測試 - choice = input("\n是否要進行互動式搜尋測試? (y/N): ").strip().lower() - if choice == 'y': - test_interactive_search() - - print("\n測試完成!") - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - print("\n\n測試已中斷") - except Exception as e: - print(f"\n測試發生錯誤: {e}") - import traceback - traceback.print_exc() \ No newline at end of file diff --git a/test_org_search.py b/test_org_search.py deleted file mode 100644 index 27c34ec..0000000 --- a/test_org_search.py +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env python3 -""" -測試組織架構搜尋功能的獨立腳本 -根據您提供的組織架構圖進行測試 -""" - -from ldap_utils import search_ldap_groups -from app import app - -def test_org_units(): - """測試組織單位搜尋""" - print("=== 測試組織單位搜尋 ===") - - # 根據您的組織架構圖測試這些關鍵字 - test_terms = [ - "PANJIT", # 主要組織 - "Audit Office", # 稽核室 - "Fab bu", # 晶圓事業部 - "CFPU", # 客戶產品事業部 - "GMO", # 總經理室 - "RBU", # 新創事業處 - "kM_ESP", # 知識管理 - "SBG", # 特殊應用事業群 - "AUBU", # 車載事業處 - "IQBU", # 檢測事業處 - "MBU1", # 行銷事業處1 - "AssEng", # 製程工程 - "scottlee", # 個人帳號 - "staceychu", # 個人帳號 - "ymirliu" # 劉經理 - ] - - for term in test_terms: - print(f"\n搜尋: '{term}'") - print("-" * 50) - - try: - results = search_ldap_groups(term, limit=10) - - if results: - print(f"找到 {len(results)} 個結果:") - for i, result in enumerate(results, 1): - type_text = "組織單位" if result['type'] == 'organizationalUnit' else "群組" - print(f" {i}. {result['name']} ({type_text})") - print(f" 成員: {result['member_count']}") - if result.get('email'): - print(f" 郵件: {result['email']}") - print(f" DN: {result['dn']}") - print() - else: - print("沒有找到結果") - - except Exception as e: - print(f"搜尋錯誤: {e}") - import traceback - traceback.print_exc() - -def test_specific_search(): - """測試特定搜尋詞""" - print("\n=== 特定搜尋測試 ===") - - while True: - term = input("\n請輸入要搜尋的組織名稱 (或輸入 'quit' 結束): ").strip() - - if term.lower() == 'quit': - break - - if not term: - continue - - print(f"\n搜尋組織: '{term}'") - print("-" * 40) - - try: - results = search_ldap_groups(term, limit=15) - - if results: - print(f"找到 {len(results)} 個組織:") - for i, result in enumerate(results, 1): - type_text = "組織單位" if result['type'] == 'organizationalUnit' else "群組" - print(f"{i}. {result['name']} ({type_text}, 成員: {result['member_count']})") - - # 提供選擇選項 - if results: - choice = input(f"\n要查看哪個組織的詳細資訊? (1-{len(results)}, 或按 Enter 跳過): ").strip() - if choice.isdigit() and 1 <= int(choice) <= len(results): - selected = results[int(choice) - 1] - print(f"\n{selected['name']} 詳細資訊:") - print(f" 類型: {selected['type']}") - print(f" 成員數: {selected['member_count']}") - print(f" DN: {selected['dn']}") - if selected.get('email'): - print(f" 群組郵件: {selected['email']}") - - else: - print("沒有找到相符的組織") - - except Exception as e: - print(f"搜尋失敗: {e}") - -def main(): - """主測試函式""" - print("組織架構搜尋測試工具") - print("=" * 50) - print("此工具將測試根據您的 AD 組織架構搜尋群組和組織單位") - - with app.app_context(): - # 自動測試常見組織名稱 - test_org_units() - - # 互動式測試 - choice = input("\n是否要進行互動式組織搜尋測試? (y/N): ").strip().lower() - if choice == 'y': - test_specific_search() - - print("\n測試完成!") - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - print("\n\n測試已中斷") - except Exception as e: - print(f"\n測試發生錯誤: {e}") - import traceback - traceback.print_exc() \ No newline at end of file diff --git a/test_startup.py b/test_startup.py deleted file mode 100644 index b244eb0..0000000 --- a/test_startup.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python3 -""" -測試系統啟動的腳本 -用於檢查各個模組是否可以正常導入和初始化 -""" - -def test_imports(): - """測試所有模組導入""" - print("🔧 測試模組導入...") - - try: - print(" ├─ 測試基本 Flask 組件...") - from flask import Flask - from flask_login import LoginManager - from flask_sqlalchemy import SQLAlchemy - print(" │ ✅ Flask 基本組件導入成功") - - print(" ├─ 測試資料庫模型...") - from models import db, User, TempSpec - print(" │ ✅ 資料庫模型導入成功") - - print(" ├─ 測試工具函式...") - from utils import admin_required, editor_or_admin_required, send_email - print(" │ ✅ 工具函式導入成功") - - print(" ├─ 測試 LDAP 工具...") - from ldap_utils import authenticate_ldap_user, search_ldap_principals - print(" │ ✅ LDAP 工具導入成功") - - print(" ├─ 測試路由模組...") - from routes.auth import auth_bp - print(" │ ✅ auth 路由導入成功") - - from routes.temp_spec import temp_spec_bp - print(" │ ✅ temp_spec 路由導入成功") - - from routes.admin import admin_bp - print(" │ ✅ admin 路由導入成功") - - from routes.api import api_bp - print(" │ ✅ api 路由導入成功") - - from routes.upload import upload_bp - print(" │ ✅ upload 路由導入成功") - - print(" ├─ 測試排程任務...") - from tasks import check_expiring_specs - print(" │ ✅ 排程任務導入成功") - - print(" └─ 所有模組導入成功!") - return True - - except Exception as e: - print(f" ❌ 模組導入失敗: {e}") - import traceback - traceback.print_exc() - return False - -def test_app_creation(): - """測試 Flask 應用程式建立""" - print("\n🔧 測試應用程式建立...") - - try: - from app import app - print(" ✅ Flask 應用程式建立成功") - - # 測試路由註冊 - with app.app_context(): - print(f" ✅ 應用程式名稱: {app.name}") - print(f" ✅ 註冊的藍圖數量: {len(app.blueprints)}") - for bp_name in app.blueprints: - print(f" - {bp_name}") - - return True - - except Exception as e: - print(f" ❌ 應用程式建立失敗: {e}") - import traceback - traceback.print_exc() - return False - -def test_config(): - """測試設定檔""" - print("\n🔧 測試設定檔...") - - try: - from config import Config - print(" ✅ 設定檔導入成功") - - # 檢查重要設定 - config = Config() - print(f" ✅ SECRET_KEY 已設定: {'是' if hasattr(config, 'SECRET_KEY') and config.SECRET_KEY else '否'}") - print(f" ✅ DATABASE_URL 已設定: {'是' if hasattr(config, 'SQLALCHEMY_DATABASE_URI') and config.SQLALCHEMY_DATABASE_URI else '否'}") - - return True - - except Exception as e: - print(f" ❌ 設定檔測試失敗: {e}") - import traceback - traceback.print_exc() - return False - -def main(): - """主測試函式""" - print("=" * 50) - print("🚀 暫時規範管理系統 V3 - 啟動測試") - print("=" * 50) - - success_count = 0 - total_tests = 3 - - # 測試模組導入 - if test_imports(): - success_count += 1 - - # 測試設定檔 - if test_config(): - success_count += 1 - - # 測試應用程式建立 - if test_app_creation(): - success_count += 1 - - # 結果統計 - print("\n" + "=" * 50) - print(f"📊 測試結果: {success_count}/{total_tests} 個測試通過") - - if success_count == total_tests: - print("🎉 所有測試通過!系統可以正常啟動。") - return True - else: - print("⚠️ 部分測試失敗,請檢查上述錯誤訊息。") - return False - -if __name__ == "__main__": - success = main() - exit(0 if success else 1) \ No newline at end of file diff --git a/utils.py b/utils.py index f0c453d..82c01a4 100644 --- a/utils.py +++ b/utils.py @@ -27,7 +27,7 @@ def _resolve_image_path(src: str) -> str: import logging -DEBUG_LOG = True # 設定為 False 可關閉 debug 訊息 +DEBUG_LOG = False # 生產環境關閉 debug 訊息 def _process_markdown_sections(doc, md_content): from bs4 import BeautifulSoup, Tag