Files
ecm_sync_system/schemas/project/project_base.py
T
2026-04-01 16:23:43 +08:00

382 lines
18 KiB
Python

"""
项目数据推送相关schema
"""
from typing import List, Optional
from fastapi import HTTPException
from pydantic import Field, BaseModel, field_validator, model_validator
from ..common.push_system import BaseUpdateRequestSchema
from ..common.base import BaseResponseWithID
from ..common.datetime import get_current_date
from ..common.validators import DateStr
from .enums import ProgressType, KeyConstraints, DBTCSCORE
class ProjectResponseBase(BaseResponseWithID):
"""
项目基础信息, 复制自project_model
不进行继承,通过检查器确保字段和模型一致
"""
# 基础信息-不允许更改
id : str = Field(..., description="项目ID", examples=["uuid"])
project_type: str = Field("", description="项目类型")
region_name: List[str] = Field(default_factory=list, description="项目所属区域名称", examples=[["华东", "江苏"]])
region_path: List[str] = Field(default_factory=list, description="项目所属区域路径", examples=[["110000", "120000", "120001"]])
province: str = Field("", description="省份(region_path第一个)", examples=["110000"])
is_domestic :bool=Field(True, description="是否是国内项目")
company_id: str = Field("", description="所属公司ID", examples=["company123"])
company_name: str = Field("", description="所属公司名称", examples=["国电投江苏公司"])
syxs: Optional[str] = Field(None, description="升压站型式")
sub_type: Optional[str] = Field(None, description="光伏类别")
scgc_type: Optional[str] = Field(None, description="送出工程建设方式")
# 基础信息-允许更改
address: str = Field("", description="项目地址", examples=["某省某市某区"])
code: str = Field("", description="项目编码", examples=["XM2025001"])
name: str = Field(..., description="项目名称", examples=["XX新能源项目"])
short_name: str = Field("", description="项目简称", examples=["XX项目"])
manager_name: str = Field("", description="负责人姓名", examples=["张三"])
manager_phone: str = Field("", description="负责人电话", examples=["13800000000"])
# 容量信息
authorization_date: Optional[DateStr] = Field(None, description="核准日期", examples=["2024-01-01"])
authorized_capacity: float = Field(0.0, description="核准容量(万千瓦)", examples=[10.0])
# 投决
investment_decision_capacity: Optional[float] = Field(None, description="投资决策容量", examples=[8.0])
investment_decision_date: Optional[DateStr] = Field(None, description="投资决策日期", examples=["2024-02-01"])
# 开工
plan_construction_capacity: float = Field(0.0, description="本年度计划开工容量", examples=[5.0])
plan_construction_date: Optional[DateStr] = Field(None, description="本年度计划开工日期", examples=["2024-03-01"])
actual_construction_capacity: float = Field(0.0, description="本年度实际开工容量", examples=[4.0])
actual_construction_date: Optional[DateStr] = Field(None, description="本年度实际开工日期", examples=["2024-04-01"])
accumulated_construction_capacity: float = Field(0.0, description="累计开工容量", examples=[12.0])
constructing_capacity: float = Field(0.0, description="在建容量", examples=[6.0])
# 投产
ensure_production_capacity: float = Field(0.0, description="本年度确保投产容量", examples=[3.0])
challenge_production_capacity: float = Field(0.0, description="本年度揭榜投产容量", examples=[2.0])
climb_production_capacity: float = Field(0.0, description="本年度登高投产容量", examples=[1.0])
plan_production_date: Optional[DateStr] = Field(None, description="计划投产日期", examples=["2024-08-01"])
plan_full_production_date: Optional[DateStr] = Field(None, description="计划全部投产日期", examples=["2024-09-01"])
actual_production_date: Optional[DateStr] = Field(None, description="实际投产日期", examples=["2024-08-15"])
actual_full_production_date: Optional[DateStr] = Field(None, description="实际全投日期", examples=["2024-09-10"])
production_capacity: Optional[float] = Field(0.0, description="本年度投产容量", examples=[2.0])
production_date: Optional[DateStr] = Field(None, description="本年度首批投产日期", examples=["2024-09-01"])
total_production_capacity: Optional[float] = Field(0.0, description="全部投产容量", examples=[2.0])
# 投资
dynamic_investment: float = Field(0.0, description="动态总投资", examples=[100000.0])
complete_investment: float = Field(0.0, description="完成投资", examples=[80000.0])
# 其他信息
progress_type: ProgressType | None = Field(None, description="推进分类", examples=[ProgressType.A])
key_constraints: KeyConstraints = Field(KeyConstraints.NONE, description="关键制约", examples=[KeyConstraints.NONE])
key_constraints_remark: Optional[str] = Field("", description="关键制约备注", examples=["无"])
# status: Optional[str] = Field(None, description="项目状态")
apply_file_ids: List[str] = Field(default_factory=list, description="申请表附件ID列表", examples=[[]])
# 拓展信息
dbtc_score_list: Optional[list] = Field(default_factory=list, description='达标投产评分列表') # 这里项目用的是list而不是List
# approval_status: Optional[str] = Field(None, description="审核状态", examples=["pending"])
class ProjectResponseBaseList(BaseModel):
page: int
page_size: int
total: int
list: List[ProjectResponseBase] = []
# ==================== Update ====================
class KeyConstraintsRemarkMixin:
"""关键制约备注校验混入类"""
@field_validator("key_constraints_remark")
def validate_key_constraints_remark(cls, v):
if v is None:
return v
if len(v) > 500:
raise HTTPException(detail="关键制约备注不能超过500个字符", status_code=400)
return v
class AuthorizationDateMixin:
"""关键制约备注校验混入类"""
@field_validator("authorization_date")
def validate_actual_date(cls, v):
if v is None:
return v
if v > get_current_date():
raise HTTPException(detail='核准/备案日期不能大于今天', status_code=400)
return v
class ProjectBaseInfoUpdate(BaseModel, KeyConstraintsRemarkMixin, AuthorizationDateMixin):
"""
项目基本信息 PROJECT_BASE_INFO
"""
# 基础信息-允许更改
id: str = Field(..., description="项目id", examples=["uuid"])
address: str | None = Field(None, description="项目地址", examples=["某省某市某区"])
code: str | None = Field(None, description="项目编码", examples=["XM2025001"])
name: str = Field(..., description="项目名称", examples=["XX新能源项目"])
short_name: str | None = Field(None, description="项目简称", examples=["XX项目"])
manager_name: str | None = Field(None, description="负责人姓名", examples=["张三"])
manager_phone: str | None = Field(None, description="负责人电话", examples=["13800000000"])
# 其他信息
progress_type: ProgressType = Field(..., description="推进分类", examples=[ProgressType.A])
key_constraints: KeyConstraints | None = Field(None, description="关键制约", examples=[KeyConstraints.NONE])
key_constraints_remark: str | None = Field(None, description="关键制约备注", examples=["无"])
apply_file_ids: List[str] | None = Field(None, description="申请表附件ID列表", examples=[[]])
# 拓展信息
dbtc_score_list: list[DBTCSCORE] | None = Field(None, description='达标投产评分列表') # 这里项目用的是list而不是List
# 核准
authorization_date: DateStr | None = Field(None, description="核准日期", examples=["2024-01-01"])
authorized_capacity: float | None = Field(None, description="核准容量(万千瓦)", examples=[10.0])
class _ProductionPlanNode(BaseModel):
year: int = Field(..., description="计划年份", examples=[2024])
capacity: float = Field(..., description="确保投产容量", examples=[3.0])
class ProjectEnsureCapacity(BaseModel):
"""
PROJECT_ENSURE_CAPACITY
填写项目确保投产容量列表,系统根据列表自动计算ensure_production_capacity字段
"""
# 确保投产容量
ensure_production_capacity_list : list[_ProductionPlanNode] = Field(..., description="确保投产容量列表(每次必须填写全部)")
class ProjectClimbCapacity(BaseModel):
"""
PROJECT_CLIMB_CAPACITY
填写项目登高投产容量列表,系统根据列表自动计算climb_production_capacity字段
"""
# 登高投产容量
climb_production_capacity_list : list[_ProductionPlanNode] = Field(..., description="登高投产容量列表(每次必须填写全部)")
class ProjectChallengeCapacity(BaseModel):
"""
PROJECT_CHALLENGE_CAPACITY
填写项目揭榜投产容量列表,系统根据列表自动计算challenge_production_capacity字段
"""
# 揭榜投产容量
challenge_production_capacity_list : list[_ProductionPlanNode] = Field(..., description="揭榜投产容量列表(每次必须填写全部)")
class _ActualNodeData(BaseModel):
capacity: float = Field(..., description="实际节点容量", examples=[4.0])
date: DateStr = Field(..., description="实际节点日期", examples=["2024-04-01"])
class ProjectActualConstruction(BaseModel):
"""
PROJECT_ACTUAL_CONSTRUCTION
项目实际开工节点
系统根据实际开工节点自动计算以下字段:
actual_construction_capacity 本年度实际开工容量
actual_construction_date 本年度实际开工日期
accumulated_construction_capacity 累计开工容量
constructing_capacity 在建容量
"""
actual_construction_list : list[_ActualNodeData] | None = Field(None, description="实际开工节点列表(每次必须填写全部)")
class _PlanNodeData(BaseModel):
capacity: float = Field(..., description="计划节点容量", examples=[5.0])
date: DateStr = Field(..., description="本年度计日期", examples=["2024-03-01"])
@field_validator("capacity")
def validate_capacity(cls, v: float) -> float:
if v < 0:
raise HTTPException(status_code=400, detail="节点容量必须大于等于0")
return v
class ProjectPlanConstruction(BaseModel):
"""
PROJECT_PLAN_CONSTRUCTION
项目计划开工节点
系统根据计划开工节点自动计算:
plan_construction_capacity 本年度计划开工容量
plan_construction_date 本年度计划开工日期
"""
plan_construction_list : list[_PlanNodeData] | None = Field(None, description="计划开工节点列表(每次必须填写全部)")
class ProjectInvestmentDecision(BaseModel):
"""
PROJECT_INVESTMENT_DECISION
投资决策容量和日期
"""
investment_decision_capacity: float = Field(..., description="投资决策容量", examples=[8.0])
investment_decision_date: DateStr = Field(..., description="投资决策日期", examples=["2024-02-01"])
@field_validator("investment_decision_date")
def validate_investment_decision_date(cls, v):
if v is None:
return v
if v > get_current_date():
raise HTTPException(detail='投资决策日期不能大于今天', status_code=400)
return v
@field_validator("investment_decision_capacity")
def validate_investment_decision_capacity(cls, v: float) -> float:
if v < 0:
raise HTTPException(status_code=400, detail="投资决策容量必须大于等于0")
return v
class ProjectKeyConstraints(BaseModel, KeyConstraintsRemarkMixin):
"""
PROJECT_KEY_CONSTRAINTS
关键制约
"""
key_constraints: KeyConstraints | None = Field(None, description="关键制约", examples=[KeyConstraints.NONE])
key_constraints_remark: str | None = Field(None, description="关键制约备注", examples=["无"])
class _ActualProductionNode(BaseModel):
capacity: float = Field(..., description="实际投产容量", examples=[2.0])
date: DateStr = Field(..., description="实际投产日期", examples=["2024-08-15"])
@field_validator("capacity")
def validate_capacity(cls, v: float) -> float:
if v < 0:
raise HTTPException(status_code=400, detail="实际投产容量必须大于等于0")
return v
@field_validator("date")
def validate_date(cls, v):
if v is None:
return v
if v > get_current_date():
raise HTTPException(detail='实际投产日期不能大于今天', status_code=400)
return v
class ProjectProductionCapacity(BaseModel):
"""
PROJECT_PRODUCTION_CAPACITY
填写项目投产数据
每个项目在各年份或一年内可能有多次实际投产
系统会根据投产节点列表数据,自动计算
production_capacity 本年度投产容量
production_date 本年度首批投产日期
total_production_capacity 全部投产容量
"""
plan_production_date: DateStr | None = Field(None, description="集团公司计划投产日期", examples=["2024-08-01"])
plan_full_production_date: DateStr | None = Field(None, description="集团公司计划全部投产日期", examples=["2024-09-01"])
actual_production_date: DateStr | None = Field(None, description="实际投产日期(只有新能源需要)", examples=["2024-08-15"])
actual_full_production_date: DateStr | None = Field(None, description="实际全投日期(只有新能源需要)", examples=["2024-09-10"])
actual_production_list : list[_ActualProductionNode] | None = Field(None, description="实际投产节点列表(每次必须填写全部)(只有新能源需要)")
@model_validator(mode="before")
@classmethod
def reject_ic_plan_dates(cls, data):
if not isinstance(data, dict):
return data
blocked_fields = [
field for field in ("ic_plan_first_production_date", "ic_plan_full_production_date")
if field in data
]
if blocked_fields:
raise HTTPException(
status_code=400,
detail=f"/production_capacity 接口不支持更新字段: {', '.join(blocked_fields)}"
)
return data
@property
def ic_plan_first_production_date(self) -> str:
return ""
@property
def ic_plan_full_production_date(self) -> str:
return ""
@field_validator("actual_production_date")
def validate_actual_production_date(cls, v):
if v is None:
return v
if v > get_current_date():
raise HTTPException(detail='实际投产日期不能大于今天', status_code=400)
return v
@field_validator("actual_full_production_date")
def validate_actual_full_production_date(cls, v):
if v is None:
return v
if v > get_current_date():
raise HTTPException(detail='实际全投日期不能大于今天', status_code=400)
return v
class ProjectCompleteInvestment(BaseModel):
"""
PROJECT_COMPLETE_INVESTMENT
完成投资投资
"""
complete_investment: float = Field(..., description="完成投资", examples=[80000.0])
@field_validator("complete_investment")
def validate_complete_investment(cls, v: float) -> float:
if v < 0:
raise HTTPException(status_code=400,detail="完成投资必须大于等于0")
return v
class ProjectSettlementInvestment(BaseModel):
"""
PROJECT_SETTLEMENT_INVESTMENT
合同结算投资情况
只有抽蓄-水电-煤电-气电项目类型可操作
"""
implementation_estimate: float = Field(..., description="执行概算", examples=[80000.0])
static_total_investment: float = Field(..., description="静态总投资", examples=[80000.0])
completed_investment: float = Field(..., description="完成投资", examples=[80000.0])
dynamic_investment: float = Field(..., description="动态总投资", examples=[80000.0])
@field_validator("implementation_estimate")
def validate_implementation_estimate(cls, v: float) -> float:
if v < 0:
raise HTTPException(status_code=400,detail="执行概算必须大于等于0")
return v
@field_validator("static_total_investment")
def validate_static_total_investment(cls, v: float) -> float:
if v < 0:
raise HTTPException(status_code=400, detail="静态总投资必须大于等于0")
return v
@field_validator("completed_investment")
def validate_completed_investment(cls, v: float) -> float:
if v < 0:
raise HTTPException(status_code=400, detail="完成投资必须大于等于0")
return v
@field_validator("dynamic_investment")
def validate_dynamic_investment(cls, v: float) -> float:
if v < 0:
raise HTTPException(status_code=400, detail="动态总投资必须大于等于0")
return v
class ProjectDynamicInvestment(BaseModel):
"""
PROJECT_DYNAMIC_INVESTMENT
"""
# 动态总投资
dynamic_investment: float | None = Field(None, description="动态总投资", examples=[100000.0])
@field_validator("dynamic_investment")
def validate_dynamic_investment(cls, v: float) -> float:
if v < 0:
raise HTTPException(status_code=400, detail="动态总投资必须大于等于0")
return v
# ======================= 水电抽蓄专用 =============================
# ==================== 枚举说明 用于文档自动生成 ====================