您好,
-暫時規範 {spec.spec_code} - {spec.title} 已由管理員啟用,並正式生效。
-詳細資訊請登入系統查看。
-生效日期: {spec.start_date.strftime('%Y-%m-%d')}
- 結束日期: {spec.end_date.strftime('%Y-%m-%d')}
此為系統自動發送的通知郵件,請勿直接回覆。
- - - """ - send_email(recipients, subject, body) - else: - # Log a warning if no recipients were found, but don't block the main process - current_app.logger.warning(f"Could not find recipients in LDAP group 'TempSpec_Approvers' for spec {spec.id}.") - # --- End of Email Notification Example --- + # --- Start of Dynamic Email Notification --- + recipients_str = request.form.get('recipients') + if recipients_str: + recipients = process_recipients(recipients_str) + if recipients: + subject = f"[暫規通知] 規範 '{spec.spec_code}' 已正式生效" + # Using f-strings and triple quotes for a readable HTML body + body = f""" + + +您好,
+暫時規範 {spec.spec_code} - {spec.title} 已由管理員啟用,並正式生效。
+詳細資訊請登入系統查看。
+生效日期: {spec.start_date.strftime('%Y-%m-%d')}
+ 結束日期: {spec.end_date.strftime('%Y-%m-%d')}
申請人: {spec.applicant}
+此為系統自動發送的通知郵件,請勿直接回覆。
+ + + """ + send_email(recipients, subject, body) + current_app.logger.info(f"Sent activation notification for spec {spec.spec_code} to {len(recipients)} recipients.") + # --- End of Dynamic Email Notification --- return redirect(url_for('temp_spec.spec_list')) return render_template('activate_spec.html', spec=spec) @@ -311,6 +310,30 @@ def terminate_spec(spec_id): spec.termination_reason = reason spec.end_date = datetime.today().date() add_history_log(spec.id, '終止', f"原因: {reason}") + + # --- Start of Dynamic Email Notification --- + recipients_str = request.form.get('recipients') + if recipients_str: + recipients = process_recipients(recipients_str) + if recipients: + subject = f"[暫規通知] 規範 '{spec.spec_code}' 已提早終止" + body = f""" + + +您好,
+暫時規範 {spec.spec_code} - {spec.title} 已被提早終止。
+終止日期: {spec.end_date.strftime('%Y-%m-%d')}
+申請人: {spec.applicant}
+終止原因: {reason}
+詳細資訊請登入系統查看。
+此為系統自動發送的通知郵件,請勿直接回覆。
+ + + """ + send_email(recipients, subject, body) + current_app.logger.info(f"Sent termination notification for spec {spec.spec_code} to {len(recipients)} recipients.") + # --- End of Dynamic Email Notification --- + db.session.commit() flash(f"規範 '{spec.spec_code}' 已被提早終止。", 'warning') return redirect(url_for('temp_spec.spec_list')) @@ -384,6 +407,28 @@ def extend_spec(spec_id): details += f",並上傳新檔案 '{new_upload.filename}'" add_history_log(spec.id, '展延', details) + # --- Start of Dynamic Email Notification --- + recipients_str = request.form.get('recipients') + if recipients_str: + recipients = process_recipients(recipients_str) + if recipients: + subject = f"[暫規通知] 規範 '{spec.spec_code}' 已展延" + body = f""" + + +您好,
+暫時規範 {spec.spec_code} - {spec.title} 已成功展延。
+新的結束日期為: {spec.end_date.strftime('%Y-%m-%d')}
+申請人: {spec.applicant}
+詳細資訊請登入系統查看。
+此為系統自動發送的通知郵件,請勿直接回覆。
+ + + """ + send_email(recipients, subject, body) + current_app.logger.info(f"Sent extension notification for spec {spec.spec_code} to {len(recipients)} recipients.") + # --- End of Dynamic Email Notification --- + db.session.commit() flash(f"規範 '{spec.spec_code}' 已成功展延!", 'success') return redirect(url_for('temp_spec.spec_list')) diff --git a/start-linux.sh b/start-linux.sh new file mode 100644 index 0000000..2b24fe0 --- /dev/null +++ b/start-linux.sh @@ -0,0 +1,368 @@ +#!/bin/bash + +# ======================================== +# 暫時規範管理系統 V3 - Linux 啟動腳本 +# ======================================== + +set -e # 遇到錯誤立即退出 + +echo +echo "=====================================" +echo "暫時規範管理系統 V3 - Linux 啟動" +echo "=====================================" +echo + +# 顏色定義 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 檢查 Python 是否已安裝 +if ! command -v python3 &> /dev/null; then + echo -e "${RED}[錯誤]${NC} Python3 未安裝" + echo "請安裝 Python 3.8+ 後重新執行此腳本" + echo + echo "Ubuntu/Debian: sudo apt update && sudo apt install python3 python3-pip python3-venv" + echo "CentOS/RHEL: sudo yum install python3 python3-pip" + echo "或使用包管理器安裝" + exit 1 +fi + +# 檢查 Docker 是否已安裝 +if ! command -v docker &> /dev/null; then + echo -e "${YELLOW}[警告]${NC} Docker 未安裝或未運行" + echo "如需使用 Docker 模式,請先安裝 Docker" + echo + USE_DOCKER=false +else + # 檢查 Docker 是否運行 + if ! docker info &> /dev/null; then + echo -e "${YELLOW}[警告]${NC} Docker 未運行" + echo "請啟動 Docker 服務: sudo systemctl start docker" + USE_DOCKER=false + else + USE_DOCKER=true + fi +fi + +# 檢查 docker-compose 是否存在 +if [[ "$USE_DOCKER" == true ]] && ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then + echo -e "${YELLOW}[警告]${NC} docker-compose 未安裝" + USE_DOCKER=false +fi + +# 檢查 .env 檔案 +if [[ ! -f ".env" ]]; then + echo -e "${BLUE}[提示]${NC} 未找到 .env 檔案,正在複製範例檔案..." + cp ".env.example" ".env" + echo + echo -e "${YELLOW}[重要]${NC} 請編輯 .env 檔案並設定正確的參數值:" + echo "- 資料庫連線資訊" + echo "- LDAP/AD 伺服器設定" + echo "- SMTP 郵件伺服器設定" + echo + read -p "是否已完成 .env 檔案設定?(y/N): " confirm + if [[ ! "$confirm" =~ ^[Yy]$ ]]; then + echo "請先設定 .env 檔案後再執行此腳本" + exit 1 + fi +fi + +# 提供啟動選項 +echo "請選擇啟動模式:" +echo +if [[ "$USE_DOCKER" == true ]]; then + echo "[1] Docker 容器化部署 (推薦)" + echo "[2] 本地 Python 直接執行" + echo "[3] 僅啟動外部服務 (MySQL + ONLYOFFICE)" + echo "[4] 生產環境部署 (含 Nginx)" + echo "[5] 系統服務安裝 (systemd)" +else + echo "[2] 本地 Python 直接執行" + echo "[5] 系統服務安裝 (systemd)" + echo + echo -e "${YELLOW}注意:Docker 相關選項不可用${NC}" +fi +echo + +read -p "請輸入選項: " choice + +case $choice in + 1) + if [[ "$USE_DOCKER" != true ]]; then + echo -e "${RED}[錯誤]${NC} Docker 不可用,請選擇其他選項" + exit 1 + fi + start_docker + ;; + 2) + start_manual + ;; + 3) + if [[ "$USE_DOCKER" != true ]]; then + echo -e "${RED}[錯誤]${NC} Docker 不可用,請選擇其他選項" + exit 1 + fi + start_services_only + ;; + 4) + if [[ "$USE_DOCKER" != true ]]; then + echo -e "${RED}[錯誤]${NC} Docker 不可用,請選擇其他選項" + exit 1 + fi + start_production + ;; + 5) + install_systemd_service + ;; + *) + if [[ "$USE_DOCKER" == true ]]; then + start_docker + else + start_manual + fi + ;; +esac + +function start_docker() { + echo + echo "=================================" + echo "使用 Docker Compose 啟動系統..." + echo "=================================" + echo + + # 檢查 compose 命令 + if command -v docker-compose &> /dev/null; then + COMPOSE_CMD="docker-compose" + else + COMPOSE_CMD="docker compose" + fi + + # 停止並清理舊的容器 + echo -e "${BLUE}[步驟 1]${NC} 清理舊容器..." + $COMPOSE_CMD down + + # 重新構建並啟動服務 + echo -e "${BLUE}[步驟 2]${NC} 構建並啟動服務..." + $COMPOSE_CMD up --build -d + + # 等待服務啟動 + echo -e "${BLUE}[步驟 3]${NC} 等待服務啟動..." + sleep 30 + + # 初始化資料庫 + echo -e "${BLUE}[步驟 4]${NC} 初始化資料庫..." + $COMPOSE_CMD exec -T app python init_db.py --auto-yes || true + + # 顯示狀態 + echo -e "${BLUE}[步驟 5]${NC} 檢查服務狀態..." + $COMPOSE_CMD ps + + echo + echo -e "${GREEN}============================" + echo "系統啟動完成!" + echo "============================${NC}" + echo + echo "服務訪問地址:" + echo "- 主系統:http://localhost:5000" + echo "- ONLYOFFICE:http://localhost:8080" + echo "- MySQL:localhost:3306" + echo + echo "管理命令:" + echo "- 查看日誌:$COMPOSE_CMD logs -f" + echo "- 停止服務:$COMPOSE_CMD down" + echo "- 重啟服務:$COMPOSE_CMD restart" + echo +} + +function start_services_only() { + echo + echo "===============================" + echo "僅啟動外部服務..." + echo "===============================" + echo + + if command -v docker-compose &> /dev/null; then + COMPOSE_CMD="docker-compose" + else + COMPOSE_CMD="docker compose" + fi + + $COMPOSE_CMD up -d mysql onlyoffice + + echo + echo -e "${GREEN}外部服務已啟動!${NC}" + echo "- MySQL:localhost:3306" + echo "- ONLYOFFICE:http://localhost:8080" + echo + echo "請使用以下命令啟動 Flask 應用:" + echo "python3 app.py" + echo +} + +function start_production() { + echo + echo "===============================" + echo "生產環境部署 (含 Nginx)..." + echo "===============================" + echo + + if command -v docker-compose &> /dev/null; then + COMPOSE_CMD="docker-compose" + else + COMPOSE_CMD="docker compose" + fi + + $COMPOSE_CMD --profile production up --build -d + + echo + echo -e "${GREEN}生產環境已啟動!${NC}" + echo "- 系統入口:http://localhost (透過 Nginx)" + echo "- 直接訪問:http://localhost:5000" + echo +} + +function start_manual() { + echo + echo "===============================" + echo "本地 Python 環境啟動..." + echo "===============================" + echo + + # 檢查虛擬環境 + if [[ -d "venv" ]]; then + echo -e "${BLUE}[步驟 1]${NC} 啟用虛擬環境..." + source venv/bin/activate + else + echo -e "${YELLOW}[警告]${NC} 未找到虛擬環境,建議先建立:" + echo "python3 -m venv venv" + echo "source venv/bin/activate" + echo "pip install -r requirements.txt" + echo + read -p "是否立即建立虛擬環境?(y/N): " create_venv + if [[ "$create_venv" =~ ^[Yy]$ ]]; then + python3 -m venv venv + source venv/bin/activate + echo -e "${GREEN}虛擬環境已建立並啟用${NC}" + fi + fi + + # 安裝依賴 + echo -e "${BLUE}[步驟 2]${NC} 檢查並安裝依賴..." + pip install -r requirements.txt + + # 檢查外部服務 + echo -e "${BLUE}[步驟 3]${NC} 檢查外部服務..." + echo + echo -e "${YELLOW}[重要提醒]${NC} 請確保以下服務已啟動:" + echo + echo "1. MySQL 資料庫伺服器" + echo " - 可使用: docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0" + echo + echo "2. ONLYOFFICE Document Server" + echo " - 可使用: docker run -d -p 8080:80 onlyoffice/documentserver" + echo + echo "3. LDAP/AD 伺服器 (如果適用)" + echo "4. SMTP 郵件伺服器 (如果適用)" + echo + + read -p "外部服務是否已準備就緒?(y/N): " confirm + if [[ ! "$confirm" =~ ^[Yy]$ ]]; then + echo "請先啟動必要的外部服務" + exit 1 + fi + + # 初始化資料庫 + echo -e "${BLUE}[步驟 4]${NC} 初始化資料庫..." + python3 init_db.py || python init_db.py + + # 啟動應用 + echo -e "${BLUE}[步驟 5]${NC} 啟動 Flask 應用..." + echo + echo "啟動中... (Ctrl+C 停止)" + echo "訪問地址: http://localhost:5000" + echo + + # 根據系統環境選擇啟動方式 + if command -v gunicorn &> /dev/null; then + echo "使用 Gunicorn 啟動生產模式..." + gunicorn -w 4 -b 0.0.0.0:5000 app:app + else + echo "使用 Flask 開發伺服器啟動..." + python3 app.py || python app.py + fi +} + +function install_systemd_service() { + echo + echo "===============================" + echo "安裝為系統服務 (systemd)..." + echo "===============================" + echo + + # 檢查是否為 root 用戶 + if [[ $EUID -ne 0 ]]; then + echo -e "${RED}[錯誤]${NC} 需要 root 權限安裝系統服務" + echo "請使用 sudo 執行此腳本" + exit 1 + fi + + # 獲取當前目錄和用戶 + CURRENT_DIR=$(pwd) + CURRENT_USER=${SUDO_USER:-$USER} + + echo "安裝目錄: $CURRENT_DIR" + echo "執行用戶: $CURRENT_USER" + echo + + # 建立服務檔案 + cat > /etc/systemd/system/tempspec.service << EOF +[Unit] +Description=Temp Spec System V3 +After=network.target mysql.service + +[Service] +Type=simple +User=$CURRENT_USER +Group=$CURRENT_USER +WorkingDirectory=$CURRENT_DIR +Environment=PATH=$CURRENT_DIR/venv/bin +ExecStart=$CURRENT_DIR/venv/bin/gunicorn -w 4 -b 0.0.0.0:5000 app:app +ExecReload=/bin/kill -HUP \$MAINPID +Restart=always +RestartSec=3 + +[Install] +WantedBy=multi-user.target +EOF + + # 重新載入 systemd + systemctl daemon-reload + + # 啟用服務 + systemctl enable tempspec.service + + echo -e "${GREEN}系統服務安裝完成!${NC}" + echo + echo "管理命令:" + echo "- 啟動服務: sudo systemctl start tempspec" + echo "- 停止服務: sudo systemctl stop tempspec" + echo "- 重啟服務: sudo systemctl restart tempspec" + echo "- 查看狀態: sudo systemctl status tempspec" + echo "- 查看日誌: sudo journalctl -u tempspec -f" + echo + + read -p "是否立即啟動服務?(y/N): " start_now + if [[ "$start_now" =~ ^[Yy]$ ]]; then + systemctl start tempspec + systemctl status tempspec --no-pager + fi +} + +# 捕捉 Ctrl+C +trap 'echo -e "\n${YELLOW}操作已取消${NC}"; exit 130' INT + +echo +echo -e "${GREEN}腳本執行完成!${NC}" \ No newline at end of file diff --git a/start-windows.bat b/start-windows.bat new file mode 100644 index 0000000..e1d0259 --- /dev/null +++ b/start-windows.bat @@ -0,0 +1,206 @@ +@echo off +REM ======================================== +REM 暫時規範管理系統 V3 - Windows 啟動腳本 +REM ======================================== + +echo. +echo ===================================== +echo 暫時規範管理系統 V3 - Windows 啟動 +echo ===================================== +echo. + +REM 檢查 Python 是否已安裝 +python --version >nul 2>&1 +if %ERRORLEVEL% neq 0 ( + echo [錯誤] Python 未安裝或未加入 PATH + echo 請安裝 Python 3.8+ 並重新執行此腳本 + pause + exit /b 1 +) + +REM 檢查 Docker 是否已安裝並運行 +docker --version >nul 2>&1 +if %ERRORLEVEL% neq 0 ( + echo [警告] Docker 未安裝或未運行 + echo 如需使用 Docker 模式,請先安裝並啟動 Docker Desktop + echo. + goto MANUAL_START +) + +REM 檢查是否存在 docker-compose.yml +if not exist "docker-compose.yml" ( + echo [警告] 找不到 docker-compose.yml 檔案 + goto MANUAL_START +) + +REM 檢查 .env 檔案 +if not exist ".env" ( + echo [提示] 未找到 .env 檔案,正在複製範例檔案... + copy ".env.example" ".env" + echo. + echo [重要] 請編輯 .env 檔案並設定正確的參數值: + echo - 資料庫連線資訊 + echo - LDAP/AD 伺服器設定 + echo - SMTP 郵件伺服器設定 + echo. + set /p confirm="是否已完成 .env 檔案設定?(y/N): " + if /i not "%confirm%"=="y" ( + echo 請先設定 .env 檔案後再執行此腳本 + pause + exit /b 1 + ) +) + +REM 提供啟動選項 +echo 請選擇啟動模式: +echo. +echo [1] Docker 容器化部署 (推薦) +echo [2] 本地 Python 直接執行 +echo [3] 僅啟動外部服務 (MySQL + ONLYOFFICE) +echo [4] 生產環境部署 (含 Nginx) +echo. +set /p choice="請輸入選項 [1-4]: " + +if "%choice%"=="1" goto DOCKER_START +if "%choice%"=="2" goto MANUAL_START +if "%choice%"=="3" goto SERVICES_ONLY +if "%choice%"=="4" goto PRODUCTION_START +goto DOCKER_START + +:DOCKER_START +echo. +echo ================================= +echo 使用 Docker Compose 啟動系統... +echo ================================= +echo. + +REM 停止並清理舊的容器 +echo [步驟 1] 清理舊容器... +docker-compose down + +REM 重新構建並啟動服務 +echo [步驟 2] 構建並啟動服務... +docker-compose up --build -d + +REM 等待服務啟動 +echo [步驟 3] 等待服務啟動... +timeout /t 30 /nobreak + +REM 初始化資料庫 +echo [步驟 4] 初始化資料庫... +docker-compose exec app python init_db.py --auto-yes + +REM 顯示狀態 +echo [步驟 5] 檢查服務狀態... +docker-compose ps + +echo. +echo ============================ +echo 系統啟動完成! +echo ============================ +echo. +echo 服務訪問地址: +echo - 主系統:http://localhost:5000 +echo - ONLYOFFICE:http://localhost:8080 +echo - MySQL:localhost:3306 +echo. +echo 查看日誌:docker-compose logs -f +echo 停止服務:docker-compose down +echo. +goto END + +:SERVICES_ONLY +echo. +echo =============================== +echo 僅啟動外部服務... +echo =============================== +echo. + +docker-compose up -d mysql onlyoffice + +echo. +echo 外部服務已啟動! +echo - MySQL:localhost:3306 +echo - ONLYOFFICE:http://localhost:8080 +echo. +echo 請使用以下命令啟動 Flask 應用: +echo python app.py +echo. +goto END + +:PRODUCTION_START +echo. +echo =============================== +echo 生產環境部署 (含 Nginx)... +echo =============================== +echo. + +docker-compose --profile production up --build -d + +echo. +echo 生產環境已啟動! +echo - 系統入口:http://localhost (透過 Nginx) +echo - 直接訪問:http://localhost:5000 +echo. +goto END + +:MANUAL_START +echo. +echo =============================== +echo 本地 Python 環境啟動... +echo =============================== +echo. + +REM 檢查虛擬環境 +if exist "venv\Scripts\activate.bat" ( + echo [步驟 1] 啟用虛擬環境... + call venv\Scripts\activate.bat +) else ( + echo [警告] 未找到虛擬環境,建議先建立: + echo python -m venv venv + echo venv\Scripts\activate + echo pip install -r requirements.txt + echo. +) + +REM 安裝依賴 +echo [步驟 2] 檢查並安裝依賴... +pip install -r requirements.txt + +REM 檢查外部服務 +echo [步驟 3] 檢查外部服務... +echo. +echo [重要提醒] 請確保以下服務已啟動: +echo. +echo 1. MySQL 資料庫伺服器 +echo - 可使用: docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0 +echo. +echo 2. ONLYOFFICE Document Server +echo - 可使用: docker run -d -p 8080:80 onlyoffice/documentserver +echo. +echo 3. LDAP/AD 伺服器 (如果適用) +echo 4. SMTP 郵件伺服器 (如果適用) +echo. + +set /p confirm="外部服務是否已準備就緒?(y/N): " +if /i not "%confirm%"=="y" ( + echo 請先啟動必要的外部服務 + pause + exit /b 1 +) + +REM 初始化資料庫 +echo [步驟 4] 初始化資料庫... +python init_db.py + +REM 啟動應用 +echo [步驟 5] 啟動 Flask 應用... +echo. +echo 啟動中... (Ctrl+C 停止) +python app.py + +goto END + +:END +echo. +pause \ No newline at end of file diff --git a/static/generated/PE1140801.docx b/static/generated/PE1140801.docx new file mode 100644 index 0000000..1612a6c Binary files /dev/null and b/static/generated/PE1140801.docx differ diff --git a/tasks.py b/tasks.py new file mode 100644 index 0000000..75e8854 --- /dev/null +++ b/tasks.py @@ -0,0 +1,55 @@ +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 + + # 定義預設的通知對象 + # 可以根據需要修改群組名稱 + default_recipients = get_ldap_group_members('TempSpec_Admins') + if not default_recipients: + print("Warning: Could not find default recipients in AD group 'TempSpec_Admins'.") + # 如果找不到預設群組,可以設定備用收件人 + default_recipients = ['admin@example.com'] # 請根據實際情況修改 + + for spec in expiring_soon: + remaining_days = (spec.end_date - today).days + + # 組合通知郵件 + subject = f"[暫規到期提醒] 規範 '{spec.spec_code}' 將於 {remaining_days} 天後到期" + body = f""" + + +您好,
+此為自動提醒郵件。
+暫時規範 {spec.spec_code} - {spec.title} 即將到期。
+結束日期: {spec.end_date.strftime('%Y-%m-%d')} (剩餘 {remaining_days} 天)
+申請人: {spec.applicant}
+請及時處理,如需展延請登入系統操作。
+此為系統自動發送的通知郵件,請勿直接回覆。
+ + + """ + + # 發送郵件給預設群組 + send_email(default_recipients, subject, body) + print(f"Sent expiry reminder for spec {spec.spec_code} to {len(default_recipients)} recipients.") \ No newline at end of file diff --git a/templates/activate_spec.html b/templates/activate_spec.html index 56745d7..b66aa2c 100644 --- a/templates/activate_spec.html +++ b/templates/activate_spec.html @@ -19,9 +19,133 @@ - + + +