修改schema
This commit is contained in:
@@ -59,6 +59,39 @@ class BaseResponseWithID(BaseModel):
|
||||
# 默认返回(已有 id)
|
||||
return data
|
||||
|
||||
|
||||
def _get_plan_node_date(node: Any) -> Any:
|
||||
if isinstance(node, dict):
|
||||
return node.get("date")
|
||||
return getattr(node, "date", None)
|
||||
|
||||
|
||||
def trim_plan_nodes_by_boundary_dates(
|
||||
plan_nodes: Optional[List[Any]],
|
||||
start_date: Optional[Any] = None,
|
||||
end_date: Optional[Any] = None,
|
||||
) -> List[Any]:
|
||||
if not plan_nodes:
|
||||
return []
|
||||
|
||||
cleaned_nodes = list(plan_nodes)
|
||||
|
||||
while (
|
||||
start_date is not None
|
||||
and cleaned_nodes
|
||||
and str(_get_plan_node_date(cleaned_nodes[0])) == str(start_date)
|
||||
):
|
||||
cleaned_nodes.pop(0)
|
||||
|
||||
while (
|
||||
end_date is not None
|
||||
and cleaned_nodes
|
||||
and str(_get_plan_node_date(cleaned_nodes[-1])) == str(end_date)
|
||||
):
|
||||
cleaned_nodes.pop()
|
||||
|
||||
return cleaned_nodes
|
||||
|
||||
class RequestApprovalMixin(BaseModel):
|
||||
"""
|
||||
请求审批相关的基础模型
|
||||
|
||||
@@ -24,7 +24,18 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import TypeVar, Generic, Optional, List, Dict, Any
|
||||
from ..project.enums import ProjectType
|
||||
from datetime import datetime
|
||||
# from backend.modules.admin.approval.models import ApprovalStep
|
||||
|
||||
|
||||
class ApprovalStep(BaseModel):
|
||||
"""审批流程中的一个步骤"""
|
||||
step_id: str = Field(description="步骤的唯一ID")
|
||||
operator_id: Optional[str] = Field("",description="操作人ID")
|
||||
operator_name: str = Field(description="操作人姓名")
|
||||
action: str = Field(description="执行的动作,如 'submit', 'approve', 'reject'")
|
||||
comment: Optional[str] = Field(None, description="审批意见")
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow, description="步骤的创建时间")
|
||||
# 定义泛型
|
||||
T = TypeVar('T', bound='BasePushData')
|
||||
|
||||
@@ -52,7 +63,7 @@ class SignatureMixin(BaseModel):
|
||||
class PushRequestMixin(BaseModel):
|
||||
"""推送ID字段混入"""
|
||||
push_id: str = Field(..., description="推送ID,客户端生成的唯一标识(UUIDv7,支持时间排序)")
|
||||
|
||||
steps : List[ApprovalStep] =Field(default_factory=list,description="当前数据的审批流程步骤") # 加入字段step用来记录审批流程
|
||||
|
||||
# ==================== 响应相关 ====================
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from pydantic import BaseModel, Field, field_validator, model_validator
|
||||
from fastapi import HTTPException
|
||||
from typing import List, Optional, Literal
|
||||
from ..common.validators import DateStr
|
||||
from ..common.base import BaseResponseWithID
|
||||
from ..common.base import BaseResponseWithID, trim_plan_nodes_by_boundary_dates
|
||||
|
||||
class PlanNode(BaseModel):
|
||||
date: DateStr = Field(..., description="计划节点时间", examples=["2024-01-01"])
|
||||
@@ -40,6 +40,15 @@ class ConstructionResponse(BaseResponseWithID):
|
||||
# 2025-10-20新增字段,延迟时间
|
||||
delay_days: Optional[int] = Field(None, description="延迟时间(天)", examples=[0])
|
||||
|
||||
@model_validator(mode='after')
|
||||
def clean_response_plan_nodes(self):
|
||||
self.plan_node = trim_plan_nodes_by_boundary_dates(
|
||||
self.plan_node,
|
||||
start_date=self.plan_start_date,
|
||||
end_date=self.plan_complete_date,
|
||||
)
|
||||
return self
|
||||
|
||||
class ConstructionCreate(BaseModel):
|
||||
"""
|
||||
施工任务创建模型
|
||||
|
||||
@@ -2,7 +2,7 @@ from pydantic import Field, BaseModel,field_validator,model_validator
|
||||
from fastapi import HTTPException
|
||||
from typing import List, Optional, Literal
|
||||
from ..common.validators import DateStr
|
||||
from ..common.base import BaseResponseWithID
|
||||
from ..common.base import BaseResponseWithID, trim_plan_nodes_by_boundary_dates
|
||||
|
||||
class PlanNode(BaseModel):
|
||||
date: DateStr = Field(..., description="计划节点时间", examples=["2024-01-01"])
|
||||
@@ -39,6 +39,15 @@ class ContractSettlementResponse(BaseResponseWithID):
|
||||
None, description="统计节点截止日期", examples=["2024-05-31"]
|
||||
)
|
||||
|
||||
@model_validator(mode='after')
|
||||
def clean_response_plan_nodes(self):
|
||||
self.plan_node = trim_plan_nodes_by_boundary_dates(
|
||||
self.plan_node,
|
||||
start_date=self.plan_start_date,
|
||||
end_date=self.plan_complete_date,
|
||||
)
|
||||
return self
|
||||
|
||||
class ContractSettlementCreate(BaseModel):
|
||||
"""
|
||||
合同结算创建模型
|
||||
@@ -90,7 +99,7 @@ class ContractSettlementPlanUpdate(BaseModel):
|
||||
cleaned_nodes and
|
||||
cleaned_nodes[0].date == self.plan_start_date):
|
||||
cleaned_nodes.pop(0)
|
||||
|
||||
|
||||
# 删除结尾的边界节点
|
||||
if (self.plan_complete_date and
|
||||
cleaned_nodes and
|
||||
|
||||
@@ -2,7 +2,7 @@ from pydantic import Field, BaseModel,field_validator,model_validator
|
||||
from fastapi import HTTPException
|
||||
from typing import List, Optional, Literal
|
||||
from ..common.validators import DateStr
|
||||
from ..common.base import BaseResponseWithID
|
||||
from ..common.base import BaseResponseWithID, trim_plan_nodes_by_boundary_dates
|
||||
|
||||
class PlanNode(BaseModel):
|
||||
date: DateStr = Field(..., description="计划节点时间", examples=["2024-01-01"])
|
||||
@@ -52,6 +52,15 @@ class MaterialResponse(BaseResponseWithID):
|
||||
None, description="统计节点截止日期", examples=["2024-05-31"]
|
||||
)
|
||||
|
||||
@model_validator(mode='after')
|
||||
def clean_response_plan_nodes(self):
|
||||
self.plan_node = trim_plan_nodes_by_boundary_dates(
|
||||
self.plan_node,
|
||||
start_date=self.plan_start_date,
|
||||
end_date=self.plan_complete_date,
|
||||
)
|
||||
return self
|
||||
|
||||
class MaterialCreate(BaseModel):
|
||||
"""
|
||||
创建物资
|
||||
|
||||
+10
-1
@@ -2,7 +2,7 @@ from pydantic import Field, BaseModel,field_validator,model_validator
|
||||
from fastapi import HTTPException
|
||||
from typing import List, Optional, Literal
|
||||
from ..common.validators import DateStr
|
||||
from ..common.base import BaseResponseWithID
|
||||
from ..common.base import BaseResponseWithID, trim_plan_nodes_by_boundary_dates
|
||||
|
||||
class PlanNode(BaseModel):
|
||||
date: DateStr = Field(..., description="计划节点时间", examples=["2024-01-01"])
|
||||
@@ -61,6 +61,15 @@ class PowerResponse(BaseResponseWithID):
|
||||
None, description="实际首批到场人数", examples=[4]
|
||||
)
|
||||
|
||||
@model_validator(mode='after')
|
||||
def clean_response_plan_nodes(self):
|
||||
self.plan_node = trim_plan_nodes_by_boundary_dates(
|
||||
self.plan_node,
|
||||
start_date=self.plan_start_date,
|
||||
end_date=self.plan_exit_date,
|
||||
)
|
||||
return self
|
||||
|
||||
class PowerCreate(BaseModel):
|
||||
"""
|
||||
创建力能
|
||||
|
||||
@@ -74,3 +74,12 @@ class ProjectInfoResponse(BaseModel):
|
||||
# 里程碑节点
|
||||
preparation_list: list[PreparationDetail] = Field(default_factory=list, description="里程碑节点列表")
|
||||
|
||||
|
||||
class ProjectListResponse(BaseModel):
|
||||
"""
|
||||
项目列表模型:分页
|
||||
"""
|
||||
page: int
|
||||
page_size: int
|
||||
total: int
|
||||
list: list[ProjectInfoResponse]
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
"""
|
||||
一个接口按 domain 返回项目聚合数据。
|
||||
"""
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ..construction.construction import ConstructionResponse
|
||||
from ..construction.construction_detail import ConstructionDetailResponse
|
||||
from ..contract.contract import ContractResponse
|
||||
from ..contract_settlement.contract_settlement import (
|
||||
ContractSettlementResponse,
|
||||
)
|
||||
from ..contract_settlement.contract_settlement_detail import (
|
||||
ContractSettlementDetailResponse,
|
||||
)
|
||||
from ..material.material import MaterialResponse
|
||||
from ..material.material_detail import MaterialDetailResponse
|
||||
from ..power.power import PowerResponse
|
||||
from ..power.power_detail import PowerDetail
|
||||
from ..project.project_base import ProjectResponseBase
|
||||
from ..project_extensions.contract_change import ContractChangeResponse
|
||||
from ..project_extensions.generator_unit import GeneratorUnitProject
|
||||
from ..project_extensions.lar import (
|
||||
LarChangeDetailSchema,
|
||||
LarDetailSchema,
|
||||
)
|
||||
from ..project_extensions.preparation import PreparationDetail
|
||||
from ..project_extensions.production import ProductionDetail
|
||||
from ..project_extensions.project_detail import ProjectDetailProject
|
||||
from ..project_multi_type import (
|
||||
GasTurbineExtension,
|
||||
HydropowerExtension,
|
||||
OffShoreWindExtension,
|
||||
OnShoreWindExtension,
|
||||
PhotovoltaicExtension,
|
||||
PumpedStorageExtension,
|
||||
ThermalPowerExtension,
|
||||
)
|
||||
|
||||
|
||||
ProjectAggregateDomain = Literal[
|
||||
"project",
|
||||
"project_detail",
|
||||
"generator_units",
|
||||
"lar",
|
||||
"lar_change",
|
||||
"contract_changes",
|
||||
"photovoltaic_extension",
|
||||
"onshore_wind_extension",
|
||||
"offshore_wind_extension",
|
||||
"hydropower_extension",
|
||||
"pumped_storage_extension",
|
||||
"gas_turbine_extension",
|
||||
"thermal_power_extension",
|
||||
"production_list",
|
||||
"preparation_list",
|
||||
"contracts",
|
||||
"materials",
|
||||
"material_details",
|
||||
"constructions",
|
||||
"construction_details",
|
||||
"contract_settlements",
|
||||
"contract_settlement_details",
|
||||
"power",
|
||||
"power_details",
|
||||
]
|
||||
|
||||
|
||||
ALL_PROJECT_AGGREGATE_DOMAINS: list[ProjectAggregateDomain] = [
|
||||
"project",
|
||||
"project_detail",
|
||||
"generator_units",
|
||||
"lar",
|
||||
"lar_change",
|
||||
"contract_changes",
|
||||
"photovoltaic_extension",
|
||||
"onshore_wind_extension",
|
||||
"offshore_wind_extension",
|
||||
"hydropower_extension",
|
||||
"pumped_storage_extension",
|
||||
"gas_turbine_extension",
|
||||
"thermal_power_extension",
|
||||
"production_list",
|
||||
"preparation_list",
|
||||
"contracts",
|
||||
"materials",
|
||||
"material_details",
|
||||
"constructions",
|
||||
"construction_details",
|
||||
"contract_settlements",
|
||||
"contract_settlement_details",
|
||||
"power",
|
||||
"power_details",
|
||||
]
|
||||
|
||||
|
||||
class ProjectAggregateData(BaseModel):
|
||||
"""按 domain 组织的项目聚合数据。"""
|
||||
|
||||
project: ProjectResponseBase | None = Field(None, description="项目基本信息")
|
||||
project_detail: list[ProjectDetailProject] = Field(default_factory=list, description="项目容量信息")
|
||||
generator_units: list[GeneratorUnitProject] = Field(default_factory=list, description="发电单元信息")
|
||||
lar: LarDetailSchema | None = Field(None, description="移民征地信息")
|
||||
lar_change: list[LarChangeDetailSchema] = Field(default_factory=list, description="移民变更信息")
|
||||
contract_changes: list[ContractChangeResponse] = Field(default_factory=list, description="合同变更信息")
|
||||
|
||||
photovoltaic_extension: PhotovoltaicExtension | None = Field(None, description="光伏项目扩展信息")
|
||||
onshore_wind_extension: OnShoreWindExtension | None = Field(None, description="陆风项目扩展信息")
|
||||
offshore_wind_extension: OffShoreWindExtension | None = Field(None, description="海风项目扩展信息")
|
||||
hydropower_extension: HydropowerExtension | None = Field(None, description="水电项目扩展信息")
|
||||
pumped_storage_extension: PumpedStorageExtension | None = Field(None, description="抽蓄项目扩展信息")
|
||||
gas_turbine_extension: GasTurbineExtension | None = Field(None, description="燃气轮机项目扩展信息")
|
||||
thermal_power_extension: ThermalPowerExtension | None = Field(None, description="火电项目扩展信息")
|
||||
|
||||
production_list: list[ProductionDetail] = Field(default_factory=list, description="投产节点列表")
|
||||
preparation_list: list[PreparationDetail] = Field(default_factory=list, description="里程碑节点列表")
|
||||
|
||||
contracts: list[ContractResponse] = Field(default_factory=list, description="合同列表")
|
||||
materials: list[MaterialResponse] = Field(default_factory=list, description="物资主表列表")
|
||||
material_details: list[MaterialDetailResponse] = Field(default_factory=list, description="物资明细列表")
|
||||
constructions: list[ConstructionResponse] = Field(default_factory=list, description="施工主表列表")
|
||||
construction_details: list[ConstructionDetailResponse] = Field(default_factory=list, description="施工明细列表")
|
||||
contract_settlements: list[ContractSettlementResponse] = Field(default_factory=list, description="合同结算主表列表")
|
||||
contract_settlement_details: list[ContractSettlementDetailResponse] = Field(default_factory=list, description="合同结算明细列表")
|
||||
power: list[PowerResponse] = Field(default_factory=list, description="力能主表列表")
|
||||
power_details: list[PowerDetail] = Field(default_factory=list, description="力能明细列表")
|
||||
|
||||
|
||||
class ProjectAggregateMeta(BaseModel):
|
||||
"""聚合请求回显和截断信息。"""
|
||||
|
||||
project_id: str = Field(..., description="项目ID")
|
||||
requested_domains: list[ProjectAggregateDomain] = Field(
|
||||
default_factory=lambda: list(ALL_PROJECT_AGGREGATE_DOMAINS),
|
||||
description="本次请求的 domain 列表",
|
||||
)
|
||||
domain_limit: int = Field(10000, description="每个 domain 的最大返回条数")
|
||||
truncated_domains: list[ProjectAggregateDomain] = Field(default_factory=list, description="被截断的 domain 列表")
|
||||
|
||||
|
||||
class ProjectAggregateResponse(BaseModel):
|
||||
"""项目聚合同步响应。"""
|
||||
|
||||
meta: ProjectAggregateMeta = Field(..., description="请求元信息")
|
||||
data: ProjectAggregateData = Field(
|
||||
default_factory=lambda: ProjectAggregateData.model_construct(),
|
||||
description="按 domain 分组的数据",
|
||||
)
|
||||
@@ -78,6 +78,7 @@ class ProjectResponseBase(BaseResponseWithID):
|
||||
|
||||
# 拓展信息
|
||||
dbtc_score_list: Optional[list] = Field(default_factory=list, description='达标投产评分列表') # 这里项目用的是list而不是List
|
||||
approval_status: Optional[str] = Field(None, description="审核状态", examples=["pending"])
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ class PostGeneratorUnits(BaseModel):
|
||||
|
||||
@field_validator('actual_production_date', mode="before")
|
||||
def validate_actual_date_not_future(cls, v: str) -> str:
|
||||
if v == "":
|
||||
return ""
|
||||
# if v == "":
|
||||
# return ""
|
||||
try:
|
||||
actual = datetime_date(v)
|
||||
except ValueError:
|
||||
|
||||
@@ -1,8 +1,39 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from fastapi import HTTPException
|
||||
from pydantic import BaseModel, Field, model_validator, field_validator
|
||||
from typing import List, Optional
|
||||
from enum import Enum
|
||||
from ..common.base import BaseResponseWithID
|
||||
|
||||
|
||||
class SupplierType(str, Enum):
|
||||
"""类型枚举"""
|
||||
PROJECT = "project" # 项目类型
|
||||
OTHER = "other" # 其他类型
|
||||
|
||||
@classmethod
|
||||
def supplier_list(cls):
|
||||
return [cls.PROJECT.value, cls.OTHER.value]
|
||||
|
||||
class SupplierNature(str, Enum):
|
||||
"""
|
||||
性质枚举
|
||||
"""
|
||||
CONSTRUCTION = "construction" # 施工单位
|
||||
DESIGN = "design" # 设计单位
|
||||
SUPERVISORY = "supervisory" # 监理单位
|
||||
SUPPLIER = "supplier" # 供应商
|
||||
SURVEY = "survey" # 2026.3.11 推送系统测试时发现与项目侧 org_role 不一致,已补充勘察单位
|
||||
GOVERMENT = "goverment" # 政府
|
||||
|
||||
@classmethod
|
||||
def supplier_type_list(cls):
|
||||
return [member.value for member in cls]
|
||||
|
||||
class SupplierStatus(str, Enum):
|
||||
"""状态枚举"""
|
||||
ACTIVE = "active" # 正常
|
||||
INACTIVE = "inactive" # 禁用
|
||||
|
||||
class SupplierDetailResponse(BaseResponseWithID):
|
||||
"""
|
||||
供应商详情模型
|
||||
@@ -41,3 +72,66 @@ class SupplierListResponse(BaseModel):
|
||||
page_size: int
|
||||
total: int
|
||||
list: List[SupplierDetailResponse] = []
|
||||
|
||||
|
||||
class SupplierCreateRequest(BaseModel):
|
||||
"""
|
||||
供应商推送创建请求模型
|
||||
"""
|
||||
name: str = Field(..., description="供应商名称", examples=["腾讯集团"])
|
||||
short_name: str = Field(..., description="供应商简称", examples=["腾讯"])
|
||||
code: str = Field(..., description="供应商编码", examples=["TX001"])
|
||||
company_type: SupplierType = Field(..., description="供应商类型", examples=["project"])
|
||||
parent_id: Optional[str] = Field(None, description="上级供应商ID", examples=[None])
|
||||
status: SupplierStatus = Field(..., description="供应商状态", examples=["active"])
|
||||
path: Optional[List[str]] = Field(default_factory=list, description="供应商路径 [..., 上级id, 本身id]")
|
||||
path_name: Optional[List[str]] = Field(default_factory=list, description="供应商路径名称 [..., 上级名称, 本身名称]")
|
||||
credit_code: str = Field(..., description="供应商信用代码", examples=["914403007109310121"])
|
||||
region_path: Optional[list] = Field(default_factory=list, description="供应商国家城市地址编码列表, 需对接集团侧地图组件, 没有对接可以不传递", examples=[[
|
||||
"100000",
|
||||
"420000",
|
||||
"420800"
|
||||
]])
|
||||
company_nature: List[SupplierNature] = Field(..., description="供应商性质数组 可多选",
|
||||
examples=[['construction',
|
||||
'supplier',
|
||||
'design',
|
||||
'survey',
|
||||
'supervisory',
|
||||
'goverment']])
|
||||
|
||||
@model_validator(mode='after')
|
||||
def validate_company_type_and_nature(self):
|
||||
data = self.model_dump()
|
||||
|
||||
company_nature = data.get("company_nature")
|
||||
if not company_nature:
|
||||
raise HTTPException(detail=f"供应商:company_nature为空", status_code=400)
|
||||
for cn in company_nature:
|
||||
if cn not in SupplierNature.supplier_type_list():
|
||||
raise HTTPException(detail=f"供应商:company_nature范围:{SupplierNature.supplier_type_list()}",
|
||||
status_code=400)
|
||||
|
||||
company_type = data.get("company_type")
|
||||
if not company_type:
|
||||
raise HTTPException(detail=f"供应商:company_type为空", status_code=400)
|
||||
if company_type not in SupplierType.supplier_list():
|
||||
raise HTTPException(
|
||||
detail=f"供应商:company_type范围({SupplierType.supplier_list()})", status_code=400)
|
||||
|
||||
for field in ["name", "short_name", "code", "credit_code", "status"]:
|
||||
field_value = data.get(field)
|
||||
if not field_value:
|
||||
raise HTTPException(detail=f"字段{field}为空", status_code=400)
|
||||
return self
|
||||
|
||||
@field_validator(
|
||||
"credit_code",
|
||||
mode="after"
|
||||
)
|
||||
def clean_credit_code(cls, field_value):
|
||||
if field_value is None:
|
||||
return None
|
||||
import re
|
||||
return re.sub(r'\s+', '', str(field_value))
|
||||
|
||||
|
||||
@@ -17,16 +17,17 @@ class UserListDetailItem(BaseResponseWithID):
|
||||
status: Optional[str] = Field(None, description="状态")
|
||||
email: Optional[EmailStr] = Field(None, description="邮箱地址")
|
||||
user_type: str = Field(..., description="用户类型")
|
||||
last_login_at: Optional[str] = Field(None, description="最后登录时间")
|
||||
updated_at: Optional[str] = Field(None, description="更新时间")
|
||||
# last_login_at: Optional[str] = Field(None, description="最后登录时间")
|
||||
# updated_at: Optional[str] = Field(None, description="更新时间")
|
||||
company_id: Optional[str] = Field(None, description="公司id")
|
||||
company_name: Optional[str] = Field(None, description="公司名称")
|
||||
created_at: Optional[str] = Field(None, description="创建时间")
|
||||
approval_status: Optional[str] = Field(None, description="审批状态")
|
||||
approval_time: Optional[str] = Field(None, description="审批通过时间")
|
||||
last_login_ip: Optional[str] = Field(None, description="最后登录ip")
|
||||
# created_at: Optional[str] = Field(None, description="创建时间")
|
||||
# approval_status: Optional[str] = Field(None, description="审批状态") # 不再区分审批状态和审批时间
|
||||
# approval_time: Optional[str] = Field(None, description="审批通过时间")
|
||||
# last_login_ip: Optional[str] = Field(None, description="最后登录ip")
|
||||
project_ids: Optional[List[str]] = Field([], description="权限内项目id")
|
||||
contract_ids: Optional[List[str]] = Field([], description="权限内合同id")
|
||||
phone_hash: Optional[str] = Field(None, description="手机号hash")
|
||||
apply_files: Optional[List[Dict]] = Field([],
|
||||
examples=[
|
||||
{"id": "file:001", "name": "文件1",
|
||||
|
||||
Reference in New Issue
Block a user