first commit
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
from pydantic import BaseModel, Field, ValidationError, model_validator
|
||||
from typing import Any, Dict
|
||||
from typing import Any, Optional, TypeVar, Generic, List
|
||||
from pydantic import BaseModel, Field, model_serializer
|
||||
|
||||
class BaseResponseWithID(BaseModel):
|
||||
"""
|
||||
Mixin: 统一处理输入中的 id/_id -> id
|
||||
约束:
|
||||
- 若 id 与 _id 同时出现且值不同 -> 报错
|
||||
- 若仅 _id 出现 -> 转成 id
|
||||
- 若两者都缺失 -> 报错
|
||||
- 最终保证存在 id: str
|
||||
"""
|
||||
id: str = Field(..., description="资源唯一标识ID")
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def normalize_id(cls, data: Any) -> Any:
|
||||
# 仅处理 dict-like 输入;其他情况交给 Pydantic 默认流程
|
||||
if not isinstance(data, dict):
|
||||
return data
|
||||
|
||||
has_id = "id" in data
|
||||
has__id = "_id" in data
|
||||
|
||||
if has_id and has__id:
|
||||
if str(data["id"]) != str(data["_id"]):
|
||||
raise ValidationError([
|
||||
{
|
||||
"type": "value_error",
|
||||
"loc": ("id",),
|
||||
"msg": "id and _id both provided but not equal",
|
||||
"input": data.get("id"),
|
||||
}
|
||||
], cls)
|
||||
# 相同则优先保留 id,去掉 _id
|
||||
data = {**data}
|
||||
data.pop("_id", None)
|
||||
return data
|
||||
|
||||
if has__id and not has_id:
|
||||
# 将 _id 统一映射为 id
|
||||
data = {**data}
|
||||
data["id"] = str(data["_id"])
|
||||
data.pop("_id", None)
|
||||
return data
|
||||
|
||||
if not has_id and not has__id:
|
||||
raise ValidationError([
|
||||
{
|
||||
"type": "value_error.missing",
|
||||
"loc": ("id",),
|
||||
"msg": "id or _id is required",
|
||||
"input": data,
|
||||
}
|
||||
], cls)
|
||||
|
||||
# 默认返回(已有 id)
|
||||
return data
|
||||
|
||||
class RequestApprovalMixin(BaseModel):
|
||||
"""
|
||||
请求审批相关的基础模型
|
||||
"""
|
||||
pass
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
# def _build_full_response_dict(code: int, message: Optional[str], data: Any) -> dict:
|
||||
# """构建与 __orjson__ 完全一致的响应字典"""
|
||||
# # 处理 data 的序列化(与 __orjson__ 逻辑同步)
|
||||
# if data is None:
|
||||
# clean_data = {}
|
||||
# elif isinstance(data, BaseModel):
|
||||
# clean_data = data.model_dump()
|
||||
# else:
|
||||
# clean_data = data
|
||||
# return {
|
||||
# "code": code,
|
||||
# "message": message,
|
||||
# "data": clean_data
|
||||
# }
|
||||
|
||||
class Success(BaseModel, Generic[T]):
|
||||
code: int = 200
|
||||
message: Optional[str] = "OK"
|
||||
data: Optional[T] = None
|
||||
|
||||
# def __init__(self, **kwargs):
|
||||
# super().__init__(**kwargs)
|
||||
# # 自动捕获完整响应结构到 push_log.result
|
||||
# push_log = current_push_log.get()
|
||||
# if push_log is not None:
|
||||
# full_response = _build_full_response_dict(self.code, self.message, self.data)
|
||||
# push_log.result = full_response
|
||||
#
|
||||
class Fail(BaseModel):
|
||||
code: int = 400
|
||||
message: Optional[str] = None
|
||||
data: Optional[Any] = Field(default_factory=dict)
|
||||
|
||||
class PaginatedList(BaseModel, Generic[T]):
|
||||
list: List[T] = Field(default_factory=list)
|
||||
total: int = 0
|
||||
page: int = 1
|
||||
page_size: int = 20
|
||||
@@ -0,0 +1,13 @@
|
||||
from datetime import datetime, date
|
||||
import pytz
|
||||
|
||||
def get_current_date(date_formate='%Y-%m-%d'):
|
||||
"""
|
||||
获取当前日期字符串,格式化为指定格式,默认格式为'YYYY-MM-DD'
|
||||
"""
|
||||
tz = pytz.timezone('Asia/Shanghai') # 设置为中国时区
|
||||
current_date = datetime.now(tz)
|
||||
return current_date.strftime(date_formate)
|
||||
|
||||
def datetime_date(date: str) -> date:
|
||||
return datetime.strptime(date, "%Y-%m-%d").date()
|
||||
@@ -0,0 +1,9 @@
|
||||
from enum import Enum
|
||||
|
||||
class ApprovalStatus(str, Enum):
|
||||
REGISTERED = "registered"
|
||||
UNDER_REVIEW = "under_review"
|
||||
APPROVED = "approved"
|
||||
REJECTED = "rejected"
|
||||
PENDING_CONFIRM = "pending_confirm"
|
||||
CONFIRMED = "confirmed"
|
||||
@@ -0,0 +1,178 @@
|
||||
"""
|
||||
推送接口通用schema
|
||||
|
||||
## 响应相关
|
||||
1. BaseResponseSchema: 所有接口响应的基础schema
|
||||
2. BasePushResponseSchema: 推送接口响应的基础schema,包含push_id
|
||||
3. PushResultSchema: 查询推送结果的schema
|
||||
|
||||
## 请求相关
|
||||
4. BaseCreateRequestSchema[T]: 创建资源的基础请求schema(泛型),业务数据在data字段
|
||||
5. BaseUpdateRequestSchema[T]: 更新资源的基础请求schema(泛型),业务数据在data字段
|
||||
6. BaseDeleteRequestSchema: 删除资源的基础请求schema,只需resource_id
|
||||
7. BulkPushRequestSchema: 批量推送请求的schema
|
||||
|
||||
## 说明
|
||||
- 所有元数据字段(timestamp, nonce, signature, uid, push_id)在请求体最外层
|
||||
- push_id使用UUIDv7格式,支持时间排序,同时作为幂等键和查询键
|
||||
- 业务数据嵌套在data字段中(删除操作除外,只需resource_id)
|
||||
- 所有业务Schema必须继承BasePushData,确保包含project_type字段
|
||||
- resource_id在外层,用于标识要操作的资源(更新/删除时必填)
|
||||
- 使用泛型T可快速定义各业务的请求schema,T必须继承BasePushData
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import TypeVar, Generic, Optional, List, Dict, Any
|
||||
from ..project.enums import ProjectType
|
||||
|
||||
# 定义泛型
|
||||
T = TypeVar('T', bound='BasePushData')
|
||||
|
||||
# ==================== 业务数据基类 ====================
|
||||
|
||||
class BasePushData(BaseModel):
|
||||
"""
|
||||
所有推送业务数据的基类
|
||||
|
||||
所有业务Schema必须继承此类,确保包含project_type字段
|
||||
"""
|
||||
project_type: ProjectType = Field(..., description="项目类型:offshore_wind/pumped_storage/photovoltaic等")
|
||||
|
||||
|
||||
# ==================== Mixin ====================
|
||||
|
||||
class SignatureMixin(BaseModel):
|
||||
"""签名字段混入"""
|
||||
timestamp: int = Field(..., description="毫秒级 Unix 时间戳")
|
||||
nonce: str = Field(..., description="随机字符串,防止重放攻击")
|
||||
sign: str = Field(..., description="签名字符串")
|
||||
uid: str = Field(..., description="集团侧用户id")
|
||||
|
||||
|
||||
class PushRequestMixin(BaseModel):
|
||||
"""推送ID字段混入"""
|
||||
push_id: str = Field(..., description="推送ID,客户端生成的唯一标识(UUIDv7,支持时间排序)")
|
||||
|
||||
|
||||
# ==================== 响应相关 ====================
|
||||
|
||||
class BaseResponseSchema(BaseModel):
|
||||
"""所有接口响应的基础schema"""
|
||||
code: int = 200
|
||||
message: Optional[str] = "OK"
|
||||
data: Optional[T] = None
|
||||
|
||||
|
||||
class BasePushResponseSchema(BaseModel):
|
||||
"""推送接口响应的基础schema"""
|
||||
biz_id: str | None = Field(None, description="资源ID(create操作成功后返回新创建的ID,update/delete返回原ID)")
|
||||
|
||||
|
||||
class PushResultSchema(BaseModel):
|
||||
"""查询推送结果的schema"""
|
||||
push_id: str = Field(..., description="推送ID(客户端生成的UUIDv7)")
|
||||
status: str = Field(..., description="推送状态:queued/processing/success/failed")
|
||||
result: Dict[str, Any] | None = Field(None, description="处理结果(成功时包含created_id等信息)")
|
||||
error: str | None = Field(None, description="错误信息(失败时)")
|
||||
created_at: str = Field(..., description="推送创建时间(ISO 8601格式)")
|
||||
updated_at: str = Field(..., description="推送更新时间(ISO 8601格式)")
|
||||
|
||||
|
||||
class PushIdsSchema(BaseModel):
|
||||
"""支持从请求体接收的 push_ids 列表字符串(逗号拼接)"""
|
||||
push_ids: Optional[str] = Field(None, description="推送id字符串逗号拼接")
|
||||
|
||||
|
||||
# ==================== 请求相关 ====================
|
||||
|
||||
class BaseCreateRequestSchema(SignatureMixin, PushRequestMixin, BaseModel, Generic[T]):
|
||||
"""
|
||||
创建资源的基础请求schema(泛型)
|
||||
|
||||
泛型T必须继承BasePushData,确保包含project_type字段
|
||||
|
||||
使用示例:
|
||||
class ProjectInputSchema(BasePushData):
|
||||
name: str = Field(...)
|
||||
capacity: float = Field(...)
|
||||
|
||||
class ProjectCreateRequest(BaseCreateRequestSchema[ProjectInputSchema]):
|
||||
pass
|
||||
"""
|
||||
# 业务数据(嵌套)
|
||||
data: T = Field(..., description="业务数据")
|
||||
|
||||
|
||||
class BaseUpdateRequestSchema(SignatureMixin, PushRequestMixin, BaseModel, Generic[T]):
|
||||
"""
|
||||
更新资源的基础请求schema(泛型)
|
||||
|
||||
泛型T必须继承BasePushData,确保包含project_type字段
|
||||
resource_id在外层,用于标识要更新的资源
|
||||
|
||||
使用示例:
|
||||
class ProjectInputSchema(BasePushData):
|
||||
name: str = Field(...)
|
||||
capacity: float = Field(...)
|
||||
|
||||
class ProjectUpdateRequest(BaseUpdateRequestSchema[ProjectInputSchema]):
|
||||
pass
|
||||
"""
|
||||
# 资源标识
|
||||
biz_id: str = Field(..., description="要更新的资源ID,类型id,如project传project_id,unit传unit_id")
|
||||
|
||||
# 业务数据(嵌套)
|
||||
data: T = Field(..., description="业务数据(只需包含要更新的字段)")
|
||||
|
||||
|
||||
class BaseDeleteRequestSchema(SignatureMixin, PushRequestMixin, BaseModel):
|
||||
"""
|
||||
删除资源的基础请求schema
|
||||
|
||||
使用示例:
|
||||
class ProjectDeleteRequest(BaseDeleteRequestSchema):
|
||||
pass
|
||||
"""
|
||||
|
||||
# 资源标识
|
||||
biz_id: str = Field(..., description="要删除的资源ID")
|
||||
|
||||
|
||||
class BulkPushItem(PushRequestMixin, BaseModel):
|
||||
"""
|
||||
批量推送中的单个项目
|
||||
|
||||
注意:
|
||||
- action为create时:data必填,resource_id留空
|
||||
- action为update时:data和resource_id都必填
|
||||
- action为delete时:resource_id必填,data留空
|
||||
- data必须继承BasePushData,确保包含project_type字段
|
||||
"""
|
||||
action: str = Field(..., description="操作类型:create/update/delete")
|
||||
resource_id: str | None = Field(None, description="资源ID(update/delete时必填,create时留空)")
|
||||
data: BasePushData | None = Field(None, description="业务数据(create/update时必填,delete时留空,必须包含project_type字段)")
|
||||
|
||||
|
||||
class BulkPushItemResult(BaseModel):
|
||||
"""批量推送中单个项目的处理结果"""
|
||||
push_id: str = Field(..., description="推送ID(对应请求中的push_id)")
|
||||
status: str = Field(..., description="处理状态:queued/processing/success/failed")
|
||||
resource_id: str | None = Field(None, description="资源ID(create成功时返回新ID,update/delete返回原ID)")
|
||||
error: str | None = Field(None, description="错误信息(失败时)")
|
||||
|
||||
|
||||
class BulkPushRequestSchema(SignatureMixin, BaseModel):
|
||||
"""
|
||||
批量推送请求的schema
|
||||
|
||||
一次请求可推送多条数据,每条都有独立的幂等键和操作类型
|
||||
"""
|
||||
# 批量数据
|
||||
items: List[BulkPushItem] = Field(..., description="批量推送的数据列表")
|
||||
|
||||
|
||||
class BulkPushResponseSchema(BaseResponseSchema):
|
||||
"""批量推送响应的schema"""
|
||||
batch_id: str = Field(..., description="批量推送的批次ID")
|
||||
total: int = Field(..., description="总数量")
|
||||
results: List[BulkPushItemResult] = Field(..., description="每条数据的处理结果,包含push_id和resource_id")
|
||||
@@ -0,0 +1,85 @@
|
||||
from datetime import datetime
|
||||
from typing import Annotated
|
||||
from pydantic import AfterValidator
|
||||
from functools import lru_cache
|
||||
import re
|
||||
|
||||
# 预编译正则表达式,避免重复编译
|
||||
DATE_PATTERN = re.compile(r'^\d{4}-\d{2}-\d{2}$')
|
||||
TIMESTAMP_PATTERNS = {
|
||||
'iso_z': re.compile(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z$'),
|
||||
'iso_offset': re.compile(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+\+00:00$'),
|
||||
'simple_space': re.compile(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$'),
|
||||
'simple_t': re.compile(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$'),
|
||||
}
|
||||
|
||||
@lru_cache(maxsize=1024) # 缓存最近1024个不同的日期
|
||||
def _validate_date_cached(v: str) -> str:
|
||||
"""缓存版本的日期验证"""
|
||||
# 快速格式检查(避免无效字符串进入 strptime)
|
||||
if not DATE_PATTERN.match(v):
|
||||
raise ValueError(f"日期格式应为 YYYY-MM-DD,实际为: {v}")
|
||||
|
||||
# 验证日期有效性(例如排除 2023-02-30)
|
||||
datetime.strptime(v, "%Y-%m-%d")
|
||||
return v
|
||||
|
||||
def validate_date(v: str) -> str:
|
||||
"""校验日期格式为 YYYY-MM-DD"""
|
||||
if v is None or v == "":
|
||||
return v
|
||||
|
||||
return _validate_date_cached(v)
|
||||
|
||||
@lru_cache(maxsize=2048) # 时间戳变化更多,给更大的缓存
|
||||
def _validate_timestamp_cached(v: str) -> str:
|
||||
"""缓存版本的时间戳验证"""
|
||||
# 按常见程度排序,优先检查最可能的格式
|
||||
if TIMESTAMP_PATTERNS['iso_z'].match(v):
|
||||
datetime.strptime(v, "%Y-%m-%dT%H:%M:%S.%fZ")
|
||||
elif TIMESTAMP_PATTERNS['simple_space'].match(v):
|
||||
datetime.strptime(v, "%Y-%m-%d %H:%M:%S")
|
||||
elif TIMESTAMP_PATTERNS['simple_t'].match(v):
|
||||
datetime.strptime(v, "%Y-%m-%dT%H:%M:%S")
|
||||
elif TIMESTAMP_PATTERNS['iso_offset'].match(v):
|
||||
datetime.strptime(v, "%Y-%m-%dT%H:%M:%S.%f+00:00")
|
||||
else:
|
||||
# 兜底逻辑:如果正则都不匹配,尝试原来的逻辑
|
||||
try:
|
||||
if v.endswith('Z'):
|
||||
datetime.strptime(v, "%Y-%m-%dT%H:%M:%S.%fZ")
|
||||
elif v.endswith('+00:00'):
|
||||
datetime.strptime(v, "%Y-%m-%dT%H:%M:%S.%f+00:00")
|
||||
else:
|
||||
try:
|
||||
datetime.strptime(v, "%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
datetime.strptime(v, "%Y-%m-%dT%H:%M:%S")
|
||||
except ValueError:
|
||||
raise ValueError("时间戳格式应为 YYYY-MM-DDTHH:MM:SS.sssZ、+00:00、YYYY-MM-DD HH:MM:SS 或 YYYY-MM-DDTHH:MM:SS")
|
||||
|
||||
return v
|
||||
|
||||
def validate_timestamp(v: str) -> str:
|
||||
"""校验时间戳格式"""
|
||||
if v is None or v == "":
|
||||
return v
|
||||
|
||||
return _validate_timestamp_cached(v)
|
||||
|
||||
# 定义新类型
|
||||
DateStr = Annotated[str, AfterValidator(validate_date)]
|
||||
TimestampStr = Annotated[str, AfterValidator(validate_timestamp)]
|
||||
|
||||
# 可选:提供缓存统计功能用于调试
|
||||
def get_cache_stats():
|
||||
"""获取缓存统计信息,用于性能调试"""
|
||||
return {
|
||||
'date_cache': _validate_date_cached.cache_info(),
|
||||
'timestamp_cache': _validate_timestamp_cached.cache_info(),
|
||||
}
|
||||
|
||||
def clear_validation_cache():
|
||||
"""清空验证缓存(用于测试或内存清理)"""
|
||||
_validate_date_cached.cache_clear()
|
||||
_validate_timestamp_cached.cache_clear()
|
||||
@@ -0,0 +1,40 @@
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from typing import List, Optional, Literal
|
||||
from ..common.base import BaseResponseWithID
|
||||
|
||||
|
||||
class CompanyResponse(BaseResponseWithID):
|
||||
code: str = Field(..., description="公司编码")
|
||||
company_type: str = Field(..., description="公司类型")
|
||||
name: str = Field(..., description="公司名称")
|
||||
short_name: str = Field(..., description="公司简称")
|
||||
parent_id: Optional[str] = Field(None, description="父公司ID")
|
||||
path: List[str] = Field(default_factory=list, description="公司路径 [..., 上级id, 本身id]")
|
||||
path_name: List[str] = Field(default_factory=list, description="公司路径名称 [..., 上级名称, 本身名称]")
|
||||
province: Optional[str] = Field(None, description="省份编码") # 存省份编码
|
||||
city: Optional[str] = Field(None, description="城市编码") # 存城市编码
|
||||
sort: int = Field(0, description="排序")
|
||||
status: str = Field("", description="公司状态")
|
||||
company_nature: Optional[list] = Field(..., description="公司性质")
|
||||
credit_code: Optional[str] = Field(None, description="信用代码", examples=["914403007109310121"])
|
||||
|
||||
city_name: Optional[str] = Field(None, description="城市名称", examples=[None])
|
||||
province_name: Optional[str] = Field(None, description="省份名称", examples=[None])
|
||||
parent_name: Optional[str] = Field(None, description="父级公司名称", examples=[None])
|
||||
|
||||
@field_validator(
|
||||
"path",
|
||||
"path_name",
|
||||
mode="before"
|
||||
)
|
||||
def formate_null_to_list(cls, field_value):
|
||||
if field_value is None:
|
||||
return []
|
||||
return field_value
|
||||
|
||||
|
||||
class CompanyListResponse(BaseModel):
|
||||
page: int
|
||||
page_size: int
|
||||
total: int
|
||||
list: List[CompanyResponse] = []
|
||||
@@ -0,0 +1,121 @@
|
||||
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
|
||||
|
||||
class PlanNode(BaseModel):
|
||||
date: DateStr = Field(..., description="计划节点时间", examples=["2024-01-01"])
|
||||
quantity: float = Field(..., description="计划完成量", examples=[100.0])
|
||||
is_audit: Optional[bool] = Field(False, description="是否已审核", examples=[False])
|
||||
|
||||
@field_validator("quantity")
|
||||
def validate_price(cls, v):
|
||||
"""验证金额必须为非负数"""
|
||||
if v < 0:
|
||||
raise HTTPException(status_code=400, detail='计划完成量不能小于等于0')
|
||||
return v
|
||||
|
||||
class ConstructionResponse(BaseResponseWithID):
|
||||
"""
|
||||
施工任务模型,存储在CouchDB中的施工任务文档
|
||||
"""
|
||||
id : str = Field(..., description="施工任务ID")
|
||||
project_id: str = Field(..., description="所属项目ID", examples=["project123"])
|
||||
contract_id: Optional[str] = Field(None, description="所属合同ID", examples=["contract456"])
|
||||
show_name: str = Field(..., description="展示名称", examples=["桩基础施工任务"])
|
||||
construction_section: str = Field(..., description="施工区域", examples=["GF"])
|
||||
status: Optional[int] = Field(None, description="施工状态", examples=[0])
|
||||
task_type: str = Field(..., description="施工任务类型", examples=["zjcsgg"])
|
||||
plan_start_date: Optional[DateStr] = Field(None, description="计划开工时间", examples=["2024-01-01"])
|
||||
plan_complete_date: Optional[DateStr] = Field(None, description="计划完工时间", examples=["2024-06-01"])
|
||||
actual_complete_date: Optional[DateStr] = Field(None, description="实际完工时间", examples=["2024-05-30"])
|
||||
complete_quantity: float = Field(0.0, description="实际完成量累计", examples=[0.0])
|
||||
complete_rate: float = Field(0.0, description="实际完成率(%)", examples=[0.0])
|
||||
contract_quantity: Optional[float] = Field(None, description="设计量", examples=[1000.0])
|
||||
plan_node: List[PlanNode] = Field(default_factory=list, description="计划节点列表")
|
||||
statistic_date: Optional[DateStr] = Field(None, description="统计节点时间", examples=["2024-05-31"])
|
||||
# sort: int = Field(0, description="排序字段", examples=[0])
|
||||
is_crucial:Optional[bool] = Field(False, description="是否为关键线路")
|
||||
# 2025-10-20新增字段,延迟时间
|
||||
delay_days: Optional[int] = Field(None, description="延迟时间(天)", examples=[0])
|
||||
|
||||
class ConstructionCreate(BaseModel):
|
||||
"""
|
||||
施工任务创建模型
|
||||
不对应operation
|
||||
"""
|
||||
project_id: str = Field(..., description="所属项目ID", examples=["project123"])
|
||||
contract_id: Optional[str] = Field(None, description="所属合同ID", examples=["contract456"])
|
||||
show_name: str = Field(..., description="展示名称", examples=["桩基础施工任务"])
|
||||
construction_section: str = Field(..., description="施工区域", examples=["GF"])
|
||||
task_type: str = Field(..., description="施工任务类型", examples=["zjcsgg"])
|
||||
is_crucial:Optional[bool] = Field(False, description="是否为关键线路")
|
||||
|
||||
|
||||
|
||||
class ConstructionUpdate(BaseModel):
|
||||
"""
|
||||
施工任务更新模型
|
||||
CONSTRUCTION_BASE_INFO = "construction.base_info"
|
||||
"""
|
||||
id: str = Field(..., description="施工任务ID")
|
||||
contract_id: Optional[str] = Field(None, description="所属合同ID", examples=["contract456"])
|
||||
show_name: str | None = Field(None, description="展示名称(仅QT类施工可以修改名称)", examples=["桩基础施工任务"])
|
||||
is_crucial:bool | None = Field(False, description="是否为关键线路")
|
||||
|
||||
class ConstructionPlanUpdate(BaseModel):
|
||||
"""
|
||||
施工计划更新模型
|
||||
对应 CONSTRUCTION_PLAN = "construction.plan"
|
||||
|
||||
业务逻辑说明:
|
||||
- API要求计划节点时间必须晚于计划开工日期(不能等于)
|
||||
- 自动清理脏数据:如果 plan_node 有2个或以上节点,删除:
|
||||
1. 开头节点:如果其 date 等于 plan_start_date
|
||||
2. 结尾节点:如果其 date 等于 plan_complete_date
|
||||
- 这样避免提交包含边界节点的数据导致API报错
|
||||
"""
|
||||
id: str = Field(..., description="施工任务ID")
|
||||
plan_start_date: Optional[str] = Field(None, description="计划开工时间", examples=["2024-01-01"])
|
||||
plan_complete_date: Optional[str] = Field(None, description="计划完工时间", examples=["2024-01-01"])
|
||||
contract_quantity: float = Field(..., description="设计量")
|
||||
plan_node: Optional[List[PlanNode]] = Field(default_factory=list, description="计划节点列表")
|
||||
|
||||
@field_validator("contract_quantity")
|
||||
def validate_price(cls, v):
|
||||
"""验证金额必须为非负数"""
|
||||
if v < 0:
|
||||
raise HTTPException(status_code=400, detail='设计量不能小于0')
|
||||
return v
|
||||
|
||||
@model_validator(mode='after')
|
||||
def clean_plan_nodes(self):
|
||||
"""
|
||||
清理计划节点列表中的边界节点
|
||||
|
||||
如果 plan_node 有2个或以上节点,删除:
|
||||
- 开头节点:date 等于 plan_start_date
|
||||
- 结尾节点:date 等于 plan_complete_date
|
||||
|
||||
这避免了API报错"计划节点时间必须晚于计划开工日期"
|
||||
"""
|
||||
if not self.plan_node or len(self.plan_node) < 2:
|
||||
return self
|
||||
|
||||
cleaned_nodes = list(self.plan_node)
|
||||
|
||||
# 删除开头的边界节点
|
||||
if (self.plan_start_date and
|
||||
cleaned_nodes and
|
||||
cleaned_nodes[0].date == self.plan_start_date):
|
||||
cleaned_nodes.pop(0)
|
||||
|
||||
# 删除结尾的边界节点
|
||||
if (self.plan_complete_date and
|
||||
cleaned_nodes and
|
||||
cleaned_nodes[-1].date == self.plan_complete_date):
|
||||
cleaned_nodes.pop()
|
||||
|
||||
self.plan_node = cleaned_nodes
|
||||
return self
|
||||
@@ -0,0 +1,133 @@
|
||||
from pydantic import BaseModel, Field,field_validator
|
||||
from ..common.datetime import get_current_date
|
||||
from fastapi import HTTPException
|
||||
from typing import List, Optional, Literal
|
||||
from ..common.validators import DateStr
|
||||
from ..common.base import BaseResponseWithID
|
||||
from ..common.enums import ApprovalStatus
|
||||
from datetime import datetime
|
||||
|
||||
class ConstructionDetailResponse(BaseResponseWithID):
|
||||
"""
|
||||
物资明细返回值
|
||||
"""
|
||||
id: str = Field(..., description="施工任务明细ID")
|
||||
project_id: str = Field(..., description="所属项目ID", examples=["project123"])
|
||||
parent_id: str = Field(..., description="所属施工任务ID", examples=["task789"])
|
||||
rate: Optional[float] = Field(None, description="实际完成率", examples=[0.0])
|
||||
quantity: float = Field(0.0, ge=0, description="实际完成量累计", examples=[0.0])
|
||||
date: DateStr = Field(..., description="节点日期", examples=["2024-05-31"])
|
||||
status: Optional[int] = Field(None, description="进度状态", examples=[0])
|
||||
change_count: int = Field(0, description="变更次数", examples=[0])
|
||||
remark: Optional[str] = Field(None, description="备注")
|
||||
is_complete: Optional[bool] = Field(
|
||||
None, description="是否为全部完成", examples=[False]
|
||||
)
|
||||
|
||||
class ConstructionDetailCreate(BaseModel):
|
||||
"""
|
||||
施工明细创建模型
|
||||
CONSTRUCTION_DETAIL = "construction.detail"
|
||||
"""
|
||||
project_id: str = Field(..., description="所属项目ID", examples=["project123"])
|
||||
parent_id: str = Field(..., description="所属施工任务ID", examples=["task789"])
|
||||
quantity: float = Field(0.0, ge=0, description="实际完成量累计", examples=[0.0])
|
||||
date: DateStr = Field(..., description="节点日期", examples=["2024-05-31"])
|
||||
remark: Optional[str] = Field(None, description="备注")
|
||||
is_complete: Optional[bool] = Field(
|
||||
False, description="是否为全部完成", examples=[False]
|
||||
)
|
||||
confirmation_time: str | None = Field(None, description="确认时间", examples=["2025-12-17 11:13:49"])
|
||||
approval_time: str | None = Field(None, description="审批时间", examples=["2025-12-17 11:13:49"])
|
||||
approval_status: ApprovalStatus = Field("registered", description="审批状态", examples=["registered"])
|
||||
|
||||
@field_validator("approval_status")
|
||||
def validate_approval_status(cls, v):
|
||||
"""验证审批字段"""
|
||||
try:
|
||||
return ApprovalStatus(v) # 尝试转换
|
||||
except ValueError:
|
||||
raise HTTPException(400, f"审批状态 '{v}' 无效")
|
||||
|
||||
@field_validator("confirmation_time", "approval_time")
|
||||
def validate_time(cls, v):
|
||||
"""验证时间"""
|
||||
if v is None:
|
||||
return v
|
||||
try:
|
||||
datetime.strptime(v, "%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
raise HTTPException(detail=f'{v}时间格式错误,应为:2025-12-17 11:13:49', status_code=400)
|
||||
return v
|
||||
|
||||
@field_validator("quantity")
|
||||
def validate_quantity(cls, v):
|
||||
"""验证实际完成量累计必须为非负数"""
|
||||
if v < 0:
|
||||
raise HTTPException(status_code=400, detail='实际完成量累计不能小于0')
|
||||
return v
|
||||
|
||||
@field_validator("date")
|
||||
def validate_date(cls, v):
|
||||
if v > get_current_date():
|
||||
raise HTTPException(detail='节点日期不能大于今天', status_code=400)
|
||||
return v
|
||||
|
||||
|
||||
class ConstructionDetailUpdate(BaseModel):
|
||||
"""
|
||||
施工明细更新模型
|
||||
CONSTRUCTION_DETAIL = "construction.detail"
|
||||
"""
|
||||
id: str = Field(..., description="施工任务明细ID")
|
||||
quantity: Optional[float] = Field(None, ge=0, description="实际完成量累计", examples=[0.0])
|
||||
date: Optional[DateStr] = Field(None, description="节点日期", examples=["2024-05-31"])
|
||||
remark: Optional[str] = Field(None, description="备注")
|
||||
is_complete: Optional[bool] = Field(
|
||||
None, description="是否为全部完成", examples=[False]
|
||||
)
|
||||
confirmation_time: str | None = Field(None, description="确认时间", examples=["2025-12-17 11:13:49"])
|
||||
approval_time: str | None = Field(None, description="审批时间", examples=["2025-12-17 11:13:49"])
|
||||
approval_status: ApprovalStatus | None = Field("registered", description="审批状态", examples=["registered"])
|
||||
|
||||
@field_validator("approval_status")
|
||||
def validate_approval_status(cls, v):
|
||||
"""验证审批字段"""
|
||||
if v is None:
|
||||
return v
|
||||
try:
|
||||
return ApprovalStatus(v) # 尝试转换
|
||||
except ValueError:
|
||||
raise HTTPException(400, f"审批状态 '{v}' 无效")
|
||||
|
||||
@field_validator("confirmation_time", "approval_time")
|
||||
def validate_time(cls, v):
|
||||
"""验证时间"""
|
||||
if v is None:
|
||||
return v
|
||||
|
||||
try:
|
||||
datetime.strptime(v, "%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
raise HTTPException(detail=f'{v}时间格式错误,应为:2025-12-17 11:13:49', status_code=400)
|
||||
return v
|
||||
|
||||
@field_validator("quantity")
|
||||
def validate_quantity(cls, v):
|
||||
"""验证实际完成量累计必须为非负数"""
|
||||
if v is None:
|
||||
return None
|
||||
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 None
|
||||
if v > get_current_date():
|
||||
raise HTTPException(detail='节点日期不能大于今天', status_code=400)
|
||||
return v
|
||||
|
||||
class ConstructionDetailBulkUpdate(BaseModel):
|
||||
detail_list: List[ConstructionDetailUpdate]
|
||||
@@ -0,0 +1,122 @@
|
||||
from pydantic import BaseModel, Field,field_validator
|
||||
from typing import List, Optional, Literal
|
||||
from ..common.datetime import datetime_date,get_current_date
|
||||
from fastapi import HTTPException
|
||||
from ..common.validators import DateStr
|
||||
from ..common.base import BaseResponseWithID
|
||||
|
||||
class ContractResponse(BaseResponseWithID):
|
||||
"""
|
||||
合同返回信息
|
||||
"""
|
||||
id : str = Field(..., description="合同ID")
|
||||
project_id: str = Field("", description="所属项目ID")
|
||||
name: str = Field("", description="合同名称")
|
||||
short_name: str = Field("", description="合同简称")
|
||||
code: str = Field("", description="合同编码")
|
||||
contract_type: str = Field("", description="合同类型")
|
||||
|
||||
currency: str = Field("CNY", description="币种")
|
||||
manager_name: str = Field("", description="负责人姓名")
|
||||
manager_phone: str = Field("", description="负责人电话")
|
||||
|
||||
sign_company: str = Field("", description="签订公司")
|
||||
company_id: str = Field("", description="签订公司id")
|
||||
sign_date: DateStr = Field("", description="签订日期", examples=["2023-10-01"])
|
||||
credit_code: Optional[str] = Field("", description="签订公司信用代码")
|
||||
|
||||
total_price: float = Field(0.0, description="合同金额")
|
||||
sub_name: Optional[str] = Field(None, description="合同子标题")
|
||||
|
||||
|
||||
class ContractCreate(BaseModel):
|
||||
"""
|
||||
创建合同
|
||||
CONTRACT = "contract"
|
||||
"""
|
||||
name: str|None = Field(None, description="合同名称")
|
||||
sub_name:str|None= Field(None, description="合同子标题")
|
||||
code: str = Field("", description="合同编码")
|
||||
contract_type: str = Field("", description="合同类型")
|
||||
|
||||
project_id: str = Field("", description="所属项目ID")
|
||||
|
||||
currency: str = Field("CNY", description="币种")
|
||||
manager_name: str = Field("", description="负责人姓名")
|
||||
manager_phone: str = Field("", description="负责人电话")
|
||||
|
||||
sign_company: str = Field("", description="签订公司")
|
||||
company_id: Optional[str] = Field(None, description="签订公司id")
|
||||
sign_date: DateStr = Field("", description="签订日期", examples=["2023-10-01"])
|
||||
credit_code: Optional[str] = Field(None, description="签订公司信用代码")
|
||||
|
||||
total_price: float = Field(0.0, description="合同金额")
|
||||
|
||||
@field_validator("total_price")
|
||||
def validate_price(cls, v):
|
||||
"""验证金额必须为非负数"""
|
||||
if v < 0:
|
||||
raise HTTPException(status_code=400, detail='合同金额不能小于0')
|
||||
return v
|
||||
|
||||
@field_validator('sign_date')
|
||||
def validate_actual_date_not_future(cls, v: str) -> str:
|
||||
if v == "":
|
||||
return ""
|
||||
try:
|
||||
actual = datetime_date(v)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail='日期格式为(YYYY-MM-DD)')
|
||||
|
||||
today = datetime_date(get_current_date())
|
||||
if actual > today:
|
||||
raise HTTPException(status_code=400, detail='签约日期不能晚于今天')
|
||||
return v
|
||||
|
||||
class ContractUpdate(BaseModel):
|
||||
"""
|
||||
更新合同
|
||||
CONTRACT = "contract"
|
||||
"""
|
||||
id : str = Field(..., description="合同ID")
|
||||
sub_name:str|None= Field(None, description="合同子标题")
|
||||
|
||||
currency: str|None = Field(None, description="币种")
|
||||
|
||||
manager_name: str|None= Field(None, description="负责人姓名")
|
||||
manager_phone: str|None= Field(None, description="负责人电话")
|
||||
|
||||
sign_company: str|None= Field(None, description="签订公司")
|
||||
company_id: Optional[str] = Field(None, description="签订公司id")
|
||||
sign_date: str|None = Field(None, description="签订日期", examples=["2023-10-01"])
|
||||
credit_code: Optional[str] = Field(None, description="签订公司信用代码")
|
||||
|
||||
total_price: float|None= Field(None, description="合同金额")
|
||||
|
||||
@field_validator("total_price")
|
||||
def validate_price(cls, v):
|
||||
"""验证金额必须为非负数"""
|
||||
if v is None:
|
||||
return None
|
||||
if v < 0:
|
||||
raise HTTPException(status_code=400, detail='合同金额不能小于0')
|
||||
return v
|
||||
|
||||
@field_validator('sign_date')
|
||||
def validate_actual_date_not_future(cls, v: str):
|
||||
if v is None:
|
||||
return None
|
||||
if v == "":
|
||||
return ""
|
||||
try:
|
||||
actual = datetime_date(v)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail='日期格式为(YYYY-MM-DD)')
|
||||
|
||||
today = datetime_date(get_current_date())
|
||||
if actual > today:
|
||||
raise HTTPException(status_code=400, detail='签约日期不能晚于今天')
|
||||
return v
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
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
|
||||
|
||||
class PlanNode(BaseModel):
|
||||
date: DateStr = Field(..., description="计划节点时间", examples=["2024-01-01"])
|
||||
quantity: float = Field(..., description="计划完成量", examples=[100.0])
|
||||
is_audit: Optional[bool] = Field(False, description="是否已审核", examples=[False])
|
||||
|
||||
@field_validator("quantity")
|
||||
def validate_quantity(cls, v):
|
||||
"""验证计划完成量必须大于0"""
|
||||
if v < 0:
|
||||
raise HTTPException(status_code=400, detail='计划完成量不能小于0')
|
||||
return v
|
||||
|
||||
class ContractSettlementResponse(BaseResponseWithID):
|
||||
"""
|
||||
合同结算返回
|
||||
"""
|
||||
project_id: str = Field(..., description="所属项目ID", examples=["project123"])
|
||||
contract_id: str = Field(..., description="所属合同ID", examples=["contract456"])
|
||||
contract_type: Optional[str] = Field(None, description="合同类型")
|
||||
show_name: Optional[str] = Field(None, description="合同简称", examples=["主变采购合同"])
|
||||
complete_price: float = Field(0.0, description="实际完成金额", examples=[100000.0])
|
||||
contract_price: float = Field(0.0, description="合同金额", examples=[120000.0])
|
||||
actual_complete_date: Optional[DateStr] = Field(None, description="实际结束时间", examples=["2024-06-30"])
|
||||
actual_start_date: Optional[DateStr] = Field(None, description="实际开始时间", examples=["2024-01-01"])
|
||||
plan_complete_date: Optional[DateStr] = Field(None, description="计划结束时间", examples=["2024-06-30"])
|
||||
plan_start_date: Optional[DateStr] = Field(None, description="计划开始时间", examples=["2024-01-01"])
|
||||
plan_node: List[PlanNode] = Field(default_factory=list, description="计划节点列表")
|
||||
status: Optional[int] = Field(None,description="供货状态", examples=[0])
|
||||
|
||||
# 2025-10-20新增字段,延迟时间
|
||||
delay_days: Optional[int] = Field(None, description="延迟时间(天)", examples=[0])
|
||||
statistic_date: Optional[DateStr] = Field(
|
||||
None, description="统计节点截止日期", examples=["2024-05-31"]
|
||||
)
|
||||
|
||||
class ContractSettlementCreate(BaseModel):
|
||||
"""
|
||||
合同结算创建模型
|
||||
CONTRACT_SETTLEMENT_BASE_INFO = "contract_settlement.base_info"
|
||||
"""
|
||||
project_id: str = Field(..., description="所属项目ID", examples=["project123"])
|
||||
contract_id: str = Field(..., description="所属合同ID", examples=["contract456"])
|
||||
show_name: Optional[str] = Field(None, description="合同简称", examples=["主变采购合同"])
|
||||
|
||||
class ContractSettlementUpdate(BaseModel):
|
||||
"""
|
||||
合同结算更新模型
|
||||
CONTRACT_SETTLEMENT_BASE_INFO = "contract_settlement.base_info"
|
||||
"""
|
||||
id: str = Field(..., description="合同结算ID")
|
||||
contract_id: str = Field(..., description="所属合同ID", examples=["contract456"])
|
||||
show_name: Optional[str] = Field(None, description="合同简称", examples=["主变采购合同"])
|
||||
|
||||
class ContractSettlementPlanUpdate(BaseModel):
|
||||
"""
|
||||
合同结算计划节点更新模型
|
||||
CONTRACT_SETTLEMENT_PLAN_NODE = "contract_settlement.plan_node"
|
||||
|
||||
开始时间和金额在合同中维护,这里只维护计划节点和计划结束时间
|
||||
|
||||
业务逻辑说明:
|
||||
- API要求计划节点时间必须晚于计划开始时间(不能等于)
|
||||
- 自动清理脏数据:如果 plan_node 有2个或以上节点,删除边界节点
|
||||
"""
|
||||
id: str = Field(..., description="合同结算ID")
|
||||
plan_start_date: Optional[DateStr] = Field(None, exclude=True, description="计划开始时间(不推往后端,仅用于本地首节点清洗)", examples=["2024-06-30"])
|
||||
plan_complete_date: Optional[DateStr] = Field(None, description="计划结束时间", examples=["2024-06-30"])
|
||||
plan_node: List[PlanNode] = Field(default_factory=list, description="计划节点列表")
|
||||
|
||||
@model_validator(mode='after')
|
||||
def clean_plan_nodes(self):
|
||||
"""
|
||||
清理计划节点列表中的边界节点
|
||||
|
||||
注:settlement的plan_start_date在contract中维护,此处利用外部提供的辅助字段清除非法首尾节点。
|
||||
"""
|
||||
if not self.plan_node or len(self.plan_node) < 2:
|
||||
return self
|
||||
|
||||
cleaned_nodes = list(self.plan_node)
|
||||
|
||||
# 删除开头的边界节点
|
||||
if (self.plan_start_date and
|
||||
cleaned_nodes and
|
||||
cleaned_nodes[0].date == self.plan_start_date):
|
||||
cleaned_nodes.pop(0)
|
||||
|
||||
# 删除结尾的边界节点
|
||||
if (self.plan_complete_date and
|
||||
cleaned_nodes and
|
||||
cleaned_nodes[-1].date == self.plan_complete_date):
|
||||
cleaned_nodes.pop()
|
||||
|
||||
self.plan_node = cleaned_nodes
|
||||
return self
|
||||
@@ -0,0 +1,128 @@
|
||||
from pydantic import Field, BaseModel,field_validator
|
||||
from fastapi import HTTPException
|
||||
from ..common.datetime import get_current_date
|
||||
from typing import List, Optional, Literal
|
||||
from ..common.validators import DateStr
|
||||
from ..common.base import BaseResponseWithID
|
||||
from ..common.enums import ApprovalStatus
|
||||
from datetime import datetime
|
||||
|
||||
class ContractSettlementDetailResponse(BaseResponseWithID):
|
||||
"""
|
||||
合同结算明细返回模型
|
||||
"""
|
||||
project_id: str = Field(..., description="所属项目ID", examples=["project123"])
|
||||
parent_id: str = Field(..., description="所属合同结算ID", examples=["settlement789"])
|
||||
date: DateStr = Field(..., description="结算日期", examples=["2024-05-31"])
|
||||
quantity: float = Field(..., description="合同金额", examples=[0])
|
||||
remark: Optional[str] = Field(None, description="填报备注", examples=[""])
|
||||
is_complete: Optional[bool] = Field(None, description="是否完成", examples=[False])
|
||||
write_off_rate: Optional[float] = Field(None, description="核销率", examples=[0.0])
|
||||
change_count: int = Field(0, description="变更次数", examples=[0])
|
||||
status: Optional[int] = Field(None, description="进度状态", examples=[0])
|
||||
rate: Optional[float] = Field(None, description="实际完成率", examples=[0.0])
|
||||
|
||||
class ContractSettlementDetailCreate(BaseModel):
|
||||
"""
|
||||
合同结算明细创建模型
|
||||
CONTRACT_SETTLEMENT_DETAIL = "contract_settlement.detail"
|
||||
"""
|
||||
project_id: str = Field(..., description="所属项目ID", examples=["project123"])
|
||||
parent_id: str = Field(..., description="所属合同结算ID", examples=["settlement789"])
|
||||
date: DateStr = Field(..., description="结算日期", examples=["2024-05-31"])
|
||||
quantity: float = Field(..., description="合同金额", examples=[0])
|
||||
remark: Optional[str] = Field(None, description="填报备注", examples=[""])
|
||||
is_complete: bool = Field(False, description="是否完成", examples=[False])
|
||||
write_off_rate: Optional[float] = Field(None, description="核销率", examples=[0.0])
|
||||
confirmation_time: str | None = Field(None, description="确认时间", examples=["2025-12-17 11:13:49"])
|
||||
approval_time: str | None = Field(None, description="审批时间", examples=["2025-12-17 11:13:49"])
|
||||
approval_status: ApprovalStatus = Field("registered", description="审批状态", examples=["registered"])
|
||||
|
||||
@field_validator("approval_status")
|
||||
def validate_approval_status(cls, v):
|
||||
"""验证审批字段"""
|
||||
try:
|
||||
return ApprovalStatus(v) # 尝试转换
|
||||
except ValueError:
|
||||
raise HTTPException(400, f"审批状态 '{v}' 无效")
|
||||
|
||||
@field_validator("confirmation_time", "approval_time")
|
||||
def validate_time(cls, v):
|
||||
"""验证时间"""
|
||||
if v is None:
|
||||
return v
|
||||
try:
|
||||
datetime.strptime(v, "%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
raise HTTPException(detail=f'{v}时间格式错误,应为:2025-12-17 11:13:49', status_code=400)
|
||||
return v
|
||||
|
||||
@field_validator("quantity")
|
||||
def validate_quantity(cls, v):
|
||||
"""验证实际完成量累计必须为非负数"""
|
||||
if v < 0:
|
||||
raise HTTPException(status_code=400, detail='合同金额不能小于0')
|
||||
return v
|
||||
|
||||
@field_validator("date")
|
||||
def validate_date(cls, v):
|
||||
if v > get_current_date():
|
||||
raise HTTPException(detail='结算日期不能大于今天', status_code=400)
|
||||
return v
|
||||
|
||||
class ContractSettlementDetailUpdate(BaseModel):
|
||||
"""
|
||||
合同结算明细更新模型
|
||||
CONTRACT_SETTLEMENT_DETAIL = "contract_settlement.detail"
|
||||
"""
|
||||
id: str = Field(..., description="合同明细ID")
|
||||
date: DateStr | None = Field(None, description="结算日期", examples=["2024-05-31"])
|
||||
quantity: float | None = Field(None, description="合同金额", examples=[0])
|
||||
remark: str | None = Field(None, description="填报备注", examples=[""])
|
||||
is_complete: bool | None = Field(None, description="是否完成", examples=[False])
|
||||
write_off_rate: float | None = Field(None, description="核销率", examples=[0.0])
|
||||
confirmation_time: str | None = Field(None, description="确认时间", examples=["2025-12-17 11:13:49"])
|
||||
approval_time: str | None = Field(None, description="审批时间", examples=["2025-12-17 11:13:49"])
|
||||
approval_status: ApprovalStatus | None = Field("registered", description="审批状态", examples=["registered"])
|
||||
|
||||
@field_validator("approval_status")
|
||||
def validate_approval_status(cls, v):
|
||||
"""验证审批字段"""
|
||||
if v is None:
|
||||
return v
|
||||
try:
|
||||
return ApprovalStatus(v) # 尝试转换
|
||||
except ValueError:
|
||||
raise HTTPException(400, f"审批状态 '{v}' 无效")
|
||||
|
||||
@field_validator("confirmation_time", "approval_time")
|
||||
def validate_time(cls, v):
|
||||
"""验证时间"""
|
||||
if v is None:
|
||||
return v
|
||||
|
||||
try:
|
||||
datetime.strptime(v, "%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
raise HTTPException(detail=f'{v}时间格式错误,应为:2025-12-17 11:13:49', status_code=400)
|
||||
return v
|
||||
|
||||
@field_validator("quantity")
|
||||
def validate_quantity(cls, v):
|
||||
"""验证实际完成量累计必须为非负数"""
|
||||
if v is None:
|
||||
return None
|
||||
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 None
|
||||
if v > get_current_date():
|
||||
raise HTTPException(detail='结算日期不能大于今天', status_code=400)
|
||||
return v
|
||||
|
||||
class ContractSettlementDetailBulkUpdate(BaseModel):
|
||||
detail_list: List[ContractSettlementDetailUpdate]
|
||||
@@ -0,0 +1,147 @@
|
||||
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
|
||||
|
||||
class PlanNode(BaseModel):
|
||||
date: DateStr = Field(..., description="计划节点时间", examples=["2024-01-01"])
|
||||
quantity: float = Field(..., description="计划完成量", examples=[100.0])
|
||||
is_audit: Optional[bool] = Field(False, description="是否已审核", examples=[False])
|
||||
|
||||
@field_validator("quantity")
|
||||
def validate_quantity(cls, v):
|
||||
"""验证实际完成量累计必须为非负数"""
|
||||
if v < 0:
|
||||
raise HTTPException(status_code=400, detail='计划完成量不能小于等于0')
|
||||
return v
|
||||
|
||||
class MaterialResponse(BaseResponseWithID):
|
||||
"""
|
||||
供货模型
|
||||
"""
|
||||
id : str = Field(..., description="物资ID", examples=["material123"])
|
||||
project_id: str = Field(..., description="所属项目ID", examples=["project123"])
|
||||
contract_id: str = Field("", description="所属合同ID", examples=["contract456"])
|
||||
show_name: str = Field("", description="展示名称", examples=["主变"])
|
||||
material_type: str = Field("", description="物资类型")
|
||||
contract_quantity: float = Field(0.0, description="合同数量", examples=[1000.0])
|
||||
plan_start_quantity: float = Field(
|
||||
0.0, description="计划首批到货量", examples=[1000.0]
|
||||
)
|
||||
complete_quantity: float = Field(0.0, description="实际到货累计", examples=[0.0])
|
||||
complete_rate: float = Field(0.0, description="实际到货率(%)", examples=[0.0])
|
||||
plan_start_date: Optional[DateStr] = Field(
|
||||
None, description="计划首批到货时间", examples=["2024-01-01"]
|
||||
)
|
||||
plan_complete_date: Optional[DateStr] = Field(
|
||||
None, description="计划全部到货时间", examples=["2024-06-01"]
|
||||
)
|
||||
actual_start_date: Optional[DateStr] = Field(
|
||||
None, description="实际首批到货时间", examples=["2024-01-15"]
|
||||
)
|
||||
actual_complete_date: Optional[DateStr] = Field(
|
||||
None, description="实际全部到货时间", examples=["2024-06-10"]
|
||||
)
|
||||
plan_node: List[PlanNode] = Field(default_factory=list, description="计划节点列表")
|
||||
status: Optional[int] = Field(None, description="供货状态", examples=[0])
|
||||
|
||||
# 2025-10-20新增字段,延迟时间
|
||||
delay_days: Optional[int] = Field(None, description="延迟时间(天)", examples=[0])
|
||||
statistic_date: Optional[DateStr] = Field(
|
||||
None, description="统计节点截止日期", examples=["2024-05-31"]
|
||||
)
|
||||
|
||||
class MaterialCreate(BaseModel):
|
||||
"""
|
||||
创建物资
|
||||
MATERIAL_BASE_INFO = "material.base_info"
|
||||
"""
|
||||
project_id: str = Field(..., description="所属项目ID", examples=["project123"])
|
||||
contract_id: str = Field(..., description="所属合同ID", examples=["contract456"])
|
||||
show_name: str = Field(..., description="展示名称", examples=["主变"])
|
||||
material_type: str = Field(..., description="物资类型")
|
||||
contract_quantity: float = Field(0.0, description="合同数量", examples=[1000.0])
|
||||
|
||||
@field_validator("contract_quantity")
|
||||
def validate_quantity(cls, v):
|
||||
"""验证实际完成量累计必须为非负数"""
|
||||
if v < 0:
|
||||
raise HTTPException(status_code=400, detail='合同数量不能小于0')
|
||||
return v
|
||||
|
||||
class MaterialUpdate(BaseModel):
|
||||
"""
|
||||
更新物资
|
||||
MATERIAL_BASE_INFO = "material.base_info"
|
||||
"""
|
||||
id: str = Field(..., description="物资ID", examples=["material123"])
|
||||
contract_id: Optional[str] = Field(None, description="所属合同ID", examples=["contract456"])
|
||||
show_name: Optional[str] = Field(None, description="展示名称", examples=["主变"])
|
||||
|
||||
|
||||
class MaterialPlanUpdate(BaseModel):
|
||||
"""
|
||||
物资计划更新模型
|
||||
MATERIAL_PLAN = "material.plan"
|
||||
|
||||
业务逻辑说明:
|
||||
- API要求计划节点时间必须晚于计划首批到货时间(不能等于)
|
||||
- 自动清理脏数据:如果 plan_node 有2个或以上节点,删除:
|
||||
1. 开头节点:如果其 date 等于 plan_start_date
|
||||
2. 结尾节点:如果其 date 等于 plan_complete_date
|
||||
- 这样避免提交包含边界节点的数据导致API报错
|
||||
"""
|
||||
id: str = Field(..., description="物资ID", examples=["material123"])
|
||||
plan_start_quantity: Optional[float] = Field(
|
||||
None, description="计划首批到货量", examples=[1000.0]
|
||||
)
|
||||
plan_start_date: Optional[str] = Field(None, description="计划首批到货时间", examples=["2024-01-01"])
|
||||
plan_complete_date: Optional[str] = Field(None, description="计划全部到货时间", examples=["2024-06-01"])
|
||||
contract_quantity: float = Field(..., description="合同数量", examples=[1000.0])
|
||||
plan_node: Optional[List[PlanNode]] = Field(default_factory=list, description="计划节点列表")
|
||||
|
||||
@field_validator("contract_quantity")
|
||||
def validate_quantity(cls, v):
|
||||
"""验证实际完成量累计必须为非负数"""
|
||||
if v < 0:
|
||||
raise HTTPException(status_code=400, detail='合同数量不能小于0')
|
||||
return v
|
||||
|
||||
@field_validator("plan_start_quantity")
|
||||
def validate_plan_start_quantity(cls, v):
|
||||
"""验证实际完成量累计必须为非负数"""
|
||||
if v < 0:
|
||||
raise HTTPException(status_code=400, detail='计划首批到货量不能小于0')
|
||||
return v
|
||||
|
||||
@model_validator(mode='after')
|
||||
def clean_plan_nodes(self):
|
||||
"""
|
||||
清理计划节点列表中的边界节点
|
||||
|
||||
如果 plan_node 有2个或以上节点,删除:
|
||||
- 开头节点:date 等于 plan_start_date
|
||||
- 结尾节点:date 等于 plan_complete_date
|
||||
|
||||
这避免了API报错“计划节点时间必须晚于计划首批到货时间”
|
||||
"""
|
||||
if not self.plan_node or len(self.plan_node) < 2:
|
||||
return self
|
||||
|
||||
cleaned_nodes = list(self.plan_node)
|
||||
|
||||
# 删除开头的边界节点
|
||||
if (self.plan_start_date and
|
||||
cleaned_nodes and
|
||||
cleaned_nodes[0].date == self.plan_start_date):
|
||||
cleaned_nodes.pop(0)
|
||||
|
||||
# 删除结尾的边界节点
|
||||
if (self.plan_complete_date and
|
||||
cleaned_nodes and
|
||||
cleaned_nodes[-1].date == self.plan_complete_date):
|
||||
cleaned_nodes.pop()
|
||||
|
||||
self.plan_node = cleaned_nodes
|
||||
return self
|
||||
@@ -0,0 +1,132 @@
|
||||
from pydantic import Field, BaseModel,field_validator
|
||||
from fastapi import HTTPException
|
||||
from ..common.datetime import get_current_date
|
||||
from typing import List, Optional, Literal
|
||||
from ..common.validators import DateStr
|
||||
from ..common.base import BaseResponseWithID
|
||||
from ..common.enums import ApprovalStatus
|
||||
from datetime import datetime
|
||||
|
||||
class MaterialDetailResponse(BaseResponseWithID):
|
||||
"""
|
||||
物资明细模型,存储在CouchDB中的物资明细文档
|
||||
"""
|
||||
project_id: str = Field(..., description="所属项目ID", examples=["project123"])
|
||||
parent_id: str = Field(..., description="所属物资ID", examples=["material789"])
|
||||
date: DateStr = Field(..., description="实际到货日期", examples=["2024-05-31"])
|
||||
rate: Optional[float] = Field(None, description="实际到货率(%)", examples=[0.0])
|
||||
quantity: float = Field(0.0, ge=0, description="实际到货量累计", examples=[0.0])
|
||||
status: Optional[int] = Field(None, description="状态 0:正常 1:超前 -1:延误", examples=[0])
|
||||
remark: Optional[str] = Field(None, description="填报备注", examples=[""])
|
||||
is_complete: Optional[bool] = Field(
|
||||
None, description="是否为全部到货", examples=[False]
|
||||
)
|
||||
|
||||
class MaterialDetailCreate(BaseModel):
|
||||
"""
|
||||
物资明细创建模型
|
||||
MATERIAL_DETAIL = "material.detail"
|
||||
"""
|
||||
project_id: str = Field(..., description="所属项目ID", examples=["project123"])
|
||||
parent_id: str = Field(..., description="所属物资ID", examples=["material789"])
|
||||
quantity: float = Field(0.0, ge=0, description="实际到货量累计", examples=[0.0])
|
||||
date: DateStr = Field(..., description="实际到货日期", examples=["2024-05-31"])
|
||||
remark: Optional[str] = Field(None, description="填报备注", examples=[""])
|
||||
is_complete: Optional[bool] = Field(
|
||||
None, description="是否为全部到货", examples=[False]
|
||||
)
|
||||
confirmation_time: str | None = Field(None, description="确认时间", examples=["2025-12-17 11:13:49"])
|
||||
approval_time: str | None = Field(None, description="审批时间", examples=["2025-12-17 11:13:49"])
|
||||
approval_status: ApprovalStatus = Field("registered", description="审批状态", examples=["registered"])
|
||||
|
||||
@field_validator("approval_status")
|
||||
def validate_approval_status(cls, v):
|
||||
"""验证审批字段"""
|
||||
try:
|
||||
return ApprovalStatus(v) # 尝试转换
|
||||
except ValueError:
|
||||
raise HTTPException(400, f"审批状态 '{v}' 无效")
|
||||
|
||||
@field_validator("confirmation_time", "approval_time")
|
||||
def validate_time(cls, v):
|
||||
"""验证时间"""
|
||||
if v is None:
|
||||
return v
|
||||
try:
|
||||
datetime.strptime(v, "%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
raise HTTPException(detail=f'{v}时间格式错误,应为:2025-12-17 11:13:49', status_code=400)
|
||||
return v
|
||||
|
||||
@field_validator("quantity")
|
||||
def validate_quantity(cls, v):
|
||||
"""验证实际完成量累计必须为非负数"""
|
||||
if v < 0:
|
||||
raise HTTPException(status_code=400, detail='实际完成量累计不能小于0')
|
||||
return v
|
||||
|
||||
@field_validator("date")
|
||||
def validate_date(cls, v):
|
||||
if v > get_current_date():
|
||||
raise HTTPException(detail='节点日期不能大于今天', status_code=400)
|
||||
return v
|
||||
|
||||
class MaterialDetailUpdate(BaseModel):
|
||||
"""
|
||||
物资明细更新模型
|
||||
MATERIAL_DETAIL = "material.detail"
|
||||
"""
|
||||
id: str = Field(..., description="物资明细ID")
|
||||
quantity: Optional[float] = Field(None, ge=0, description="实际到货量累计", examples=[0.0])
|
||||
date: Optional[DateStr] = Field(None, description="实际到货日期", examples=["2024-05-31"])
|
||||
remark: Optional[str] = Field(None, description="填报备注", examples=[""])
|
||||
is_complete: Optional[bool] = Field(
|
||||
None, description="是否为全部到货", examples=[False]
|
||||
)
|
||||
confirmation_time: str | None = Field(None, description="确认时间", examples=["2025-12-17 11:13:49"])
|
||||
approval_time: str | None = Field(None, description="审批时间", examples=["2025-12-17 11:13:49"])
|
||||
approval_status: ApprovalStatus | None = Field("registered", description="审批状态", examples=["registered"])
|
||||
|
||||
@field_validator("approval_status")
|
||||
def validate_approval_status(cls, v):
|
||||
"""验证审批字段"""
|
||||
if v is None:
|
||||
return v
|
||||
try:
|
||||
return ApprovalStatus(v) # 尝试转换
|
||||
except ValueError:
|
||||
raise HTTPException(400, f"审批状态 '{v}' 无效")
|
||||
|
||||
@field_validator("confirmation_time", "approval_time")
|
||||
def validate_time(cls, v):
|
||||
"""验证时间"""
|
||||
if v is None:
|
||||
return v
|
||||
|
||||
try:
|
||||
datetime.strptime(v, "%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
raise HTTPException(detail=f'{v}时间格式错误,应为:2025-12-17 11:13:49', status_code=400)
|
||||
return v
|
||||
|
||||
@field_validator("quantity")
|
||||
def validate_quantity(cls, v):
|
||||
"""验证实际完成量累计必须为非负数"""
|
||||
if v is None:
|
||||
return None
|
||||
|
||||
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 None
|
||||
|
||||
if v > get_current_date():
|
||||
raise HTTPException(detail='节点日期不能大于今天', status_code=400)
|
||||
return v
|
||||
|
||||
class MaterialDetailBulkUpdate(BaseModel):
|
||||
detail_list: List[MaterialDetailUpdate]
|
||||
@@ -0,0 +1,144 @@
|
||||
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
|
||||
|
||||
class PlanNode(BaseModel):
|
||||
date: DateStr = Field(..., description="计划节点时间", examples=["2024-01-01"])
|
||||
quantity: float = Field(..., description="计划完成量", examples=[100.0])
|
||||
is_audit: Optional[bool] = Field(False, description="是否已审核", examples=[False])
|
||||
|
||||
@field_validator("quantity")
|
||||
def validate_quantity(cls, v):
|
||||
"""验证计划到场人数必须为非负数"""
|
||||
if v < 0:
|
||||
raise HTTPException(status_code=400, detail='计划节点人数不能小于0')
|
||||
return v
|
||||
|
||||
class PowerResponse(BaseResponseWithID):
|
||||
"""
|
||||
力能模型
|
||||
"""
|
||||
code: Optional[str] = Field(
|
||||
"personnel",
|
||||
description="力能类型(人员模块拓展为力能模块,可以在本字段保存人员/机具等编码)",
|
||||
examples=["personnel"],
|
||||
)
|
||||
project_id: str = Field(..., description="所属项目", examples=["project123"])
|
||||
contract_id: Optional[str] = Field(
|
||||
None,
|
||||
description="所属合同(目前前端页面显示没有按这个字段,仅通过脚本绑定了epc合同,用于接口数据推送。后续行为待讨论)",
|
||||
examples=["contract456"],
|
||||
)
|
||||
show_name: str = Field("", description="展示名称", examples=["施工班组A"])
|
||||
status: Optional[int] = Field(None, description="人员状态", examples=[0])
|
||||
plan_attendance: int = Field(
|
||||
0,
|
||||
description="计划出勤人数(暂时定为最后一次报明细时的计划出勤人数)",
|
||||
examples=[10],
|
||||
)
|
||||
actual_daily_attendance: int = Field(0, description="实际日均出勤", examples=[8])
|
||||
attendance_rate: float = Field(0.0, description="出勤率", examples=[0.8])
|
||||
statistic_date: Optional[DateStr] = Field(
|
||||
None, description="统计节点", examples=["2024-05-31"]
|
||||
)
|
||||
plan_exit_date: Optional[DateStr] = Field(
|
||||
None, description="计划退场时间", examples=["2024-06-30"]
|
||||
)
|
||||
actual_exit_date: Optional[DateStr] = Field(
|
||||
None, description="实际退场时间", examples=["2024-07-05"]
|
||||
)
|
||||
plan_node: List[PlanNode] = Field(default_factory=list, description="计划节点列表")
|
||||
plan_start_date: Optional[DateStr] = Field(
|
||||
None, description="计划首批到场时间", examples=["2024-05-01"]
|
||||
)
|
||||
plan_start_quantity: int = Field(0, description="计划首批到场人数", examples=[5], ge=0)
|
||||
actual_start_date: Optional[DateStr] = Field(
|
||||
None, description="实际首批到场时间", examples=["2024-05-02"]
|
||||
)
|
||||
actual_start_quantity: Optional[int] = Field(
|
||||
None, description="实际首批到场人数", examples=[4]
|
||||
)
|
||||
|
||||
class PowerCreate(BaseModel):
|
||||
"""
|
||||
创建力能
|
||||
POWER_BASE_INFO = "personnel.base_info"
|
||||
"""
|
||||
project_id: str = Field(..., description="所属项目ID", examples=["project123"])
|
||||
contract_id: Optional[str] = Field(
|
||||
None, description="所属合同ID", examples=["contract456"]
|
||||
)
|
||||
#show_name: str = Field(..., description="展示名称", examples=["施工班组A"])
|
||||
code: Optional[str] = Field(
|
||||
"personnel",
|
||||
description="力能类型(人员模块拓展为力能模块,可以在本字段保存人员/机具等编码)",
|
||||
examples=["personnel"],
|
||||
)
|
||||
|
||||
class PowerUpdate(BaseModel):
|
||||
"""
|
||||
更新力能
|
||||
POWER_BASE_INFO = "personnel.base_info"
|
||||
"""
|
||||
id: str = Field(..., description="力能ID")
|
||||
contract_id: Optional[str] = Field(
|
||||
None, description="所属合同ID", examples=["contract456"]
|
||||
)
|
||||
#show_name: Optional[str] = Field(None, description="展示名称", examples=["施工班组A"])
|
||||
|
||||
class PowerPlanUpdate(BaseModel):
|
||||
"""
|
||||
力能计划更新模型
|
||||
POWER_PLAN = "personnel.plan"
|
||||
|
||||
业务逻辑说明:
|
||||
- API要求计划节点时间必须晚于计划首批到场时间(不能等于)
|
||||
- 自动清理脏数据:如果 plan_node 有2个或以上节点,删除边界节点
|
||||
"""
|
||||
id: str = Field(..., description="力能ID")
|
||||
plan_start_date: str = Field(..., description="计划首批到场时间")
|
||||
plan_start_quantity: int = Field(..., description="计划首批到场人数")
|
||||
plan_exit_date: DateStr = Field(
|
||||
..., description="计划退场时间", examples=["2024-06-30"]
|
||||
)
|
||||
plan_node: List[PlanNode] = Field(default_factory=list, description="计划节点列表")
|
||||
|
||||
@field_validator("plan_start_quantity")
|
||||
def validate_quantity(cls, v):
|
||||
"""验证计划首批到场人数必须为非负数"""
|
||||
if v < 0:
|
||||
raise HTTPException(status_code=400, detail='计划首批进场人数必须大于等于0')
|
||||
return v
|
||||
|
||||
@model_validator(mode='after')
|
||||
def clean_plan_nodes(self):
|
||||
"""
|
||||
清理计划节点列表中的边界节点
|
||||
|
||||
如果 plan_node 有2个或以上节点,删除:
|
||||
- 开头节点:date 等于 plan_start_date
|
||||
- 结尾节点:date 等于 plan_exit_date
|
||||
|
||||
这避免了API报错“计划节点时间必须晚于计划首批到场时间”
|
||||
"""
|
||||
if not self.plan_node or len(self.plan_node) < 2:
|
||||
return self
|
||||
|
||||
cleaned_nodes = list(self.plan_node)
|
||||
|
||||
# 删除开头的边界节点
|
||||
if (self.plan_start_date and
|
||||
cleaned_nodes and
|
||||
cleaned_nodes[0].date == self.plan_start_date):
|
||||
cleaned_nodes.pop(0)
|
||||
|
||||
# 删除结尾的边界节点
|
||||
if (self.plan_exit_date and
|
||||
cleaned_nodes and
|
||||
cleaned_nodes[-1].date == self.plan_exit_date):
|
||||
cleaned_nodes.pop()
|
||||
|
||||
self.plan_node = cleaned_nodes
|
||||
return self
|
||||
@@ -0,0 +1,133 @@
|
||||
from pydantic import Field, BaseModel,field_validator
|
||||
from fastapi import HTTPException
|
||||
from ..common.datetime import get_current_date
|
||||
from typing import List, Optional, Literal
|
||||
from ..common.validators import DateStr
|
||||
from ..common.base import BaseResponseWithID
|
||||
from ..common.enums import ApprovalStatus
|
||||
from datetime import datetime
|
||||
|
||||
class PowerDetail(BaseResponseWithID):
|
||||
"""
|
||||
力能配置明细
|
||||
"""
|
||||
project_id: str = Field(..., description="所属项目", examples=["project123"])
|
||||
parent_id: str = Field(..., description="所属力能配置ID", examples=["personnel789"])
|
||||
date: DateStr = Field(..., description="本次日期", examples=["2024-05-31"])
|
||||
quantity: int = Field(..., description="累计完成量", examples=[0], ge=0)
|
||||
remark: Optional[str] = Field(None, description="填报备注", examples=[""])
|
||||
is_complete: Optional[bool] = Field(
|
||||
None, description="是否为全部退场", examples=[False]
|
||||
)
|
||||
change_count: int = Field(0, description="变更次数", examples=[0])
|
||||
status: Optional[int] = Field(None, description="进度状态", examples=[0])
|
||||
rate: Optional[float] = Field(None, description="出勤率", examples=[0])
|
||||
|
||||
class PowerDetailCreate(BaseModel):
|
||||
"""
|
||||
力能配置明细创建模型
|
||||
POWER_DETAIL = "power.detail"
|
||||
"""
|
||||
project_id: str = Field(..., description="所属项目", examples=["project123"])
|
||||
parent_id: str = Field(..., description="所属力能配置ID", examples=["personnel789"])
|
||||
date: DateStr = Field(..., description="本次日期", examples=["2024-05-31"])
|
||||
quantity: int = Field(..., description="累计完成量", examples=[0], ge=0)
|
||||
remark: Optional[str] = Field(None, description="填报备注", examples=[""])
|
||||
is_complete: Optional[bool] = Field(
|
||||
None, description="是否为全部退场", examples=[False]
|
||||
)
|
||||
confirmation_time: str | None = Field(None, description="确认时间", examples=["2025-12-17 11:13:49"])
|
||||
approval_time: str | None = Field(None, description="审批时间", examples=["2025-12-17 11:13:49"])
|
||||
approval_status: ApprovalStatus = Field("registered", description="审批状态", examples=["registered"])
|
||||
|
||||
@field_validator("approval_status")
|
||||
def validate_approval_status(cls, v):
|
||||
"""验证审批字段"""
|
||||
try:
|
||||
return ApprovalStatus(v) # 尝试转换
|
||||
except ValueError:
|
||||
raise HTTPException(400, f"审批状态 '{v}' 无效")
|
||||
|
||||
@field_validator("confirmation_time", "approval_time")
|
||||
def validate_time(cls, v):
|
||||
"""验证时间"""
|
||||
if v is None:
|
||||
return v
|
||||
try:
|
||||
datetime.strptime(v, "%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
raise HTTPException(detail=f'{v}时间格式错误,应为:2025-12-17 11:13:49', status_code=400)
|
||||
return v
|
||||
|
||||
@field_validator("quantity")
|
||||
def validate_quantity(cls, v):
|
||||
"""验证实际完成量累计必须为非负数"""
|
||||
if v < 0:
|
||||
raise HTTPException(status_code=400, detail='实际完成量累计不能小于0')
|
||||
return v
|
||||
|
||||
@field_validator("date")
|
||||
def validate_date(cls, v):
|
||||
if v > get_current_date():
|
||||
raise HTTPException(detail='节点日期不能大于今天', status_code=400)
|
||||
return v
|
||||
|
||||
class PowerDetailUpdate(BaseModel):
|
||||
"""
|
||||
力能配置明细更新模型
|
||||
POWER_DETAIL = "power.detail"
|
||||
"""
|
||||
id: str = Field(..., description="力能配置明细ID")
|
||||
date: Optional[DateStr] = Field(None, description="本次日期", examples=["2024-05-31"])
|
||||
quantity: Optional[int] = Field(None, description="累计完成量", examples=[0], ge=0)
|
||||
remark: Optional[str] = Field(None, description="填报备注", examples=[""])
|
||||
is_complete: Optional[bool] = Field(
|
||||
None, description="是否为全部退场", examples=[False]
|
||||
)
|
||||
confirmation_time: str | None = Field(None, description="确认时间", examples=["2025-12-17 11:13:49"])
|
||||
approval_time: str | None = Field(None, description="审批时间", examples=["2025-12-17 11:13:49"])
|
||||
approval_status: ApprovalStatus | None = Field("registered", description="审批状态", examples=["registered"])
|
||||
|
||||
@field_validator("approval_status")
|
||||
def validate_approval_status(cls, v):
|
||||
"""验证审批字段"""
|
||||
if v is None:
|
||||
return v
|
||||
try:
|
||||
return ApprovalStatus(v) # 尝试转换
|
||||
except ValueError:
|
||||
raise HTTPException(400, f"审批状态 '{v}' 无效")
|
||||
|
||||
@field_validator("confirmation_time", "approval_time")
|
||||
def validate_time(cls, v):
|
||||
"""验证时间"""
|
||||
if v is None:
|
||||
return v
|
||||
|
||||
try:
|
||||
datetime.strptime(v, "%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
raise HTTPException(detail=f'{v}时间格式错误,应为:2025-12-17 11:13:49', status_code=400)
|
||||
return v
|
||||
|
||||
@field_validator("quantity")
|
||||
def validate_quantity(cls, v):
|
||||
"""验证实际完成量累计必须为非负数"""
|
||||
if v is None:
|
||||
return None
|
||||
|
||||
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 None
|
||||
|
||||
if v > get_current_date():
|
||||
raise HTTPException(detail='节点日期不能大于今天', status_code=400)
|
||||
return v
|
||||
|
||||
class PowerDetailBulkUpdate(BaseModel):
|
||||
detail_list: List[PowerDetailUpdate]
|
||||
@@ -0,0 +1,30 @@
|
||||
from enum import Enum
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class ProgressType(str, Enum):
|
||||
"""推进分类枚举"""
|
||||
A = "A"
|
||||
B = "B"
|
||||
C = "C"
|
||||
|
||||
class KeyConstraints(str, Enum):
|
||||
NONE = "NONE"
|
||||
LAND = "LAND"
|
||||
TRANSMISSION = "TRANSMISSION"
|
||||
BOTH = "BOTH"
|
||||
QT = "QT"
|
||||
|
||||
class DBTCSCORE(BaseModel):
|
||||
year: str = Field(..., description="评分年份, yyyy格式")
|
||||
is_jg: bool = Field(..., description="是否为工达标投产")
|
||||
score: float = Field(..., description="集团考核评分")
|
||||
|
||||
class ProjectType(str, Enum):
|
||||
"""项目类型枚举"""
|
||||
PHOTOVOLTAIC = "photovoltaic" # 光伏
|
||||
OFFSHORE_WIND = "offshore_wind" # 海风
|
||||
ONSHORE_WIND = "onshore_wind" # 陆风
|
||||
HYDROPOWER = "hydropower" # 水电
|
||||
THERMAL_POWER = "thermal_power" # 火电
|
||||
PUMPED_STORAGE = "pumped_storage" # 抽蓄
|
||||
GAS_TURBINE = "gas_turbine" # 气电
|
||||
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
项目全部信息的增删查改模板,聚合了项目相关schema
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field, field_serializer
|
||||
from typing import Optional
|
||||
from .project_base import (
|
||||
ProjectResponseBase,
|
||||
ProjectEnsureCapacity,
|
||||
ProjectClimbCapacity,
|
||||
ProjectChallengeCapacity,
|
||||
ProjectActualConstruction,
|
||||
ProjectPlanConstruction,
|
||||
ProjectInvestmentDecision,
|
||||
ProjectKeyConstraints,
|
||||
ProjectProductionCapacity,
|
||||
ProjectCompleteInvestment,
|
||||
ProjectDynamicInvestment
|
||||
)
|
||||
from ..project_extensions.preparation import PreparationDetail
|
||||
from ..project_extensions.production import ProductionDetail
|
||||
from ..project_extensions.project_detail import ProjectDetailProject
|
||||
from ..project_extensions.generator_unit import (
|
||||
GeneratorUnitProject,
|
||||
PostGeneratorUnits
|
||||
)
|
||||
from ..project_extensions.lar import (
|
||||
LarDetailSchema,
|
||||
LarChangeListSchema,
|
||||
LarChangeUpdateSchema,
|
||||
LarMainInfoUpdateSchema,
|
||||
LarResettlementSchema,
|
||||
LarInvestmentSchema, LarChangeDetailSchema
|
||||
)
|
||||
from ..project_extensions.contract_change import (
|
||||
ContractChangeResponse,
|
||||
PostContractChange
|
||||
)
|
||||
|
||||
from ..project_multi_type import (
|
||||
PhotovoltaicExtension,
|
||||
OnShoreWindExtension,
|
||||
OffShoreWindExtension,
|
||||
HydropowerExtension,
|
||||
PumpedStorageExtension,
|
||||
GasTurbineExtension,
|
||||
ThermalPowerExtension
|
||||
)
|
||||
|
||||
|
||||
class ProjectInfoResponse(BaseModel):
|
||||
"""
|
||||
项目全部信息响应模型
|
||||
"""
|
||||
|
||||
project: ProjectResponseBase = Field(..., description="项目基本信息")
|
||||
project_detail: list[ProjectDetailProject] = Field(..., description="项目容量信息")
|
||||
generator_units: list[GeneratorUnitProject] = Field(..., description="发电单元信息列表(仅限水电、抽蓄、煤电、气电)")
|
||||
lar: Optional[LarDetailSchema] = Field(None, description="移民征地信息(仅限水电、抽蓄)")
|
||||
lar_change: list[LarChangeDetailSchema] = Field(default_factory=list, description="移民变更信息列表(仅限水电、抽蓄)")
|
||||
contract_changes: list[ContractChangeResponse] = Field(..., 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="里程碑节点列表")
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
"""
|
||||
项目数据推送相关schema
|
||||
"""
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import HTTPException
|
||||
from pydantic import Field, BaseModel, field_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 = Field(..., 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
|
||||
|
||||
|
||||
|
||||
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"])
|
||||
ic_plan_first_production_date: DateStr | None = Field(None, description="区域公司计划首批投产日期(只有新能源需要)",examples=["2024-08-01"])
|
||||
ic_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="实际投产节点列表(每次必须填写全部)(只有新能源需要)")
|
||||
@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
|
||||
|
||||
# ======================= 水电抽蓄专用 =============================
|
||||
|
||||
|
||||
# ==================== 枚举说明 用于文档自动生成 ====================
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
"""
|
||||
contract_change相关schemea
|
||||
"""
|
||||
from ..common.validators import DateStr
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
from ..common.base import BaseResponseWithID
|
||||
from pydantic import field_validator
|
||||
from fastapi import HTTPException
|
||||
|
||||
class ContractChangeResponse(BaseResponseWithID):
|
||||
"""
|
||||
合同变更模型
|
||||
"""
|
||||
id: Optional[str] = Field(default=None, description="合同变更ID")
|
||||
project_id : Optional[str] = Field(None, description="所属项目ID")
|
||||
contract_id: Optional[str] = Field(None, description="关联合同ID")
|
||||
name: Optional[str] = Field(None, description="合同名称")
|
||||
sub_name: Optional[str] = Field(None, description="合同子标题(epc合同和pc合同会有子标题)")
|
||||
sign_company:Optional[str] = Field(None, description="签约公司")
|
||||
title: Optional[str] = Field(None, description="变更事项")
|
||||
reason: Optional[str] = Field(None, description="变更理由")
|
||||
before_amount: Optional[float] = Field(None, description="变更前金额")
|
||||
after_amount: Optional[float] = Field(None, description="变更后金额")
|
||||
change_count: Optional[float] = Field(None, description="变更金额")
|
||||
change_rate: Optional[str] = Field(None, description="变更比例")
|
||||
change_time: Optional[str] = Field(None, description="变更时间(格式:YYYY-MM-DD)")
|
||||
attachment_ids:Optional[list] = Field(None, description="附件ID列表")
|
||||
attachment_names:Optional[list] = Field(None, description="附件名称列表")
|
||||
created_at:Optional[str]=Field(None, description="创建时间")
|
||||
created_by_name: Optional[str] = Field(None, description="创建人名称")
|
||||
|
||||
class PostContractChange(BaseModel):
|
||||
"""
|
||||
合同变更请求(水火抽蓄气电)
|
||||
PROJECT_CONTRACT_CHANGE = "project.contract-change"
|
||||
"""
|
||||
|
||||
|
||||
id: str = Field(..., description="合同变更ID,更新时传入,创建时不传")
|
||||
project_id: str = Field(..., description="所属项目ID")
|
||||
contract_id: str = Field(..., description="合同ID")
|
||||
title: str = Field(..., description="变更事项")
|
||||
reason: str = Field(..., description="变更理由")
|
||||
before_amount: float = Field(..., description="变更前金额")
|
||||
after_amount: float = Field(..., description="变更后金额")
|
||||
change_time: DateStr = Field(..., description="变更时间(格式:YYYY-MM-DD)")
|
||||
attachment_ids: list = Field(..., description="附件ID列表")
|
||||
|
||||
@field_validator("after_amount")
|
||||
def validate_after_amount(cls, v: float) -> float:
|
||||
if v < 0:
|
||||
raise HTTPException(status_code=400, detail="变更后金额必须小于等于0")
|
||||
return v
|
||||
|
||||
class CreateContractChange(BaseModel):
|
||||
"""
|
||||
创建合同变更请求(水火抽蓄气电)
|
||||
PROJECT_CONTRACT_CHANGE = "project.contract-change"
|
||||
"""
|
||||
|
||||
project_id: str = Field(..., description="所属项目ID")
|
||||
contract_id: str = Field(..., description="合同ID")
|
||||
title: str = Field(..., description="变更事项")
|
||||
reason: str = Field(..., description="变更理由")
|
||||
before_amount: float = Field(..., description="变更前金额")
|
||||
after_amount: float = Field(..., description="变更后金额")
|
||||
change_time: DateStr = Field(..., description="变更时间(格式:YYYY-MM-DD)")
|
||||
attachment_ids: list = Field(..., description="附件ID列表")
|
||||
|
||||
@field_validator("after_amount")
|
||||
def validate_after_amount(cls, v: float) -> float:
|
||||
if v < 0:
|
||||
raise HTTPException(status_code=400, detail="变更后金额不能小于0")
|
||||
return v
|
||||
@@ -0,0 +1,34 @@
|
||||
from pydantic import BaseModel, Field,field_validator
|
||||
from typing import Optional
|
||||
from ..common.base import BaseResponseWithID
|
||||
from ..common.datetime import datetime_date,get_current_date
|
||||
from fastapi import HTTPException
|
||||
|
||||
class GeneratorUnitProject(BaseResponseWithID):
|
||||
project_id : Optional[str]= Field(None, description="项目ID")
|
||||
serial_number: Optional[int] = Field(None, description="机组序号")
|
||||
approval_status:Optional[str]=Field(None, description="审批状态")
|
||||
capacity: Optional[float] = Field(None, description="单机容量(MW)")
|
||||
plan_production_date: Optional[str] = Field(None, description="计划投产日期")
|
||||
actual_production_date: Optional[str] = Field(None, description="实际投产日期")
|
||||
|
||||
class PostGeneratorUnits(BaseModel):
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
id:str = Field(..., description="机组ID")
|
||||
project_id: str = Field(..., description="所属项目ID")
|
||||
actual_production_date: str = Field(..., alias="actual_date", description="实际投产日期(格式:YYYY-MM-DD)")
|
||||
|
||||
@field_validator('actual_production_date', mode="before")
|
||||
def validate_actual_date_not_future(cls, v: str) -> str:
|
||||
if v == "":
|
||||
return ""
|
||||
try:
|
||||
actual = datetime_date(v)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400,detail='日期格式为(YYYY-MM-DD)')
|
||||
|
||||
today = datetime_date(get_current_date())
|
||||
if actual > today:
|
||||
raise HTTPException(status_code=400,detail='机组日期不能晚于今天')
|
||||
return v
|
||||
@@ -0,0 +1,206 @@
|
||||
"""
|
||||
移民征地相关schema
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from typing import Optional, List
|
||||
from ..common.base import BaseResponseWithID
|
||||
from ..common.validators import DateStr
|
||||
from ..common.datetime import get_current_date
|
||||
|
||||
|
||||
class LarDetailSchema(BaseResponseWithID):
|
||||
id: str = Field(..., description="移民征地id")
|
||||
project_id: Optional[str] = Field(None, description="项目ID")
|
||||
plan_resettlement_pop: Optional[int] = Field(None, description="计划搬迁人数")
|
||||
completed_resettlement_pop: Optional[int] = Field(None, description="完成搬迁人数")
|
||||
|
||||
plan_investment_amt: Optional[float] = Field(None, description="计划投资金额 ")
|
||||
completed_investment_amt: Optional[float] = Field(None, description="完成投资金额 ")
|
||||
|
||||
plan_urban_comp: Optional[float] = Field(None, description="计划城镇补偿金额 ")
|
||||
actual_urban_comp: Optional[float] = Field(None, description="实际城镇补偿金额 ")
|
||||
|
||||
plan_rural_comp: Optional[float] = Field(None, description="计划农村补偿金额 ")
|
||||
actual_rural_comp: Optional[float] = Field(None, description="实际农村补偿金额 ")
|
||||
|
||||
plan_spec_project_reconstr: Optional[float] = Field(None, description="计划专项工程复建 ")
|
||||
actual_spec_project_reconstr: Optional[float] = Field(None, description="实际专项工程复建 ")
|
||||
|
||||
plan_reservoir_bottom_cleanup: Optional[float] = Field(None, description="计划库底清理 ")
|
||||
actual_reservoir_bottom_cleanup: Optional[float] = Field(None, description="实际库底清理 ")
|
||||
|
||||
|
||||
class LarMainInfoUpdateSchema(BaseResponseWithID):
|
||||
id: str = Field(..., description="移民征地id")
|
||||
project_id: str = Field(..., description="项目id")
|
||||
|
||||
plan_urban_comp: float = Field(..., description="计划城镇补偿金额 ")
|
||||
actual_urban_comp: float = Field(..., description="实际城镇补偿金额 ")
|
||||
|
||||
plan_rural_comp: float = Field(..., description="计划农村补偿金额 ")
|
||||
actual_rural_comp: float = Field(..., description="实际农村补偿金额 ")
|
||||
|
||||
plan_spec_project_reconstr: float = Field(..., description="计划专项工程复建 ")
|
||||
actual_spec_project_reconstr: float = Field(..., description="实际专项工程复建 ")
|
||||
|
||||
plan_reservoir_bottom_cleanup: float = Field(..., description="计划库底清理 ")
|
||||
actual_reservoir_bottom_cleanup: float = Field(..., description="实际库底清理 ")
|
||||
|
||||
@field_validator(
|
||||
"plan_urban_comp",
|
||||
"actual_urban_comp",
|
||||
"plan_rural_comp",
|
||||
"actual_rural_comp",
|
||||
"plan_spec_project_reconstr",
|
||||
"actual_spec_project_reconstr",
|
||||
"plan_reservoir_bottom_cleanup",
|
||||
"actual_reservoir_bottom_cleanup",
|
||||
mode="before",
|
||||
)
|
||||
def check_amount_field(cls, v):
|
||||
from fastapi import HTTPException
|
||||
if v is None or v == "":
|
||||
raise HTTPException(detail='金额字段不能为空', status_code=400)
|
||||
if float(v) < 0:
|
||||
raise HTTPException(detail='金额字段需大于等于0', status_code=400)
|
||||
return v
|
||||
|
||||
|
||||
class LarResettlementSchema(BaseResponseWithID):
|
||||
id: str = Field(..., description="移民征地id")
|
||||
project_id: str = Field(..., description="项目id")
|
||||
|
||||
plan_resettlement_pop: int = Field(..., description="计划搬迁人数")
|
||||
completed_resettlement_pop: int = Field(..., description="完成搬迁人数")
|
||||
|
||||
@field_validator(
|
||||
"plan_resettlement_pop",
|
||||
"completed_resettlement_pop",
|
||||
mode="before",
|
||||
)
|
||||
def check_amount_field(cls, v):
|
||||
from fastapi import HTTPException
|
||||
if v is None or v == "":
|
||||
raise HTTPException(detail='搬迁人数不能为空', status_code=400)
|
||||
if float(v) < 0:
|
||||
raise HTTPException(detail='搬迁人数需大于等于0', status_code=400)
|
||||
return v
|
||||
|
||||
|
||||
class LarInvestmentSchema(BaseResponseWithID):
|
||||
id: str = Field(..., description="移民征地id")
|
||||
project_id: str = Field(..., description="项目id")
|
||||
|
||||
plan_investment_amt: float = Field(..., description="计划投资金额")
|
||||
completed_investment_amt: float = Field(..., description="完成投资金额")
|
||||
|
||||
@field_validator(
|
||||
"plan_investment_amt",
|
||||
"completed_investment_amt",
|
||||
mode="before",
|
||||
)
|
||||
def check_amount_field(cls, v):
|
||||
from fastapi import HTTPException
|
||||
if v is None or v == "":
|
||||
raise HTTPException(detail='金额字段不能为空', status_code=400)
|
||||
if float(v) < 0:
|
||||
raise HTTPException(detail='金额字段数需大于等于0', status_code=400)
|
||||
return v
|
||||
|
||||
|
||||
class LarChangeDetailSchema(BaseResponseWithID):
|
||||
project_id: str = Field(..., description="项目ID")
|
||||
title: Optional[str] = Field(None, description="变更事项")
|
||||
reason: Optional[str] = Field(None, description="变更理由")
|
||||
|
||||
before_amount: Optional[float] = Field(None, description="变更前金额")
|
||||
after_amount: Optional[float] = Field(None, description="变更后金额")
|
||||
|
||||
change_time: Optional[str] = Field(None, description="变更时间")
|
||||
attachment_ids: List[str] = Field(default_factory=list, description="附件ID列表", examples=[[]])
|
||||
|
||||
created_at: Optional[str] = Field(None, description="创建时间")
|
||||
created_by_name: Optional[str] = Field(None, description="创建人")
|
||||
|
||||
change_amount: Optional[float] = Field(None, description="变更金额")
|
||||
change_percentage: Optional[str] = Field('', description="变更比例")
|
||||
|
||||
approval_status: Optional[str] = Field(None, description="审批状态")
|
||||
|
||||
attachment_info: List[dict] = Field(
|
||||
default_factory=list, description="文件信息", examples=[[
|
||||
{
|
||||
"id": "54a0daf903a04c8e87cea1175458459d",
|
||||
"name": "54a0daf903a04c8e87cea1175458459d.pdf",
|
||||
"original_name": "新用户注册申请表-项目公司.pdf",
|
||||
"url": "/api/file_download/54a0daf903a04c8e87cea1175458459d"
|
||||
}
|
||||
]]
|
||||
)
|
||||
|
||||
|
||||
class LarChangeInfoSchema(BaseResponseWithID):
|
||||
title: Optional[str] = Field(None, description="变更事项")
|
||||
reason: Optional[str] = Field(None, description="变更理由")
|
||||
|
||||
before_amount: Optional[float] = Field(None, description="变更前金额")
|
||||
after_amount: Optional[float] = Field(None, description="变更后金额")
|
||||
|
||||
change_time: Optional[str] = Field(None, description="变更时间")
|
||||
attachment_ids: List[str] = Field(default_factory=list, description="附件ID列表", examples=[[]])
|
||||
|
||||
approval_status: Optional[str] = Field(None, description="审批状态")
|
||||
|
||||
before_amount_is_edit: Optional[bool] = Field(None, description="变更前金额是否可编辑")
|
||||
|
||||
changed_count: Optional[int] = Field(None, description="已办更次数")
|
||||
|
||||
|
||||
class LarChangeListSchema(BaseModel):
|
||||
page: int
|
||||
page_size: int
|
||||
total: int
|
||||
list: List[LarChangeDetailSchema]
|
||||
|
||||
|
||||
class LarChangeCreateSchema(BaseModel):
|
||||
title: str = Field(..., description="变更事项")
|
||||
reason: str = Field(..., description="变更理由")
|
||||
|
||||
before_amount: float = Field(..., description="变更前金额")
|
||||
after_amount: float = Field(..., description="变更后金额")
|
||||
|
||||
change_time: DateStr = Field(..., description="变更时间")
|
||||
attachment_ids: List[str] = Field(default_factory=list, description="附件ID列表", examples=[[]])
|
||||
|
||||
project_id: str = Field(..., description="项目id")
|
||||
|
||||
@field_validator(
|
||||
"before_amount",
|
||||
"after_amount",
|
||||
mode="before",
|
||||
)
|
||||
def check_amount_field(cls, v):
|
||||
from fastapi import HTTPException
|
||||
if v is None or v == "":
|
||||
raise HTTPException(detail='金额字段不能为空', status_code=400)
|
||||
if float(v) < 0:
|
||||
raise HTTPException(detail='金额字段需大于等于0', status_code=400)
|
||||
return v
|
||||
|
||||
@field_validator(
|
||||
"change_time",
|
||||
mode="before",
|
||||
)
|
||||
def check_actual_time_field(cls, v):
|
||||
from fastapi import HTTPException
|
||||
if v is None or v == "":
|
||||
raise HTTPException(detail='移民变更时间必填', status_code=400)
|
||||
if v > get_current_date():
|
||||
raise HTTPException(detail='移民变更时间不能大于当前日期', status_code=400)
|
||||
return v
|
||||
|
||||
|
||||
class LarChangeUpdateSchema(LarChangeCreateSchema):
|
||||
id: str = Field(..., description="移民变更id")
|
||||
@@ -0,0 +1,39 @@
|
||||
from pydantic import BaseModel, Field,field_validator
|
||||
from typing import Optional
|
||||
|
||||
from ..common.base import BaseResponseWithID
|
||||
from ..common.datetime import datetime_date,get_current_date
|
||||
from fastapi import HTTPException
|
||||
from ..common.validators import DateStr
|
||||
|
||||
class PostPreparation(BaseModel):
|
||||
id:str = Field(..., description="里程碑ID")
|
||||
code: str= Field(..., description="里程碑的节点编码")
|
||||
acquire_date: DateStr|None = Field(None,description="取得时间")
|
||||
is_exist: bool= Field(..., description="是否取得")
|
||||
|
||||
@field_validator('acquire_date')
|
||||
def validate_acquire_date(cls, v: Optional[DateStr]) -> Optional[DateStr]:
|
||||
if v is None:
|
||||
return None
|
||||
try:
|
||||
actual = datetime_date(v)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400,detail='日期格式为(YYYY-MM-DD)')
|
||||
|
||||
today = datetime_date(get_current_date())
|
||||
if actual > today:
|
||||
raise HTTPException(status_code=400,detail='取得时间不能晚于今天')
|
||||
return v
|
||||
|
||||
|
||||
class PreparationDetail(BaseResponseWithID):
|
||||
id: Optional[str] = Field(None, description="里程碑ID")
|
||||
project_id: Optional[str] = Field(None, description="项目ID")
|
||||
code: Optional[str] = Field(None, description="里程碑节点编码")
|
||||
acquire_date: Optional[DateStr] = Field(None, description="取得时间")
|
||||
is_exist: Optional[bool] = Field(None, description="是否取得")
|
||||
approval_status: Optional[str] = Field(None, description="审批状态")
|
||||
name: Optional[str] = Field(None, description="里程碑名称")
|
||||
group: Optional[str] = Field(None, description="分组条件", examples=['KGTJLS'])
|
||||
is_acquired: Optional[int] = Field(0, description="是否取得")
|
||||
@@ -0,0 +1,39 @@
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from typing import Optional
|
||||
|
||||
from ..common.base import BaseResponseWithID
|
||||
from ..common.datetime import datetime_date, get_current_date
|
||||
from fastapi import HTTPException
|
||||
from ..common.validators import DateStr
|
||||
|
||||
|
||||
class PostProduction(BaseModel):
|
||||
id: str = Field(..., description="投产节点ID")
|
||||
code: str = Field(..., description="节点编码")
|
||||
acquire_date: DateStr | None = Field(None, description="取得时间")
|
||||
is_exist: bool = Field(..., description="是否取得")
|
||||
|
||||
@field_validator('acquire_date')
|
||||
def validate_acquire_date(cls, v: Optional[DateStr]) -> Optional[DateStr]:
|
||||
if v is None:
|
||||
return None
|
||||
try:
|
||||
actual = datetime_date(v)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail='日期格式为(YYYY-MM-DD)')
|
||||
|
||||
today = datetime_date(get_current_date())
|
||||
if actual > today:
|
||||
raise HTTPException(status_code=400, detail='取得时间不能晚于今天')
|
||||
return v
|
||||
|
||||
|
||||
class ProductionDetail(BaseResponseWithID):
|
||||
id: Optional[str] = Field(None, description="投产节点ID")
|
||||
project_id: Optional[str] = Field(None, description="项目ID")
|
||||
code: Optional[str] = Field(None, description="投产节点编码")
|
||||
acquire_date: Optional[DateStr] = Field(None, description="取得时间")
|
||||
is_exist: Optional[bool] = Field(None, description="是否取得")
|
||||
approval_status: Optional[str] = Field(None, description="审批状态")
|
||||
name: Optional[str] = Field(None, description="投产节点名称")
|
||||
is_acquired: Optional[int] = Field(0, description="是否取得")
|
||||
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
项目容量信息schema
|
||||
只提供返回,不提供更新接口
|
||||
"""
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List
|
||||
from ..common.base import BaseResponseWithID
|
||||
|
||||
|
||||
class ProjectDetailKey(str, Enum):
|
||||
"""项目子类类型枚举"""
|
||||
PLAN_CONSTRUCTION = "plan_construction" # 计划在建容量
|
||||
ACTUAL_CONSTRUCTION = "actual_construction" # 实际在建容量
|
||||
PRODUCTION = "production" # 投产容量
|
||||
ENSURE_PRODUCTION = "ensure_production" # 确保容量
|
||||
CLIMB_PRODUCTION = "climb_production" # 登高容量
|
||||
CHALLENGE_PRODUCTION = "challenge_production" # 揭榜容量
|
||||
COMPLETE_INVESTMENT = "complete_investment" # 完成投资容量
|
||||
|
||||
|
||||
class CapacityItem(BaseModel):
|
||||
year: Optional[int] = Field(None, description='年')
|
||||
date: Optional[str] = Field(None, description="日期,格式YYYY-MM-DD")
|
||||
capacity: float = Field(..., description='容量')
|
||||
is_audit: Optional[bool] = Field(False, description='是否可编辑')
|
||||
|
||||
|
||||
class ProjectDetailProject(BaseResponseWithID):
|
||||
project_id:str= Field(..., description="所属项目ID", examples=["project123"])
|
||||
key: Optional[ProjectDetailKey] = Field(None, description='项目子类类型')
|
||||
value: Optional[List[CapacityItem]] = Field(None, description='项目子类类型值')
|
||||
@@ -0,0 +1,7 @@
|
||||
from .photovotaic import PhotovoltaicExtension
|
||||
from .onshore_wind import OnShoreWindExtension
|
||||
from .offshore_wind import OffShoreWindExtension
|
||||
from .hydropower import HydropowerExtension
|
||||
from .pumped_storage import PumpedStorageExtension
|
||||
from .gas_turbine import GasTurbineExtension
|
||||
from .thermal_power import ThermalPowerExtension
|
||||
@@ -0,0 +1,9 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional, Literal
|
||||
from ..common.validators import DateStr
|
||||
|
||||
class GasTurbineExtension(BaseModel):
|
||||
"""
|
||||
燃气轮机项目特定字段
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,26 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional, Literal
|
||||
from ..common.validators import DateStr
|
||||
|
||||
class HydropowerExtension(BaseModel):
|
||||
"""
|
||||
抽蓄项目特定字段
|
||||
"""
|
||||
dam_type: Optional[str] = Field(None, description="大坝坝型")
|
||||
storage_capacity: Optional[float] = Field(None, description="总库容")
|
||||
normal_storage_level: Optional[float] = Field(None, description="正常蓄水位")
|
||||
regulation_performance: Optional[str] = Field(None, description="调节性能")
|
||||
workshop_type: Optional[str] = Field(None, description="厂房类型")
|
||||
|
||||
precon_start_date: Optional[str] = Field(None, description="筹建准备期起始时间")
|
||||
precon_end_date: Optional[str] = Field(None, description="筹建准备期终止时间")
|
||||
maincon_start_date: Optional[str] = Field(None, description="主体施工期起始时间")
|
||||
maincon_end_date: Optional[str] = Field(None, description="主体施⼯期终止时间")
|
||||
comm_start_date: Optional[str] = Field(None, description="完建期开始起始时间")
|
||||
comm_end_date: Optional[str] = Field(None, description="完建期终止时间")
|
||||
|
||||
implementation_estimate: Optional[float] = Field(None, description="执行概算")
|
||||
static_total_investment: Optional[float] = Field(None, description="静态总投资")
|
||||
completed_investment: Optional[float] = Field(None, description="完成投资")
|
||||
total_contract_quantity: Optional[float] = Field(None, description="总合同数量")
|
||||
completed_settlement_quantity: Optional[float] = Field(None, description="完成结算数量")
|
||||
@@ -0,0 +1,17 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional, Literal
|
||||
from ..common.validators import DateStr
|
||||
|
||||
class OffShoreWindExtension(BaseModel):
|
||||
"""
|
||||
完整的抽蓄项⽬模型,继承了所有通⽤字段和特定字段
|
||||
"""
|
||||
syxs: Optional[str] = Field(None, description="升压站型式")
|
||||
|
||||
group_filing_form_id: Optional[str] = Field(None, examples=["集团备案表文件id"])
|
||||
partial_production_filing_form_id: Optional[str] = Field(None, examples=["部分投产备案表文件id"])
|
||||
all_production_filing_form_id: Optional[str] = Field(None, examples=["全部投产备案表文件id"])
|
||||
ic_plan_first_production_date: Optional[DateStr] = Field(None, description="区域公司计划首批投产日期",
|
||||
examples=["2024-08-01"])
|
||||
ic_plan_full_production_date: Optional[DateStr] = Field(None, description="区域公司计划全投日期",
|
||||
examples=["2024-09-01"])
|
||||
@@ -0,0 +1,18 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional, Literal
|
||||
from ..common.validators import DateStr
|
||||
|
||||
class OnShoreWindExtension(BaseModel):
|
||||
"""
|
||||
ONSHORE_WIND_EXTENSION
|
||||
陆风项目扩展信息
|
||||
"""
|
||||
group_filing_form_id: Optional[str] = Field(None, examples=["集团备案表文件id"])
|
||||
partial_production_filing_form_id: Optional[str] = Field(None, examples=["部分投产备案表文件id"])
|
||||
all_production_filing_form_id: Optional[str] = Field(None, examples=["全部投产备案表文件id"])
|
||||
ic_plan_first_production_date: Optional[DateStr] = Field(None, description="区域公司计划首批投产日期",
|
||||
examples=["2024-08-01"])
|
||||
ic_plan_full_production_date: Optional[DateStr] = Field(None, description="区域公司计划全投日期",
|
||||
examples=["2024-09-01"])
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional, Literal
|
||||
from ..common.validators import DateStr
|
||||
|
||||
class PhotovoltaicExtension(BaseModel):
|
||||
"""
|
||||
完整的光伏项⽬模型,继承了所有通⽤字段和特定字段
|
||||
"""
|
||||
group_filing_form_id: Optional[str] = Field(None, examples=["集团备案表文件id"])
|
||||
partial_production_filing_form_id: Optional[str] = Field(None, examples=["部分投产备案表文件id"])
|
||||
all_production_filing_form_id: Optional[str] = Field(None, examples=["全部投产备案表文件id"])
|
||||
ic_plan_first_production_date: Optional[DateStr] = Field(None, description="区域公司计划首批投产日期",
|
||||
examples=["2024-08-01"])
|
||||
ic_plan_full_production_date: Optional[DateStr] = Field(None, description="区域公司计划全投日期",
|
||||
examples=["2024-09-01"])
|
||||
@@ -0,0 +1,29 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional, Literal
|
||||
from ..common.validators import DateStr
|
||||
|
||||
|
||||
class PumpedStorageExtension(BaseModel):
|
||||
"""
|
||||
抽蓄项目特定字段
|
||||
"""
|
||||
upper_dam_type: Optional[str] = Field(None, description="上水库坝型")
|
||||
upper_storage_capacity: Optional[float] = Field(None, description="上库库容(万m³)")
|
||||
lower_dam_type: Optional[str] = Field(None, description="下水库坝型")
|
||||
lower_storage_capacity: Optional[float] = Field(None, description="下库库容(万m³)")
|
||||
rated_head: Optional[float] = Field(None, description="额定水头")
|
||||
distance_to_head_ratio: Optional[float] = Field(None, description="距高比")
|
||||
continuous_full_load_hours: Optional[float] = Field(None, description="连续满发小时数")
|
||||
kwh_static_cost: Optional[float] = Field(None, description="单位千瓦静态投资")
|
||||
precon_start_date: Optional[str] = Field(None, description="筹建准备期起始时间")
|
||||
precon_end_date: Optional[str] = Field(None, description="筹建准备期终止时间")
|
||||
maincon_start_date: Optional[str] = Field(None, description="主体施工期起始时间")
|
||||
maincon_end_date: Optional[str] = Field(None, description="主体施⼯期终止时间")
|
||||
comm_start_date: Optional[str] = Field(None, description="完建期开始起始时间")
|
||||
comm_end_date: Optional[str] = Field(None, description="完建期终止时间")
|
||||
|
||||
implementation_estimate:Optional[float] = Field(None, description="执行概算")
|
||||
static_total_investment: Optional[float] = Field(None, description="静态总投资")
|
||||
completed_investment: Optional[float] = Field(None, description="完成投资")
|
||||
total_contract_quantity: Optional[float] = Field(None, description="总合同数量")
|
||||
completed_settlement_quantity: Optional[float] = Field(None, description="完成结算数量")
|
||||
@@ -0,0 +1,10 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional, Literal
|
||||
from ..common.validators import DateStr
|
||||
|
||||
class ThermalPowerExtension(BaseModel):
|
||||
"""
|
||||
THERMAL_POWER_EXTENSION
|
||||
火电项目扩展信息
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,43 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional
|
||||
from ..common.base import BaseResponseWithID
|
||||
|
||||
|
||||
class SupplierDetailResponse(BaseResponseWithID):
|
||||
"""
|
||||
供应商详情模型
|
||||
"""
|
||||
id: str = Field(..., description="供应商ID", examples=["company:001"])
|
||||
name: str = Field(..., description="供应商名称", examples=["腾讯集团"])
|
||||
short_name: str = Field(..., description="供应商简称", examples=["腾讯"])
|
||||
code: str = Field(..., description="供应商编码", examples=["TX001"])
|
||||
company_type: str = Field(..., description="供应商类型", examples=["other"])
|
||||
parent_id: Optional[str] = Field(None, description="上级供应商ID", examples=[None])
|
||||
path: List[str] = Field(..., description="供应商所处路径", examples=[["company:001"]])
|
||||
status: str = Field(..., description="供应商状态", examples=["active"])
|
||||
sort: int = Field(..., description="排序", examples=[1])
|
||||
credit_code: Optional[str] = Field(None, description="信用代码", examples=["914403007109310121"])
|
||||
created_by: str = Field(..., description="创建者ID", examples=["user:001"])
|
||||
created_by_name: str = Field(..., description="创建者名称", examples=["张三"])
|
||||
created_at: str = Field(..., description="创建时间", examples=["2023-10-01T12:00:00Z"])
|
||||
updated_by: str = Field(..., description="更新者ID", examples=["user:001"])
|
||||
updated_by_name: str = Field(..., description="更新者名称", examples=["张三"])
|
||||
updated_at: str = Field(..., description="更新时间", examples=["2023-10-01T12:00:00Z"])
|
||||
province: Optional[str] = Field(None, description="省份编码", examples=[None])
|
||||
city: Optional[str] = Field(None, description="城市编码", examples=[None])
|
||||
company_nature: List[Optional[str]] = Field(None, description="供应商性质数组", examples=[['supplier']])
|
||||
city_name: Optional[str] = Field(None, description="城市名称", examples=[None])
|
||||
province_name: Optional[str] = Field(None, description="省份名称", examples=[None])
|
||||
region_path: Optional[list] = Field(default_factory=list, description="供应商国家城市地址编码列表")
|
||||
country: Optional[str] = Field(None, description="国家编码", examples=[None])
|
||||
country_name: Optional[str] = Field(None, description="国家名称", examples=[None])
|
||||
|
||||
|
||||
class SupplierListResponse(BaseModel):
|
||||
"""
|
||||
供应商list
|
||||
"""
|
||||
page: int
|
||||
page_size: int
|
||||
total: int
|
||||
list: List[SupplierDetailResponse] = []
|
||||
@@ -0,0 +1,49 @@
|
||||
from typing import List, Optional, Dict
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
from ..common.base import BaseResponseWithID
|
||||
|
||||
|
||||
class UserListDetailItem(BaseResponseWithID):
|
||||
"""
|
||||
用户列表内明细模型
|
||||
"""
|
||||
id: Optional[str] = Field(default=None, description="唯一标识符")
|
||||
username: Optional[str] = Field(None, description="用户名")
|
||||
name: Optional[str] = Field(None, description="姓名")
|
||||
phone: Optional[str] = Field(None, description="手机号")
|
||||
phone_country: Optional[str] = Field(None, description="手机号国家前缀")
|
||||
country_code: Optional[str] = Field(None, description="国家编码")
|
||||
id_card: Optional[str] = Field(None, description="身份证号")
|
||||
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="更新时间")
|
||||
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")
|
||||
project_ids: Optional[List[str]] = Field([], description="权限内项目id")
|
||||
contract_ids: Optional[List[str]] = Field([], description="权限内合同id")
|
||||
apply_files: Optional[List[Dict]] = Field([],
|
||||
examples=[
|
||||
{"id": "file:001", "name": "文件1",
|
||||
"url": "https://example.com/file1.pdf"}
|
||||
],
|
||||
description='申请文件'
|
||||
)
|
||||
|
||||
|
||||
class UserListResponse(BaseModel):
|
||||
"""
|
||||
用户列表响应模型
|
||||
"""
|
||||
|
||||
page: int
|
||||
page_size: int
|
||||
total: int
|
||||
list: List[UserListDetailItem]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user