yue 8 months ago
parent
commit
37643e1cae
27 changed files with 556 additions and 568 deletions
  1. 1 1
      SourceCode/IntelligentRailwayCosting/.script/cmd/BuildApp.run.xml
  2. 12 0
      SourceCode/IntelligentRailwayCosting/.script/cmd/BuildBaseImage.run.xml
  3. 1 1
      SourceCode/IntelligentRailwayCosting/.script/cmd/RunApp.run.xml
  4. 13 19
      SourceCode/IntelligentRailwayCosting/.script/init.sql
  5. 23 25
      SourceCode/IntelligentRailwayCosting/.script/init_sqlserver.sql
  6. 22 23
      SourceCode/IntelligentRailwayCosting/Docker/Dockerfile
  7. 85 0
      SourceCode/IntelligentRailwayCosting/Docker/DockerfileBase
  8. 11 3
      SourceCode/IntelligentRailwayCosting/app/core/dtos/excel_parse.py
  9. 9 4
      SourceCode/IntelligentRailwayCosting/app/core/dtos/project_quota.py
  10. 2 6
      SourceCode/IntelligentRailwayCosting/app/core/dtos/project_task.py
  11. 4 3
      SourceCode/IntelligentRailwayCosting/app/core/dtos/quota_input.py
  12. 2 4
      SourceCode/IntelligentRailwayCosting/app/core/models/project_quota.py
  13. 1 3
      SourceCode/IntelligentRailwayCosting/app/core/models/project_task.py
  14. 40 21
      SourceCode/IntelligentRailwayCosting/app/executor/task_processor.py
  15. 4 4
      SourceCode/IntelligentRailwayCosting/app/executor/task_sender.py
  16. 10 17
      SourceCode/IntelligentRailwayCosting/app/routes/project_quota.py
  17. 1 17
      SourceCode/IntelligentRailwayCosting/app/routes/project_task.py
  18. 21 24
      SourceCode/IntelligentRailwayCosting/app/services/project_quota.py
  19. 9 106
      SourceCode/IntelligentRailwayCosting/app/services/project_task.py
  20. 4 4
      SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_mysql/log.py
  21. 70 53
      SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_mysql/project_quota.py
  22. 40 85
      SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_mysql/project_task.py
  23. 0 2
      SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_sqlserver/log.py
  24. 22 30
      SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_sqlserver/project_quota.py
  25. 1 79
      SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_sqlserver/project_task.py
  26. 52 2
      SourceCode/IntelligentRailwayCosting/app/views/static/base/js/utils.js
  27. 96 32
      SourceCode/IntelligentRailwayCosting/app/views/static/project/quota_info.js

+ 1 - 1
SourceCode/IntelligentRailwayCosting/.script/cmd/Build_App.run.xml → SourceCode/IntelligentRailwayCosting/.script/cmd/BuildApp.run.xml

@@ -1,5 +1,5 @@
 <component name="ProjectRunConfigurationManager">
-  <configuration default="false" name="Build_App" type="docker-deploy" factoryName="dockerfile" server-name="104">
+  <configuration default="false" name="BuildApp" type="docker-deploy" factoryName="dockerfile" server-name="104">
     <deployment type="dockerfile">
       <settings>
         <option name="imageTag" value="railway_costing-app:1.0.0" />

+ 12 - 0
SourceCode/IntelligentRailwayCosting/.script/cmd/BuildBaseImage.run.xml

@@ -0,0 +1,12 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="BuildBaseImage" type="docker-deploy" factoryName="dockerfile" server-name="104">
+    <deployment type="dockerfile">
+      <settings>
+        <option name="imageTag" value="intelligent-railway-base:1.0.0" />
+        <option name="buildOnly" value="true" />
+        <option name="sourceFilePath" value="Docker/DockerfileBase" />
+      </settings>
+    </deployment>
+    <method v="2" />
+  </configuration>
+</component>

+ 1 - 1
SourceCode/IntelligentRailwayCosting/.script/cmd/Run_App.run.xml → SourceCode/IntelligentRailwayCosting/.script/cmd/RunApp.run.xml

@@ -1,5 +1,5 @@
 <component name="ProjectRunConfigurationManager">
-  <configuration default="false" name="Run_App" type="docker-deploy" factoryName="docker-compose.yml" server-name="104">
+  <configuration default="false" name="RunApp" type="docker-deploy" factoryName="docker-compose.yml" server-name="104">
     <deployment type="docker-compose.yml">
       <settings>
         <option name="composeProjectName" value="railway_costing" />

+ 13 - 19
SourceCode/IntelligentRailwayCosting/.script/init.sql

@@ -5,16 +5,14 @@ USE iwb_railway_costing_v1;
 CREATE TABLE IF NOT EXISTS project_task (
     id INT AUTO_INCREMENT PRIMARY KEY,
     task_name VARCHAR(255) NOT NULL COMMENT '任务名称',
-    task_sort TINYINT DEFAULT 0 COMMENT '任务排序',
+    task_sort INT DEFAULT 0 COMMENT '任务排序',
     task_desc VARCHAR(1000) COMMENT '任务描述',
+    is_cover TINYINT DEFAULT 0 COMMENT '是否覆盖',
     project_id VARCHAR(50) NOT NULL COMMENT '项目编号',
-    budget_id int NOT NULL DEFAULT 0 COMMENT '概算序号',
-    item_id int NOT NULL COMMENT '条目序号',
+    budget_id INT NOT NULL DEFAULT 0 COMMENT '概算序号',
+    item_id INT NOT NULL COMMENT '条目序号',
     item_code VARCHAR(255) NOT NULL COMMENT '条目编号',
-    file_path text COMMENT '文件路径',
-    collect_status TINYINT NOT NULL DEFAULT 0 COMMENT '采集状态(0:未开始, 1:进行中, 2:已完成, 3:采集失败)',
-    collect_time DATETIME COMMENT '采集时间',
-    collect_error VARCHAR(4000) COMMENT '采集错误信息',
+    file_path TEXT COMMENT '文件路径',
     process_status TINYINT NOT NULL DEFAULT 0 COMMENT '处理状态(0:未处理,1:处理中, 2:已处理, 3:处理失败)',
     process_time DATETIME COMMENT '处理时间',
     process_error VARCHAR(4000) COMMENT '处理错误信息',
@@ -38,24 +36,22 @@ CREATE TABLE IF NOT EXISTS project_task (
 -- 创建项目定额表
 CREATE TABLE IF NOT EXISTS project_quota (
     id INT AUTO_INCREMENT PRIMARY KEY,
-    task_id int NOT NULL COMMENT '任务编号',
+    task_id INT NOT NULL COMMENT '任务编号',
     project_id VARCHAR(50) NOT NULL COMMENT '项目编号',
-    budget_id int NOT NULL COMMENT '概算序号',
+    budget_id INT NOT NULL COMMENT '概算序号',
     budget_code VARCHAR(50) COMMENT '概算编号',
-    item_id int NOT NULL COMMENT '条目序号',
+    item_id INT NOT NULL COMMENT '条目序号',
     item_code VARCHAR(255) NOT NULL COMMENT '条目编号',
+    quota_id INT NULL DEFAULT 0 COMMENT '定额序号',
     quota_code VARCHAR(50) COMMENT '定额编号',
     entry_name VARCHAR(255) COMMENT '工程或费用项目名称',
     units VARCHAR(20) COMMENT '单位',
     amount FLOAT COMMENT '数量',
     ex_file VARCHAR(4000) COMMENT 'excel文件',
-    ex_cell VARCHAR(50) COMMENT '数量单元格位置,例如"C17"',
-    ex_row VARCHAR(4000) COMMENT '该行内容,由逗号连接多个单元格得到',
+    ex_cell VARCHAR(50) COMMENT '数量单元格位置',
+    ex_row VARCHAR(4000) COMMENT '该行内容',
     ex_unit VARCHAR(50) COMMENT 'excel中给出的单位',
     ex_amount FLOAT COMMENT 'excel中给出的数量',
-    process_status TINYINT NOT NULL DEFAULT 0 COMMENT '处理状态(0:未处理,1:处理中, 2:已处理, 3:处理失败)',
-    process_time DATETIME COMMENT '处理时间',
-    process_error VARCHAR(4000) COMMENT '处理错误信息',
     send_status TINYINT NOT NULL DEFAULT 0 COMMENT '发送状态(0:未发送,1:发送中 ,2:已发送, 3:发送失败)',
     send_time DATETIME COMMENT '发送时间',
     send_error VARCHAR(4000) COMMENT '发送错误信息',
@@ -73,15 +69,13 @@ CREATE TABLE IF NOT EXISTS project_quota (
     INDEX idx_created_at (created_at)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='项目定额表';
 
-
-
 -- 创建日志表
 CREATE TABLE IF NOT EXISTS sys_log (
     id INT AUTO_INCREMENT PRIMARY KEY,
     username VARCHAR(255) NOT NULL COMMENT '用户名',
     operation_type VARCHAR(50) NOT NULL COMMENT '操作类型',
-    operation_desc VARCHAR(4000) COMMENT '操作描述',
-    operation_result TINYINT COMMENT '操作结果(0:失败, 1:成功)',
+    operation_desc VARCHAR(1000) COMMENT '操作描述',
+    operation_result TINYINT COMMENT '操作结果',
     operation_module VARCHAR(100) COMMENT '操作模块',
     operation_data TEXT COMMENT '操作数据',
     data_changes TEXT COMMENT '数据变更记录',

+ 23 - 25
SourceCode/IntelligentRailwayCosting/.script/init_sqlserver.sql

@@ -10,27 +10,14 @@ GO
 USE iwb_railway_costing_v1
 GO
 
--- -- 删除项目任务表
--- IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[project_task]') AND type in (N'U'))
--- BEGIN
---     DROP TABLE [dbo].[project_task]
--- END
--- GO
 
--- -- 删除项目定额表
--- IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[project_quota]') AND type in (N'U'))
--- BEGIN
---     DROP TABLE [dbo].[project_quota]
--- END
--- GO
-
--- -- 删除日志表
--- IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[sys_log]') AND type in (N'U'))
--- BEGIN
---     DROP TABLE [dbo].[sys_log]
--- END
--- GO
 
+/*-- 删除项目任务表
+IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[project_task]') AND type in (N'U'))
+BEGIN
+    DROP TABLE [dbo].[project_task]
+END
+GO*/
 
 -- 创建项目任务表
 IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[project_task]') AND type in (N'U'))
@@ -40,14 +27,12 @@ CREATE TABLE [dbo].[project_task] (
     [task_name] NVARCHAR(255) NOT NULL,
     [task_sort] INT DEFAULT 0,
     [task_desc] NVARCHAR(1000) NULL,
+    [is_cover]  INT DEFAULT 0,
     [project_id] NVARCHAR(50) NOT NULL,
     [budget_id] INT DEFAULT 0,
     [item_id] INT NOT NULL,
     [item_code] NVARCHAR(255) NOT NULL,
     [file_path] NVARCHAR(MAX) NULL,
-    [collect_status] INT NOT NULL DEFAULT 0,
-    [collect_time] DATETIME NULL,
-    [collect_error] NVARCHAR(4000) NULL,
     [process_status] INT NOT NULL DEFAULT 0,
     [process_time] DATETIME NULL,
     [process_error] NVARCHAR(4000) NULL,
@@ -71,6 +56,13 @@ CREATE INDEX [idx_created_at] ON [dbo].[project_task] ([created_at])
 END
 GO
 
+/* -- 删除项目定额表
+   IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[project_quota]') AND type in (N'U'))
+   BEGIN
+       DROP TABLE [dbo].[project_quota]
+   END
+   GO
+*/
 -- 创建项目定额表
 IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[project_quota]') AND type in (N'U'))
 BEGIN
@@ -82,6 +74,7 @@ CREATE TABLE [dbo].[project_quota] (
     [budget_code] NVARCHAR(50) NULL,
     [item_id] INT NOT NULL,
     [item_code] NVARCHAR(255) NOT NULL,
+    [quota_id] INT NULL DEFAULT 0,
     [quota_code] NVARCHAR(50) NULL,
     [entry_name] NVARCHAR(255) NULL,
     [units] NVARCHAR(20) NULL,
@@ -91,9 +84,6 @@ CREATE TABLE [dbo].[project_quota] (
     [ex_row] NVARCHAR(4000) NULL,
     [ex_unit] NVARCHAR(50) NULL,
     [ex_amount] FLOAT NULL,
-    [process_status] INT NOT NULL DEFAULT 0,
-    [process_time] DATETIME NULL,
-    [process_error] NVARCHAR(4000) NULL,
     [send_status] INT NOT NULL DEFAULT 0,
     [send_time] DATETIME NULL,
     [send_error] NVARCHAR(4000) NULL,
@@ -114,6 +104,14 @@ CREATE INDEX [idx_created_at] ON [dbo].[project_quota] ([created_at])
 END
 GO
 
+
+/*-- 删除日志表
+IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[sys_log]') AND type in (N'U'))
+BEGIN
+    DROP TABLE [dbo].[sys_log]
+END
+GO*/
+
 -- 创建日志表
 IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[sys_log]') AND type in (N'U'))
 BEGIN

+ 22 - 23
SourceCode/IntelligentRailwayCosting/Docker/Dockerfile

@@ -1,35 +1,34 @@
-# 第一阶段:构建
-# 使用官方的 Python 基础镜像
-FROM python:3.13-slim AS builder
+# 本地构建Dockerfile
+# 基于多阶段构建,使用本地资源和基础镜像
 
-RUN mkdir /app
+# 第一阶段:使用基础镜像
+FROM intelligent-railway-base:1.0.0 AS base-system
 
-WORKDIR /app
-# 明确指定 requirements.txt 的路径
-COPY requirements.txt .
-# 安装项目依赖
-RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
-# 在 builder 阶段添加调试命令
-# RUN pip freeze > installed-packages.txt
+# 第二阶段:Python依赖构建
+FROM base-system AS builder
 
-# 复制项目文件到工作目录
-COPY app/ /app
+# 复制项目依赖文件
+COPY requirements.txt .
 
-# 将/etc/localtime链接到上海时区文件
-RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+# 安装Python依赖
+RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
 
-# 第二阶段:运行
-FROM python:3.13-slim
+# 第三阶段:最终应用镜像
+FROM base-system AS final
 
+# 设置工作目录
 WORKDIR /app
+
+# 从builder阶段复制已安装的Python包
 COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages
-COPY --from=builder /app /app
+COPY --from=builder /usr/local/bin /usr/local/bin
 
-# 暴露端口(如果有需要)
-EXPOSE 5123
+# 复制应用程序代码
+COPY app/ /app
 
-# 设置环境变量(如果有需要)
-# ENV MY_VARIABLE=value
 
-# 运行项目
+# 暴露端口
+EXPOSE 5123
+
+# 启动命令
 CMD ["python", "main.py"]

+ 85 - 0
SourceCode/IntelligentRailwayCosting/Docker/DockerfileBase

@@ -0,0 +1,85 @@
+# 基础镜像构建Dockerfile
+# 包含系统依赖和ODBC组件
+
+# 基础系统依赖镜像
+FROM python:3.13-slim
+
+# 创建并配置阿里云APT源
+RUN echo "deb http://mirrors.aliyun.com/debian bullseye main contrib non-free" > /etc/apt/sources.list && \
+    echo "deb http://mirrors.aliyun.com/debian bullseye-updates main contrib non-free" >> /etc/apt/sources.list && \
+    echo "deb http://mirrors.aliyun.com/debian-security bullseye-security main contrib non-free" >> /etc/apt/sources.list
+
+# 修改动态生成的APT源配置文件
+RUN find /etc/apt/sources.list.d/ -type f -exec sed -i 's|http://deb.debian.org|http://mirrors.aliyun.com|g' {} \; && \
+    find /etc/apt/sources.list.d/ -type f -exec sed -i 's|http://security.debian.org|http://mirrors.aliyun.com|g' {} \;
+
+# 清理APT缓存并更新源列表
+RUN rm -rf /var/lib/apt/lists/* && \
+    apt-get update
+
+RUN mkdir /app
+# 设置工作目录
+WORKDIR /app
+
+# 安装基础依赖
+RUN apt-get install -y --no-install-recommends \
+    apt-utils \
+    libc6 \
+    && rm -rf /var/lib/apt/lists/*
+
+COPY ./apt-packages/base/* /tmp/apt-packages/
+RUN set -eux; \
+    # 安装基础依赖包
+    dpkg -i /tmp/apt-packages/*.deb || true; \
+    apt-get update && apt-get -f install -y --no-install-recommends; \
+    # 清理临时文件
+    rm -rf /tmp/apt-packages /var/lib/apt/lists/*
+
+# 安装ODBC依赖 - 分步安装以避免包冲突
+# 第1步:复制所有依赖包
+COPY ./apt-packages/odbc/* /tmp/apt-packages/
+
+# 第2步:安装基础依赖
+RUN set -eux; \
+    apt-get update && apt-get install -y --no-install-recommends \
+    libltdl7 \
+    libodbc1 \
+    odbcinst \
+    odbcinst1debian2 \
+    unixodbc-common && \
+    rm -rf /var/lib/apt/lists/*
+
+# 第3步:逐个安装ODBC依赖包
+RUN set -eux; \
+    # 设置环境变量接受许可协议
+    export DOCKER_BUILDKIT=1; \
+    # 先安装libodbcinst2
+    dpkg -i /tmp/apt-packages/libodbcinst2*.deb || true; \
+    apt-get update && apt-get -f install -y --no-install-recommends; \
+    # 再安装libodbc1
+    dpkg -i /tmp/apt-packages/libodbc1*.deb || true; \
+    apt-get update && apt-get -f install -y --no-install-recommends; \
+    # 最后安装unixodbc和unixodbc-dev
+    dpkg -i /tmp/apt-packages/unixodbc*.deb || true; \
+    apt-get update && apt-get -f install -y --no-install-recommends && \
+    rm -rf /var/lib/apt/lists/*
+
+COPY ./odbc-drivers/* /tmp/odbc-drivers/
+# 第4步:安装Microsoft ODBC驱动
+RUN set -eux; \
+    # 设置环境变量接受许可协议
+    export DOCKER_BUILDKIT=1; \
+    export ACCEPT_EULA=Y; \
+    export DEBIAN_FRONTEND=noninteractive; \
+    # 安装Microsoft ODBC驱动
+    dpkg -i /tmp/odbc-drivers/*.deb || true; \
+    apt-get update && apt-get -f install -y --no-install-recommends; \
+    # 更新动态链接库缓存
+    ldconfig; \
+    # 显示安装的ODBC库文件
+    ls -l /usr/lib/x86_64-linux-gnu/libodbc*; \
+    ldconfig -p | grep libodbc; \
+    # 清理临时文件
+    rm -rf /tmp/apt-packages /tmp/odbc-drivers /var/lib/apt/lists/*
+
+

+ 11 - 3
SourceCode/IntelligentRailwayCosting/app/core/dtos/excel_parse.py

@@ -51,11 +51,14 @@ class ExcelParseFileDto:
                  content:str
                  ):
         self.file_id = file_id
+        # 从file_id路径中获取文件类型
+        self.file_type = file_id.split('.')[-1]
         self.content = content
 
     def to_dict(self):
         return {
             "file_id": base64.b64encode(self.file_id.encode()).decode(),
+            "file_type": self.file_type,
             "content": self.content
         }
 
@@ -70,7 +73,7 @@ class ExcelParseDto:
                  zgs_list:list[ExcelParseZgsDto],
                  hierarchy:list[ExcelParseItemDto],
                  components:list[ExcelParseItemDto],
-                 file_excel:list[ExcelParseFileDto]):
+                 files:list[ExcelParseFileDto]):
         self.task_id = task_id
         self.version = version
         self.project_id = project_id
@@ -80,7 +83,7 @@ class ExcelParseDto:
         self.zgs_list = zgs_list
         self.hierarchy = hierarchy
         self.components = components
-        self.file_excel = file_excel
+        self.files = files
 
     def to_dict(self):
         return {
@@ -90,7 +93,7 @@ class ExcelParseDto:
             "project_name":self.project_name,
             "project_stage":self.project_stage,
             "selected_zgs_id":self.selected_zgs_id,
-            "file_excel":[file.to_dict() for file in self.file_excel],
+            "files":[file.to_dict() for file in self.files],
             "zgs_list":[zgs.to_dict() for zgs in self.zgs_list],
             "hierarchy":[item.to_dict() for item in self.hierarchy],
             "components":[item.to_dict() for item in self.components],
@@ -106,6 +109,7 @@ class ExcelParseResultDataDto:
     #     "entry_name": ⼯程或费⽤项⽬名称,来⾃于定额表,
     #     "units": 单位,
     #     "amount": 数量, // number
+    #     "target_id": ⽤户数据库中条⽬的id,-1表示没有, // int
     #     "ex_file_id": excel⽂件id, // str
     #     "ex_cell": 数量单元格位置,例如 "C17",
     #     "ex_row": 该⾏内容,由逗号连接多个单元格得到,
@@ -121,6 +125,7 @@ class ExcelParseResultDataDto:
                  dinge_code:str, # 定额编号,
                  units:str, # 单位,
                  amount:float, # 数量,
+                 target_id:int, # ⽤户数据库中条⽬的id,-1表示没有,
                  ex_file_id:str, # excel⽂件id,
                  ex_cell:str, # 数量单元格位置,例如 "C17",
                  ex_row:str, # 该⾏内容,由逗号连接多个单元格得到,
@@ -135,6 +140,7 @@ class ExcelParseResultDataDto:
         self.dinge_code = dinge_code
         self.units = units
         self.amount = amount
+        self.target_id = target_id
         self.ex_file = ex_file_id
         self.ex_file_id = ex_file_id
         self.ex_cell = ex_cell
@@ -154,6 +160,7 @@ class ExcelParseResultDataDto:
             dinge_code=data.get('dinge_code', ''),
             units=data.get('units', ''),
             amount=data.get('amount', 0.0),
+            target_id=data.get('target_id', -1),
             ex_file_id= base64.b64decode(data.get('ex_file_id', '').encode('utf-8')).decode('utf-8') if data.get('ex_file_id', '') else '',
             ex_cell=data.get('ex_cell', ''),
             ex_row=data.get('ex_row', ''),
@@ -172,6 +179,7 @@ class ExcelParseResultDataDto:
             "units": self.units,
             "amount": self.amount,
             "ex_file": self.ex_file_id,
+            "target_id": self.target_id,
             # "ex_file_id": base64.b64encode(self.ex_file_id.encode('utf-8')).decode('utf-8') if self.ex_file_id else self.ex_file_id,
             "ex_file_id": self.ex_file_id,
             "ex_cell": self.ex_cell,

+ 9 - 4
SourceCode/IntelligentRailwayCosting/app/core/dtos/project_quota.py

@@ -2,7 +2,6 @@ from pydantic import BaseModel
 from typing import Optional
 from datetime import datetime
 
-from sqlalchemy import update
 from core.models import ProjectQuotaModel
 
 class ProjectQuotaDto(BaseModel):
@@ -14,6 +13,7 @@ class ProjectQuotaDto(BaseModel):
     budget_code: Optional[str] =None
     item_id: int
     item_code: str
+    quota_id: Optional[int] = 0
     quota_code: Optional[str] = None
     entry_name: Optional[str] = None
     units: Optional[str] = None
@@ -23,9 +23,6 @@ class ProjectQuotaDto(BaseModel):
     ex_row: Optional[str] = None
     ex_unit: Optional[str] = None
     ex_amount: Optional[float] = None
-    process_status: int = 0,
-    process_time: Optional[datetime] = None
-    process_error: Optional[str] = None
     send_status: int = 0
     send_time: Optional[datetime] = None
     send_error: Optional[str] = None
@@ -46,6 +43,7 @@ class ProjectQuotaDto(BaseModel):
             budget_code=model.budget_code,
             item_id=model.item_id,
             item_code=model.item_code,
+            quota_id=model.quota_id,
             quota_code=model.quota_code,
             entry_name=model.entry_name,
             units = model.units,
@@ -55,6 +53,13 @@ class ProjectQuotaDto(BaseModel):
             ex_row=model.ex_row,
             ex_unit=model.ex_unit,
             ex_amount=model.ex_amount,
+            send_status=model.send_status,
+            send_time=model.send_time,
+            send_error=model.send_error,
+            created_at=model.created_at,
+            created_by=model.created_by,
+            updated_at=model.updated_at,
+            updated_by=model.updated_by,
         )
 
     def to_dict(self) -> dict:

+ 2 - 6
SourceCode/IntelligentRailwayCosting/app/core/dtos/project_task.py

@@ -9,14 +9,12 @@ class ProjectTaskDto(BaseModel):
     task_name: str
     task_sort: int = 0
     task_desc: Optional[str] = None
+    is_cover: Optional[int] = 0
     project_id: str
     budget_id: Optional[int] = 0
     item_id: int
     item_code: Optional[str] = None
     file_path: Optional[str] = None
-    collect_status: Optional[int] = 0
-    collect_time: Optional[datetime] = None
-    collect_error: Optional[str] = None
     process_status: Optional[int] = 0
     process_time: Optional[datetime] = None
     process_error: Optional[str] = None
@@ -39,14 +37,12 @@ class ProjectTaskDto(BaseModel):
             task_name=model.task_name,
             task_sort=model.task_sort,
             task_desc=model.task_desc,
+            is_cover=model.is_cover,
             project_id=model.project_id,
             budget_id=model.budget_id,
             item_id=model.item_id,
             item_code=model.item_code,
             file_path=model.file_path,
-            collect_status=model.collect_status,
-            collect_time=model.collect_time,
-            collect_error=model.collect_error,
             process_status=model.process_status,
             process_time=model.process_time,
             process_error=model.process_error,

+ 4 - 3
SourceCode/IntelligentRailwayCosting/app/core/dtos/quota_input.py

@@ -15,9 +15,9 @@ class QuotaInputDto(BaseModel):
     project_quantity: Optional[float] = None
     # project_quantity_input: Optional[str] = None
     # quota_adjustment: Optional[str] = None
-    unit_price: Optional[float] = None
+    # unit_price: Optional[float] = None
     # compilation_unit_price: Optional[float] = None
-    total_price: Optional[float] = None
+    # total_price: Optional[float] = None
     # compilation_total_price: Optional[float] = None
     # unit_weight: Optional[float] = None
     # total_weight: Optional[float] = None
@@ -99,12 +99,13 @@ class QuotaInputDto(BaseModel):
     @classmethod
     def from_quota_dto(cls, quota_dto: ProjectQuotaDto):
         return cls(
+            quota_id=quota_dto.quota_id,
             budget_id=quota_dto.budget_id,
             item_id=quota_dto.item_id,
             quota_code=quota_dto.quota_code,
             # sequence_number=quota_dto.sequence_number,
             project_name=quota_dto.entry_name,
-            unit=quota_dto.unit,
+            unit=quota_dto.units,
             project_quantity=quota_dto.amount,
             # project_quantity_input=quota_dto.project_quantity_input,
             # quota_adjustment=quota_dto.quota_adjustment,

+ 2 - 4
SourceCode/IntelligentRailwayCosting/app/core/models/project_quota.py

@@ -1,4 +1,4 @@
-from sqlalchemy import Column, Integer, String, DateTime, Float, Text
+from sqlalchemy import Column, Integer, String, DateTime, Float
 from sqlalchemy.sql import func
 from sqlalchemy.ext.declarative import declarative_base
 
@@ -13,6 +13,7 @@ class ProjectQuotaModel(Base):
     budget_code = Column(String(50), comment='概算编号')
     item_id = Column(Integer, nullable=False, comment='条目序号')
     item_code = Column(String(255), nullable=False, comment='条目编号')
+    quota_id = Column(Integer, nullable=False, default=0, comment='定额序号')
     quota_code = Column(String(50), comment='定额编号')
     entry_name = Column(String(255), comment='工程或费用项目名称')
     units = Column(String(20), comment='单位')
@@ -22,9 +23,6 @@ class ProjectQuotaModel(Base):
     ex_row = Column(String(5000), comment='该⾏内容,由逗号连接多个单元格得到')
     ex_unit = Column(String(50), comment='excel中给出的单位')
     ex_amount = Column(Float, comment='excel中给出的数量')
-    process_status = Column(Integer, nullable=False, default=0, comment='处理状态(0:未处理,1:处理中, 2:已处理, 3:处理失败)')
-    process_time = Column(DateTime, comment='处理时间')
-    process_error = Column(String(5000), comment='处理错误信息')
     send_status = Column(Integer, nullable=False, default=0, comment='发送状态(0:未发送,1:发送中 ,2:已发送, 3:发送失败)')
     send_time = Column(DateTime, comment='发送时间')
     send_error = Column(String(5000), comment='发送错误信息')

+ 1 - 3
SourceCode/IntelligentRailwayCosting/app/core/models/project_task.py

@@ -10,14 +10,12 @@ class ProjectTaskModel(Base):
     task_name = Column(String(255), nullable=False, comment='任务名称')
     task_sort = Column(Integer, nullable=False, default=0, comment='任务排序')
     task_desc = Column(String(1000), comment='任务描述')
+    is_cover = Column(Integer, nullable=False, default=0, comment='是否覆盖(0:不覆盖, 1:覆盖)')
     project_id = Column(String(50), nullable=False, comment='项目编号')
     budget_id = Column(Integer, comment='概算序号')
     item_id = Column(Integer, nullable=False, comment='条目序号')
     item_code = Column(String(255), nullable=False, comment='条目编号')
     file_path = Column(Text, comment='文件路径')
-    collect_status = Column(Integer, nullable=False, default=0, comment='采集状态(0:未开始, 1:进行中, 2:已完成, 3:采集失败)')
-    collect_time = Column(DateTime, comment='采集时间')
-    collect_error = Column(String(5000), comment='采集错误信息')
     process_status = Column(Integer, nullable=False, default=0, comment='处理状态(0:未处理,1:处理中, 2:已处理, 3:处理失败)')
     process_time = Column(DateTime, comment='处理时间')
     process_error = Column(String(5000), comment='处理错误信息')

+ 40 - 21
SourceCode/IntelligentRailwayCosting/app/executor/task_processor.py

@@ -139,7 +139,7 @@ class TaskProcessor:
                 zgs_list=budgets_data,
                 hierarchy=parents_data,
                 components=children_data,
-                file_excel=files
+                files=files
             )
             return data, ""
         except Exception as e:
@@ -171,28 +171,47 @@ class TaskProcessor:
     def _insert_data(self, task:ProjectTaskDto,project:ProjectDto,data:list[ExcelParseResultDataDto]):
         try:
             self._logger.debug(f"开始插入数据:{task.task_name}")
-
             for item in data:
                 self._logger.debug(f"数据:{item.to_dict()}")
-                quota =  ProjectQuotaDto(
-                    task_id=task.id,
-                    budget_id=item.zgs_id,
-                    budget_code=item.zgs_code,
-                    project_id=project.project_id,
-                    item_code=item.item_code,
-                    item_id=item.item_id,
-                    quota_code=item.dinge_code,
-                    entry_name=item.entry_name,
-                    units=item.units,
-                    amount=item.amount,
-                    ex_file=item.ex_file_id,
-                    ex_cell=item.ex_cell,
-                    ex_row=item.ex_row,
-                    ex_unit=item.ex_unit,
-                    ex_amount=item.ex_amount,
-                    created_by=task.created_by,
-                )
-                self._quota_store.create_quota(quota)
+                quota_dto = self._quota_store.get_quota_by_quota_input(project.project_id, task.budget_id, item.target_id)
+                if quota_dto:
+                    self._logger.debug(f"新增数据:{item.item_id}/{item.item_code} {item.dinge_code}")
+                    quota_dto.item_code=item.item_code
+                    quota_dto.item_id=item.item_id
+                    quota_dto.quota_id=item.target_id and item.target_id>0  or 0
+                    quota_dto.quota_code=item.dinge_code
+                    quota_dto.entry_name=item.entry_name
+                    quota_dto.units=item.units
+                    quota_dto.amount=item.amount
+                    quota_dto.ex_file=item.ex_file_id
+                    quota_dto.ex_cell=item.ex_cell
+                    quota_dto.ex_row=item.ex_row
+                    quota_dto.ex_unit=item.ex_unit
+                    quota_dto.ex_amount=item.ex_amount
+                    quota_dto.send_error=None
+                    self._quota_store.update_quota(quota_dto)
+                else:
+                    self._logger.debug(f"更新数据[{item.target_id}]:{item.item_id}/{item.item_code} {item.dinge_code}")
+                    quota_dto = ProjectQuotaDto(
+                        task_id=task.id,
+                        budget_id=item.zgs_id,
+                        budget_code=item.zgs_code,
+                        project_id=project.project_id,
+                        item_code=item.item_code,
+                        item_id=item.item_id,
+                        quota_id=item.target_id,
+                        quota_code=item.dinge_code,
+                        entry_name=item.entry_name,
+                        units=item.units,
+                        amount=item.amount,
+                        ex_file=item.ex_file_id,
+                        ex_cell=item.ex_cell,
+                        ex_row=item.ex_row,
+                        ex_unit=item.ex_unit,
+                        ex_amount=item.ex_amount,
+                        created_by=task.created_by,
+                    )
+                    self._quota_store.create_quota(quota_dto)
             self._logger.debug(f"插入数据完成:{task.task_name}")
             return True
         except Exception as e:

+ 4 - 4
SourceCode/IntelligentRailwayCosting/app/executor/task_sender.py

@@ -50,13 +50,13 @@ class TaskSender:
 
     def _save_quota(self,quota:QuotaInputDto,project_id:str):
         try:
-            self._logger.info(f"开始保存定额:{quota.id}")
-            data = self._quota_input_store.get_quota(project_id, quota.budget_id,quota.item_id,quota.quota_code)
-            if data:
+            self._logger.info(f"开始保存定额:{quota.project_name} {quota.quota_code} {quota.quota_id}")
+            # data = self._quota_input_store.get_quota(project_id, quota.budget_id,quota.item_id,quota.quota_code)
+            if quota.quota_id and quota.quota_id > 0:
                 self._quota_input_store.update_quota(project_id, quota)
             else:
                 self._quota_input_store.create_quota(project_id, quota)
         except Exception as e:
             msg = f"保存定额失败,原因:{e}"
-            self._logger.error(f"保存定额:{quota.id},{msg}")
+            self._logger.error(f"保存定额:{quota.quota_id},{msg}")
             raise Exception(msg)

+ 10 - 17
SourceCode/IntelligentRailwayCosting/app/routes/project_quota.py

@@ -16,9 +16,8 @@ def get_page_list(budget_id:int,project_id:str,item_code:str):
         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
-        data,count = quota_service.get_quotas_paginated(budget_id, project_id, item_code, page, per_page, keyword, process_status, send_status)
+        data,count = quota_service.get_quotas_paginated(budget_id, project_id, item_code, page, per_page, keyword, send_status)
         return TableResponse.success(data,count)
     except Exception as e:
         return ResponseBase.error(f'获取定额条目列表失败:{str(e)}')
@@ -30,7 +29,6 @@ def get_quotas_by_task_paginated(task_id:int,budget_id:int,project_id:str,item_c
         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
         data,count = quota_service.get_quotas_by_task_paginated(task_id, budget_id, project_id, item_code,page, per_page, keyword, send_status)
         return TableResponse.success(data,count)
@@ -54,7 +52,10 @@ def save_quota():
         data = request.get_json()
         quota_dto = ProjectQuotaDto(**data)
         run_now = data.get('run_now',False)
+        is_cover = data.get('is_cover',False)
         quota_dto = quota_service.save_quota(quota_dto)
+        if not is_cover:
+            quota_dto.quota_id = 0
         if run_now:
             quota_service.start_send(quota_dto.id)
         return ResponseBase.success(quota_dto.to_dict())
@@ -70,22 +71,14 @@ def delete_quota(quota_id:int):
     except Exception as e:
         return ResponseBase.error(f'删除定额条目失败:{str(e)}')
 
-# @project_quota_api.route('/start_process/<int:quota_id>', methods=['POST'])
-# @Permission.authorize
-# def start_process(quota_id:int):
-#     try:
-#         msg = quota_service.start_process(quota_id)
-#         if msg:
-#             return ResponseBase.error(msg)
-#         return ResponseBase.success()
-#     except Exception as e:
-#         return ResponseBase.error(f'启动定额条目失败:{str(e)}')
-
-@project_quota_api.route('/start_send/<int:quota_id>', methods=['POST'])
+@project_quota_api.route('/start_send', methods=['POST'])
 @Permission.authorize
-def start_send(quota_id:int):
+def start_send():
     try:
-        msg = quota_service.start_send(quota_id)
+        data = request.get_json()
+        ids = data.get('ids')
+        is_cover = data.get('is_cover','false') =='true'
+        msg = quota_service.start_send_by_ids(ids,is_cover)
         if msg:
             return ResponseBase.error(msg)
         return ResponseBase.success()

+ 1 - 17
SourceCode/IntelligentRailwayCosting/app/routes/project_task.py

@@ -16,29 +16,13 @@ def get_page_list(project_id:str,item_code:str):
         page = int(data.get('pageNum', 1))
         per_page = int(data.get('pageSize', 10))
         keyword = data.get('keyword')
-        collect_status = int(data.get('collect_status')) if data.get('collect_status') else None
         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, collect_status, process_status, send_status)
+        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)}')
 
-# @project_task_api.route('/list/<int:budget_id>/<project_id>/<item_code>', methods=['POST'])
-# @Permission.authorize
-# def get_page_list_bk(budget_id:int,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')
-#         collect_status = int(data.get('collect_status')) if data.get('collect_status') else None
-#         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(budget_id, project_id, item_code, page, per_page, keyword, collect_status, process_status, send_status)
-#         return TableResponse.success(task, total_count)
-#     except Exception as e:
-#         return ResponseBase.error(f'获取任务列表失败:{str(e)}')
 
 @project_task_api.route('/get/<int:task_id>', methods=['POST'])
 @Permission.authorize

+ 21 - 24
SourceCode/IntelligentRailwayCosting/app/services/project_quota.py

@@ -13,8 +13,7 @@ class ProjectQuotaService:
         self._logger = utils.get_logger()
 
     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, process_status: Optional[int] = None,
-                             send_status: Optional[int] = None):
+                             keyword: Optional[str] = None, send_status: Optional[int] = None):
         """获取项目定额列表
 
         Args:
@@ -24,7 +23,6 @@ class ProjectQuotaService:
             page: 页码
             page_size: 每页数量
             keyword: 关键字
-            process_status: 处理状态
             send_status: 发送状态
 
         Returns:
@@ -38,7 +36,6 @@ class ProjectQuotaService:
                 page=page,
                 page_size=page_size,
                 keyword=keyword,
-                process_status=process_status,
                 send_status=send_status
             )
             return [ProjectQuotaDto.from_model(quota).to_dict() for quota in data.get('data',[])],data.get('total',0)
@@ -154,23 +151,6 @@ class ProjectQuotaService:
             self._logger.error(f"删除项目定额失败: {str(e)}")
             raise
 
-    def update_process_status(self, quota_id: int, status: int, err: str = None) -> bool:
-        """更新处理状态
-
-        Args:
-            quota_id: 定额ID
-            status: 状态值
-            err: 错误信息
-
-        Returns:
-            bool: 更新是否成功
-        """
-        try:
-            return self.store.update_process_status(quota_id, status, err)
-        except Exception as e:
-            self._logger.error(f"更新项目定额处理状态失败: {str(e)}")
-            raise
-
     def update_send_status(self, quota_id: int, status: int, err: str = None) -> bool:
         """更新发送状态
 
@@ -208,13 +188,30 @@ class ProjectQuotaService:
     #         self.update_process_status(quota.id, 3, str(e))
     #         raise
 
-    def start_send(self, quota_id: int) -> Optional[str]:
+    def start_send_by_ids(self, ids: str,is_cover: bool = False):
+        try:
+            id_list= ids.split(',')
+            err =""
+            for _id in id_list:
+                msg = self.start_send(int(_id),is_cover)
+                if msg:
+                    err += f"{msg}[{_id}],"
+            return err
+        except Exception as e:
+            self._logger.error(f"批量启动定额条目发送失败: {str(e)}")
+            raise
+
+
+    def start_send(self, _id: int, is_cover: bool = False) -> Optional[str]:
         """启动发送"""
-        quota = self.get_quota_dto(quota_id)
+        quota = self.get_quota_dto(_id)
         if quota:
-            self.update_send_status(quota_id, 1)
+            self.update_send_status(_id, 1)
+            if not is_cover:
+                quota.quota_id = 0
             thread = threading.Thread(target=self._send_quota, args=(quota,))
             thread.start()
+            return None
         else:
             return "定额条目没有查询到"
 

+ 9 - 106
SourceCode/IntelligentRailwayCosting/app/services/project_task.py

@@ -1,9 +1,8 @@
-from time import sleep
 from typing import Optional
 
 import tools.utils as utils, core.configs as configs, os, threading
 from core.log.log_record import LogRecordHelper
-from core.enum import OperationModule, OperationType, TaskStatusEnum
+from core.enum import OperationModule, OperationType, TaskStatusEnum, SendStatusEnum
 from core.dtos import ProjectTaskDto
 from core.models import ProjectTaskModel
 from stores import ProjectTaskStore
@@ -52,7 +51,7 @@ class ProjectTaskService:
         task_lock = self._get_task_lock(task.id)
         with task_lock:
             try:
-                msg = executor.run_task(task)
+                executor.run_task(task)
                 # if not msg:
                 #     self.start_send_task(task.id)
             except Exception as e:
@@ -66,9 +65,9 @@ class ProjectTaskService:
             task = self.store.get_task_dto(task_id)
             if not task:
                 return '没有查询到任务'
-            if task.collect_status != 2:
-                return '还未采集处理完成'
-            if task.send_status == 1:
+            if task.process_status != TaskStatusEnum.SUCCESS.value:
+                return '还未处理完成'
+            if task.send_status == SendStatusEnum.PROCESSING.value:
                 return '正在发送中'
 
             try:
@@ -92,7 +91,7 @@ class ProjectTaskService:
                 raise
 
     def get_tasks_paginated(self, project_id: str, item_code: str, page: int = 1, page_size: int = 10,
-                            keyword: Optional[str] = None, collect_status: Optional[int] = None,
+                            keyword: Optional[str] = None,
                             process_status: Optional[int] = None, send_status: Optional[int] = None):
         """获取项目任务列表
 
@@ -102,7 +101,6 @@ class ProjectTaskService:
             page: 页码
             page_size: 每页数量
             keyword: 关键字
-            collect_status: 采集状态
             process_status: 处理状态
             send_status: 发送状态
 
@@ -116,7 +114,6 @@ class ProjectTaskService:
                 page=page,
                 page_size=page_size,
                 keyword=keyword,
-                collect_status=collect_status,
                 process_status=process_status,
                 send_status=send_status
             )
@@ -125,41 +122,6 @@ class ProjectTaskService:
             self._logger.error(f"获取项目任务列表失败: {str(e)}")
             raise
 
-    def get_tasks_paginated_bk(self, budget_id: int, project_id: str, item_code: str, page: int = 1, page_size: int = 10,
-                            keyword: Optional[str] = None, collect_status: Optional[int] = None,
-                            process_status: Optional[int] = None, send_status: Optional[int] = None):
-        """获取项目任务列表
-
-        Args:
-            project_id: 项目编号
-            budget_id: 概算序号
-            item_code: 条目编号
-            page: 页码
-            page_size: 每页数量
-            keyword: 关键字
-            collect_status: 采集状态
-            process_status: 处理状态
-            send_status: 发送状态
-
-        Returns:
-            dict: 包含总数和任务列表的字典
-        """
-        try:
-            data =  self.store.get_tasks_paginated_bk(
-                budget_id=budget_id,
-                project_id=project_id,
-                item_code=item_code,
-                page=page,
-                page_size=page_size,
-                keyword=keyword,
-                collect_status=collect_status,
-                process_status=process_status,
-                send_status=send_status
-            )
-            return [ProjectTaskDto.from_model(task).to_dict() for task in data.get('data',[])],data.get('total',0)
-        except Exception as e:
-            self._logger.error(f"获取项目任务列表失败: {str(e)}")
-            raise
 
     def get_task(self, task_id: int) -> Optional[ProjectTaskModel]:
         """获取单个项目任务
@@ -232,7 +194,7 @@ class ProjectTaskService:
         """处理文件上传流程"""
 
         self._logger.info(f"保存处理文件,项目ID:{task.project_id},任务ID:{task.id}")
-        delete_old = task.collect_status == 0
+        delete_old = task.process_status == 0
         if delete_old and task.file_path:
             delete_paths=[]
             for file_path in task.file_path.split(','):
@@ -253,8 +215,8 @@ class ProjectTaskService:
                     original_dir = os.path.dirname(file_path)
                     if not os.listdir(original_dir):
                         os.rmdir(original_dir)
-        if len(delete_paths) > 0:
-            LogRecordHelper.log_success(OperationType.DELETE, OperationModule.TASK,
+            if len(delete_paths) > 0:
+                LogRecordHelper.log_success(OperationType.DELETE, OperationModule.TASK,
                                         f"删除任务文件:{task.task_name}", utils.to_str(delete_paths))
 
         # file_paths = [] if delete_old or not task.file_path else task.file_path.split(',')
@@ -335,22 +297,6 @@ class ProjectTaskService:
             self._logger.error(f"删除项目任务失败: {str(e)}")
             raise
 
-    def update_collect_status(self, task_id: int, status: int, err: str = None) -> bool:
-        """更新采集状态
-
-        Args:
-            task_id: 任务ID
-            status: 状态值
-            err: 错误信息
-
-        Returns:
-            bool: 更新是否成功
-        """
-        try:
-            return self.store.update_collect_status(task_id, status, err)
-        except Exception as e:
-            self._logger.error(f"更新项目任务采集状态失败: {str(e)}")
-            raise
 
     def update_process_status(self, task_id: int, status: int, err: str = None) -> bool:
         """更新处理状态
@@ -397,46 +343,3 @@ class ProjectTaskService:
         else:
             return '没有查询到任务'
 
-    # def start_collect(self,task_id:int):
-    #     task = self.store.get_task_dto(task_id)
-    #     if task:
-    #         if not task.file_path or  task.file_path.strip() == '':
-    #             return '没有上传文件'
-    #         if task.collect_status == 1:
-    #             return  '正在采集中'
-    #         thread = threading.Thread(target=self._collect_task, args=(task,))
-    #         thread.start()
-    #         return None
-    #     else:
-    #         return '没有查询到任务'
-    #
-    # def _collect_task(self,task:ProjectTaskDto):
-    #     try:
-    #         msg = executor.collect_task(task)
-    #         if not msg:
-    #             self.start_send(task.id)
-    #     except Exception as e:
-    #         self._logger.error(f"采集项目任务失败: {str(e)}")
-    #         raise
-    #
-    # def start_process(self,task_id:int):
-    #     task = self.store.get_task_dto(task_id)
-    #     if task:
-    #         if task.collect_status != 2:
-    #             return '还未采集完成'
-    #         if task.process_status == 1:
-    #             return  '正在处理中'
-    #         thread = threading.Thread(target=self._process_task, args=(task,))
-    #         thread.start()
-    #         return None
-    #     else:
-    #         return '没有查询到任务'
-    #
-    # def _process_task(self,task:ProjectTaskDto):
-    #     try:
-    #        msg = executor.process_task(task)
-    #        if not msg:
-    #             self.start_send(task.id)
-    #     except Exception as e:
-    #         self._logger.error(f"处理项目任务失败: {str(e)}")
-    #         raise

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

@@ -1,13 +1,14 @@
 from typing import List, Optional, Dict, Any
 from datetime import datetime
-from sqlalchemy import and_,  desc
+from sqlalchemy import and_
 import tools.db_helper as db_helper
 
 from core.models import LogModel
 
 class LogStore:
     def __init__(self):
-        self._database= None
+        # self._database= None
+        self._database= 'Iwb_RailwayCosting'
 
     def query_logs_paginated(
         self,
@@ -33,7 +34,6 @@ class LogStore:
         :return: 包含总记录数和日志列表的字典
         """
         with db_helper.mysql_query_session(self._database) as db_session:
-            pass
             query = db_session.query(LogModel)
 
             # 构建查询条件
@@ -58,7 +58,7 @@ class LogStore:
             total = query.count()
 
             # 分页并按创建时间倒序排序
-            logs = query.order_by(desc(LogModel.created_at))\
+            logs = query.order_by(LogModel.created_at.desc())\
                 .offset((page - 1) * page_size)\
                 .limit(page_size)\
                 .all()

+ 70 - 53
SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_mysql/project_quota.py

@@ -4,12 +4,14 @@ from typing import Optional
 
 import tools.db_helper as db_helper
 from core.dtos import ProjectQuotaDto
+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= None
+        self._database = 'Iwb_RailwayCosting'
         self._current_user = None
 
     @property
@@ -26,7 +28,6 @@ class ProjectQuotaStore:
         page: int = 1,
         page_size: int = 10,
         keyword: Optional[str] = None,
-        process_status: Optional[int] = None,
         send_status: Optional[int] = None,
     ):
         """分页查询定额列表
@@ -38,7 +39,6 @@ class ProjectQuotaStore:
             budget_id: 概算序号
             item_code: 条目序号
             keyword: 关键字
-            process_status: 处理状态
             send_status: 发送状态
 
         Returns:
@@ -46,7 +46,6 @@ class ProjectQuotaStore:
         """
         with db_helper.mysql_query_session(self._database) as db_session:
             query = db_session.query(ProjectQuotaModel)
-
             # 构建查询条件
             conditions = [
                 ProjectQuotaModel.is_del == 0,
@@ -55,8 +54,6 @@ class ProjectQuotaStore:
                 ProjectQuotaModel.item_code.like(f"{item_code}%")
             ]
 
-            if process_status is not None:
-                conditions.append(ProjectQuotaModel.process_status == process_status)
             if send_status is not None:
                 conditions.append(ProjectQuotaModel.send_status == send_status)
             if keyword:
@@ -70,7 +67,39 @@ class ProjectQuotaStore:
             total_count = query.count()
 
             # 分页
-            query = query.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):
+        with db_helper.mysql_query_session(self._database) as db_session:
+            query = db_session.query(ProjectQuotaModel).filter(
+                and_(
+                    ProjectQuotaModel.task_id == task_id,
+                    ProjectQuotaModel.budget_id == budget_id,
+                    ProjectQuotaModel.project_id == project_id,
+                    ProjectQuotaModel.item_code.like(f"{item_code}%"),
+                    ProjectQuotaModel.is_del == 0
+                )
+            )
+            if 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)
 
             quotas = query.all()
 
@@ -125,6 +154,18 @@ 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):
+        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
+                )
+            ).first()
+            return ProjectQuotaDto.from_model(quota) if quota else None
+
     def create_quota(self, quota_dto: ProjectQuotaDto) -> ProjectQuotaDto:
         """创建定额
 
@@ -138,23 +179,19 @@ class ProjectQuotaStore:
             quota = ProjectQuotaModel(
                 project_id=quota_dto.project_id,
                 budget_id=quota_dto.budget_id,
+                task_id=quota_dto.task_id,
                 item_id=quota_dto.item_id,
                 item_code=quota_dto.item_code,
                 quota_code=quota_dto.quota_code,
-                project_name=quota_dto.entry_name,
-                unit=quota_dto.unit,
-                project_quantity=quota_dto.project_quantity,
-                project_quantity_input=quota_dto.project_quantity_input,
-                quota_adjustment=quota_dto.quota_adjustment,
-                unit_price=quota_dto.unit_price,
-                total_price=quota_dto.total_price,
-                unit_weight=quota_dto.unit_weight,
-                total_weight=quota_dto.total_weight,
-                labor_cost=quota_dto.labor_cost,
-                process_status=quota_dto.process_status,
-                process_time=quota_dto.process_time,
-                process_error=quota_dto.process_error,
-                send_status=quota_dto.send_status,
+                entry_name=quota_dto.entry_name,
+                units=quota_dto.units,
+                amount=quota_dto.amount,
+                ex_file=quota_dto.ex_file,
+                ex_cell=quota_dto.ex_cell,
+                ex_row=quota_dto.ex_row,
+                ex_unit=quota_dto.ex_unit,
+                ex_amount=quota_dto.ex_amount,
+                send_status=SendStatusEnum.NEW.value,
                 send_time=quota_dto.send_time,
                 send_error=quota_dto.send_error,
                 created_by=quota_dto.created_by or self.current_user.username,
@@ -179,17 +216,18 @@ class ProjectQuotaStore:
         if not quota:
             return None
         with db_helper.mysql_session(self._database) as db_session:
+            quota.quota_id = quota_dto.quota_id
             quota.quota_code = quota_dto.quota_code
             quota.entry_name = quota_dto.entry_name
-            quota.unit = quota_dto.unit
-            quota.project_quantity = quota_dto.project_quantity
-            quota.project_quantity_input = quota_dto.project_quantity_input
-            quota.quota_adjustment = quota_dto.quota_adjustment
-            quota.unit_price = quota_dto.unit_price
-            quota.total_price = quota_dto.total_price
-            quota.unit_weight = quota_dto.unit_weight
-            quota.total_weight = quota_dto.total_weight
-            quota.labor_cost = quota_dto.labor_cost
+            quota.units = quota_dto.units
+            quota.amount = quota_dto.amount
+            quota.ex_file = quota_dto.ex_file
+            quota.ex_cell = quota_dto.ex_cell
+            quota.ex_row = quota_dto.ex_row
+            quota.ex_unit = quota_dto.ex_unit
+            quota.ex_amount = quota_dto.ex_amount
+            quota.send_status = SendStatusEnum.CHANGE.value
+            quota.send_error = None
             quota.updated_by = self.current_user.username
             quota.updated_at = datetime.now()
 
@@ -214,27 +252,7 @@ class ProjectQuotaStore:
             quota.is_del = 1
             quota.deleted_by = self.current_user.username
             quota.deleted_at = datetime.now()
-            quota = db_session.merge(quota)
-            return True
-
-    def update_process_status(self,quota_id:int, status:int, err:str = None):
-        """
-        更新项目定额的流程状态
-        Args:
-            quota_id: 定额ID
-            status: 流程状态
-            err:
-        Returns:
-            bool
-        """
-        quota = self.get_quota(quota_id)
-        if not quota:
-            return False
-        with db_helper.mysql_session(self._database) as db_session:
-            quota.process_status = status
-            quota.process_error = err
-            quota.process_time = datetime.now()
-            quota = db_session.merge(quota)
+            db_session.merge(quota)
             return True
 
     def update_send_status(self,quota_id:int, status:int, err:str = None) -> bool:
@@ -254,7 +272,7 @@ class ProjectQuotaStore:
             quota.send_status = status
             quota.send_error = err
             quota.send_time = datetime.now()
-            quota = db_session.merge(quota)
+            db_session.merge(quota)
             return True
 
     def update_quota_code(self, quota_dto: ProjectQuotaDto):
@@ -272,7 +290,6 @@ class ProjectQuotaStore:
             quota.unit = quota_dto.unit
             quota.unit_price = quota_dto.unit_price
             quota.total_price = quota_dto.total_price
-
             quota.updated_by = self.current_user.username
             quota.updated_at = datetime.now()
             db_session.merge(quota)

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

@@ -4,21 +4,22 @@ from typing import Optional
 
 import tools.db_helper as db_helper
 from core.dtos import ProjectTaskDto
+from core.enum import TaskStatusEnum
 from core.models import ProjectTaskModel
 from core.user_session import UserSession
 
 
 class ProjectTaskStore:
     def __init__(self):
+        # self._database= None
+        self._database = 'Iwb_RailwayCosting'
         self._current_user = None
-        self._database=None
 
     @property
     def current_user(self):
         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,
@@ -26,7 +27,6 @@ class ProjectTaskStore:
         page: int = 1,
         page_size: int = 10,
         keyword: Optional[str] = None,
-        collect_status: Optional[int] = None,
         process_status: Optional[int] = None,
         send_status: Optional[int] = None,
     ) :
@@ -38,14 +38,13 @@ class ProjectTaskStore:
             project_id: 项目编号
             item_code: 条目编号
             keyword: 关键字
-            collect_status: 采集状态
             process_status: 处理状态
             send_status: 发送状态
 
         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)
 
             # 构建查询条件
@@ -57,8 +56,6 @@ class ProjectTaskStore:
             ]
             if keyword:
                 conditions.append(ProjectTaskModel.task_name.like(f'%{keyword}%'))
-            if collect_status is not None:
-                conditions.append(ProjectTaskModel.collect_status == collect_status)
             if process_status is not None:
                 conditions.append(ProjectTaskModel.process_status == process_status)
             if send_status is not None:
@@ -70,69 +67,9 @@ class ProjectTaskStore:
             total_count = query.count()
 
             # 分页
-            query = query.offset((page - 1) * page_size).limit(page_size)
-
-            tasks = query.all()
-
-            return {
-                'total': total_count,
-                'data': tasks
-            }
-
-    def get_tasks_paginated_bk(
-        self,
-        budget_id: int,
-        project_id: str,
-        item_code: str,
-        page: int = 1,
-        page_size: int = 10,
-        keyword: Optional[str] = None,
-        collect_status: Optional[int] = None,
-        process_status: Optional[int] = None,
-        send_status: Optional[int] = None,
-    ) :
-        """分页查询任务列表
-
-        Args:
-            page: 页码,从1开始
-            page_size: 每页数量
-            project_id: 项目编号
-            budget_id: 概算序号
-            item_code: 条目编号
-            keyword: 关键字
-            collect_status: 采集状态
-            process_status: 处理状态
-            send_status: 发送状态
-
-        Returns:
-
-        """
-        with db_helper.mysql_query_session(self._database) as db_session:
-            query = db_session.query(ProjectTaskModel)
-
-            # 构建查询条件
-            conditions = [
-                ProjectTaskModel.is_del == 0,
-                ProjectTaskModel.project_id == project_id,
-                ProjectTaskModel.budget_id == budget_id,
-                ProjectTaskModel.item_code.like(f"{item_code}%")
-            ]
-            if keyword:
-                conditions.append(ProjectTaskModel.task_name.like(f'%{keyword}%'))
-            if collect_status is not None:
-                conditions.append(ProjectTaskModel.collect_status == collect_status)
-            if process_status is not None:
-                conditions.append(ProjectTaskModel.process_status == process_status)
-            if send_status is not None:
-                conditions.append(ProjectTaskModel.send_status == send_status)
-
-            query = query.filter(and_(*conditions))
-
-            # 计算总数
-            total_count = query.count()
-
-            # 分页
-            query = query.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()
 
@@ -150,7 +87,6 @@ class ProjectTaskStore:
                 )).first()
             return task
 
-
     def get_task_dto(self, task_id: int) -> Optional[ProjectTaskDto]:
         """根据ID查询任务
 
@@ -163,6 +99,26 @@ 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):
+        """查询待处理的任务
+
+        Args:
+            project_id: 项目编号
+
+        Returns:
+
+        """
+        with db_helper.mysql_query_session(self._database) as db_session:
+            query = db_session.query(ProjectTaskModel)
+            query = query.filter(
+                and_(
+                    ProjectTaskModel.is_del == 0,
+                    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 create_task(self, task_dto: ProjectTaskDto) -> ProjectTaskDto:
         """创建任务
 
@@ -175,6 +131,7 @@ class ProjectTaskStore:
         task = ProjectTaskModel(
             task_name=task_dto.task_name,
             task_desc=task_dto.task_desc,
+            task_sort=task_dto.task_sort,
             project_id=task_dto.project_id,
             budget_id=task_dto.budget_id,
             item_id=task_dto.item_id,
@@ -205,8 +162,9 @@ class ProjectTaskStore:
         with db_helper.mysql_session(self._database) as db_session:
             task.task_name = task_dto.task_name
             task.task_desc = task_dto.task_desc
+            task.task_sort = task_dto.task_sort
+            task.budget_id = task_dto.budget_id
             # task.project_id = task_dto.project_id
-            # task.budget_id = task_dto.budget_id
             # task.item_id = task_dto.item_id
             # task.item_code = task_dto.item_code
             # task.file_path = task_dto.file_path
@@ -215,15 +173,12 @@ class ProjectTaskStore:
             task = db_session.merge(task)
             return ProjectTaskDto.from_model(task)
 
-
     def update_task_files(self, task_id: int,files: str):
         task = self.get_task(task_id)
         if not task:
             return None
         with db_helper.mysql_session(self._database) as db_session:
             task.file_path = files
-            if task.collect_status != 0:
-                task.collect_status = 4
             if task.process_status != 0:
                 task.process_status = 4
             if task.send_status != 0:
@@ -246,26 +201,26 @@ class ProjectTaskStore:
         if not task:
             return False
 
-
         with db_helper.mysql_session(self._database) as db_session:
             task.is_del = 1
             task.deleted_by = self.current_user.username
             task.deleted_at = datetime.now()
-            task = db_session.merge(task)
+            db_session.merge(task)
             return True
-
-    def update_collect_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
         with db_helper.mysql_session(self._database) as db_session:
-            task.collect_status = status
+            task.process_status = status
             if err:
-                task.collect_error = err
-            task.collect_time = datetime.now()
-            task = db_session.merge(task)
+                task.process_error = err
+            task.process_time = datetime.now()
+            db_session.merge(task)
             return True
 
+
+
     def update_process_status(self,task_id:int, status:int, err:str = None):
         task = self.get_task(task_id)
         if not task:
@@ -275,7 +230,7 @@ class ProjectTaskStore:
             if err:
                 task.process_error = err
             task.process_time = datetime.now()
-            task = db_session.merge(task)
+            db_session.merge(task)
             return True
 
     def update_send_status(self,task_id:int, status:int, err:str = None):
@@ -287,6 +242,6 @@ class ProjectTaskStore:
             if err:
                 task.send_error = err
             task.send_time = datetime.now()
-            task = db_session.merge(task)
+            db_session.merge(task)
             return True
 

+ 0 - 2
SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_sqlserver/log.py

@@ -34,9 +34,7 @@ class LogStore:
         :return: 包含总记录数和日志列表的字典
         """
         with db_helper.sqlserver_query_session(self._database) as db_session:
-            pass
             query = db_session.query(LogModel)
-
             # 构建查询条件
             conditions = []
             if username:

+ 22 - 30
SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_sqlserver/project_quota.py

@@ -4,6 +4,7 @@ from typing import Optional
 
 import tools.db_helper as db_helper
 from core.dtos import ProjectQuotaDto
+from core.enum import SendStatusEnum
 from core.models import ProjectQuotaModel
 from core.user_session import UserSession
 
@@ -27,7 +28,6 @@ class ProjectQuotaStore:
         page: int = 1,
         page_size: int = 10,
         keyword: Optional[str] = None,
-        process_status: Optional[int] = None,
         send_status: Optional[int] = None,
     ):
         """分页查询定额列表
@@ -39,7 +39,6 @@ class ProjectQuotaStore:
             budget_id: 概算序号
             item_code: 条目序号
             keyword: 关键字
-            process_status: 处理状态
             send_status: 发送状态
 
         Returns:
@@ -47,7 +46,6 @@ class ProjectQuotaStore:
         """
         with db_helper.sqlserver_query_session(self._database) as db_session:
             query = db_session.query(ProjectQuotaModel)
-
             # 构建查询条件
             conditions = [
                 ProjectQuotaModel.is_del == 0,
@@ -56,8 +54,6 @@ class ProjectQuotaStore:
                 ProjectQuotaModel.item_code.like(f"{item_code}%")
             ]
 
-            if process_status is not None:
-                conditions.append(ProjectQuotaModel.process_status == process_status)
             if send_status is not None:
                 conditions.append(ProjectQuotaModel.send_status == send_status)
             if keyword:
@@ -158,6 +154,18 @@ 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):
+        with db_helper.sqlserver_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
+                )
+            ).first()
+            return ProjectQuotaDto.from_model(quota) if quota else None
+
     def create_quota(self, quota_dto: ProjectQuotaDto) -> ProjectQuotaDto:
         """创建定额
 
@@ -183,10 +191,7 @@ class ProjectQuotaStore:
                 ex_row=quota_dto.ex_row,
                 ex_unit=quota_dto.ex_unit,
                 ex_amount=quota_dto.ex_amount,
-                process_status=0,
-                process_time=quota_dto.process_time,
-                process_error=quota_dto.process_error,
-                send_status=0,
+                send_status=SendStatusEnum.NEW.value,
                 send_time=quota_dto.send_time,
                 send_error=quota_dto.send_error,
                 created_by=quota_dto.created_by or self.current_user.username,
@@ -211,10 +216,18 @@ class ProjectQuotaStore:
         if not quota:
             return None
         with db_helper.sqlserver_session(self._database) as db_session:
+            quota.quota_id = quota_dto.quota_id
             quota.quota_code = quota_dto.quota_code
             quota.entry_name = quota_dto.entry_name
             quota.units = quota_dto.units
             quota.amount = quota_dto.amount
+            quota.ex_file = quota_dto.ex_file
+            quota.ex_cell = quota_dto.ex_cell
+            quota.ex_row = quota_dto.ex_row
+            quota.ex_unit = quota_dto.ex_unit
+            quota.ex_amount = quota_dto.ex_amount
+            quota.send_status = SendStatusEnum.CHANGE.value
+            quota.send_error = None
             quota.updated_by = self.current_user.username
             quota.updated_at = datetime.now()
 
@@ -242,26 +255,6 @@ class ProjectQuotaStore:
             db_session.merge(quota)
             return True
 
-    def update_process_status(self,quota_id:int, status:int, err:str = None):
-        """
-        更新项目定额的流程状态
-        Args:
-            quota_id: 定额ID
-            status: 流程状态
-            err:
-        Returns:
-            bool
-        """
-        quota = self.get_quota(quota_id)
-        if not quota:
-            return False
-        with db_helper.sqlserver_session(self._database) as db_session:
-            quota.process_status = status
-            quota.process_error = err
-            quota.process_time = datetime.now()
-            db_session.merge(quota)
-            return True
-
     def update_send_status(self,quota_id:int, status:int, err:str = None) -> bool:
         """
         更新发送状态
@@ -297,7 +290,6 @@ class ProjectQuotaStore:
             quota.unit = quota_dto.unit
             quota.unit_price = quota_dto.unit_price
             quota.total_price = quota_dto.total_price
-
             quota.updated_by = self.current_user.username
             quota.updated_at = datetime.now()
             db_session.merge(quota)

+ 1 - 79
SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_sqlserver/project_task.py

@@ -27,7 +27,6 @@ class ProjectTaskStore:
         page: int = 1,
         page_size: int = 10,
         keyword: Optional[str] = None,
-        collect_status: Optional[int] = None,
         process_status: Optional[int] = None,
         send_status: Optional[int] = None,
     ) :
@@ -39,7 +38,6 @@ class ProjectTaskStore:
             project_id: 项目编号
             item_code: 条目编号
             keyword: 关键字
-            collect_status: 采集状态
             process_status: 处理状态
             send_status: 发送状态
 
@@ -58,8 +56,6 @@ class ProjectTaskStore:
             ]
             if keyword:
                 conditions.append(ProjectTaskModel.task_name.like(f'%{keyword}%'))
-            if collect_status is not None:
-                conditions.append(ProjectTaskModel.collect_status == collect_status)
             if process_status is not None:
                 conditions.append(ProjectTaskModel.process_status == process_status)
             if send_status is not None:
@@ -82,68 +78,6 @@ class ProjectTaskStore:
                 'data': tasks
             }
 
-    def get_tasks_paginated_bk(
-        self,
-        budget_id: int,
-        project_id: str,
-        item_code: str,
-        page: int = 1,
-        page_size: int = 10,
-        keyword: Optional[str] = None,
-        collect_status: Optional[int] = None,
-        process_status: Optional[int] = None,
-        send_status: Optional[int] = None,
-    ) :
-        """分页查询任务列表
-
-        Args:
-            page: 页码,从1开始
-            page_size: 每页数量
-            project_id: 项目编号
-            budget_id: 概算序号
-            item_code: 条目编号
-            keyword: 关键字
-            collect_status: 采集状态
-            process_status: 处理状态
-            send_status: 发送状态
-
-        Returns:
-
-        """
-        with db_helper.sqlserver_query_session(self._database) as db_session:
-            query = db_session.query(ProjectTaskModel)
-
-            # 构建查询条件
-            conditions = [
-                ProjectTaskModel.is_del == 0,
-                ProjectTaskModel.project_id == project_id,
-                ProjectTaskModel.budget_id == budget_id,
-                ProjectTaskModel.item_code.like(f"{item_code}%")
-            ]
-            if keyword:
-                conditions.append(ProjectTaskModel.task_name.like(f'%{keyword}%'))
-            if collect_status is not None:
-                conditions.append(ProjectTaskModel.collect_status == collect_status)
-            if process_status is not None:
-                conditions.append(ProjectTaskModel.process_status == process_status)
-            if send_status is not None:
-                conditions.append(ProjectTaskModel.send_status == send_status)
-
-            query = query.filter(and_(*conditions))
-
-            # 计算总数
-            total_count = query.count()
-
-            # 分页
-            query = query.order_by(ProjectTaskModel.created_at.desc()).order_by(ProjectTaskModel.task_sort.desc()).offset((page - 1) * page_size).limit(page_size)
-
-            tasks = query.all()
-
-            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(
@@ -245,8 +179,6 @@ class ProjectTaskStore:
             return None
         with db_helper.sqlserver_session(self._database) as db_session:
             task.file_path = files
-            if task.collect_status != 0:
-                task.collect_status = 4
             if task.process_status != 0:
                 task.process_status = 4
             if task.send_status != 0:
@@ -287,17 +219,7 @@ class ProjectTaskStore:
             db_session.merge(task)
             return True
 
-    def update_collect_status(self,task_id:int, status:int, err:str = None):
-        task = self.get_task(task_id)
-        if not task:
-            return False
-        with db_helper.sqlserver_session(self._database) as db_session:
-            task.collect_status = status
-            if err:
-                task.collect_error = err
-            task.collect_time = datetime.now()
-            db_session.merge(task)
-            return True
+
 
     def update_process_status(self,task_id:int, status:int, err:str = None):
         task = self.get_task(task_id)

+ 52 - 2
SourceCode/IntelligentRailwayCosting/app/views/static/base/js/utils.js

@@ -63,6 +63,11 @@ function IwbTable(table, opts,isReload) {
 	const $tableBox = $table.closest('.table-box')
 	if (table.length === 0) return
 	const defaultOptions = {
+		checkBox: false,
+		idFiled:'id',
+		tableCheckboxName:'table-id',
+		selected_ids:new Set(),
+		checkBoxWidth:50,
 		pageSize: 15,
 		pageNum: 1,
 		search: {
@@ -72,6 +77,7 @@ function IwbTable(table, opts,isReload) {
 	const options = isReload ? defaultOptions : ($table.data('options') || defaultOptions)
 	const tableOptions = $.extend({}, options, opts || {})
 	let isSearch = false
+	const checkBoxDiv ='<div class="form-check form-check-sm form-check-custom form-check-solid"><input name="{1}" class="form-check-input" type="checkbox" {2} value="{0}" /></div>'
 	$table.data('init',true)
 
 	function ajaxTable(opt) {
@@ -120,7 +126,14 @@ function IwbTable(table, opts,isReload) {
 	function renderTable(rows, total) {
 		const opt = $table.data('options')
 		let head_str = '',
-			body_str = ''
+			body_str = '',
+			check_all = true
+		// console.log('渲染表格:', opt.selected_ids,rows, total)
+		if(opt.checkBox){
+			head_str += `<th style="${opt.checkBoxWidth ? `width: ${opt.checkBoxWidth};` : ``}">
+						${checkBoxDiv.format('',`${opt.tableCheckboxName}_head`,'')}
+					</th>`
+		}
 		for (let i = 0; i < opt.columns.length; i++) {
 			const col = opt.columns[i]
 			head_str += `<th style="${col.width ? `width: ${col.width};` : ``}${col.style ? `${col.style};` : ``}">${col.title}</th>`
@@ -129,6 +142,14 @@ function IwbTable(table, opts,isReload) {
 			for (let i = 0; i < rows.length; i++) {
 				const row = rows[i]
 				body_str += '<tr>'
+				if(opt.checkBox){
+					if(opt.selected_ids.size && opt.selected_ids.has(row[opt.idFiled]+"")){
+						body_str +=`<td>${checkBoxDiv.format(row[opt.idFiled],`${opt.tableCheckboxName}`,'checked')}</td>`
+					}else{
+						check_all = false
+						body_str +=`<td>${checkBoxDiv.format(row[opt.idFiled],`${opt.tableCheckboxName}`,'')}</td>`
+					}
+				}
 				for (let j = 0; j < opt.columns.length; j++) {
 					const col = opt.columns[j]
 					if (col.render) {
@@ -146,6 +167,33 @@ function IwbTable(table, opts,isReload) {
 			let page_str = formatPage(opt.pageNum, opt.pageSize, total)
 			$table.parent().find('.pagination-row').html(page_str)
 			$table.html(`<thead><tr>${head_str}</tr></thead><tbody>${body_str}</tbody>`)
+			$tableBox.find(`input[name=${opt.tableCheckboxName}_head]`).on('change', function () {
+				const checked = $(this).is(':checked')
+				if (checked) {
+					$tableBox.find(`input[name=${opt.tableCheckboxName}]`).prop('checked', true)
+					// 页面上所有的的checkbox都选中,值添加到selected_ids中,不是赋值
+					rows.forEach(row => opt.selected_ids.add(row[opt.idFiled]+""))
+				} else {
+					$tableBox.find(`input[name=${opt.tableCheckboxName}]`).prop('checked', false)
+					rows.forEach(row => opt.selected_ids.delete(row[opt.idFiled]+""))
+				}
+			})
+			$tableBox.find(`input[name=${opt.tableCheckboxName}]`).on('change', function () {
+				if ($tableBox.find(`input[name=${opt.tableCheckboxName}]:not(:checked)`).length) {
+					$tableBox.find(`input[name=${opt.tableCheckboxName}_head]`).prop('checked', false)
+				} else {
+					$tableBox.find(`input[name=${opt.tableCheckboxName}_head]`).prop('checked', true)
+				}
+				const checked = $(this).is(':checked')
+				if (checked) {
+					opt.selected_ids.add($(this).val())
+				} else {
+					opt.selected_ids.delete($(this).val())
+				}
+			})
+			if(check_all){
+				$tableBox.find(`input[name=${opt.tableCheckboxName}_head]`).prop('checked', true)
+			}
 			$tableBox.fadeIn(500,function (){
 				$tableBox.find(`[data-bs-toggle="tooltip"]`).each(function (){
 					new bootstrap.Tooltip(this)
@@ -249,7 +297,9 @@ function IwbTableChangePageSize(that) {
 	const pageSize = parseInt($(that).val())
 	IwbTable($table, { pageSize: pageSize })
 }
-
+function IwbTableGetSelectedIds(table) {
+	return $(table) && $(table).data('options') ? Array.from($(table).data('options').selected_ids).sort((a, b) => a - b) : []
+}
 function AddModal(modal, callback) {
 	const $modal = $(modal)
 	$modal.find('.modal-header .modal-title span.prefix').html('添加')

+ 96 - 32
SourceCode/IntelligentRailwayCosting/app/views/static/project/quota_info.js

@@ -14,6 +14,7 @@ const quota_modal_template = `
       <div class="modal-body">
         <form>
           <div class="form-group">
+            <input type="hidden" name="id" value="">
             <input type="hidden" name="budget_id" value="">
             <input type="hidden" name="project_id" value="">
             <input type="hidden" name="item_id" value="">
@@ -58,13 +59,13 @@ const quota_modal_template = `
               <input type="text" class="form-control" name="quota_code" id="quota_code" placeholder="请输入定额编号" />
             </div>
 			<div class="d-flex pt-3 justify-content-center">
-			  <div class="form-check form-check-custom form-check-solid form-check-danger mb-3" id="is_edit_box">
-			    <input class="form-check-input" type="radio" checked value="0"  name="is_edit" id="is_edit_1"/>
-				<label class="form-check-label ms-3 text-danger" for="is_edit_1">覆盖(新增)源数据</label>
+			  <div class="form-check form-check-custom form-check-solid form-check-danger mb-3 is_cover_box">
+			    <input class="form-check-input" type="radio" checked value="1"  name="is_cover" id="is_cover_1"/>
+				<label class="form-check-label ms-3 text-danger" for="is_cover_1">覆盖(修改)源数据</label>
 			  </div>
-              <div class="form-check form-check-solid ms-5 mb-3" id="is_edit_box">
-                <input class="form-check-input" type="radio" value="1"  name="is_edit" id="is_edit_2"/>
-				<label class="form-check-label ms-3 text-primary" for="is_edit_2">修改(新增)源数据</label>
+              <div class="form-check form-check-solid ms-5 mb-3 is_cover_box">
+                <input class="form-check-input" type="radio" value="0"  name="is_cover" id="is_cover_2"/>
+				<label class="form-check-label ms-3 text-primary" for="is_cover_2">新增源数据</label>
               </div>
             </div>
           </div>
@@ -85,7 +86,7 @@ const	nav_template = `<ul id="nav_tab" class="nav nav-tabs nav-line-tabs nav-lin
 			<button type="button" class="nav-link {2} btn-light-primary btn-active-color-primary" data-id="{0}"  data-bs-toggle="tab" data-bs-target="#iwb_tab_{0}">{1}</button>
 		</li>`,
 	tab_content_template = `<div class="tab-pane h-100" id="iwb_tab_{0}" role="tabpanel">{1}</div>`,
-	table_add_quota_btn_template = `` //`<button type="button" class="quota_add_btn btn btn-primary btn-sm" onclick="Add_Quota('{0}')">添加定额</button>`,
+	table_add_quota_btn_template = `<button type="button" class="quota_add_btn btn btn-primary btn-sm" onclick="Send_Quota_Batch('{0}')">批量推送</button>` //`<button type="button" class="quota_add_btn btn btn-primary btn-sm" onclick="Add_Quota('{0}')">添加定额</button>`,
 	table_run_select_template = `<select class="form-select form-select-sm me-5" name="process_status">
 												<option value="">全部运行状态</option>
 												<option value="0">草稿</option>
@@ -97,11 +98,11 @@ const	nav_template = `<ul id="nav_tab" class="nav nav-tabs nav-line-tabs nav-lin
 												<!--<option value="4">已修改</option>-->
 											</select>`,
 	table_send_select_template = `<select class="form-select form-select-sm me-5" name="send_status">
-												<option value="">全部送状态</option>
-												<option value="0">未送</option>
-												<option value="1">送中</option>
-												<option value="200">送成功</option>
-												<option value="2">送失败</option>
+												<option value="">全部送状态</option>
+												<option value="0">未送</option>
+												<option value="1">送中</option>
+												<option value="200">送成功</option>
+												<option value="2">送失败</option>
 												<option value="3">数据变更</option>
 											</select>`,
 	table_template = `<div class="table-box table-responsive" data-id="{0}" id="table_box_{0}">
@@ -314,6 +315,7 @@ function  LoadQuotaTable(table){
 	const url = `${$quotaBox.data("table-url")}/${budget_id}/${project_id}/${item_code}`
     IwbTable(table,{
 		url,
+		checkBox: true,
 		columns: [
 			{
 				title: '序号',
@@ -398,13 +400,13 @@ function  LoadQuotaTable(table){
 					// 	str+= `<span class="badge badge-danger">数据变更</span>`
 					// }
 					if(row.send_status === 0){
-						str+= `<span class="badge badge-primary ms-3">未送</span>`
+						str+= `<span class="badge badge-primary ms-3">未送</span>`
 					}else if (row.send_status === 1){
-						str+= `<span class="badge badge-warning ms-3">送中</span>`
+						str+= `<span class="badge badge-warning ms-3">送中</span>`
 					}else if (row.send_status === 200){
-						str+= `<span class="badge badge-success ms-3">送成功</span>`
+						str+= `<span class="badge badge-success ms-3">送成功</span>`
 					}else if (row.send_status === 2){
-						str+= `<span class="badge badge-danger ms-3">送失败</span>`
+						str+= `<span class="badge badge-danger ms-3">送失败</span>`
 					}else if (row.send_status === 3){
 						str+= `<span class="badge badge-danger ms-3">数据变更</span>`
 					}
@@ -429,13 +431,13 @@ function  LoadQuotaTable(table){
 					// 	str += `<button type="button" class="btn btn-icon btn-sm btn-light-info" data-bs-toggle="tooltip" data-bs-placement="top" title="重新处理" onclick="ReStartProcessQuota(${row.id}, ${row.budget_id})"><i class="ki-duotone ki-book-square fs-1"><span class="path1"></span><span class="path2"></span><span class="path3"></span></i></button>`
 					// }
 					if (row.send_status === 0) {
-						str += `<button type="button" class="btn btn-icon btn-sm btn-light-primary" data-bs-toggle="tooltip" data-bs-placement="top" title="开始发送" onclick="StartSendQuota(${row.id}, ${row.budget_id})"><i class="ki-duotone ki-send fs-1"><span class="path1"></span><span class="path2"></span></i></button>`
+						str += `<button type="button" class="btn btn-icon btn-sm ${row.quota_id?'btn-primary':'btn-light-primary'}" data-bs-toggle="tooltip" data-bs-placement="top" title="开始推送" onclick="StartSendQuota(${row.id}, ${row.budget_id}, ${row.quota_id})"><i class="ki-duotone ki-send fs-3"><span class="path1"></span><span class="path2"></span></i></button>`
 					} else if (row.send_status === 200) {
-						str += `<button type="button" class="btn btn-icon btn-sm btn-light-warning" data-bs-toggle="tooltip" data-bs-placement="top" title="重新发送" onclick="ReStartSendQuota(${row.id}, ${row.budget_id})"><i class="ki-duotone ki-send fs-1"><span class="path1"></span><span class="path2"></span></i></button>`
+						str += `<button type="button" class="btn btn-icon btn-sm  ${row.quota_id?'btn-warning':'btn-light-warning'}" data-bs-toggle="tooltip" data-bs-placement="top" title="重新推送" onclick="ReStartSendQuota(${row.id}, ${row.budget_id}, ${row.quota_id})"><i class="ki-duotone ki-send fs-3"><span class="path1"></span><span class="path2"></span></i></button>`
 					} else if (row.send_status === 2) {
-						str += `<button type="button" class="btn btn-icon btn-sm btn-light-danger" data-bs-toggle="tooltip" data-bs-placement="top" title="重新发送" onclick="ReStartSendQuota(${row.id}, ${row.budget_id})"><i class="ki-duotone ki-send fs-1"><span class="path1"></span><span class="path2"></span></i></button>`
+						str += `<button type="button" class="btn btn-icon btn-sm  ${row.quota_id?'btn-danger':'btn-light-danger'}" data-bs-toggle="tooltip" data-bs-placement="top" title="重新推送" onclick="ReStartSendQuota(${row.id}, ${row.budget_id}, ${row.quota_id})"><i class="ki-duotone ki-send fs-3"><span class="path1"></span><span class="path2"></span></i></button>`
 					} else if (row.send_status === 3) {
-						str += `<button type="button" class="btn btn-icon btn-sm btn-light-info" data-bs-toggle="tooltip" data-bs-placement="top" title="重新发送" onclick="ReStartSendQuota(${row.id}, ${row.budget_id})"><i class="ki-duotone ki-send fs-1"><span class="path1"></span><span class="path2"></span></i></button>`
+						str += `<button type="button" class="btn btn-icon btn-sm  ${row.quota_id?'btn-info':'btn-light-info'}" data-bs-toggle="tooltip" data-bs-placement="top" title="重新推送" onclick="ReStartSendQuota(${row.id}, ${row.budget_id}, ${row.quota_id})"><i class="ki-duotone ki-send fs-3"><span class="path1"></span><span class="path2"></span></i></button>`
 					}
 					str+=`<button type="button" class="btn btn-icon btn-sm btn-light-primary" data-bs-toggle="tooltip" data-bs-placement="top" title="编辑" onclick="Edit_Quota(${row.id}, ${row.budget_id})"><i class="ki-duotone ki-message-edit fs-1"><span class="path1"></span><span class="path2"></span></i></button>`
 					str+=`<button type="button" class="btn btn-icon btn-sm btn-light-danger"  data-bs-toggle="tooltip" data-bs-placement="top" title="删除" onclick="Delete_Quota(${row.id}, ${row.budget_id})"><i class="ki-duotone ki-trash-square fs-1"><span class="path1"></span><span class="path2"></span><span class="path3"></span><span class="path4"></span></i></button>`
@@ -455,14 +457,17 @@ function SetBudgetData($el){
 function Add_Quota(budget_id,) {
 	AddModal($modalQuota, () => {
 		SetBudgetData($modalQuota)
-		$modalQuota.find('[name="quota_id"]').val('0');
+		$modalQuota.find('[name="id"]').val('0');
 		$modalQuota.find('[name="quota_id"]').val('0');
 		$modalQuota.find('[name="task_id"]').val('0');
 		$modalQuota.find('[name="budget_id"]').val(budget_id);
+		$modalQuota.find('[name="is_cover"]').val('0')
+		$modalQuota.find('.is_cover_box').hide()
 	})
 }
 
 function Edit_Quota(id) {
+	$modalQuota.find('.is_cover_box').hide()
     EditModal($modalQuota,()=>{
         IwbAjax_1({
             url:`/api/quota/get/${id}`,
@@ -471,9 +476,11 @@ function Edit_Quota(id) {
 					console.error(res.message)
 					return
 				}
-				const data = res.data
+				const data = res.data,
 				// SetBudgetData(budget_id)
-        		$modalQuota.find('[name="quota_id"]').val(data.id);
+				quota_id = data.quota_id
+        		$modalQuota.find('[name="id"]').val(data.id);
+        		$modalQuota.find('[name="quota_id"]').val(quota_id);
         		$modalQuota.find('[name="task_id"]').val(data.task_id);
 				$modalQuota.find('[name="budget_id"]').val(data.budget_id);
 				$modalQuota.find('[name="project_id"]').val(data.project_id);
@@ -487,14 +494,18 @@ function Edit_Quota(id) {
                 $modalQuota.find('#ex_amount').html(data.ex_amount);
                 $modalQuota.find('#ex_cell').html(data.ex_cell);
                 $modalQuota.find('#ex_unit').html(data.ex_unit);
-
+				$modalQuota.find('[name="is_cover"]').val('0')
+				if(quota_id){
+					$modalQuota.find('.is_cover_box').show()
+				}
             }
         })
     })
 }
 
 function SaveQuota(isSubmit){
-	const quota_id = $modalQuota.find('[name="quota_id"]').val(),
+	const id = $modalQuota.find('[name="id"]').val(),
+		quota_id = $modalQuota.find('[name="quota_id"]').val(),
 		task_id = $modalQuota.find('[name="task_id"]').val(),
 		budget_id = $modalQuota.find('[name="budget_id"]').val(),
 		project_id = $modalQuota.find('[name="project_id"]').val(),
@@ -503,11 +514,13 @@ function SaveQuota(isSubmit){
 		entry_name = $modalQuota.find('[name="entry_name"]').val(),
 		amount = $modalQuota.find('[name="amount"]').val(),
 		units = $modalQuota.find('[name="units"]').val(),
-		quota_code = $modalQuota.find('[name="quota_code"]').val()
+		quota_code = $modalQuota.find('[name="quota_code"]').val(),
+		is_cover = $modalQuota.find('[name="is_cover"]').val()
 	IwbAjax({
 		url:`/api/quota/save`,
 		data:{
-			id: quota_id,
+			id,
+			quota_id,
 			task_id,
 			budget_id,
 			project_id,
@@ -517,7 +530,8 @@ function SaveQuota(isSubmit){
 			amount,
 			units,
 			quota_code,
-			run_now: isSubmit?'true':'false'
+			run_now: isSubmit?'true':'false',
+			is_cover: is_cover?'true':'false'
 		},
 		modal:$modalQuota,
 		table:`#table_${budget_id}`
@@ -536,9 +550,59 @@ function Delete_Quota(id,budget_id){
 // function ReStartProcessQuota(id,budget_id){
 // 	ConfirmUrl('确定重新开始处理吗?',`/api/quota/start_process/${id}`,`#table_${budget_id}`)
 // }
-function StartSendQuota(id,budget_id){
-	ConfirmUrl('确定开始发送吗?',`/api/quota/start_send/${id}`,`#table_${budget_id}`)
+
+function StartSendQuota(ids,budget_id,is_cover){
+	SendQuota('确定开始推送数据吗?',ids,budget_id,is_cover)
+}
+function ReStartSendQuota(ids,budget_id,is_cover){
+	SendQuota('确定重新开始推送数据吗?',ids,budget_id,is_cover)
 }
-function ReStartSendQuota(id,budget_id){
-	ConfirmUrl('确定重新开始发送吗?',`/api/quota/start_send/${id}`,`#table_${budget_id}`)
+function Send_Quota_Batch(budget_id){
+	const $table = `#table_${budget_id}`,
+		select_ids = IwbTableGetSelectedIds($table)
+	if(select_ids.length){
+		const select_ids_str = select_ids.join(',')
+		SendQuota(`确定批量推送这些数据吗?[${select_ids_str}]`,select_ids_str,budget_id,true)
+	}else{
+		MsgWarning('没有选择数据,请选择要推送的数据!')
+	}
 }
+function SendQuota(title,ids,budget_id,is_cover){
+	const opts={
+        text: title,
+        icon: "info",
+        buttonsStyling: false,
+		showCancelButton: true,
+		showConfirmButton: true,
+		showDenyButton: !!is_cover,
+        cancelButtonText: "取消",
+        confirmButtonText: "新增",
+        denyButtonText: "覆盖",
+        customClass: {
+			cancelButton: "btn btn-light mx-2",
+            confirmButton: "btn btn-primary mx-2",
+            denyButton: "btn btn-danger mx-2"
+        },
+		toast:false
+    }
+	Swal.fire(opts).then((result)=>{
+		console.log("CONFIRM",result)
+		if(result.isConfirmed){
+			_send(false)
+		}else if(result.isDenied){
+			_send(true)
+		}
+	});
+	function _send(is_cover){
+		console.log("is_cover",is_cover,ids)
+		IwbAjax({
+			url:`/api/quota/start_send`,
+			data:{
+				ids,
+				is_cover:is_cover?'true':'false'
+			},
+			modal:$modalQuota,
+			table:`#table_${budget_id}`
+		})
+	}
+}