yue 3 months ago
parent
commit
22b81a185c
33 changed files with 844 additions and 538 deletions
  1. 5 5
      SourceCode/IntelligentRailwayCosting/app/config.yml
  2. 5 5
      SourceCode/IntelligentRailwayCosting/app/core/api/response.py
  3. 11 11
      SourceCode/IntelligentRailwayCosting/app/executor/task_processor.py
  4. 14 6
      SourceCode/IntelligentRailwayCosting/app/executor/task_runner.py
  5. 23 13
      SourceCode/IntelligentRailwayCosting/app/flask_app/__init__.py
  6. 2 1
      SourceCode/IntelligentRailwayCosting/app/routes/__init__.py
  7. 28 20
      SourceCode/IntelligentRailwayCosting/app/routes/auth.py
  8. 24 15
      SourceCode/IntelligentRailwayCosting/app/routes/log.py
  9. 1 1
      SourceCode/IntelligentRailwayCosting/app/routes/project_quota.py
  10. 74 61
      SourceCode/IntelligentRailwayCosting/app/routes/project_task.py
  11. 0 1
      SourceCode/IntelligentRailwayCosting/app/services/__init__.py
  12. 53 18
      SourceCode/IntelligentRailwayCosting/app/services/log.py
  13. 102 38
      SourceCode/IntelligentRailwayCosting/app/services/project.py
  14. 6 5
      SourceCode/IntelligentRailwayCosting/app/services/user.py
  15. 5 2
      SourceCode/IntelligentRailwayCosting/app/stores/__init__.py
  16. 2 2
      SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_mysql/__init__.py
  17. 31 31
      SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_mysql/log.py
  18. 81 54
      SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_mysql/project_quota.py
  19. 44 40
      SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_mysql/project_task.py
  20. 2 2
      SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_sqlserver/__init__.py
  21. 32 32
      SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_sqlserver/log.py
  22. 44 40
      SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_sqlserver/project_task.py
  23. 65 49
      SourceCode/IntelligentRailwayCosting/app/tools/db_helper/mysql_helper.py
  24. 53 34
      SourceCode/IntelligentRailwayCosting/app/tools/db_helper/sqlserver_helper.py
  25. 20 1
      SourceCode/IntelligentRailwayCosting/app/tools/utils/__init__.py
  26. 51 18
      SourceCode/IntelligentRailwayCosting/app/tools/utils/ai_helper.py
  27. 3 1
      SourceCode/IntelligentRailwayCosting/app/tools/utils/config_helper.py
  28. 13 6
      SourceCode/IntelligentRailwayCosting/app/tools/utils/file_helper.py
  29. 10 4
      SourceCode/IntelligentRailwayCosting/app/tools/utils/string_helper.py
  30. 2 1
      SourceCode/IntelligentRailwayCosting/app/views/__init__.py
  31. 11 9
      SourceCode/IntelligentRailwayCosting/app/views/log.py
  32. 4 3
      SourceCode/IntelligentRailwayCosting/app/views/login.py
  33. 23 9
      SourceCode/IntelligentRailwayCosting/app/views/project.py

+ 5 - 5
SourceCode/IntelligentRailwayCosting/app/config.yml

@@ -26,7 +26,7 @@ db:
   sqlserver_mian_2020:
     driver: '{ODBC Driver 17 for SQL Server}'
     server: 192.168.0.81,1433
-#    server: shvber.com,5030
+    #    server: shvber.com,5030
     username: iwb
     password: 123456Qsc
     database: Iwb_RecoData2020
@@ -34,7 +34,7 @@ db:
   Iwb_RailwayCosting:
     driver: '{ODBC Driver 17 for SQL Server}'
     server: 192.168.0.81,1433
-#    server: shvber.com,5030
+    #    server: shvber.com,5030
     username: iwb
     password: 123456Qsc
     database: iwb_railway_costing_v1
@@ -44,9 +44,9 @@ db:
     server: 192.168.0.81,1433
     username: iwb
     password: 123456Qsc
-#    server: shvber.com,50535
-#    username: sa
-#    password: Iwb2017
+    #    server: shvber.com,50535
+    #    username: sa
+    #    password: Iwb2017
     database: Iwb_RecoData2024
     trusted_connection: false
   Iwb_RecoData2020:

+ 5 - 5
SourceCode/IntelligentRailwayCosting/app/core/api/response.py

@@ -33,7 +33,7 @@ class ResponseBase:
 
     @staticmethod
     def error(
-            message: str = "操作失败", code: int = 400, data: Optional[Any] = None
+        message: str = "操作失败", code: int = 400, data: Optional[Any] = None
     ) -> Response:
         """错误响应
         Args:
@@ -51,10 +51,10 @@ class ResponseBase:
 
     @staticmethod
     def json_response(
-            success: bool = True,
-            code: int = 200,
-            message: str = "",
-            data: Optional[Any] = None,
+        success: bool = True,
+        code: int = 200,
+        message: str = "",
+        data: Optional[Any] = None,
     ) -> Response:
         """自定义响应
         Args:

+ 11 - 11
SourceCode/IntelligentRailwayCosting/app/executor/task_processor.py

@@ -172,13 +172,13 @@ class TaskProcessor:
             return None, msg
 
     def _build_api_body(
-            self,
-            task: ProjectTaskDto,
-            project: ProjectDto,
-            budgets: list[TotalBudgetInfoDto],
-            parents: list[ChapterDto],
-            children: list[ChapterDto],
-            files: list[ExcelParseFileDto],
+        self,
+        task: ProjectTaskDto,
+        project: ProjectDto,
+        budgets: list[TotalBudgetInfoDto],
+        parents: list[ChapterDto],
+        children: list[ChapterDto],
+        files: list[ExcelParseFileDto],
     ):
         try:
             budgets_data = [ExcelParseZgsDto.from_dto(budget) for budget in budgets]
@@ -226,10 +226,10 @@ class TaskProcessor:
             raise Exception(msg)
 
     def _insert_data(
-            self,
-            task: ProjectTaskDto,
-            project: ProjectDto,
-            data: list[ExcelParseResultDataDto],
+        self,
+        task: ProjectTaskDto,
+        project: ProjectDto,
+        data: list[ExcelParseResultDataDto],
     ):
         try:
             self._logger.debug(f"开始插入数据:{task.task_name}")

+ 14 - 6
SourceCode/IntelligentRailwayCosting/app/executor/task_runner.py

@@ -20,6 +20,8 @@ class TaskRunner:
     _task_store = ProjectTaskStore()
     _lock = threading.Lock()
     _task_processor = None
+    _task_empty_wait_count = 0
+    _task_empty_wait_max_count = 20
 
     @classmethod
     def run_task(cls, task: ProjectTaskDto):
@@ -263,18 +265,24 @@ class TaskRunner:
             if total_tasks > 0:
                 if len(cls._running_projects) < cls._max_concurrent_projects:
                     cls._logger.debug(f"同步待运行队列,同步{total_tasks}条数据,运行")
+                    cls._task_empty_wait_count = 0
                     cls._run()
                 else:
                     cls._logger.debug(
                         f"同步待运行队列,同步{total_tasks}条数据。暂无空闲线程,稍后运行"
                     )
             else:
-                cls._logger.info(
-                    f"同步待运行队列,无新增数据,等待{cls._task_sleep_interval}秒后同步数据运行"
-                )
-                sleep(cls._task_sleep_interval)
-                cls._sync_wait_list()
-
+                cls._task_empty_wait_count += 1
+                if cls._task_empty_wait_count < cls._task_empty_wait_max_count:
+                    cls._logger.info(
+                        f"同步待运行队列,无新增数据,等待{cls._task_sleep_interval}秒后同步数据运行,已连续 {cls._task_empty_wait_count} 次无新增数据"
+                    )
+                    sleep(cls._task_sleep_interval)
+                    cls._sync_wait_list()
+                else:
+                    cls._logger.info(
+                        f"同步待运行队列,无新增数据,等待{cls._task_sleep_interval}秒后同步数据运行,已连续 {cls._task_empty_wait_count} 次无新增数据,已超过最大等待次数,停止运行"
+                    )
             return total_tasks
         except Exception as e:
             msg = f"同步待运行队列失败,原因:{e}"

+ 23 - 13
SourceCode/IntelligentRailwayCosting/app/flask_app/__init__.py

@@ -13,18 +13,26 @@ class CustomJSONProvider(JSONProvider):
             return str(obj)
 
     def encode(self, obj):
-        return super().encode(obj).encode('utf-8').decode('unicode_escape')
+        return super().encode(obj).encode("utf-8").decode("unicode_escape")
 
 
 def create_app():
-    app = Flask(__name__, static_folder='../views/static')
+    app = Flask(__name__, static_folder="../views/static")
     app.secret_key = "1qwe2iwb3vber"
-    app.config['JSON_AS_ASCII'] = False
-    app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 限制上传文件大小为16MB
-    app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.pdf', '.doc', '.docx', '.xls', '.xlsx']  # 允许的文件类型
-    app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
-    app.config['PRESERVE_CONTEXT_ON_EXCEPTION'] = False
-    app.config['REQUEST_BODY_LIMIT'] = '16MB'
+    app.config["JSON_AS_ASCII"] = False
+    app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024  # 限制上传文件大小为16MB
+    app.config["UPLOAD_EXTENSIONS"] = [
+        ".jpg",
+        ".png",
+        ".pdf",
+        ".doc",
+        ".docx",
+        ".xls",
+        ".xlsx",
+    ]  # 允许的文件类型
+    app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024
+    app.config["PRESERVE_CONTEXT_ON_EXCEPTION"] = False
+    app.config["REQUEST_BODY_LIMIT"] = "16MB"
     app.json_provider_class = CustomJSONProvider
 
     login_manager.init_app(app)
@@ -36,12 +44,14 @@ def create_app():
     # 注册自定义过滤器
     for rule in app.url_map.iter_rules():
         route = {
-            'endpoint': rule.endpoint,
-            'methods': sorted(rule.methods),
-            'path': str(rule)
+            "endpoint": rule.endpoint,
+            "methods": sorted(rule.methods),
+            "path": str(rule),
         }
-        print(f"URL [{str(rule)}] ==> endpoint: {rule.endpoint}, methods: {sorted(rule.methods)}")
+        print(
+            f"URL [{str(rule)}] ==> endpoint: {rule.endpoint}, methods: {sorted(rule.methods)}"
+        )
     return app
 
 
-__all__ = ['create_app']
+__all__ = ["create_app"]

+ 2 - 1
SourceCode/IntelligentRailwayCosting/app/routes/__init__.py

@@ -5,8 +5,9 @@ from routes.project import project_api
 from routes.project_task import project_task_api
 from routes.project_quota import project_quota_api
 
+
 def register_api(app):
-    url_prefix = '/api'
+    url_prefix = "/api"
     # API蓝图注册
     app.register_blueprint(auth_api, url_prefix=f"{url_prefix}/auth")
     app.register_blueprint(log_api, url_prefix=f"{url_prefix}/log")

+ 28 - 20
SourceCode/IntelligentRailwayCosting/app/routes/auth.py

@@ -1,18 +1,19 @@
 from flask import Blueprint, request, jsonify, session
 from flask_login import LoginManager, login_user, logout_user
 
-from core.log import  LogRecordHelper
-from core.enum import OperationType,OperationModule
-from core.api import  ResponseBase
-from core.user_session import  UserSession,CurrentUser
-from services import  UserService
+from core.log import LogRecordHelper
+from core.enum import OperationType, OperationModule
+from core.api import ResponseBase
+from core.user_session import UserSession, CurrentUser
+from services import UserService
 
-auth_api = Blueprint('auth_api', __name__)
+auth_api = Blueprint("auth_api", __name__)
 user_service = UserService()
 
 login_manager = LoginManager()
-login_manager.login_view = 'auth_api.login'
-login_manager.login_message = '请先登录'
+login_manager.login_view = "auth_api.login"
+login_manager.login_message = "请先登录"
+
 
 @login_manager.user_loader
 def load_user(user_id):
@@ -23,10 +24,10 @@ def load_user(user_id):
     return None
 
 
-@auth_api.route('/login', methods=['POST'])
+@auth_api.route("/login", methods=["POST"])
 def login():
-    username = request.json.get('username')
-    password = request.json.get('password')
+    username = request.json.get("username")
+    password = request.json.get("password")
 
     # 调用UserService进行用户验证
     user, msg = user_service.authenticate_user(username, password)
@@ -36,23 +37,30 @@ def login():
         login_user(current_user)
         UserSession.set_user(user)
         # 记录日志
-        LogRecordHelper.log_success(OperationType.LOGIN, OperationModule.ACCOUNT, '用户登录成功', username=user.username)
+        LogRecordHelper.log_success(
+            OperationType.LOGIN,
+            OperationModule.ACCOUNT,
+            "用户登录成功",
+            username=user.username,
+        )
 
-        return ResponseBase.success({
-            'user_id': user.id,
-            'username': user.username
-        })
+        return ResponseBase.success({"user_id": user.id, "username": user.username})
     else:
         # 登录失败,返回错误信息
         return ResponseBase.error(msg)
 
 
-@auth_api.route('/logout', methods=['POST'])
+@auth_api.route("/logout", methods=["POST"])
 def logout():
-    username = session.get('username')
+    username = session.get("username")
     if username:
         logout_user()
         UserSession.clear_user()
-        LogRecordHelper.log_success(OperationType.LOGIN, OperationModule.ACCOUNT, '用户退出登录', username=username)
+        LogRecordHelper.log_success(
+            OperationType.LOGIN,
+            OperationModule.ACCOUNT,
+            "用户退出登录",
+            username=username,
+        )
         return jsonify(ResponseBase.success())
-    return jsonify(ResponseBase.error('用户未登录'))
+    return jsonify(ResponseBase.error("用户未登录"))

+ 24 - 15
SourceCode/IntelligentRailwayCosting/app/routes/log.py

@@ -1,33 +1,42 @@
 from flask import Blueprint, request
 from core.user_session import Permission
-from core.api import  ResponseBase,TableResponse
-from services import  LogService
+from core.api import ResponseBase, TableResponse
+from services import LogService
 
-log_api = Blueprint('log_api', __name__)
+log_api = Blueprint("log_api", __name__)
 log_service = LogService()
 
-@log_api.route('/list', methods=['POST'])
+
+@log_api.route("/list", methods=["POST"])
 @Permission.authorize
 def get_page_list():
     try:
         data = request.get_json()
-        page = int(data.get('pageNum', 1))
-        per_page = int(data.get('pageSize', 10))
-        username = data.get('username')
-        operation_type = data.get('operationType')
-        operation_module = data.get('operationModule')
-        operation_result = data.get('operationResult')
+        page = int(data.get("pageNum", 1))
+        per_page = int(data.get("pageSize", 10))
+        username = data.get("username")
+        operation_type = data.get("operationType")
+        operation_module = data.get("operationModule")
+        operation_result = data.get("operationResult")
         operation_result = int(operation_result) if operation_result else None
         start_date = None
         end_date = None
-        date = data.get('date')
+        date = data.get("date")
         if date:
-            date = date.split(' - ')
+            date = date.split(" - ")
             start_date = date[0]
             end_date = date[1]
 
-        logs, total_count = log_service.get_logs_paginated(page, per_page, username, operation_type, operation_module, operation_result,
-                                                           start_date, end_date)
+        logs, total_count = log_service.get_logs_paginated(
+            page,
+            per_page,
+            username,
+            operation_type,
+            operation_module,
+            operation_result,
+            start_date,
+            end_date,
+        )
         return TableResponse.success(logs, total_count)
     except Exception as e:
-        return ResponseBase.error(f'获取日志失败:{str(e)}')
+        return ResponseBase.error(f"获取日志失败:{str(e)}")

+ 1 - 1
SourceCode/IntelligentRailwayCosting/app/routes/project_quota.py

@@ -34,7 +34,7 @@ def get_page_list(budget_id: int, project_id: str, item_code: str):
 )
 @Permission.authorize
 def get_quotas_by_task_paginated(
-        task_id: int, budget_id: int, project_id: str, item_code: str
+    task_id: int, budget_id: int, project_id: str, item_code: str
 ):
     try:
         data = request.get_json()

+ 74 - 61
SourceCode/IntelligentRailwayCosting/app/routes/project_task.py

@@ -1,58 +1,64 @@
-from flask import Blueprint, request,send_from_directory
+from flask import Blueprint, request, send_from_directory
 import os
 from core.dtos import ProjectTaskDto
 from core.user_session import Permission
-from core.api import  ResponseBase,TableResponse
-from services import  ProjectTaskService
+from core.api import ResponseBase, TableResponse
+from services import ProjectTaskService
 
-project_task_api = Blueprint('project_task_api', __name__)
+project_task_api = Blueprint("project_task_api", __name__)
 task_service = ProjectTaskService()
 
-@project_task_api.route('/list/<project_id>/<item_code>', methods=['POST'])
+
+@project_task_api.route("/list/<project_id>/<item_code>", methods=["POST"])
 @Permission.authorize
-def get_page_list(project_id:str,item_code:str):
+def get_page_list(project_id: str, item_code: str):
     try:
         data = request.get_json()
-        page = int(data.get('pageNum', 1))
-        per_page = int(data.get('pageSize', 10))
-        keyword = data.get('keyword')
-        process_status = int(data.get('process_status')) if data.get('process_status') else None
-        send_status = int(data.get('send_status')) if data.get('send_status') else None
-        task, total_count = task_service.get_tasks_paginated( project_id, item_code, page, per_page, keyword, process_status, send_status)
+        page = int(data.get("pageNum", 1))
+        per_page = int(data.get("pageSize", 10))
+        keyword = data.get("keyword")
+        process_status = (
+            int(data.get("process_status")) if data.get("process_status") else None
+        )
+        send_status = int(data.get("send_status")) if data.get("send_status") else None
+        task, total_count = task_service.get_tasks_paginated(
+            project_id, item_code, page, per_page, keyword, process_status, send_status
+        )
         return TableResponse.success(task, total_count)
     except Exception as e:
-        return ResponseBase.error(f'获取任务列表失败:{str(e)}')
+        return ResponseBase.error(f"获取任务列表失败:{str(e)}")
 
 
-@project_task_api.route('/get/<int:task_id>', methods=['POST'])
+@project_task_api.route("/get/<int:task_id>", methods=["POST"])
 @Permission.authorize
-def get_task(task_id:int):
+def get_task(task_id: int):
     try:
         task = task_service.get_task_dto(task_id)
         return ResponseBase.success(task.to_dict())
     except Exception as e:
-        return ResponseBase.error(f'获取项目失败:{str(e)}')
+        return ResponseBase.error(f"获取项目失败:{str(e)}")
 
-@project_task_api.route('/save/<int:task_id>', methods=['POST'])
+
+@project_task_api.route("/save/<int:task_id>", methods=["POST"])
 @Permission.authorize
-def save_task(task_id:int):
+def save_task(task_id: int):
     try:
         # 从请求中获取表单数据
         form_data = request.form.to_dict()
-        budget_id = int(form_data.get('budget_id')) if form_data.get('budget_id') else 0
-        item_id = int(form_data.get('item_id')) if form_data.get('item_id') else None
-        project_id = form_data.get('project_id')
-        item_code = form_data.get('item_code')
-        task_name = form_data.get('task_name')
-        task_desc = form_data.get('task_desc')
-        task_sort = int(form_data.get('task_sort')) if form_data.get('task_sort') else 0
-        run_now = form_data.get('run_now')=='true'
+        budget_id = int(form_data.get("budget_id")) if form_data.get("budget_id") else 0
+        item_id = int(form_data.get("item_id")) if form_data.get("item_id") else None
+        project_id = form_data.get("project_id")
+        item_code = form_data.get("item_code")
+        task_name = form_data.get("task_name")
+        task_desc = form_data.get("task_desc")
+        task_sort = int(form_data.get("task_sort")) if form_data.get("task_sort") else 0
+        run_now = form_data.get("run_now") == "true"
         # delete_old = form_data.get('delete_old', 'false').lower() == 'true'
         # 获取上传的文件
-        files = request.files.getlist('files')
+        files = request.files.getlist("files")
         # 验证必要参数
-        if not all([ project_id, task_name]):
-            return ResponseBase.error('缺少必要参数:project_id、task_name')
+        if not all([project_id, task_name]):
+            return ResponseBase.error("缺少必要参数:project_id、task_name")
         # 构建任务DTO
         task_dto = ProjectTaskDto(
             item_id=item_id,
@@ -62,53 +68,58 @@ def save_task(task_id:int):
             task_name=task_name,
             task_desc=task_desc,
             task_sort=task_sort,
-            file_path=None
+            file_path=None,
         )
-        
+
         # 保存任务
         task = task_service.save_task(task_id, task_dto, files)
-        msg =""
+        msg = ""
         if run_now:
             msg = task_service.start_run_task(task.id)
-            if msg == '0':
-                msg = '项目有正在运行的任务,已加入等待列表中'
+            if msg == "0":
+                msg = "项目有正在运行的任务,已加入等待列表中"
             elif msg:
                 return ResponseBase.error(msg)
         return ResponseBase.success(task.to_dict(), msg)
     except ValueError as ve:
-        return ResponseBase.error(f'参数格式错误:{str(ve)}')
+        return ResponseBase.error(f"参数格式错误:{str(ve)}")
     except Exception as e:
-        return ResponseBase.error(f'保存任务失败:{str(e)}')
+        return ResponseBase.error(f"保存任务失败:{str(e)}")
 
-@project_task_api.route('/delete/<int:task_id>', methods=['POST'])
+
+@project_task_api.route("/delete/<int:task_id>", methods=["POST"])
 @Permission.authorize
-def delete_task(task_id:int):
+def delete_task(task_id: int):
     try:
         task = task_service.delete_task(task_id)
         return ResponseBase.success(task)
     except Exception as e:
-        return ResponseBase.error(f'删除任务失败:{str(e)}')
+        return ResponseBase.error(f"删除任务失败:{str(e)}")
+
 
-@project_task_api.route('/download', methods=['POST'])
+@project_task_api.route("/download", methods=["POST"])
 @Permission.authorize
 def download_file():
-    filename = request.args.get('filename', type=str)
+    filename = request.args.get("filename", type=str)
     if not filename:
-        return ResponseBase.error('文件名不能为空')
+        return ResponseBase.error("文件名不能为空")
     try:
         # 安全处理文件名
         pure_filename = os.path.basename(filename)
         safe_filename = os.path.basename(pure_filename)
-        path = filename.replace(safe_filename, '')
+        path = filename.replace(safe_filename, "")
         upload_folder = os.path.abspath(os.path.join(os.getcwd(), path))
         if not os.path.exists(upload_folder):
-            return ResponseBase.error('项目目录不存在')
+            return ResponseBase.error("项目目录不存在")
         full_path = os.path.join(upload_folder, safe_filename)
         if not os.path.exists(full_path):
-            return ResponseBase.error('文件不存在')
+            return ResponseBase.error("文件不存在")
     except Exception as e:
-        return ResponseBase.error(f'非法文件路径{str(e)}')
-    return send_from_directory(upload_folder.replace('\\', '/'), safe_filename, as_attachment=True)
+        return ResponseBase.error(f"非法文件路径{str(e)}")
+    return send_from_directory(
+        upload_folder.replace("\\", "/"), safe_filename, as_attachment=True
+    )
+
 
 # @project_task_api.route('/start_collect/<int:task_id>', methods=['POST'])
 # @Permission.authorize
@@ -132,40 +143,42 @@ def download_file():
 #     except Exception as e:
 #         return ResponseBase.error(f'启动处理失败:{str(e)}')
 
-@project_task_api.route('/start_task/<int:task_id>', methods=['POST'])
+
+@project_task_api.route("/start_task/<int:task_id>", methods=["POST"])
 @Permission.authorize
-def start_task(task_id:int):
+def start_task(task_id: int):
     try:
         msg = task_service.start_run_task(task_id)
-        if msg == '0':
-            msg = '项目有正在运行的任务,已加入等待列表中'
+        if msg == "0":
+            msg = "项目有正在运行的任务,已加入等待列表中"
         elif msg:
             return ResponseBase.error(msg)
         else:
-            msg = '运行成功'
+            msg = "运行成功"
         return ResponseBase.success(message=msg)
     except Exception as e:
-        return ResponseBase.error(f'运行失败:{str(e)}')
+        return ResponseBase.error(f"运行失败:{str(e)}")
+
 
-@project_task_api.route('/cancel_task/<int:task_id>', methods=['POST'])
+@project_task_api.route("/cancel_task/<int:task_id>", methods=["POST"])
 @Permission.authorize
-def cancel_task(task_id:int):
+def cancel_task(task_id: int):
     try:
         msg = task_service.cancel_run_task(task_id)
         if msg:
             return ResponseBase.error(msg)
-        return ResponseBase.success(message='启动采集成功')
+        return ResponseBase.success(message="启动采集成功")
     except Exception as e:
-        return ResponseBase.error(f'启动采集失败:{str(e)}')
+        return ResponseBase.error(f"启动采集失败:{str(e)}")
 
 
-@project_task_api.route('/start_send/<int:task_id>', methods=['POST'])
+@project_task_api.route("/start_send/<int:task_id>", methods=["POST"])
 @Permission.authorize
-def start_send(task_id:int):
+def start_send(task_id: int):
     try:
         msg = task_service.start_send_task(task_id)
         if msg:
             return ResponseBase.error(msg)
-        return ResponseBase.success(message='启动发送成功')
+        return ResponseBase.success(message="启动发送成功")
     except Exception as e:
-        return ResponseBase.error(f'启动发送失败:{str(e)}')
+        return ResponseBase.error(f"启动发送失败:{str(e)}")

+ 0 - 1
SourceCode/IntelligentRailwayCosting/app/services/__init__.py

@@ -3,4 +3,3 @@ from .log import LogService
 from .project import ProjectService
 from .project_quota import ProjectQuotaService
 from .project_task import ProjectTaskService
-

+ 53 - 18
SourceCode/IntelligentRailwayCosting/app/services/log.py

@@ -1,37 +1,72 @@
-from datetime import datetime,timedelta
+from datetime import datetime, timedelta
 from core.dtos import LogDto
 from stores import LogStore
 
+
 class LogService:
     def __init__(self):
         self.log_store = LogStore()
         pass
 
-    def get_logs_paginated(self, page:int, page_size:int, username:str, operation_type:str, operation_module:str, operation_result:int, start_time:str, end_time:str):
+    def get_logs_paginated(
+            self,
+            page: int,
+            page_size: int,
+            username: str,
+            operation_type: str,
+            operation_module: str,
+            operation_result: int,
+            start_time: str,
+            end_time: str,
+    ):
         # 处理开始时间和结束时间
         start_datetime = None
         end_datetime = None
         try:
             if start_time:
-                start_datetime = datetime.strptime(start_time, '%Y-%m-%d').replace(hour=0, minute=0, second=0)
+                start_datetime = datetime.strptime(start_time, "%Y-%m-%d").replace(
+                    hour=0, minute=0, second=0
+                )
             if end_time:
-                end_datetime = datetime.strptime(end_time, '%Y-%m-%d').replace(hour=0, minute=0, second=0)
+                end_datetime = datetime.strptime(end_time, "%Y-%m-%d").replace(
+                    hour=0, minute=0, second=0
+                )
                 # 使用timedelta来处理结束时间加一天
                 end_datetime = end_datetime + timedelta(days=1)
         except ValueError as e:
             raise ValueError(f"日期格式错误,请使用YYYY-MM-DD格式: {str(e)}")
-        data =  self.log_store.query_logs_paginated(page, page_size, username, operation_type, operation_module, operation_result, start_datetime, end_datetime)
-        return [LogDto.from_model(item).to_dict() for  item in data.get('data',[])], data.get('total',0)
-
+        data = self.log_store.query_logs_paginated(
+            page,
+            page_size,
+            username,
+            operation_type,
+            operation_module,
+            operation_result,
+            start_datetime,
+            end_datetime,
+        )
+        return [
+            LogDto.from_model(item).to_dict() for item in data.get("data", [])
+        ], data.get("total", 0)
 
-    def add_operation_log(self,
-                          username:str,
-                          operation_type:str,
-                          operation_module:str,
-                          operation_desc:str,
-                          operation_result:int,
-                          operation_data:str = None,
-                          data_changes:str = None,
-                          operation_ip:str = None,
-                          ):
-        self.log_store.insert_log(username,operation_type,operation_desc,operation_result,operation_module,operation_data,data_changes,operation_ip)
+    def add_operation_log(
+            self,
+            username: str,
+            operation_type: str,
+            operation_module: str,
+            operation_desc: str,
+            operation_result: int,
+            operation_data: str = None,
+            data_changes: str = None,
+            operation_ip: str = None,
+    ):
+        self.log_store.insert_log(
+            username,
+            operation_type,
+            operation_desc,
+            operation_result,
+            operation_module,
+            operation_data,
+            data_changes,
+            operation_ip,
+        )

+ 102 - 38
SourceCode/IntelligentRailwayCosting/app/services/project.py

@@ -5,7 +5,7 @@ from core.dtos import TotalBudgetInfoDto, TotalBudgetItemDto
 from core.dtos.project import ProjectDto
 from core.dtos.tree import TreeDto
 from core.user_session import UserSession
-from stores import ProjectStore,BudgetStore,ChapterStore
+from stores import ProjectStore, BudgetStore, ChapterStore
 
 
 class ProjectService:
@@ -15,41 +15,55 @@ class ProjectService:
         self._budget_store = BudgetStore()
         self._chapter_store = ChapterStore()
 
-    def get_projects_paginated(self, page: int, page_size: int, keyword: Optional[str] = None,
-        start_time: Optional[str] = None,
-        end_time: Optional[str] = None, can_edit:Optional[int]=0,):
-        
+    def get_projects_paginated(
+            self,
+            page: int,
+            page_size: int,
+            keyword: Optional[str] = None,
+            start_time: Optional[str] = None,
+            end_time: Optional[str] = None,
+            can_edit: Optional[int] = 0,
+    ):
+
         # 处理开始时间和结束时间
         start_datetime = None
         end_datetime = None
         try:
             if start_time:
-                start_datetime = datetime.strptime(start_time, '%Y-%m-%d').replace(hour=0, minute=0, second=0)
+                start_datetime = datetime.strptime(start_time, "%Y-%m-%d").replace(
+                    hour=0, minute=0, second=0
+                )
             if end_time:
-                end_datetime = datetime.strptime(end_time, '%Y-%m-%d').replace(hour=0, minute=0, second=0)
+                end_datetime = datetime.strptime(end_time, "%Y-%m-%d").replace(
+                    hour=0, minute=0, second=0
+                )
                 # 使用timedelta来处理结束时间加一天
                 end_datetime = end_datetime + timedelta(days=1)
         except ValueError as e:
             raise ValueError(f"日期格式错误,请使用YYYY-MM-DD格式: {str(e)}")
 
-        data =  self._project_store.get_user_projects_paginated(page, page_size, keyword, start_datetime, end_datetime, can_edit)
-        return [ProjectDto.from_model(item).to_dict() for item in data.get('data',[])],data.get('total',0)
+        data = self._project_store.get_user_projects_paginated(
+            page, page_size, keyword, start_datetime, end_datetime, can_edit
+        )
+        return [
+            ProjectDto.from_model(item).to_dict() for item in data.get("data", [])
+        ], data.get("total", 0)
 
     def get_budget_info(self, project_id: str):
         msg = self._check_project_db_exit(project_id)
         if msg:
             return None, msg
-        data =  self._budget_store.get_budget_info(project_id)
-        return [TotalBudgetInfoDto.from_model(item).to_dict() for item in data],""
+        data = self._budget_store.get_budget_info(project_id)
+        return [TotalBudgetInfoDto.from_model(item).to_dict() for item in data], ""
 
     def get_top_budget_items(self, budget_id: int, project_id: str):
         msg = self._check_project_db_exit(project_id)
         if msg:
             return None, msg
-        items = self._budget_store.get_top_budget_items(project_id,budget_id)
-        return [TotalBudgetItemDto.from_model(item).to_dict() for item in items],""
+        items = self._budget_store.get_top_budget_items(project_id, budget_id)
+        return [TotalBudgetItemDto.from_model(item).to_dict() for item in items], ""
 
-    def get_chapter_items(self, project_id: str, item_code:str):
+    def get_chapter_items(self, project_id: str, item_code: str):
         msg = self._check_project_db_exit(project_id)
         if msg:
             return None, msg
@@ -58,24 +72,48 @@ class ProjectService:
             team_item_code = None
             current_user = UserSession.get_current_user()
             if not current_user.is_admin:
-                team_item_code_str = self._project_store.get_team_project_item_code(project_id,current_user.username)
+                team_item_code_str = self._project_store.get_team_project_item_code(
+                    project_id, current_user.username
+                )
                 if team_item_code_str:
-                    team_item_code = None if team_item_code_str == 'None' or team_item_code_str == '0' or team_item_code_str == ''  else team_item_code_str.split(',')
-            items =  self._chapter_store.get_top_chapter_items(project_id,team_item_code)
+                    team_item_code = (
+                        None
+                        if team_item_code_str == "None"
+                           or team_item_code_str == "0"
+                           or team_item_code_str == ""
+                        else team_item_code_str.split(",")
+                    )
+            items = self._chapter_store.get_top_chapter_items(
+                project_id, team_item_code
+            )
         else:
-            items = self._chapter_store.get_child_chapter_items(project_id,item_code)
+            items = self._chapter_store.get_child_chapter_items(project_id, item_code)
         parent = "#"
         if item_code:
-            item = self._chapter_store.get_chapter_item_by_item_code(project_id,item_code)
+            item = self._chapter_store.get_chapter_item_by_item_code(
+                project_id, item_code
+            )
             parent = item.item_id
         for item in items:
-            text = f"第{item.chapter}章、{item.project_name}" if item.chapter else ( f"{item.section}  {item.project_name}" if item.section else item.project_name)
-            data_list.append(TreeDto(item.item_id,parent,text,item.children_count>0,item).to_dict())
-        return data_list,""
+            text = (
+                f"第{item.chapter}章、{item.project_name}"
+                if item.chapter
+                else (
+                    f"{item.section}  {item.project_name}"
+                    if item.section
+                    else item.project_name
+                )
+            )
+            data_list.append(
+                TreeDto(
+                    item.item_id, parent, text, item.children_count > 0, item
+                ).to_dict()
+            )
+        return data_list, ""
 
-    def get_budget_items(self, budget_id: int, project_id: str, item_code:str):
+    def get_budget_items(self, budget_id: int, project_id: str, item_code: str):
         if not budget_id:
-            return None,'budget_id不能为空'
+            return None, "budget_id不能为空"
         msg = self._check_project_db_exit(project_id)
         if msg:
             return None, msg
@@ -84,26 +122,52 @@ class ProjectService:
             team_item_code = None
             current_user = UserSession.get_current_user()
             if not current_user.is_admin:
-                team_item_code_str = self._project_store.get_team_project_item_code(project_id,current_user.username)
+                team_item_code_str = self._project_store.get_team_project_item_code(
+                    project_id, current_user.username
+                )
                 if team_item_code_str:
-                    team_item_code = None if team_item_code_str == 'None' or team_item_code_str == '0' or team_item_code_str == ''  else team_item_code_str.split(',')
-            items =  self._budget_store.get_top_budget_items(project_id,budget_id,team_item_code)
+                    team_item_code = (
+                        None
+                        if team_item_code_str == "None"
+                           or team_item_code_str == "0"
+                           or team_item_code_str == ""
+                        else team_item_code_str.split(",")
+                    )
+            items = self._budget_store.get_top_budget_items(
+                project_id, budget_id, team_item_code
+            )
         else:
-            items = self._budget_store.get_child_budget_items(project_id,budget_id,item_code)
+            items = self._budget_store.get_child_budget_items(
+                project_id, budget_id, item_code
+            )
         parent = "#"
         if item_code:
-            item = self._budget_store.get_budget_item_by_item_code(project_id,budget_id,item_code)
+            item = self._budget_store.get_budget_item_by_item_code(
+                project_id, budget_id, item_code
+            )
             parent = item.item_id
         for item in items:
-            text = f"第{item.chapter}章、{item.project_name}" if item.chapter else ( f"{item.section}  {item.project_name}" if item.section else item.project_name)
-            data_list.append(TreeDto(item.item_id,parent,text,item.children_count>0,item).to_dict())
-        return data_list,""
+            text = (
+                f"第{item.chapter}章、{item.project_name}"
+                if item.chapter
+                else (
+                    f"{item.section}  {item.project_name}"
+                    if item.section
+                    else item.project_name
+                )
+            )
+            data_list.append(
+                TreeDto(
+                    item.item_id, parent, text, item.children_count > 0, item
+                ).to_dict()
+            )
+        return data_list, ""
 
-    def _check_project_db_exit(self, project_id:str):
+    def _check_project_db_exit(self, project_id: str):
         if not project_id:
-            return 'project_id不能为空'
+            return "project_id不能为空"
         if not self._project_store.get(project_id):
-            return '项目不存在'
-        if not project_id.startswith('Reco'):
-            return '项目id格式错误'
-        return None
+            return "项目不存在"
+        if not project_id.startswith("Reco"):
+            return "项目id格式错误"
+        return None

+ 6 - 5
SourceCode/IntelligentRailwayCosting/app/services/user.py

@@ -1,5 +1,7 @@
 from stores import UserStore
 from core.dtos import UserDto
+
+
 class UserService:
     def __init__(self):
         self.user_store = UserStore()
@@ -19,14 +21,13 @@ class UserService:
     def authenticate_user(self, username: str, password: str):
         user = self.user_store.get_user_by_username(username)
         if not user:
-            return None,"用户不存在"
+            return None, "用户不存在"
         user = self.user_store.authenticate_user(username, password)
         if user:
-            return UserDto.from_model(user),""
-        return None,"密码错误"
+            return UserDto.from_model(user), ""
+        return None, "密码错误"
 
     def get_all_users(self) -> list[UserDto]:
         users = self.user_store.get_all_users()
-        user_list = [UserDto.from_model(user) for  user in users]
+        user_list = [UserDto.from_model(user) for user in users]
         return user_list
-

+ 5 - 2
SourceCode/IntelligentRailwayCosting/app/stores/__init__.py

@@ -1,9 +1,12 @@
 # from stores.railway_costing_mysql import LogStore,ProjectQuotaStore,ProjectTaskStore
-from stores.railway_costing_sqlserver import LogStore,ProjectQuotaStore,ProjectTaskStore
+from stores.railway_costing_sqlserver import (
+    LogStore,
+    ProjectQuotaStore,
+    ProjectTaskStore,
+)
 
 from .user import UserStore
 from .project import ProjectStore
 from .budget import BudgetStore
 from .chapter import ChapterStore
 from .quota_input import QuotaInputStore
-

+ 2 - 2
SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_mysql/__init__.py

@@ -1,3 +1,3 @@
-from .project_task import  ProjectTaskStore
+from .project_task import ProjectTaskStore
 from .project_quota import ProjectQuotaStore
-from .log import LogStore
+from .log import LogStore

+ 31 - 31
SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_mysql/log.py

@@ -5,21 +5,22 @@ import tools.db_helper as db_helper
 
 from core.models import LogModel
 
+
 class LogStore:
     def __init__(self):
         # self._database= None
-        self._database= 'Iwb_RailwayCosting'
+        self._database = "Iwb_RailwayCosting"
 
     def query_logs_paginated(
-        self,
-        page: int = 1,
-        page_size: int = 10,
-        username: Optional[str] = None,
-        operation_type: Optional[str] = None,
-        operation_module: Optional[str] = None,
-        operation_result: Optional[int] = None,
-        start_time: Optional[datetime] = None,
-        end_time: Optional[datetime] = None
+            self,
+            page: int = 1,
+            page_size: int = 10,
+            username: Optional[str] = None,
+            operation_type: Optional[str] = None,
+            operation_module: Optional[str] = None,
+            operation_result: Optional[int] = None,
+            start_time: Optional[datetime] = None,
+            end_time: Optional[datetime] = None,
     ) -> Dict[str, Any]:
         """
         分页查询日志记录
@@ -39,7 +40,7 @@ class LogStore:
             # 构建查询条件
             conditions = []
             if username:
-                conditions.append(LogModel.username.like(f'%{username}%'))
+                conditions.append(LogModel.username.like(f"%{username}%"))
             if operation_type:
                 conditions.append(LogModel.operation_type == operation_type)
             if operation_module:
@@ -58,26 +59,25 @@ class LogStore:
             total = query.count()
 
             # 分页并按创建时间倒序排序
-            logs = query.order_by(LogModel.created_at.desc())\
-                .offset((page - 1) * page_size)\
-                .limit(page_size)\
+            logs = (
+                query.order_by(LogModel.created_at.desc())
+                .offset((page - 1) * page_size)
+                .limit(page_size)
                 .all()
+            )
+
+            return {"total": total, "data": logs}
 
-            return {
-                'total': total,
-                'data': logs
-            }
-    
     def insert_log(
-        self,
-        username: str,
-        operation_type: str,
-        operation_desc: Optional[str] = None,
-        operation_result: Optional[int] = None,
-        operation_module: Optional[str] = None,
-        operation_data: Optional[str] = None,
-        data_changes: Optional[str] = None,
-        operation_ip: Optional[str] = None
+            self,
+            username: str,
+            operation_type: str,
+            operation_desc: Optional[str] = None,
+            operation_result: Optional[int] = None,
+            operation_module: Optional[str] = None,
+            operation_data: Optional[str] = None,
+            data_changes: Optional[str] = None,
+            operation_ip: Optional[str] = None,
     ) -> LogModel:
         """
         插入单条日志记录
@@ -99,12 +99,12 @@ class LogStore:
             operation_module=operation_module,
             operation_data=operation_data,
             data_changes=data_changes,
-            operation_ip=operation_ip
+            operation_ip=operation_ip,
         )
         with db_helper.mysql_session(self._database) as db_session:
             db_session.add(log)
             return log
-    
+
     def batch_insert_logs(self, logs: List[Dict[str, Any]]) -> List[LogModel]:
         """
         批量插入日志记录
@@ -114,4 +114,4 @@ class LogStore:
         log_models = [LogModel(**log) for log in logs]
         with db_helper.mysql_session(self._database) as db_session:
             db_session.add_all(log_models)
-            return log_models
+            return log_models

+ 81 - 54
SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_mysql/project_quota.py

@@ -8,10 +8,11 @@ from core.enum import SendStatusEnum
 from core.models import ProjectQuotaModel
 from core.user_session import UserSession
 
+
 class ProjectQuotaStore:
     def __init__(self):
         # self._database= None
-        self._database = 'Iwb_RailwayCosting'
+        self._database = "Iwb_RailwayCosting"
         self._current_user = None
 
     @property
@@ -21,14 +22,14 @@ class ProjectQuotaStore:
         return self._current_user
 
     def get_quotas_paginated(
-        self,
-        budget_id: int,
-        project_id: str,
-        item_code: str,
-        page: int = 1,
-        page_size: int = 10,
-        keyword: Optional[str] = None,
-        send_status: Optional[int] = None,
+            self,
+            budget_id: int,
+            project_id: str,
+            item_code: str,
+            page: int = 1,
+            page_size: int = 10,
+            keyword: Optional[str] = None,
+            send_status: Optional[int] = None,
     ):
         """分页查询定额列表
 
@@ -51,32 +52,45 @@ class ProjectQuotaStore:
                 ProjectQuotaModel.is_del == 0,
                 ProjectQuotaModel.project_id == project_id,
                 ProjectQuotaModel.budget_id == budget_id,
-                ProjectQuotaModel.item_code.like(f"{item_code}%")
+                ProjectQuotaModel.item_code.like(f"{item_code}%"),
             ]
 
             if send_status is not None:
                 conditions.append(ProjectQuotaModel.send_status == send_status)
             if keyword:
-                conditions.append(or_(
-                    ProjectQuotaModel.quota_code.like(f"%{keyword}%"),
-                    ProjectQuotaModel.entry_name.like(f"%{keyword}%"),
-                ))
+                conditions.append(
+                    or_(
+                        ProjectQuotaModel.quota_code.like(f"%{keyword}%"),
+                        ProjectQuotaModel.entry_name.like(f"%{keyword}%"),
+                    )
+                )
             query = query.filter(and_(*conditions))
 
             # 计算总数
             total_count = query.count()
 
             # 分页
-            query = query.order_by(ProjectQuotaModel.created_at.desc()).offset((page - 1) * page_size).limit(page_size)
+            query = (
+                query.order_by(ProjectQuotaModel.created_at.desc())
+                .offset((page - 1) * page_size)
+                .limit(page_size)
+            )
 
             quotas = query.all()
 
-            return {
-                'total': total_count,
-                'data': quotas
-            }
-
-    def get_quotas_by_task_paginated(self, task_id: int, budget_id, project_id, item_code, page: int = 1, page_size: int = 10,keyword: Optional[str]=None,send_status: Optional[int] = None):
+            return {"total": total_count, "data": quotas}
+
+    def get_quotas_by_task_paginated(
+            self,
+            task_id: int,
+            budget_id,
+            project_id,
+            item_code,
+            page: int = 1,
+            page_size: int = 10,
+            keyword: Optional[str] = None,
+            send_status: Optional[int] = None,
+    ):
         with db_helper.mysql_query_session(self._database) as db_session:
             query = db_session.query(ProjectQuotaModel).filter(
                 and_(
@@ -84,46 +98,49 @@ class ProjectQuotaStore:
                     ProjectQuotaModel.budget_id == budget_id,
                     ProjectQuotaModel.project_id == project_id,
                     ProjectQuotaModel.item_code.like(f"{item_code}%"),
-                    ProjectQuotaModel.is_del == 0
+                    ProjectQuotaModel.is_del == 0,
                 )
             )
             if keyword:
-                query = query.filter(or_(
-                    ProjectQuotaModel.quota_code.like(f"%{keyword}%"),
-                    ProjectQuotaModel.entry_name.like(f"%{keyword}%"),
-                ))
+                query = query.filter(
+                    or_(
+                        ProjectQuotaModel.quota_code.like(f"%{keyword}%"),
+                        ProjectQuotaModel.entry_name.like(f"%{keyword}%"),
+                    )
+                )
             if send_status is not None:
                 query = query.filter(ProjectQuotaModel.send_status == send_status)
             # 计算总数
             total_count = query.count()
 
             # 分页
-            query = query.order_by(ProjectQuotaModel.created_at.desc()).offset((page - 1) * page_size).limit(
-                page_size)
+            query = (
+                query.order_by(ProjectQuotaModel.created_at.desc())
+                .offset((page - 1) * page_size)
+                .limit(page_size)
+            )
 
             quotas = query.all()
 
-            return {
-                'total': total_count,
-                'data': quotas
-            }
-
+            return {"total": total_count, "data": quotas}
 
-    def get_quotas_by_task_id(self,task_id:int, with_quota_code:bool=False):
+    def get_quotas_by_task_id(self, task_id: int, with_quota_code: bool = False):
         with db_helper.mysql_query_session(self._database) as db_session:
             query = db_session.query(ProjectQuotaModel).filter(
                 and_(
-                    ProjectQuotaModel.task_id == task_id,
-                    ProjectQuotaModel.is_del == 0
+                    ProjectQuotaModel.task_id == task_id, ProjectQuotaModel.is_del == 0
                 )
             )
             if with_quota_code:
-                query = query.filter(and_(ProjectQuotaModel.quota_code!=None,ProjectQuotaModel.quota_code!='') )
+                query = query.filter(
+                    and_(
+                        ProjectQuotaModel.quota_code != None,
+                        ProjectQuotaModel.quota_code != "",
+                    )
+                )
             quotas = query.all()
             return quotas
 
-
-
     def get_quota(self, quota_id: int) -> Optional[ProjectQuotaModel]:
         """根据ID查询定额
 
@@ -134,13 +151,17 @@ class ProjectQuotaStore:
             Optional[ProjectQuotaDto]
         """
         with db_helper.mysql_query_session(self._database) as db_session:
-            quota = db_session.query(ProjectQuotaModel).filter(
-                and_(
-                    ProjectQuotaModel.id == quota_id,
-                    ProjectQuotaModel.is_del == 0
+            quota = (
+                db_session.query(ProjectQuotaModel)
+                .filter(
+                    and_(
+                        ProjectQuotaModel.id == quota_id, ProjectQuotaModel.is_del == 0
+                    )
                 )
-            ).first()
+                .first()
+            )
             return quota
+
     def get_quota_dto(self, quota_id: int) -> Optional[ProjectQuotaDto]:
         """根据ID查询定额
 
@@ -154,16 +175,22 @@ class ProjectQuotaStore:
 
         return ProjectQuotaDto.from_model(quota) if quota else None
 
-    def get_quota_by_quota_input(self, project_id:str, budget_id:int,quota_input_id:int):
+    def get_quota_by_quota_input(
+            self, project_id: str, budget_id: int, quota_input_id: int
+    ):
         with db_helper.mysql_query_session(self._database) as db_session:
-            quota = db_session.query(ProjectQuotaModel).filter(
-                and_(
-                    ProjectQuotaModel.project_id == project_id,
-                    ProjectQuotaModel.budget_id == budget_id,
-                    ProjectQuotaModel.quota_id == quota_input_id,
-                    ProjectQuotaModel.is_del == 0
+            quota = (
+                db_session.query(ProjectQuotaModel)
+                .filter(
+                    and_(
+                        ProjectQuotaModel.project_id == project_id,
+                        ProjectQuotaModel.budget_id == budget_id,
+                        ProjectQuotaModel.quota_id == quota_input_id,
+                        ProjectQuotaModel.is_del == 0,
+                    )
                 )
-            ).first()
+                .first()
+            )
             return ProjectQuotaDto.from_model(quota) if quota else None
 
     def create_quota(self, quota_dto: ProjectQuotaDto) -> ProjectQuotaDto:
@@ -255,7 +282,7 @@ class ProjectQuotaStore:
             db_session.merge(quota)
             return True
 
-    def update_send_status(self,quota_id:int, status:int, err:str = None) -> bool:
+    def update_send_status(self, quota_id: int, status: int, err: str = None) -> bool:
         """
         更新发送状态
         Args:
@@ -293,4 +320,4 @@ class ProjectQuotaStore:
             quota.updated_by = self.current_user.username
             quota.updated_at = datetime.now()
             db_session.merge(quota)
-            return True
+            return True

+ 44 - 40
SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_mysql/project_task.py

@@ -12,7 +12,7 @@ from core.user_session import UserSession
 class ProjectTaskStore:
     def __init__(self):
         # self._database= None
-        self._database = 'Iwb_RailwayCosting'
+        self._database = "Iwb_RailwayCosting"
         self._current_user = None
 
     @property
@@ -20,16 +20,17 @@ class ProjectTaskStore:
         if self._current_user is None:
             self._current_user = UserSession.get_current_user()
         return self._current_user
+
     def get_tasks_paginated(
-        self,
-        project_id: str,
-        item_code: str,
-        page: int = 1,
-        page_size: int = 10,
-        keyword: Optional[str] = None,
-        process_status: Optional[int] = None,
-        send_status: Optional[int] = None,
-    ) :
+            self,
+            project_id: str,
+            item_code: str,
+            page: int = 1,
+            page_size: int = 10,
+            keyword: Optional[str] = None,
+            process_status: Optional[int] = None,
+            send_status: Optional[int] = None,
+    ):
         """分页查询任务列表
 
         Args:
@@ -44,7 +45,7 @@ class ProjectTaskStore:
         Returns:
 
         """
-        with (db_helper.mysql_query_session(self._database) as db_session):
+        with db_helper.mysql_query_session(self._database) as db_session:
             query = db_session.query(ProjectTaskModel)
 
             # 构建查询条件
@@ -52,10 +53,10 @@ class ProjectTaskStore:
                 ProjectTaskModel.is_del == 0,
                 ProjectTaskModel.project_id == project_id,
                 # ProjectTaskModel.budget_id == budget_id,
-                ProjectTaskModel.item_code.like(f"{item_code}%")
+                ProjectTaskModel.item_code.like(f"{item_code}%"),
             ]
             if keyword:
-                conditions.append(ProjectTaskModel.task_name.like(f'%{keyword}%'))
+                conditions.append(ProjectTaskModel.task_name.like(f"%{keyword}%"))
             if process_status is not None:
                 conditions.append(ProjectTaskModel.process_status == process_status)
             if send_status is not None:
@@ -67,24 +68,26 @@ class ProjectTaskStore:
             total_count = query.count()
 
             # 分页
-            query = query.order_by(ProjectTaskModel.task_sort.desc())\
-                .order_by(ProjectTaskModel.created_at.desc())\
-                .offset((page - 1) * page_size).limit(page_size)
+            query = (
+                query.order_by(ProjectTaskModel.task_sort.desc())
+                .order_by(ProjectTaskModel.created_at.desc())
+                .offset((page - 1) * page_size)
+                .limit(page_size)
+            )
 
             tasks = query.all()
 
-            return {
-                'total': total_count,
-                'data': tasks
-            }
+            return {"total": total_count, "data": tasks}
 
     def get_task(self, task_id: int) -> Optional[ProjectTaskModel]:
         with db_helper.mysql_query_session(self._database) as db_session:
-            task = db_session.query(ProjectTaskModel).filter(
-                and_(
-                    ProjectTaskModel.id == task_id,
-                    ProjectTaskModel.is_del == 0
-                )).first()
+            task = (
+                db_session.query(ProjectTaskModel)
+                .filter(
+                    and_(ProjectTaskModel.id == task_id, ProjectTaskModel.is_del == 0)
+                )
+                .first()
+            )
             return task
 
     def get_task_dto(self, task_id: int) -> Optional[ProjectTaskDto]:
@@ -99,7 +102,7 @@ class ProjectTaskStore:
         task_dto = self.get_task(task_id)
         return ProjectTaskDto.from_model(task_dto) if task_dto else None
 
-    def get_wait_tasks(self,project_id:str=None):
+    def get_wait_tasks(self, project_id: str = None):
         """查询待处理的任务
 
         Args:
@@ -113,14 +116,15 @@ class ProjectTaskStore:
             query = query.filter(
                 and_(
                     ProjectTaskModel.is_del == 0,
-                    ProjectTaskModel.process_status == TaskStatusEnum.WAIT.value))
+                    ProjectTaskModel.process_status == TaskStatusEnum.WAIT.value,
+                )
+            )
             if project_id:
                 query = query.filter(ProjectTaskModel.project_id == project_id)
             query.order_by(ProjectTaskModel.task_sort.desc())
             tasks = query.all()
             return [ProjectTaskDto.from_model(task) for task in tasks]
 
-
     def get_tasks_by_status(self, status: int):
         """查询指定状态的任务"""
         with db_helper.mysql_query_session(self._database) as db_session:
@@ -128,7 +132,9 @@ class ProjectTaskStore:
             query = query.filter(
                 and_(
                     ProjectTaskModel.is_del == 0,
-                    ProjectTaskModel.process_status == status))
+                    ProjectTaskModel.process_status == status,
+                )
+            )
             query.order_by(ProjectTaskModel.task_sort.desc())
             tasks = query.all()
             return [ProjectTaskDto.from_model(task) for task in tasks]
@@ -182,12 +188,12 @@ class ProjectTaskStore:
             # task.item_id = task_dto.item_id
             # task.item_code = task_dto.item_code
             # task.file_path = task_dto.file_path
-            task.updated_by=self.current_user.username
-            task.updated_at=datetime.now()
+            task.updated_by = self.current_user.username
+            task.updated_at = datetime.now()
             task = db_session.merge(task)
             return ProjectTaskDto.from_model(task)
 
-    def update_task_files(self, task_id: int,files: str):
+    def update_task_files(self, task_id: int, files: str):
         task = self.get_task(task_id)
         if not task:
             return None
@@ -197,8 +203,8 @@ class ProjectTaskStore:
                 task.process_status = 4
             if task.send_status != 0:
                 task.send_status = 4
-            task.updated_by=self.current_user.username
-            task.updated_at=datetime.now()
+            task.updated_by = self.current_user.username
+            task.updated_at = datetime.now()
             task = db_session.merge(task)
             return ProjectTaskDto.from_model(task)
 
@@ -221,7 +227,8 @@ class ProjectTaskStore:
             task.deleted_at = datetime.now()
             db_session.merge(task)
             return True
-    def update_task_status(self,task_id:int, status:int, err:str = None):
+
+    def update_task_status(self, task_id: int, status: int, err: str = None):
         task = self.get_task(task_id)
         if not task:
             return False
@@ -233,9 +240,7 @@ class ProjectTaskStore:
             db_session.merge(task)
             return True
 
-
-
-    def update_process_status(self,task_id:int, status:int, err:str = None):
+    def update_process_status(self, task_id: int, status: int, err: str = None):
         task = self.get_task(task_id)
         if not task:
             return False
@@ -247,7 +252,7 @@ class ProjectTaskStore:
             db_session.merge(task)
             return True
 
-    def update_send_status(self,task_id:int, status:int, err:str = None):
+    def update_send_status(self, task_id: int, status: int, err: str = None):
         task = self.get_task(task_id)
         if not task:
             return False
@@ -258,4 +263,3 @@ class ProjectTaskStore:
             task.send_time = datetime.now()
             db_session.merge(task)
             return True
-

+ 2 - 2
SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_sqlserver/__init__.py

@@ -1,3 +1,3 @@
-from .project_task import  ProjectTaskStore
+from .project_task import ProjectTaskStore
 from .project_quota import ProjectQuotaStore
-from .log import LogStore
+from .log import LogStore

+ 32 - 32
SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_sqlserver/log.py

@@ -1,25 +1,26 @@
 from typing import List, Optional, Dict, Any
 from datetime import datetime
-from sqlalchemy import and_,  desc
+from sqlalchemy import and_, desc
 import tools.db_helper as db_helper
 
 from core.models import LogModel
 
+
 class LogStore:
     def __init__(self):
         # self._database= None
-        self._database= 'Iwb_RailwayCosting'
+        self._database = "Iwb_RailwayCosting"
 
     def query_logs_paginated(
-        self,
-        page: int = 1,
-        page_size: int = 10,
-        username: Optional[str] = None,
-        operation_type: Optional[str] = None,
-        operation_module: Optional[str] = None,
-        operation_result: Optional[int] = None,
-        start_time: Optional[datetime] = None,
-        end_time: Optional[datetime] = None
+            self,
+            page: int = 1,
+            page_size: int = 10,
+            username: Optional[str] = None,
+            operation_type: Optional[str] = None,
+            operation_module: Optional[str] = None,
+            operation_result: Optional[int] = None,
+            start_time: Optional[datetime] = None,
+            end_time: Optional[datetime] = None,
     ) -> Dict[str, Any]:
         """
         分页查询日志记录
@@ -38,7 +39,7 @@ class LogStore:
             # 构建查询条件
             conditions = []
             if username:
-                conditions.append(LogModel.username.like(f'%{username}%'))
+                conditions.append(LogModel.username.like(f"%{username}%"))
             if operation_type:
                 conditions.append(LogModel.operation_type == operation_type)
             if operation_module:
@@ -57,26 +58,25 @@ class LogStore:
             total = query.count()
 
             # 分页并按创建时间倒序排序
-            logs = query.order_by(LogModel.created_at.desc())\
-                .offset((page - 1) * page_size)\
-                .limit(page_size)\
+            logs = (
+                query.order_by(LogModel.created_at.desc())
+                .offset((page - 1) * page_size)
+                .limit(page_size)
                 .all()
+            )
+
+            return {"total": total, "data": logs}
 
-            return {
-                'total': total,
-                'data': logs
-            }
-    
     def insert_log(
-        self,
-        username: str,
-        operation_type: str,
-        operation_desc: Optional[str] = None,
-        operation_result: Optional[int] = None,
-        operation_module: Optional[str] = None,
-        operation_data: Optional[str] = None,
-        data_changes: Optional[str] = None,
-        operation_ip: Optional[str] = None
+            self,
+            username: str,
+            operation_type: str,
+            operation_desc: Optional[str] = None,
+            operation_result: Optional[int] = None,
+            operation_module: Optional[str] = None,
+            operation_data: Optional[str] = None,
+            data_changes: Optional[str] = None,
+            operation_ip: Optional[str] = None,
     ) -> LogModel:
         """
         插入单条日志记录
@@ -98,12 +98,12 @@ class LogStore:
             operation_module=operation_module,
             operation_data=operation_data,
             data_changes=data_changes,
-            operation_ip=operation_ip
+            operation_ip=operation_ip,
         )
         with db_helper.sqlserver_session(self._database) as db_session:
             db_session.add(log)
             return log
-    
+
     def batch_insert_logs(self, logs: List[Dict[str, Any]]) -> List[LogModel]:
         """
         批量插入日志记录
@@ -113,4 +113,4 @@ class LogStore:
         log_models = [LogModel(**log) for log in logs]
         with db_helper.sqlserver_session(self._database) as db_session:
             db_session.add_all(log_models)
-            return log_models
+            return log_models

+ 44 - 40
SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_sqlserver/project_task.py

@@ -12,7 +12,7 @@ from core.user_session import UserSession
 class ProjectTaskStore:
     def __init__(self):
         # self._database= None
-        self._database = 'Iwb_RailwayCosting'
+        self._database = "Iwb_RailwayCosting"
         self._current_user = None
 
     @property
@@ -20,16 +20,17 @@ class ProjectTaskStore:
         if self._current_user is None:
             self._current_user = UserSession.get_current_user()
         return self._current_user
+
     def get_tasks_paginated(
-        self,
-        project_id: str,
-        item_code: str,
-        page: int = 1,
-        page_size: int = 10,
-        keyword: Optional[str] = None,
-        process_status: Optional[int] = None,
-        send_status: Optional[int] = None,
-    ) :
+            self,
+            project_id: str,
+            item_code: str,
+            page: int = 1,
+            page_size: int = 10,
+            keyword: Optional[str] = None,
+            process_status: Optional[int] = None,
+            send_status: Optional[int] = None,
+    ):
         """分页查询任务列表
 
         Args:
@@ -44,7 +45,7 @@ class ProjectTaskStore:
         Returns:
 
         """
-        with (db_helper.sqlserver_query_session(self._database) as db_session):
+        with db_helper.sqlserver_query_session(self._database) as db_session:
             query = db_session.query(ProjectTaskModel)
 
             # 构建查询条件
@@ -52,10 +53,10 @@ class ProjectTaskStore:
                 ProjectTaskModel.is_del == 0,
                 ProjectTaskModel.project_id == project_id,
                 # ProjectTaskModel.budget_id == budget_id,
-                ProjectTaskModel.item_code.like(f"{item_code}%")
+                ProjectTaskModel.item_code.like(f"{item_code}%"),
             ]
             if keyword:
-                conditions.append(ProjectTaskModel.task_name.like(f'%{keyword}%'))
+                conditions.append(ProjectTaskModel.task_name.like(f"%{keyword}%"))
             if process_status is not None:
                 conditions.append(ProjectTaskModel.process_status == process_status)
             if send_status is not None:
@@ -67,24 +68,26 @@ class ProjectTaskStore:
             total_count = query.count()
 
             # 分页
-            query = query.order_by(ProjectTaskModel.task_sort.desc())\
-                .order_by(ProjectTaskModel.created_at.desc())\
-                .offset((page - 1) * page_size).limit(page_size)
+            query = (
+                query.order_by(ProjectTaskModel.task_sort.desc())
+                .order_by(ProjectTaskModel.created_at.desc())
+                .offset((page - 1) * page_size)
+                .limit(page_size)
+            )
 
             tasks = query.all()
 
-            return {
-                'total': total_count,
-                'data': tasks
-            }
+            return {"total": total_count, "data": tasks}
 
     def get_task(self, task_id: int) -> Optional[ProjectTaskModel]:
         with db_helper.sqlserver_query_session(self._database) as db_session:
-            task = db_session.query(ProjectTaskModel).filter(
-                and_(
-                    ProjectTaskModel.id == task_id,
-                    ProjectTaskModel.is_del == 0
-                )).first()
+            task = (
+                db_session.query(ProjectTaskModel)
+                .filter(
+                    and_(ProjectTaskModel.id == task_id, ProjectTaskModel.is_del == 0)
+                )
+                .first()
+            )
             return task
 
     def get_task_dto(self, task_id: int) -> Optional[ProjectTaskDto]:
@@ -99,7 +102,7 @@ class ProjectTaskStore:
         task_dto = self.get_task(task_id)
         return ProjectTaskDto.from_model(task_dto) if task_dto else None
 
-    def get_wait_tasks(self,project_id:str=None):
+    def get_wait_tasks(self, project_id: str = None):
         """查询待处理的任务
 
         Args:
@@ -113,7 +116,9 @@ class ProjectTaskStore:
             query = query.filter(
                 and_(
                     ProjectTaskModel.is_del == 0,
-                    ProjectTaskModel.process_status == TaskStatusEnum.WAIT.value))
+                    ProjectTaskModel.process_status == TaskStatusEnum.WAIT.value,
+                )
+            )
             if project_id:
                 query = query.filter(ProjectTaskModel.project_id == project_id)
             query.order_by(ProjectTaskModel.task_sort.desc())
@@ -127,12 +132,13 @@ class ProjectTaskStore:
             query = query.filter(
                 and_(
                     ProjectTaskModel.is_del == 0,
-                    ProjectTaskModel.process_status == status))
+                    ProjectTaskModel.process_status == status,
+                )
+            )
             query.order_by(ProjectTaskModel.task_sort.desc())
             tasks = query.all()
             return [ProjectTaskDto.from_model(task) for task in tasks]
 
-
     def create_task(self, task_dto: ProjectTaskDto) -> ProjectTaskDto:
         """创建任务
 
@@ -182,12 +188,12 @@ class ProjectTaskStore:
             # task.item_id = task_dto.item_id
             # task.item_code = task_dto.item_code
             # task.file_path = task_dto.file_path
-            task.updated_by=self.current_user.username
-            task.updated_at=datetime.now()
+            task.updated_by = self.current_user.username
+            task.updated_at = datetime.now()
             task = db_session.merge(task)
             return ProjectTaskDto.from_model(task)
 
-    def update_task_files(self, task_id: int,files: str):
+    def update_task_files(self, task_id: int, files: str):
         task = self.get_task(task_id)
         if not task:
             return None
@@ -197,8 +203,8 @@ class ProjectTaskStore:
                 task.process_status = 4
             if task.send_status != 0:
                 task.send_status = 4
-            task.updated_by=self.current_user.username
-            task.updated_at=datetime.now()
+            task.updated_by = self.current_user.username
+            task.updated_at = datetime.now()
             task = db_session.merge(task)
             return ProjectTaskDto.from_model(task)
 
@@ -221,7 +227,8 @@ class ProjectTaskStore:
             task.deleted_at = datetime.now()
             db_session.merge(task)
             return True
-    def update_task_status(self,task_id:int, status:int, err:str = None):
+
+    def update_task_status(self, task_id: int, status: int, err: str = None):
         task = self.get_task(task_id)
         if not task:
             return False
@@ -233,9 +240,7 @@ class ProjectTaskStore:
             db_session.merge(task)
             return True
 
-
-
-    def update_process_status(self,task_id:int, status:int, err:str = None):
+    def update_process_status(self, task_id: int, status: int, err: str = None):
         task = self.get_task(task_id)
         if not task:
             return False
@@ -247,7 +252,7 @@ class ProjectTaskStore:
             db_session.merge(task)
             return True
 
-    def update_send_status(self,task_id:int, status:int, err:str = None):
+    def update_send_status(self, task_id: int, status: int, err: str = None):
         task = self.get_task(task_id)
         if not task:
             return False
@@ -258,4 +263,3 @@ class ProjectTaskStore:
             task.send_time = datetime.now()
             db_session.merge(task)
             return True
-

+ 65 - 49
SourceCode/IntelligentRailwayCosting/app/tools/db_helper/mysql_helper.py

@@ -6,31 +6,34 @@ from sqlalchemy.engine import Engine
 from .base import DBHelper
 from pymysql import Error
 
+
 class MySQLHelper(DBHelper):
     def __init__(self):
         super().__init__()
         self._connections: Dict[str, pymysql.Connection] = {}
         self._default_config = {
-            'db': '',
-            'host': 'localhost',
-            'port': 3306,
-            'user': '',
-            'password': '',
-            'charset': 'utf8mb4',
-            'pool_size': 5,
-            'max_overflow': 10,
-            'pool_timeout': 30,
-            'pool_recycle': 3600
+            "db": "",
+            "host": "localhost",
+            "port": 3306,
+            "user": "",
+            "password": "",
+            "charset": "utf8mb4",
+            "pool_size": 5,
+            "max_overflow": 10,
+            "pool_timeout": 30,
+            "pool_recycle": 3600,
         }
         self.main_database_name = "mysql_main"
 
-    def get_engine(self, database: str, config: Optional[Dict[str, Any]] = None) -> Engine:
+    def get_engine(
+            self, database: str, config: Optional[Dict[str, Any]] = None
+    ) -> Engine:
         """获取或创建数据库引擎
-        
+
         Args:
             database: 数据库名称
             config: 可选的连接配置
-            
+
         Returns:
             SQLAlchemy引擎实例
         """
@@ -39,10 +42,10 @@ class MySQLHelper(DBHelper):
         conn_config.update(db_config)
         if config:
             conn_config.update(config)
-        
-        if 'db' not in conn_config or not conn_config['db']:
-            conn_config['db'] = database
-        
+
+        if "db" not in conn_config or not conn_config["db"]:
+            conn_config["db"] = database
+
         url = f"mysql+pymysql://{conn_config['user']}:{conn_config['password']}@{conn_config['host']}:{conn_config['port']}/{conn_config['db']}"
         engine = create_engine(
             url,
@@ -50,17 +53,19 @@ class MySQLHelper(DBHelper):
             max_overflow=5,  # 减小最大溢出连接数
             pool_timeout=30,
             pool_recycle=1800,  # 缩短连接回收时间为30分钟
-            pool_pre_ping=True  # 启用连接健康检查
+            pool_pre_ping=True,  # 启用连接健康检查
         )
         return engine
-    
-    def connect(self, database: str, config: Optional[Dict[str, str]] = None) -> pymysql.Connection:
+
+    def connect(
+            self, database: str, config: Optional[Dict[str, str]] = None
+    ) -> pymysql.Connection:
         """连接到指定数据库
-        
+
         Args:
             database: 数据库名称
             config: 可选的连接配置,如果不提供则使用默认配置
-            
+
         Returns:
             数据库连接对象
         """
@@ -74,27 +79,27 @@ class MySQLHelper(DBHelper):
                 except Error:
                     pass
                 del self._connections[database]
-        
+
         conn_config = self._default_config.copy()
         db_config = self.get_config_for_database(database)
         conn_config.update(db_config)
         if config:
             conn_config.update(config)
-        
-        if 'db' not in conn_config or not conn_config['db']:
-            conn_config['db'] = database
-        
+
+        if "db" not in conn_config or not conn_config["db"]:
+            conn_config["db"] = database
+
         connection = pymysql.connect(**conn_config)
         self._connections[database] = connection
         return connection
-    
+
     @contextmanager
     def connection(self, database: str):
         """获取数据库连接的上下文管理器
-        
+
         Args:
             database: 数据库名称
-            
+
         Yields:
             数据库连接对象
         """
@@ -103,56 +108,67 @@ class MySQLHelper(DBHelper):
             yield connection
         finally:
             pass
-    
-    def execute_query(self, database: str, query: str, params: Optional[Tuple] = None) -> List[Tuple]:
+
+    def execute_query(
+            self, database: str, query: str, params: Optional[Tuple] = None
+    ) -> List[Tuple]:
         connection = self.connect(database)
         cursor = connection.cursor()
-        
+
         try:
             if params:
                 cursor.execute(query, params)
             else:
                 cursor.execute(query)
-            
+
             results = cursor.fetchall()
             return [tuple(row) for row in results]
         finally:
             cursor.close()
-    
-    def execute_non_query(self, database: str, query: str, params: Optional[Tuple] = None) -> int:
+
+    def execute_non_query(
+            self, database: str, query: str, params: Optional[Tuple] = None
+    ) -> int:
         connection = self.connect(database)
         cursor = connection.cursor()
-        
+
         try:
             if params:
                 cursor.execute(query, params)
             else:
                 cursor.execute(query)
-            
+
             connection.commit()
             return cursor.rowcount
         finally:
             cursor.close()
-    
-    def execute_scalar(self, database: str, query: str, params: Optional[Tuple] = None) -> Any:
+
+    def execute_scalar(
+            self, database: str, query: str, params: Optional[Tuple] = None
+    ) -> Any:
         connection = self.connect(database)
         cursor = connection.cursor()
-        
+
         try:
             if params:
                 cursor.execute(query, params)
             else:
                 cursor.execute(query)
-            
+
             row = cursor.fetchone()
             return row[0] if row else None
         finally:
             cursor.close()
-    
-    def execute_procedure(self, database: str, procedure_name: str, params: Optional[Dict[str, Any]] = None) -> List[Tuple]:
+
+    def execute_procedure(
+            self,
+            database: str,
+            procedure_name: str,
+            params: Optional[Dict[str, Any]] = None,
+    ) -> List[Tuple]:
         connection = self.connect(database)
         cursor = connection.cursor()
-        
+
         try:
             if params:
                 param_placeholders = ", ".join([f"%s" for _ in params.keys()])
@@ -160,12 +176,12 @@ class MySQLHelper(DBHelper):
                 cursor.execute(query, list(params.values()))
             else:
                 cursor.execute(f"CALL {procedure_name}()")
-            
+
             results = cursor.fetchall()
             return [tuple(row) for row in results]
         finally:
             cursor.close()
-    
+
     def dispose_all(self) -> None:
         """释放所有数据库连接"""
         for conn in self._connections.values():
@@ -174,7 +190,7 @@ class MySQLHelper(DBHelper):
             except Error:
                 pass
         self._connections.clear()
-    
+
     def __del__(self):
         """析构函数,确保所有连接被关闭"""
-        self.dispose_all()
+        self.dispose_all()

+ 53 - 34
SourceCode/IntelligentRailwayCosting/app/tools/db_helper/sqlserver_helper.py

@@ -12,30 +12,37 @@ class SQLServerHelper(DBHelper):
         super().__init__()
         self._session_makers: Dict[str, sessionmaker] = {}
         self._default_config = {
-            'driver': 'ODBC Driver 17 for SQL Server',
-            'server': 'localhost',
-            'username': '',
-            'password': '',
-            'trusted_connection': 'yes'
+            "driver": "ODBC Driver 17 for SQL Server",
+            "server": "localhost",
+            "username": "",
+            "password": "",
+            "trusted_connection": "yes",
         }
         self._pool_config = {
-            'pool_size': 5,  # 减少初始连接数以降低资源占用
-            'max_overflow': 10,  # 适当减少最大溢出连接数
-            'pool_timeout': 60,  # 增加池等待超时时间
-            'pool_recycle': 1800,  # 每30分钟回收连接
-            'pool_pre_ping': True,  # 启用连接健康检查
-            'connect_args': {
-                'timeout': 60,  # 连接超时时间
-                'driver_connects_timeout': 60,  # 驱动连接超时
-                'connect_timeout': 60,  # ODBC连接超时
-                'connect_retries': 3,  # 连接重试次数
-                'connect_retry_interval': 10,  # 重试间隔增加到10秒
-                'connection_timeout': 60  # 额外的连接超时设置
-            }
+            "pool_size": 5,  # 减少初始连接数以降低资源占用
+            "max_overflow": 10,  # 适当减少最大溢出连接数
+            "pool_timeout": 60,  # 增加池等待超时时间
+            "pool_recycle": 1800,  # 每30分钟回收连接
+            "pool_pre_ping": True,  # 启用连接健康检查
+            "connect_args": {
+                "timeout": 60,  # 连接超时时间
+                "driver_connects_timeout": 60,  # 驱动连接超时
+                "connect_timeout": 60,  # ODBC连接超时
+                "connect_retries": 3,  # 连接重试次数
+                "connect_retry_interval": 10,  # 重试间隔增加到10秒
+                "connection_timeout": 60,  # 额外的连接超时设置
+            },
         }
 
-        self.main_database_name = f"sqlserver_mian_{configs.app.version}" if configs.app.use_version else "sqlserver_mian"
-    def _build_connection_string(self, database: str, config: Optional[Dict[str, str]] = None) -> str:
+        self.main_database_name = (
+            f"sqlserver_mian_{configs.app.version}"
+            if configs.app.use_version
+            else "sqlserver_mian"
+        )
+
+    def _build_connection_string(
+            self, database: str, config: Optional[Dict[str, str]] = None
+    ) -> str:
         """构建连接字符串"""
         conn_config = self._default_config.copy()
         db_config = self.get_config_for_database(database)
@@ -45,30 +52,31 @@ class SQLServerHelper(DBHelper):
 
         # 构建认证字符串
         auth_params = []
-        if conn_config.get('trusted_connection', True):
+        if conn_config.get("trusted_connection", True):
             auth_params.append("Trusted_Connection=yes")
         else:
-            auth_params.extend([
-                f"UID={conn_config['username']}",
-                f"PWD={conn_config['password']}"
-            ])
+            auth_params.extend(
+                [f"UID={conn_config['username']}", f"PWD={conn_config['password']}"]
+            )
 
         # 构建ODBC连接字符串
         conn_parts = [
             f"DRIVER={conn_config['driver']}",
             f"SERVER={conn_config['server']}",
             f"DATABASE={conn_config['database'] if 'database' in conn_config else database}",
-            "CHARSET=UTF-8"
+            "CHARSET=UTF-8",
         ]
         conn_parts.extend(auth_params)
-        
+
         # 构建SQLAlchemy连接URL
         conn_str = ";".join(conn_parts)
         conn_url = f"mssql+pyodbc:///?odbc_connect={conn_str}"
 
         return conn_url
 
-    def get_engine(self, database: str, config: Optional[Dict[str, str]] = None) -> Engine:
+    def get_engine(
+            self, database: str, config: Optional[Dict[str, str]] = None
+    ) -> Engine:
         """获取或创建数据库引擎"""
         conn_str = self._build_connection_string(database, config)
         engine = create_engine(conn_str, **self._pool_config)
@@ -77,31 +85,42 @@ class SQLServerHelper(DBHelper):
             conn.execute(text("SELECT 1"))
         return engine
 
-    def execute_query(self, database: str, query: str, params: Optional[Dict[str, Any]] = None) -> List[Tuple]:
+    def execute_query(
+            self, database: str, query: str, params: Optional[Dict[str, Any]] = None
+    ) -> List[Tuple]:
         """执行查询并返回结果"""
         with self.session_scope(database) as session:
             result = session.execute(text(query), params or {})
             return [tuple(row) for row in result.fetchall()]
 
-    def execute_non_query(self, database: str, query: str, params: Optional[Dict[str, Any]] = None) -> int:
+    def execute_non_query(
+            self, database: str, query: str, params: Optional[Dict[str, Any]] = None
+    ) -> int:
         """执行非查询操作(如INSERT, UPDATE, DELETE)"""
         with self.session_scope(database) as session:
             result = session.execute(text(query), params or {})
             return result.rowcount
 
-    def execute_scalar(self, database: str, query: str, params: Optional[Dict[str, Any]] = None) -> Any:
+    def execute_scalar(
+            self, database: str, query: str, params: Optional[Dict[str, Any]] = None
+    ) -> Any:
         """执行查询并返回第一行第一列的值"""
         with self.session_scope(database) as session:
             result = session.execute(text(query), params or {})
             row = result.fetchone()
             return row[0] if row else None
 
-    def execute_procedure(self, database: str, procedure_name: str, params: Optional[Dict[str, Any]] = None) -> List[Tuple]:
+    def execute_procedure(
+            self,
+            database: str,
+            procedure_name: str,
+            params: Optional[Dict[str, Any]] = None,
+    ) -> List[Tuple]:
         """执行存储过程"""
         params = params or {}
         param_str = ", ".join([f"@{key}=:{key}" for key in params.keys()])
         query = f"EXEC {procedure_name} {param_str}"
-        
+
         with self.session_scope(database) as session:
             result = session.execute(text(query), params)
             return [tuple(row) for row in result.fetchall()]
@@ -112,4 +131,4 @@ class SQLServerHelper(DBHelper):
 
     def __del__(self):
         """析构函数,确保所有引擎资源被释放"""
-        self.dispose_all()
+        self.dispose_all()

+ 20 - 1
SourceCode/IntelligentRailwayCosting/app/tools/utils/__init__.py

@@ -6,6 +6,7 @@ from tools.utils.file_helper import FileHelper
 from tools.utils.logger_helper import LoggerHelper
 from tools.utils.string_helper import StringHelper
 
+
 def get_logger():
     """
     获取日志记录器实例。
@@ -18,6 +19,7 @@ def get_logger():
     """
     return LoggerHelper.get_logger()
 
+
 def clean_log_file(day: int):
     """
     清理指定天数之前的日志文件。
@@ -26,6 +28,7 @@ def clean_log_file(day: int):
     """
     LoggerHelper.clean_log_file(day)
 
+
 def get_config():
     """
     获取配置管理器实例。
@@ -36,6 +39,7 @@ def get_config():
     """
     return ConfigHelper()
 
+
 def reload_config():
     """
     重新加载配置文件。
@@ -44,6 +48,7 @@ def reload_config():
     """
     get_config().load_config()
 
+
 def get_config_value(key: str, default: str = None):
     """
     获取配置项的值。
@@ -54,6 +59,7 @@ def get_config_value(key: str, default: str = None):
     """
     return get_config().get(key, default)
 
+
 def get_config_int(key: str, default: int = None):
     """
     获取配置项的整数值。
@@ -64,6 +70,7 @@ def get_config_int(key: str, default: int = None):
     """
     return get_config().get_int(key, default)
 
+
 def get_config_object(key: str, default: dict = None):
     """
     获取配置项的JSON对象。
@@ -73,6 +80,8 @@ def get_config_object(key: str, default: dict = None):
     :return: 字典,配置项的JSON对象。
     """
     return get_config().get_object(key, default)
+
+
 def get_config_bool(key: str):
     """
     获取配置项的布尔值。
@@ -82,6 +91,7 @@ def get_config_bool(key: str):
     """
     return get_config().get_bool(key)
 
+
 def download_remote_file(file_url: str, file_name: str) -> str:
     """
     下载远程文件并保存到本地。
@@ -92,6 +102,7 @@ def download_remote_file(file_url: str, file_name: str) -> str:
     """
     return FileHelper().download_remote_file(file_url, file_name)
 
+
 def clean_attach_file(day: int):
     """
     清理指定天数之前的附件文件。
@@ -100,6 +111,7 @@ def clean_attach_file(day: int):
     """
     FileHelper().clean_attach_file(day)
 
+
 def save_report_excel(data, file_name: str = None) -> str:
     """
     保存报表数据到Excel文件。
@@ -110,6 +122,7 @@ def save_report_excel(data, file_name: str = None) -> str:
     """
     return FileHelper().save_report_excel(data, file_name)
 
+
 def clean_report_file(day: int):
     """
     清理指定天数之前的报表文件。
@@ -118,9 +131,11 @@ def clean_report_file(day: int):
     """
     FileHelper().clean_report_file(day)
 
+
 def encode_file(path: str):
     return FileHelper.encode_file(path)
 
+
 def to_array(s: str, split: str = ",") -> list[str]:
     """
     将字符串按指定分隔符拆分为数组。
@@ -131,7 +146,8 @@ def to_array(s: str, split: str = ",") -> list[str]:
     """
     return StringHelper.to_array(s, split)
 
-def to_str(data:dict|list|tuple):
+
+def to_str(data: dict | list | tuple):
     """
     将对象转成字符串
     :param data:
@@ -139,6 +155,7 @@ def to_str(data:dict|list|tuple):
     """
     return StringHelper.to_str(data)
 
+
 def is_email(email: str) -> bool:
     """
     判断字符串是否为有效的电子邮件地址。
@@ -148,6 +165,7 @@ def is_email(email: str) -> bool:
     """
     return StringHelper.is_email(email)
 
+
 def is_phone(phone: str) -> bool:
     """
     判断字符串是否为有效的手机号码。
@@ -157,6 +175,7 @@ def is_phone(phone: str) -> bool:
     """
     return StringHelper.is_phone(phone)
 
+
 def call_openai(system_prompt: str, user_prompt: str) -> json:
     """
     调用OpenAI API进行对话。

+ 51 - 18
SourceCode/IntelligentRailwayCosting/app/tools/utils/ai_helper.py

@@ -1,16 +1,16 @@
 import json, re
 
-import tools.utils as  utils
+import tools.utils as utils
 from openai import OpenAI
 from pathlib import Path
 
-class AiHelper:
 
+class AiHelper:
     _ai_api_key = None
     _ai_api_url = None
     _ai_max_tokens = 150
 
-    def __init__(self, api_url: str=None, api_key: str=None, api_model: str=None):
+    def __init__(self, api_url: str = None, api_key: str = None, api_model: str = None):
         self._ai_api_url = api_url if api_url else utils.get_config_value("ai.url")
         self._ai_api_key = api_key if api_key else utils.get_config_value("ai.key")
         self._api_model = api_model if api_model else utils.get_config_value("ai.model")
@@ -18,9 +18,18 @@ class AiHelper:
         if max_tokens:
             self._ai_max_tokens = int(max_tokens)
 
-    def call_openai(self, system_prompt: str, user_prompt: str,api_url: str=None,api_key: str=None,api_model: str=None) -> json:
+    def call_openai(
+            self,
+            system_prompt: str,
+            user_prompt: str,
+            api_url: str = None,
+            api_key: str = None,
+            api_model: str = None,
+    ) -> json:
         self.check_api(api_key, api_model, api_url)
-        utils.get_logger().info(f"调用AI API ==> Url:{self._ai_api_url},Model:{self._api_model}")
+        utils.get_logger().info(
+            f"调用AI API ==> Url:{self._ai_api_url},Model:{self._api_model}"
+        )
 
         client = OpenAI(api_key=self._ai_api_key, base_url=self._ai_api_url)
         completion = client.chat.completions.create(
@@ -85,18 +94,19 @@ class AiHelper:
             raise Exception("AI 响应中未找到有效的 choices 或 message 数据")
 
         # 移除多余的 ```json 和 ```
-        if message_content.startswith("```json") and message_content.endswith(
-                "```"):
+        if message_content.startswith("```json") and message_content.endswith("```"):
             message_content = message_content[6:-3]
 
         # 去除开头的 'n' 字符
         if message_content.startswith("n"):
             message_content = message_content[1:]
         # 移除无效的转义字符和时间戳前缀
-        message_content = re.sub(r"\\[0-9]{2}", "",
-                                 message_content)  # 移除 \32 等无效转义字符
-        message_content = re.sub(r"\d{4}-\d{2}-\dT\d{2}:\d{2}:\d{2}\.\d+Z", "",
-                                 message_content)  # 移除时间戳
+        message_content = re.sub(
+            r"\\[0-9]{2}", "", message_content
+        )  # 移除 \32 等无效转义字符
+        message_content = re.sub(
+            r"\d{4}-\d{2}-\dT\d{2}:\d{2}:\d{2}\.\d+Z", "", message_content
+        )  # 移除时间戳
         message_content = message_content.strip()  # 去除首尾空白字符
 
         # 替换所有的反斜杠
@@ -112,7 +122,9 @@ class AiHelper:
 
         except json.JSONDecodeError as e:
             if first:
-                utils.get_logger().error(f"JSON 解析错误,去除部分特殊字符重新解析一次: {e}")
+                utils.get_logger().error(
+                    f"JSON 解析错误,去除部分特殊字符重新解析一次: {e}"
+                )
                 # 替换中文引号为空
                 message_content = re.sub(r"[“”]", "", response)  # 替换双引号
                 message_content = re.sub(r"[‘’]", "", message_content)  # 替换单引号
@@ -120,22 +132,43 @@ class AiHelper:
             else:
                 raise Exception(f"解析 AI 响应错误: {response} {e}")
 
-    def call_openai_with_image(self, image_path,system_prompt: str, user_prompt: str, api_url: str=None,api_key: str=None,api_model: str=None) -> json:
+    def call_openai_with_image(
+            self,
+            image_path,
+            system_prompt: str,
+            user_prompt: str,
+            api_url: str = None,
+            api_key: str = None,
+            api_model: str = None,
+    ) -> json:
         pass
 
-    def call_openai_with_file(self, file_path,system_prompt: str, user_prompt: str, api_url: str=None,api_key: str=None,api_model: str=None)->json:
+    def call_openai_with_file(
+            self,
+            file_path,
+            system_prompt: str,
+            user_prompt: str,
+            api_url: str = None,
+            api_key: str = None,
+            api_model: str = None,
+    ) -> json:
         self.check_api(api_key, api_model, api_url)
-        utils.get_logger().info(f"调用AI API File==> Url:{self._ai_api_url},Model:{self._api_model}")
+        utils.get_logger().info(
+            f"调用AI API File==> Url:{self._ai_api_url},Model:{self._api_model}"
+        )
 
         client = OpenAI(api_key=self._ai_api_key, base_url=self._ai_api_url)
-        file_object = client.files.create(    file=Path(file_path),purpose='file-extract',)
+        file_object = client.files.create(
+            file=Path(file_path),
+            purpose="file-extract",
+        )
         completion = client.chat.completions.create(
             model=self._api_model,
             messages=[
                 {
                     "role": "system",
                     # "content": system_prompt,
-                    'content': f'fileid://{file_object.id}'
+                    "content": f"fileid://{file_object.id}",
                 },
                 {
                     "role": "user",
@@ -165,4 +198,4 @@ class AiHelper:
             return result
         except Exception as e:
             raise Exception(f"解析 AI 响应错误: {e}")
-        pass
+        pass

+ 3 - 1
SourceCode/IntelligentRailwayCosting/app/tools/utils/config_helper.py

@@ -1,5 +1,6 @@
 import os, yaml
 
+
 class ConfigHelper:
     _instance = None
 
@@ -35,7 +36,7 @@ class ConfigHelper:
     def _merge_env_vars(self, env_prefix="APP_"):  # 环境变量前缀为 APP_
         for key, value in os.environ.items():
             if key.startswith(env_prefix):
-                config_key = key[len(env_prefix) :].lower()
+                config_key = key[len(env_prefix):].lower()
                 self._set_nested_key(self._config, config_key.split("__"), value)
 
     def _set_nested_key(self, config, keys, value):
@@ -69,6 +70,7 @@ class ConfigHelper:
         except yaml.YAMLError as e:
             print(f"Error loading YAML object: {e}")
             return default
+
     def get_bool(self, key: str) -> bool:
         val = self.get(key, "0")
         if isinstance(val, bool):

+ 13 - 6
SourceCode/IntelligentRailwayCosting/app/tools/utils/file_helper.py

@@ -11,7 +11,6 @@ import base64
 
 
 class FileHelper:
-
     DEFAULT_ATTACH_PATH = "./temp_files/attaches/"
     DEFAULT_REPORT_PATH = "./temp_files/report/"
 
@@ -104,9 +103,13 @@ class FileHelper:
                                     f"  删除目录及其内容: {dir_path}"
                                 )
                             except PermissionError:
-                                utils.get_logger().error(f"  权限错误,无法删除目录: {dir_path}")
+                                utils.get_logger().error(
+                                    f"  权限错误,无法删除目录: {dir_path}"
+                                )
                             except Exception as e:
-                                utils.get_logger().error(f"  删除目录失败: {dir_path}。Exception: {e}")
+                                utils.get_logger().error(
+                                    f"  删除目录失败: {dir_path}。Exception: {e}"
+                                )
                     except ValueError:
                         # 如果目录名称不符合 %Y-%m/%d 格式,跳过
                         continue
@@ -153,9 +156,13 @@ class FileHelper:
                                     f"  Report 删除目录及其内容: {dir_path}"
                                 )
                             except PermissionError:
-                                utils.get_logger().error(f"  Report 权限错误,无法删除目录: {dir_path}")
+                                utils.get_logger().error(
+                                    f"  Report 权限错误,无法删除目录: {dir_path}"
+                                )
                             except Exception as e:
-                                utils.get_logger().error(f"  Report 删除目录失败: {dir_path}。Exception: {e}")
+                                utils.get_logger().error(
+                                    f"  Report 删除目录失败: {dir_path}。Exception: {e}"
+                                )
                     except ValueError:
                         # 如果目录名称不符合 %Y-%m/%d 格式,跳过
                         continue
@@ -170,7 +177,7 @@ class FileHelper:
         # 根据文件扩展名获取 MIME 类型
         mime_type, _ = mimetypes.guess_type(file_path)
         if mime_type is None:
-            mime_type = 'image/jpeg'  # 默认使用 jpeg 类型
+            mime_type = "image/jpeg"  # 默认使用 jpeg 类型
         # 将图片编码为 base64 字符串
         with open(file_path, "rb") as image_file:
             encoded_string = base64.b64encode(image_file.read())

+ 10 - 4
SourceCode/IntelligentRailwayCosting/app/tools/utils/string_helper.py

@@ -1,4 +1,6 @@
 import json
+
+
 class StringHelper:
 
     @staticmethod
@@ -76,12 +78,14 @@ class StringHelper:
         return " ".join(s.split())
 
     @staticmethod
-    def to_str(data:dict|list|tuple):
+    def to_str(data: dict | list | tuple):
         def datetime_handler(obj):
-            if hasattr(obj, 'isoformat'):
+            if hasattr(obj, "isoformat"):
                 return obj.isoformat()
             return str(obj)
+
         return json.dumps(data, ensure_ascii=False, default=datetime_handler)
+
     @staticmethod
     def is_email(s: str) -> bool:
         """
@@ -91,7 +95,8 @@ class StringHelper:
         :return: 如果是有效的邮箱地址返回True,否则返回False。
         """
         import re
-        pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
+
+        pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
         return bool(re.match(pattern, s)) if s else False
 
     @staticmethod
@@ -103,5 +108,6 @@ class StringHelper:
         :return: 如果是有效的手机号码返回True,否则返回False。
         """
         import re
-        pattern = r'^1[3-9]\d{9}$'
+
+        pattern = r"^1[3-9]\d{9}$"
         return bool(re.match(pattern, s)) if s else False

+ 2 - 1
SourceCode/IntelligentRailwayCosting/app/views/__init__.py

@@ -2,7 +2,8 @@ from views.login import auth_bp
 from views.project import project_bp
 from views.log import log_bp
 
+
 def register_views(app):
     app.register_blueprint(auth_bp)
     app.register_blueprint(project_bp)
-    app.register_blueprint(log_bp)
+    app.register_blueprint(log_bp)

+ 11 - 9
SourceCode/IntelligentRailwayCosting/app/views/log.py

@@ -1,18 +1,20 @@
 from flask import Blueprint, render_template
-from core.enum import OperationType,OperationModule,OperationResult
+from core.enum import OperationType, OperationModule, OperationResult
 from core.user_session import Permission
 
-log_bp = Blueprint('log', __name__, template_folder='templates')
+log_bp = Blueprint("log", __name__, template_folder="templates")
 
-@log_bp.route('/log', methods=['GET'])
+
+@log_bp.route("/log", methods=["GET"])
 @Permission.authorize
 def index():
     operation_type_list = [op.value for op in OperationType]
     operation_module_list = [op.value for op in OperationModule]
     operation_result_list = OperationResult.get_dict()
-    return render_template('log/index.html',
-                           page_active='log',
-                           operation_type_list=operation_type_list,
-                           operation_module_list=operation_module_list,
-                           operation_result_list=operation_result_list,
-                           )
+    return render_template(
+        "log/index.html",
+        page_active="log",
+        operation_type_list=operation_type_list,
+        operation_module_list=operation_module_list,
+        operation_result_list=operation_result_list,
+    )

+ 4 - 3
SourceCode/IntelligentRailwayCosting/app/views/login.py

@@ -3,13 +3,14 @@ from flask_login import logout_user
 from core.user_session import UserSession
 from core.configs import config
 
-auth_bp = Blueprint('auth', __name__, template_folder='templates')
+auth_bp = Blueprint("auth", __name__, template_folder="templates")
 
-@auth_bp.route('/login', methods=['GET'])
+
+@auth_bp.route("/login", methods=["GET"])
 def login():
     # 如果用户已登录,重定向到首页
     # if 'user_id' in session:
     #     return redirect(url_for('project.index'))
     logout_user()
     UserSession.clear_user()
-    return render_template('account/login.html', app_name=config.app.name)
+    return render_template("account/login.html", app_name=config.app.name)

+ 23 - 9
SourceCode/IntelligentRailwayCosting/app/views/project.py

@@ -2,27 +2,41 @@ from flask import Blueprint, render_template
 from core.user_session import Permission
 from stores import BudgetStore, ProjectStore, ProjectTaskStore
 
-project_bp = Blueprint('project', __name__, template_folder='templates')
+project_bp = Blueprint("project", __name__, template_folder="templates")
 project_store = ProjectStore()
 budget_store = BudgetStore()
 project_task_store = ProjectTaskStore()
 
-@project_bp.route('/', methods=['GET'])
+
+@project_bp.route("/", methods=["GET"])
 @Permission.authorize
 def index():
-   return render_template('project/index.html',page_active='project')
+    return render_template("project/index.html", page_active="project")
+
 
-@project_bp.route('/budget_info/<project_id>', methods=['GET'])
+@project_bp.route("/budget_info/<project_id>", methods=["GET"])
 @Permission.authorize
-def budget_info(project_id:str):
+def budget_info(project_id: str):
     project = project_store.get(project_id)
     budgets = budget_store.get_budget_info(project_id)
-    return render_template('project/budget_info.html',page_active='project',project=project,budgets=budgets)
+    return render_template(
+        "project/budget_info.html",
+        page_active="project",
+        project=project,
+        budgets=budgets,
+    )
+
 
-@project_bp.route('/quota_info/<project_id>/<task_id>', methods=['GET'])
+@project_bp.route("/quota_info/<project_id>/<task_id>", methods=["GET"])
 @Permission.authorize
-def quota_info(project_id:str,task_id:str):
+def quota_info(project_id: str, task_id: str):
     project = project_store.get(project_id)
     budgets = budget_store.get_budget_info(project_id)
     task = project_task_store.get_task_dto(task_id)
-    return render_template('project/quota_info.html',page_active='project',project=project,budgets=budgets,task=task)
+    return render_template(
+        "project/quota_info.html",
+        page_active="project",
+        project=project,
+        budgets=budgets,
+        task=task,
+    )