#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Google Drive 自動匯入路由 """ from flask import Blueprint, render_template, jsonify, request import logging from services.import_service import humanize_import_error, import_service from services.google_drive_service import drive_service from services.pchome_sales_acquisition_service import pchome_sales_acquisition_service # 建立 Blueprint auto_import_bp = Blueprint('auto_import', __name__) logger = logging.getLogger(__name__) @auto_import_bp.route('/auto_import') def auto_import_index(): """自動匯入主頁""" return render_template('auto_import_index.html', active_page='auto_import') @auto_import_bp.route('/api/import_jobs', methods=['GET']) def get_import_jobs(): """取得匯入任務清單""" try: limit = request.args.get('limit', 20, type=int) jobs = import_service.get_recent_jobs(limit=limit) return jsonify({ 'success': True, 'data': jobs }) except Exception: logger.exception("取得匯入任務清單失敗") return jsonify({ 'success': False, 'message': '取得匯入任務清單失敗,請稍後再試。' }), 500 @auto_import_bp.route('/api/import_jobs/', methods=['GET']) def get_import_job(job_id): """取得單一匯入任務狀態""" try: job = import_service.get_job_status(job_id) if job: return jsonify({ 'success': True, 'data': job }) else: return jsonify({ 'success': False, 'message': '找不到該任務' }), 404 except Exception: logger.exception("取得匯入任務狀態失敗") return jsonify({ 'success': False, 'message': '取得匯入任務狀態失敗,請稍後再試。' }), 500 @auto_import_bp.route('/api/import_config', methods=['GET']) def get_import_config(): """取得匯入配置""" try: folder_path = import_service.get_config('gdrive_folder_path', '業績報表/當日業績') file_pattern = import_service.get_config('gdrive_file_pattern', '即時業績_當日') return jsonify({ 'success': True, 'data': { 'folder_path': folder_path, 'file_pattern': file_pattern } }) except Exception: logger.exception("取得匯入配置失敗") return jsonify({ 'success': False, 'message': '取得匯入配置失敗,請稍後再試。' }), 500 @auto_import_bp.route('/api/import_config', methods=['POST']) def set_import_config(): """設定匯入配置""" try: data = request.json folder_path = data.get('folder_path') file_pattern = data.get('file_pattern') if folder_path: import_service.set_config( 'gdrive_folder_path', folder_path, 'string', 'Google Drive 資料夾路徑' ) if file_pattern: import_service.set_config( 'gdrive_file_pattern', file_pattern, 'string', 'Excel 檔案名稱模式' ) return jsonify({ 'success': True, 'message': '配置已更新' }) except Exception: logger.exception("設定匯入配置失敗") return jsonify({ 'success': False, 'message': '設定匯入配置失敗,請稍後再試。' }), 500 @auto_import_bp.route('/api/test_drive_connection', methods=['POST']) def test_drive_connection(): """測試 Google Drive 連接""" try: # 嘗試認證 if drive_service.authenticate(): readiness = drive_service.check_auth_readiness(refresh_expired=False) return jsonify({ 'success': True, 'message': 'Google Drive 連接成功', 'auth_ready': True, 'auth_status': readiness.get('kind') or 'ready', }) else: error_kind = getattr(drive_service, 'last_error_kind', None) or 'authentication_failed' error_message = getattr(drive_service, 'last_error', None) or error_kind return jsonify({ 'success': False, 'message': humanize_import_error(error_message) or 'Google Drive 認證失敗,請重新確認雲端授權。', 'auth_ready': False, 'auth_status': error_kind, }), 400 except Exception as e: logger.exception("測試 Google Drive 連接失敗") return jsonify({ 'success': False, 'message': f'測試連接失敗。{humanize_import_error(e)}' }), 500 @auto_import_bp.route('/api/list_drive_files', methods=['POST']) def list_drive_files(): """列出 Google Drive 檔案""" try: data = request.json folder_path = data.get('folder_path', '業績報表/當日業績') file_pattern = data.get('file_pattern', '即時業績_當日') files = drive_service.list_files_in_folder(folder_path, file_pattern) error_kind = getattr(drive_service, 'last_error_kind', None) if error_kind: error_message = getattr(drive_service, 'last_error', None) or error_kind return jsonify({ 'success': False, 'message': humanize_import_error(error_message) or 'Google Drive 連線或授權異常,無法確認檔案清單。', 'auth_status': error_kind, 'data': [], 'count': 0, }), 400 return jsonify({ 'success': True, 'data': files, 'count': len(files) }) except Exception as e: logger.exception("列出 Google Drive 檔案失敗") return jsonify({ 'success': False, 'message': f'列出檔案失敗。{humanize_import_error(e)}' }), 500 @auto_import_bp.route('/api/manual_import', methods=['POST']) def manual_import(): """Backward-compatible command endpoint for the automated acquisition run.""" try: result = pchome_sales_acquisition_service.run(trigger='operator_command') return jsonify(result) except Exception as e: logger.exception("手動匯入失敗") return jsonify({ 'success': False, 'message': f'手動匯入失敗。{humanize_import_error(e)}' }), 500 @auto_import_bp.route('/api/auto_import/readiness', methods=['GET']) def auto_import_readiness(): """Return public-safe provider, freshness, and runtime-closure state.""" try: return jsonify(pchome_sales_acquisition_service.readiness()) except Exception: logger.exception("取得業績來源自動化狀態失敗") return jsonify({ 'success': False, 'message': '暫時無法取得業績來源自動化狀態。' }), 500 @auto_import_bp.route('/api/auto_import/run', methods=['POST']) def run_auto_import(): """Run the governed acquisition, controlled apply, and verifier chain.""" try: return jsonify(pchome_sales_acquisition_service.run(trigger='operator_command')) except Exception as e: logger.exception("執行業績來源自動化失敗") return jsonify({ 'success': False, 'status': 'failed_rolled_back', 'message': f'自動取得與匯入失敗。{humanize_import_error(e)}' }), 500 @auto_import_bp.route('/api/reset_stuck_jobs', methods=['POST']) def reset_stuck_jobs(): """重置逾時未完成的匯入任務""" try: count = import_service.reset_stuck_jobs(older_than_minutes=60) return jsonify({ 'success': True, 'message': f'已重置 {count} 筆逾時任務,可重新執行匯入。' }) except Exception: logger.exception("重置逾時匯入任務失敗") return jsonify({ 'success': False, 'message': '重置失敗,請稍後再試或通知維護人員。' }), 500 @auto_import_bp.route('/api/import_jobs//fail', methods=['POST']) def fail_import_job(job_id): """取消單一匯入任務""" try: if import_service.fail_job(job_id): return jsonify({ 'success': True, 'message': '任務已取消,可重新執行匯入。' }) return jsonify({ 'success': False, 'message': '找不到該任務' }), 404 except Exception: logger.exception("取消匯入任務失敗") return jsonify({ 'success': False, 'message': '取消失敗,請稍後再試或通知維護人員。' }), 500