Pārlūkot izejas kodu

Update 完善数据推送,重建任务等需求

yue 1 mēnesi atpakaļ
vecāks
revīzija
bbd81e24c0
19 mainītis faili ar 300 papildinājumiem un 125 dzēšanām
  1. 1 0
      SourceCode/IntelligentRailwayCosting/.script/init_mysql.sql
  2. 7 1
      SourceCode/IntelligentRailwayCosting/app/core/configs/app_config.py
  3. 37 35
      SourceCode/IntelligentRailwayCosting/app/core/dtos/excel_parse.py
  4. 9 4
      SourceCode/IntelligentRailwayCosting/app/core/dtos/project_task.py
  5. 51 25
      SourceCode/IntelligentRailwayCosting/app/core/models/project_task.py
  6. 29 14
      SourceCode/IntelligentRailwayCosting/app/executor/task_processor.py
  7. 2 1
      SourceCode/IntelligentRailwayCosting/app/executor/task_sender.py
  8. 31 3
      SourceCode/IntelligentRailwayCosting/app/routes/excel_test.py
  9. 1 1
      SourceCode/IntelligentRailwayCosting/app/routes/project_quota.py
  10. 2 0
      SourceCode/IntelligentRailwayCosting/app/routes/project_task.py
  11. 43 8
      SourceCode/IntelligentRailwayCosting/app/services/project_quota.py
  12. 3 1
      SourceCode/IntelligentRailwayCosting/app/stores/quota_input.py
  13. 12 2
      SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_mysql/project_quota.py
  14. 1 0
      SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_mysql/project_task.py
  15. 17 3
      SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_sqlserver/project_quota.py
  16. 13 5
      SourceCode/IntelligentRailwayCosting/app/views/static/base/css/styles.css
  17. 3 9
      SourceCode/IntelligentRailwayCosting/app/views/static/base/js/utils.js
  18. 25 0
      SourceCode/IntelligentRailwayCosting/app/views/static/project/budget_info.js
  19. 13 13
      SourceCode/IntelligentRailwayCosting/app/views/static/project/quota_info.js

+ 1 - 0
SourceCode/IntelligentRailwayCosting/.script/init_mysql.sql

@@ -37,6 +37,7 @@ 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 '任务编号',
+    parent_id INT NOT NULL COMMENT '父任务编号',
     project_id VARCHAR(50) NOT NULL COMMENT '项目编号',
     budget_id INT NOT NULL COMMENT '概算序号',
     budget_code VARCHAR(50) COMMENT '概算编号',

+ 7 - 1
SourceCode/IntelligentRailwayCosting/app/core/configs/app_config.py

@@ -8,7 +8,8 @@ class AppConfig:
     _task_api_url = ""
     _task_max_projects_count = 10
     _task_interval = 300  # 任务执行间隔时间,单位秒
-    _ai_flag = "_AI"
+    _ai_flag = "[AI]"
+    _ai_name_flag = "★"
 
     @property
     def name(self):
@@ -42,6 +43,10 @@ class AppConfig:
     def ai_flag(self) -> str:
         return self._ai_flag
 
+    @property
+    def ai_name_flag(self) -> str:
+        return self._ai_name_flag
+
     def update_config(self, config):
         """更新应用配置
 
@@ -56,3 +61,4 @@ class AppConfig:
         self._task_max_projects_count = int(config.get("task_max_projects", 10))
         self._task_interval = int(config.get("task_interval", 300))
         self._ai_flag = config.get("ai_flag", "[AI]")
+        self._ai_name_flag = config.get("ai_name_flag", "★")

+ 37 - 35
SourceCode/IntelligentRailwayCosting/app/core/dtos/excel_parse.py

@@ -36,34 +36,36 @@ class ExcelParseItemDto:
 
 
 class ExcelParseFileDto:
-    def __init__(self, file_id: str, content: str):
+    def __init__(self, file_id: str, content: str, is_new: int = 1):
         self.file_id = file_id
         # 从file_id路径中获取文件类型
         self.file_type = file_id.split(".")[-1]
         self.content = content
+        self.is_new = is_new
 
     def to_dict(self):
         return {
             "file_id": base64.b64encode(self.file_id.encode()).decode(),
             "file_type": self.file_type,
             "content": self.content,
+            "is_new": self.is_new,
         }
 
 
 class ExcelParseDto:
     def __init__(
-            self,
-            task_id: int,
-            version: str,
-            project_id: str,
-            project_name: str,
-            project_stage: str,
-            selected_zgs_id: int,
-            zgs_list: list[ExcelParseZgsDto],
-            selected_chapter: ExcelParseItemDto,
-            # hierarchy: list[ExcelParseItemDto],
-            # components: list[ExcelParseItemDto],
-            files: list[ExcelParseFileDto],
+        self,
+        task_id: int,
+        version: str,
+        project_id: str,
+        project_name: str,
+        project_stage: str,
+        selected_zgs_id: int,
+        zgs_list: list[ExcelParseZgsDto],
+        selected_chapter: ExcelParseItemDto,
+        # hierarchy: list[ExcelParseItemDto],
+        # components: list[ExcelParseItemDto],
+        files: list[ExcelParseFileDto],
     ):
         self.task_id = task_id
         self.version = version
@@ -113,23 +115,23 @@ class ExcelParseResultDataDto:
     #     "note": 说明,例如"需根据标号和运距调整"
     # }
     def __init__(
-            self,
-            zgs_id: int,  # 总概算id
-            zgs_code: str,  # 总概算编号
-            item_id: int,  # 条⽬序号
-            item_code: str,  # 条⽬编码
-            entry_name: str,  # ⼯程或费⽤项⽬名称,来⾃于定额表,
-            dinge_code: str,  # 定额编号,
-            dinge_adjust: str,  # 定额调整,
-            units: str,  # 单位,
-            amount: float,  # 数量,
-            target_id: int,  # ⽤户数据库中条⽬的id,-1表示没有,
-            ex_file_id: str,  # excel⽂件id,
-            ex_cell: str,  # 数量单元格位置,例如 "C17",
-            ex_row: str,  # 该⾏内容,由逗号连接多个单元格得到,
-            ex_unit: str,  # excel中给出的单位,
-            ex_amount: float,  # excel中给出的数量,
-            note: str,  # 说明,
+        self,
+        zgs_id: int,  # 总概算id
+        zgs_code: str,  # 总概算编号
+        item_id: int,  # 条⽬序号
+        item_code: str,  # 条⽬编码
+        entry_name: str,  # ⼯程或费⽤项⽬名称,来⾃于定额表,
+        dinge_code: str,  # 定额编号,
+        dinge_adjust: str,  # 定额调整,
+        units: str,  # 单位,
+        amount: float,  # 数量,
+        target_id: int,  # ⽤户数据库中条⽬的id,-1表示没有,
+        ex_file_id: str,  # excel⽂件id,
+        ex_cell: str,  # 数量单元格位置,例如 "C17",
+        ex_row: str,  # 该⾏内容,由逗号连接多个单元格得到,
+        ex_unit: str,  # excel中给出的单位,
+        ex_amount: float,  # excel中给出的数量,
+        note: str,  # 说明,
     ):
         self.zgs_id = zgs_id
         self.zgs_code = zgs_code
@@ -199,11 +201,11 @@ class ExcelParseResultDataDto:
 
 class ExcelParseResultDto:
     def __init__(
-            self,
-            task_id: int,
-            result: int = -1,
-            reason: str = "",
-            data: list[ExcelParseResultDataDto] = None,
+        self,
+        task_id: int,
+        result: int = -1,
+        reason: str = "",
+        data: list[ExcelParseResultDataDto] = None,
     ):
         self.task_id = task_id
         self.result = result  # -1-失败;0-运行中;1-成功

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

@@ -3,9 +3,12 @@ from typing import Optional
 from datetime import datetime
 from ..models import ProjectTaskModel
 
+
 class ProjectTaskDto(BaseModel):
     """项目任务DTO"""
+
     id: Optional[int] = None
+    parent_id: int = 0
     task_name: str
     task_sort: int = 0
     task_desc: Optional[str] = None
@@ -30,10 +33,11 @@ class ProjectTaskDto(BaseModel):
     updated_at: Optional[datetime] = None
 
     @classmethod
-    def from_model(cls, model: ProjectTaskModel) -> 'ProjectTaskDto':
+    def from_model(cls, model: ProjectTaskModel) -> "ProjectTaskDto":
         """从数据库模型创建DTO对象"""
         return cls(
             id=model.id,
+            parent_id=model.parent_id,
             task_name=model.task_name,
             task_sort=model.task_sort,
             task_desc=model.task_desc,
@@ -55,11 +59,11 @@ class ProjectTaskDto(BaseModel):
             created_by=model.created_by,
             created_at=model.created_at,
             updated_by=model.updated_by,
-            updated_at=model.updated_at
+            updated_at=model.updated_at,
         )
 
     @classmethod
-    def from_dict(cls, data: dict) -> 'ProjectTaskDto':
+    def from_dict(cls, data: dict) -> "ProjectTaskDto":
         """从字典创建DTO对象"""
         # if 'budget_id' in data and 'budget_id' not in data:
         #     data['budget_id'] = data.pop('budget_id')
@@ -71,5 +75,6 @@ class ProjectTaskDto(BaseModel):
 
     def get_path(self):
         return f"{self.project_id}_{self.item_id}({self.item_code})_{self.id}/{datetime.now().strftime('%Y%m%d%H%M%S')}"
+
     class Config:
-        from_attributes = True
+        from_attributes = True

+ 51 - 25
SourceCode/IntelligentRailwayCosting/app/core/models/project_task.py

@@ -1,34 +1,60 @@
-from sqlalchemy import Column, Integer, String, DateTime,  Text
+from sqlalchemy import Column, Integer, String, DateTime, Text
 from sqlalchemy.sql import func
 from sqlalchemy.ext.declarative import declarative_base
 
 Base = declarative_base()
+
+
 class ProjectTaskModel(Base):
-    __tablename__ = 'project_task'
+    __tablename__ = "project_task"
 
     id = Column(Integer, primary_key=True, autoincrement=True)
-    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='文件路径')
-    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='发送错误信息')
-    is_del = Column(Integer, nullable=False, default=0, comment='是否删除(0:否, 1:是)')
-    deleted_by = Column(String(50), comment='删除人')
-    deleted_at = Column(DateTime, comment='删除时间')
-    created_by = Column(String(50), comment='创建人')
-    created_at = Column(DateTime, nullable=False, server_default=func.current_timestamp(), comment='创建时间')
-    updated_by = Column(String(50), comment='更新人')
-    updated_at = Column(DateTime, nullable=False, server_default=func.current_timestamp(), server_onupdate=func.current_timestamp(), comment='更新时间')
+    parent_id = Column(Integer, nullable=False, default=0, comment="父任务")
+    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="文件路径")
+    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="发送错误信息")
+    is_del = Column(Integer, nullable=False, default=0, comment="是否删除(0:否, 1:是)")
+    deleted_by = Column(String(50), comment="删除人")
+    deleted_at = Column(DateTime, comment="删除时间")
+    created_by = Column(String(50), comment="创建人")
+    created_at = Column(
+        DateTime,
+        nullable=False,
+        server_default=func.current_timestamp(),
+        comment="创建时间",
+    )
+    updated_by = Column(String(50), comment="更新人")
+    updated_at = Column(
+        DateTime,
+        nullable=False,
+        server_default=func.current_timestamp(),
+        server_onupdate=func.current_timestamp(),
+        comment="更新时间",
+    )
 
     def __repr__(self):
-        return f"<ProjectTask(id='{self.id}', task_name='{self.task_name}')>"
+        return f"<ProjectTask(id='{self.id}', task_name='{self.task_name}')>"

+ 29 - 14
SourceCode/IntelligentRailwayCosting/app/executor/task_processor.py

@@ -47,6 +47,15 @@ class TaskProcessor:
             files, msg = self._read_files(task.file_path)
             if not files or len(files) == 0:
                 raise Exception(msg)
+            if task.parent_id:
+                parent_task = self._task_store.get_task(task.parent_id)
+                if parent_task:
+                    old_files, msg = self._read_files(parent_task.file_path, 0)
+                    if not files or len(files) == 0:
+                        raise Exception(msg)
+                    else:
+                        files.extend(old_files)
+
             project = self._project_store.get(task.project_id)
             if not project:
                 raise Exception("项目不存在")
@@ -122,6 +131,7 @@ class TaskProcessor:
                         task.id, TaskStatusEnum.SUCCESS.value
                     )
                     return "取消失败,任务已运行完成"
+                return None
             else:
                 self._logger.error(f"取消运行任务:{task.id}失败,原因:任务状态错误")
                 return "任务状态错误"
@@ -155,19 +165,24 @@ class TaskProcessor:
                 else:
                     self._logger.debug(f"运行任务:{task.task_name}请求中,等待结果")
                     self.query_task(task, project)
+                return None
         except Exception as e:
             msg = f"任务状态查询失败,原因:{e}"
             self._logger.error(msg)
             raise Exception(msg)
 
-    def _read_files(self, paths: str) -> (list[ExcelParseFileDto], str):
+    def _read_files(
+        self, paths: str, is_new: int = 1
+    ) -> (list[ExcelParseFileDto], str):
         try:
             files = []
             self._logger.debug(f"开始读取文件:{paths}")
             path_list = paths.split(",")
             for path in path_list:
                 file = utils.encode_file(path)
-                files.append(ExcelParseFileDto(file_id=path, content=file))
+                files.append(
+                    ExcelParseFileDto(file_id=path, content=file, is_new=is_new)
+                )
             self._logger.debug(f"读取文件完成:{paths}")
             return files, ""
         except Exception as e:
@@ -176,14 +191,14 @@ class TaskProcessor:
             return None, msg
 
     def _build_api_body(
-            self,
-            task: ProjectTaskDto,
-            project: ProjectDto,
-            budgets: list[TotalBudgetInfoDto],
-            chapter: ChapterDto,
-            # parents: list[ChapterDto],
-            # children: list[ChapterDto],
-            files: list[ExcelParseFileDto],
+        self,
+        task: ProjectTaskDto,
+        project: ProjectDto,
+        budgets: list[TotalBudgetInfoDto],
+        chapter: ChapterDto,
+        # parents: list[ChapterDto],
+        # children: list[ChapterDto],
+        files: list[ExcelParseFileDto],
     ):
         try:
             budgets_data = [ExcelParseZgsDto.from_dto(budget) for budget in budgets]
@@ -233,10 +248,10 @@ class TaskProcessor:
             raise Exception(msg)
 
     def _insert_data(
-            self,
-            task: ProjectTaskDto,
-            project: ProjectDto,
-            data: list[ExcelParseResultDataDto],
+        self,
+        task: ProjectTaskDto,
+        project: ProjectDto,
+        data: list[ExcelParseResultDataDto],
     ):
         try:
             self._logger.debug(f"开始插入数据:{task.task_name}")

+ 2 - 1
SourceCode/IntelligentRailwayCosting/app/executor/task_sender.py

@@ -44,7 +44,7 @@ class TaskSender:
             self._logger.debug(f"开始推送定额输入[{quota.id}]")
             self._quota_store.update_send_status(quota.id, SendStatusEnum.SUCCESS.value)
             quota_input = QuotaInputDto.from_quota_dto(quota)
-            quota_input.project_name = f"{quota_input.project_name}{configs.app.ai_flag if quota.is_change else ''}"
+            quota_input.project_name = f"{quota_input.project_name}{configs.app.ai_flag if quota.is_change else configs.app.ai_name_flag}"
             self._save_quota(quota_input, quota.project_id, quota.id)
             # self._quota_store.update_send_status(quota.id, SendStatusEnum.SUCCESS.value)
             self._logger.debug(f"推送定额输入[{quota.id}]完成")
@@ -65,6 +65,7 @@ class TaskSender:
                     f"修改定额输入[{quota.quota_id}]:{quota.project_name} {quota.quota_code} {quota.quota_id}"
                 )
                 self._quota_input_store.update_quota(project_id, quota)
+                self._quota_store.update_quota_change(project_quota_id, False)
             else:
                 dto = self._quota_input_store.create_quota(project_id, quota)
                 self._quota_store.update_quota_id(project_quota_id, dto.quota_id)

+ 31 - 3
SourceCode/IntelligentRailwayCosting/app/routes/excel_test.py

@@ -60,12 +60,18 @@ def build_test_data(task_data):
         else:
             selected_zgs = selected_zgs[0]  # 取第一个匹配项
         zgs_code = selected_zgs.get("zgs_name")
-
     file_excel = task_data.get("files", None)
+    files = []
+    if file_excel:
+        for file in file_excel:
+            # print(f"File IS_NEW: {file.get('is_new')} File ID:{file.get("file_id")}")
+            if file.get("is_new") == 1:
+                files.append(file)
+
     # 从file_excel随机选择文件
     ex_file_id = ""
-    if file_excel:
-        file = random.choice(file_excel)
+    if len(files) > 0:
+        file = random.choice(files)
         ex_file_id = file.get("file_id")
     project_id = task_data.get("project_id", None)
     selected_chapter = task_data.get("selected_chapter", None)
@@ -118,6 +124,28 @@ def build_test_data(task_data):
         ex_row=ex_row,
         ex_unit=units,
         ex_amount=amount,
+        note=random.choice(
+            [
+                "",
+                "",
+                "",
+                "",
+                "",
+                "",
+                "",
+                "",
+                "说明:123456",
+                "",
+                "",
+                "",
+                "",
+                "",
+                "",
+                "",
+                "",
+                "说明:34567",
+            ]
+        ),
     )
 
 

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

@@ -77,7 +77,7 @@ def save_quota():
         run_now = data.get("run_now", "false") == "true"
         if run_now:
             is_cover = data.get("is_cover", "false") == "true"
-            quota_service.start_send(quota_dto.id, is_cover)
+            quota_service.start_send(quota_dto.id, is_cover, clear_num=True)
         return ResponseBase.success(quota_dto.to_dict())
     except Exception as e:
         return ResponseBase.error(f"保存定额条目失败:{str(e)}")

+ 2 - 0
SourceCode/IntelligentRailwayCosting/app/routes/project_task.py

@@ -48,6 +48,7 @@ def save_task(task_id: int):
         budget_id = int(form_data.get("budget_id")) if form_data.get("budget_id") else 0
         item_id = int(form_data.get("item_id")) if form_data.get("item_id") else None
         project_id = form_data.get("project_id")
+        parent_id = int(form_data.get("parent_id")) if form_data.get("task_sort") else 0
         item_code = form_data.get("item_code")
         task_name = form_data.get("task_name")
         task_desc = form_data.get("task_desc")
@@ -65,6 +66,7 @@ def save_task(task_id: int):
             budget_id=budget_id,
             project_id=project_id,
             item_code=item_code,
+            parent_id=parent_id,
             task_name=task_name,
             task_desc=task_desc,
             task_sort=task_sort,

+ 43 - 8
SourceCode/IntelligentRailwayCosting/app/services/project_quota.py

@@ -16,6 +16,7 @@ class ProjectQuotaService:
         self.quota_input_store = QuotaInputStore()
         self.chapter_store = ChapterStore()
         self._logger = utils.get_logger()
+        self._quota_sequence_num = {}
 
     def get_quotas_paginated(
             self,
@@ -186,7 +187,11 @@ class ProjectQuotaService:
                 quota_dto.ex_unit = quota.ex_unit
                 quota_dto.ex_amount = quota.ex_amount
                 quota_dto.note = quota.note
-                quota_dto.send_status = quota.send_status
+                quota_dto.send_status = (
+                    SendStatusEnum.CHANGE.value
+                    if quota.send_status == SendStatusEnum.SUCCESS.value
+                    else quota.send_status
+                )
                 quota_dto.send_time = quota.send_time
                 quota_dto.send_error = ""
 
@@ -224,6 +229,11 @@ class ProjectQuotaService:
                 quota_dto.is_change = 0
             else:
                 quota_dto.is_change = 1
+            quota_dto.send_status = (
+                SendStatusEnum.CHANGE.value
+                if quota_dto.send_status == SendStatusEnum.SUCCESS.value
+                else quota_dto.send_status
+            )
             # self.update_process_status(quota_dto.id,4)
             LogRecordHelper.log_success(
                 OperationType.UPDATE,
@@ -258,6 +268,11 @@ class ProjectQuotaService:
                         continue
                     quota_dto.quota_code = quota_code
                     quota_dto.is_change = 1
+                    quota_dto.send_status = (
+                        SendStatusEnum.CHANGE.value
+                        if quota_dto.send_status == SendStatusEnum.SUCCESS.value
+                        else quota_dto.send_status
+                    )
                     self.store.update_quota(quota_dto)
                 except Exception as e:
                     msg += f"{id}: {str(e)};"
@@ -392,6 +407,7 @@ class ProjectQuotaService:
         try:
             id_list = ids.split(",")
             err = ""
+            self._quota_sequence_num = {}
             for _id in id_list:
                 msg = self.start_send(int(_id), is_cover, False)
                 if msg:
@@ -424,6 +440,7 @@ class ProjectQuotaService:
         try:
             quota_list = self.store.get_quotas_by_task_id(task_id)
             err = ""
+            self._quota_sequence_num = {}
             for quota in quota_list:
                 msg = self.start_send(
                     quota.id, True if quota.quota_id else False, False
@@ -455,14 +472,27 @@ class ProjectQuotaService:
             raise
 
     def start_send(
-            self, _id: int, is_cover: bool = False, is_log: bool = True
+            self,
+            _id: int,
+            is_cover: bool = False,
+            is_log: bool = True,
+            clear_num: bool = False,
     ) -> Optional[str]:
         """启动推送"""
+        if clear_num:
+            self._quota_sequence_num = {}
         quota = self.get_quota_dto(_id)
         if quota:
             self.update_send_status(_id, SendStatusEnum.SUCCESS.value)
             if not is_cover:
                 quota.quota_id = 0
+            quota.sequence_number = (
+                self._get_quota_sequence_num(
+                    quota.project_id, quota.budget_id, quota.item_id
+                )
+                if not is_cover or quota.is_change == 2
+                else quota.sequence_number
+            )
             # thread = threading.Thread(target=self._send_quota, args=(quota,))
             # thread.start()
             if is_log:
@@ -472,12 +502,6 @@ class ProjectQuotaService:
                     f"推送定额条目",
                     f"ID:{_id},是否覆盖:{is_cover}",
                 )
-
-            if not is_cover:
-                num = self.quota_input_store.query_quota_sequence_number(
-                    quota.project_id, quota.budget_id, quota.item_id
-                )
-                quota.sequence_number = num
             if self._send_quota(quota):
                 return None
             else:
@@ -485,6 +509,17 @@ class ProjectQuotaService:
         else:
             return "定额条目没有查询到"
 
+    def _get_quota_sequence_num(self, project_id, budget_id, item_id):
+        num = self._quota_sequence_num.get(f"{project_id}_{budget_id}_{item_id}")
+        if num:
+            num += 1
+        else:
+            num = self.quota_input_store.query_quota_sequence_number(
+                project_id, budget_id, item_id
+            )
+        self._quota_sequence_num[f"{project_id}_{budget_id}_{item_id}"] = num
+        return num
+
     def _send_quota(self, quota: ProjectQuotaDto):
         try:
             executor.send_quota(quota)

+ 3 - 1
SourceCode/IntelligentRailwayCosting/app/stores/quota_input.py

@@ -190,7 +190,9 @@ class QuotaInputStore:
             model.budget_id = dto.budget_id
             model.item_id = dto.item_id
             model.quota_code = dto.quota_code
-            model.sequence_number = model.sequence_number
+            model.sequence_number = (
+                dto.sequence_number if dto.sequence_number else model.sequence_number
+            )
             model.project_name = dto.project_name
             model.unit = dto.unit
             model.project_quantity = dto.project_quantity

+ 12 - 2
SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_mysql/project_quota.py

@@ -274,15 +274,24 @@ class ProjectQuotaStore:
             raise Exception(f"定额条目[{id}]不存在")
         with db_helper.mysql_session(self._database) as db_session:
             quota.quota_id = quota_id
+            quota.is_change = 0
             quota = db_session.merge(quota)
             return ProjectQuotaDto.from_model(quota)
 
-    def update_quota_change(self, quota_id: int):
+    def update_quota_change(self, quota_id: int, is_change: bool):
         quota = self.get_quota(quota_id)
         if not quota:
             raise Exception(f"定额条目[{quota_id}]不存在")
         with db_helper.mysql_session(self._database) as db_session:
-            quota.is_change = 1
+            if is_change:
+                quota.is_change = 1
+                quota.send_status = (
+                    SendStatusEnum.CHANGE.value
+                    if quota.send_status == SendStatusEnum.SUCCESS.value
+                    else quota.send_status
+                )
+            else:
+                quota.is_change = 0
             quota = db_session.merge(quota)
             return ProjectQuotaDto.from_model(quota)
 
@@ -295,6 +304,7 @@ class ProjectQuotaStore:
         with db_helper.mysql_session(self._database) as db_session:
             quota.item_id = item_id
             quota.item_code = item_code
+            quota.is_change = 2
             quota.send_status = (
                 SendStatusEnum.CHANGE.value
                 if quota.send_status == SendStatusEnum.SUCCESS.value

+ 1 - 0
SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_mysql/project_task.py

@@ -149,6 +149,7 @@ class ProjectTaskStore:
             ProjectTaskDto
         """
         task = ProjectTaskModel(
+            parent_id=task_dto.parent_id,
             task_name=task_dto.task_name,
             task_desc=task_dto.task_desc,
             task_sort=task_dto.task_sort,

+ 17 - 3
SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_sqlserver/project_quota.py

@@ -268,12 +268,20 @@ class ProjectQuotaStore:
             quota = db_session.merge(quota)
             return ProjectQuotaDto.from_model(quota)
 
-    def update_quota_change(self, quota_id: int):
+    def update_quota_change(self, quota_id: int, is_change: bool):
         quota = self.get_quota(quota_id)
         if not quota:
             raise Exception(f"定额条目[{quota_id}]不存在")
-        with db_helper.sqlserver_session(self._database) as db_session:
-            quota.is_change = 1
+        with db_helper.mysql_session(self._database) as db_session:
+            if is_change:
+                quota.is_change = 1
+                quota.send_status = (
+                    SendStatusEnum.CHANGE.value
+                    if quota.send_status == SendStatusEnum.SUCCESS.value
+                    else quota.send_status
+                )
+            else:
+                quota.is_change = 0
             quota = db_session.merge(quota)
             return ProjectQuotaDto.from_model(quota)
 
@@ -286,6 +294,12 @@ class ProjectQuotaStore:
         with db_helper.sqlserver_session(self._database) as db_session:
             quota.item_id = item_id
             quota.item_code = item_code
+            quota.is_change = 2
+            quota.send_status = (
+                SendStatusEnum.CHANGE.value
+                if quota.send_status == SendStatusEnum.SUCCESS.value
+                else quota.send_status
+            )
             quota.updated_by = self.current_user.username
             quota.updated_at = datetime.now()
             quota = db_session.merge(quota)

+ 13 - 5
SourceCode/IntelligentRailwayCosting/app/views/static/base/css/styles.css

@@ -109,14 +109,22 @@ body > .container-fluid {
     color: #666;
 }
 
-.table-box tr.tr-waring td {
-    background: #FFFACD;
+.table-box tr.tr-warning td {
+    background: #FFF8DD;
     box-shadow: none;
 }
 
-.table-box tr.tr-waring:hover td {
-    --bs-table-bg-state: #FFE4B5;
-    background: #FFE4B5;
+.table-box tr.tr-warning:hover td {
+    background: #FFF1BC;
+}
+
+.table-box tr.tr-primary td {
+    background: #E4F0FF;
+    box-shadow: none;
+}
+
+.table-box tr.tr-primary:hover td {
+    background: #D2E6FF;
 }
 
 .table-box td .one-line {

+ 3 - 9
SourceCode/IntelligentRailwayCosting/app/views/static/base/js/utils.js

@@ -244,6 +244,7 @@ function IwbTable(table, opts, isReload) {
                 const $this = $(this), $input = $this.find('.edit-td-input'), $label = $this.find('.edit-label')
                 // $tableBox.find(`.edit-td-input`).hide()
                 // $tableBox.find(`.edit-label`).show()
+                $input.find('.form-control').val($this.data('val'))
                 $(document).on("click.td-edit", function (event) {
                     if ($(event.target).closest($this).length) {
                         return
@@ -272,16 +273,9 @@ function IwbTable(table, opts, isReload) {
                                 key: key,
                                 val: val
                             },
+                            table: $table,
                             success: function (res) {
-                                if (res.success) {
-                                    $this.data('val', val)
-                                    $label.find('.edit-text').text(val)
-                                    if ($label.attr('data-bs-original-title')) {
-                                        $label.attr('data-bs-original-title', val)
-                                    }
-                                    $input.off("keyup.edit").hide();
-                                    $label.show()
-                                } else {
+                                if (!res.success) {
                                     $input.val($this.data('val'))
                                     MsgError(res.msg)
                                 }

+ 25 - 0
SourceCode/IntelligentRailwayCosting/app/views/static/project/budget_info.js

@@ -19,6 +19,7 @@ const task_modal_template = `
             <input type="hidden" name="item_id" value="">
             <input type="hidden" name="item_code" value="">
             <input type="hidden" name="task_id" value="">
+            <input type="hidden" name="parent_id" value="">
             <div class="fv-row form-group mb-3">
               <label for="task_name" class="form-label required">任务名称</label>
               <input type="text" class="form-control" name="task_name" id="task_name" placeholder="请输入任务名称" required />
@@ -177,6 +178,11 @@ function RenderRightBox_Custom(data) {
         const $table = $taskBox.find('.table')
         IwbTable($table, {
             url: `/api/task/list/${project_id}/${data.item_code}`,
+            trClass: (row) => {
+                if (row.parent_id && row.parent_id !== 0) {
+                    return 'tr-primary'
+                }
+            },
             columns: [
                 {
                     title: '任务编号',
@@ -306,6 +312,7 @@ function RenderRightBox_Custom(data) {
                         str += `<!--<button type="button" class="btn btn-icon btn-sm btn-light-primary" data-bs-toggle="tooltip" data-bs-placement="top" title="编辑" onclick="Edit(${row.id})"><i class="ki-duotone ki-message-edit fs-1"><span class="path1"></span><span class="path2"></span></i></button>-->`
                         if (row.process_status === 2 || row.process_status === 200) {
                             str += `<button type="button" class="btn btn-icon btn-sm btn-light-primary"  data-bs-toggle="tooltip" data-bs-placement="top" title="定额输入列表" onclick="GoTo('/quota_info/${project_id}/${row.id}',0)"><i class="ki-duotone ki-eye fs-1"><span class="path1"></span><span class="path2"></span><span class="path3"></span><span class="path4"></span></i></button>`
+                            str += `<button type="button" class="btn btn-icon btn-sm btn-light-info"  data-bs-toggle="tooltip" data-bs-placement="top" title="再次提交任务" onclick="ReCreate(${row.id})"><i class="ki-duotone ki-add-notepad fs-1"><span class="path1"></span><span class="path2"></span><span class="path3"></span><span class="path4"></span></i></button>`
                         }
                         if (row.process_status !== 2 && row.process_status !== 1) {
                             str += `<button type="button" class="btn btn-icon btn-sm btn-light-danger"  data-bs-toggle="tooltip" data-bs-placement="top" title="删除" onclick="Delete(${row.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>`
@@ -330,10 +337,25 @@ function SetBudgetData($el) {
 function Add() {
     _fileUploadDropzone.removeAllFiles()
     AddModal($modal, () => {
+        $modal.find('[name="parent_id"]').val('0');
+        $modal.find('[name="task_id"]').val('0');
+        $modal.find('[name="budget_id"]').val(0);
+        $modal.find('[name="task_sort"]').val(0);
+        $modal.find('#delete_file_box').hide();
+        // $modal.find('[name="delete_file"]').prop('checked',false)
+        SetBudgetData($modal)
+    })
+}
+
+function ReCreate(parent_id) {
+    _fileUploadDropzone.removeAllFiles()
+    AddModal($modal, () => {
+        $modal.find('[name="parent_id"]').val(parent_id);
         $modal.find('[name="task_id"]').val('0');
         $modal.find('[name="budget_id"]').val(0);
         $modal.find('[name="task_sort"]').val(0);
         $modal.find('#delete_file_box').hide();
+        $modal.find('.modal-header .modal-title span.prefix').html('重新提交')
         // $modal.find('[name="delete_file"]').prop('checked',false)
         SetBudgetData($modal)
     })
@@ -353,6 +375,7 @@ function Edit(id) {
                 // $modal.find('#delete_file_box').show();
                 // SetBudgetData(budget_id)
                 $modal.find('[name="task_id"]').val(data.id);
+                $modal.find('[name="parent_id"]').val(data.parent_id);
                 $modal.find('[name="budget_id"]').val(data.budget_id);
                 $modal.find('[name="project_id"]').val(data.project_id);
                 $modal.find('[name="item_id"]').val(data.item_id);
@@ -374,6 +397,7 @@ function SaveProject(is_submit) {
         item_code = $modal.find('[name="item_code"]').val(),
         project_id = $modal.find('[name="project_id"]').val(),
         task_id = $modal.find('[name="task_id"]').val(),
+        parent_id = $modal.find('[name="parent_id"]').val(),
         task_name = $modal.find('[name="task_name"]').val(),
         task_sort = $modal.find('[name="task_sort"]').val(),
         task_desc = $modal.find('[name="task_desc"]').val(),
@@ -394,6 +418,7 @@ function SaveProject(is_submit) {
     formData.append('item_code', item_code)
     formData.append('project_id', project_id)
     formData.append('task_id', task_id)
+    formData.append('parent_id', parent_id)
     formData.append('task_name', task_name)
     formData.append('task_sort', task_sort)
     formData.append('task_desc', task_desc)

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

@@ -383,22 +383,22 @@ function LoadQuotaTable(table) {
         // checkBoxDisable: (row) => {
         //     return row.send_status === 200
         // },
-        // canEdit: (row) => {
-        //     return row.send_status !== 200
-        // },
+        canEdit: (row) => {
+            return true
+        },
         trClass: (row) => {
-            return row.note || !row.quota_code ? 'tr-waring' : ''
+            return row.note || !row.quota_code ? 'tr-warning' : ''
         },
         columns: [
-            // {
-            //     title: '序号',
-            //     data: 'id',
-            //     width: '80px',
-            // },
+            {
+                title: '序号',
+                data: 'id',
+                width: '80px',
+            },
             {
                 title: '条⽬编号',
                 data: 'item_code',
-                width: '200px',
+                width: '190px',
                 tdStyle: "text-align:right;padding-right:15px;",
                 render: (row) => {
                     return `<span class="one-line edit-label" data-bs-toggle="tooltip" data-bs-placement="top" title="${row.item_code}" ><span class="edit-text">${row.item_code}</span></span>`
@@ -407,7 +407,7 @@ function LoadQuotaTable(table) {
             {
                 title: '工程或费用项目名称',
                 data: 'entry_name',
-                width: '255px',
+                width: '240px',
                 isEdit: true,
                 render: (row) => {
                     return `<span class="one-line edit-label" data-bs-toggle="tooltip" data-bs-placement="top" title="${row.entry_name}" ><span class="edit-text">${row.entry_name}</span></span>`
@@ -435,9 +435,9 @@ function LoadQuotaTable(table) {
                         return `<span class="badge badge-light-danger edit-label" data-bs-toggle="tooltip" data-bs-placement="top" title="${row.note ? `说明:${row.note}` : '未查询到'}" ><span class="edit-text">未查询到</span></span>`
                     }
                     if (row.quota_id) {
-                        return `<span class="badge badge-light-info edit-label" data-bs-toggle="tooltip" data-bs-placement="top" title="可覆盖系统数据:${row.quota_id} | ${row.note ? `说明:${row.note}` : ''}" ><span class="edit-text">${row.quota_code}</span></span>`
+                        return `<span class="badge badge-light-info edit-label" data-bs-toggle="tooltip" data-bs-placement="top" title="${row.quota_id}${row.note ? `说明:${row.note}` : ''}" ><span class="edit-text">${row.quota_code}</span></span>`
                     } else {
-                        return `<span class="badge badge-light-primary edit-label" data-bs-toggle="tooltip" data-bs-placement="top" title="未关联系统数据 | ${row.note ? `说明:${row.note}` : ''}" ><span class="edit-text">${row.quota_code}</span></span>`
+                        return `<span class="badge badge-light-primary edit-label" data-bs-toggle="tooltip" data-bs-placement="top" title="${row.note ? `说明:${row.note}` : ''}" ><span class="edit-text">${row.quota_code}</span></span>`
                     }
                 }
             },