PayrollManagementSystem/backend/app/schemas/customer.py

151 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""客户管理的请求/响应模型(需求 2.1.3)。"""
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field
from app.core.response import PageQuery
class CustomerOut(BaseModel):
"""客户响应(列表、详情与写操作回包共用)。
owner_personnel_name 由 LEFT JOIN 带出:归属人员不可考(被物理删除的
脏数据)时为 null,owner_personnel_id 仍返回原值供追溯 —— 与归属留痕
「ID 固化、姓名随联表」同一口径。
rating_total / rating_grade_name 由 LEFT JOIN customer_ratings 带出
(列表评级列免 N+1):未评级时均为 null;总分按分值红线以字符串出参,
等级为保存时点的快照固化值(见 models/customer_rating.py)。
"""
model_config = ConfigDict(from_attributes=True)
id: int
company_name: str
owner_name: str
contact_person: str | None = None
contact_phone: str | None = None
address: str | None = None
industry: str | None = None
customer_type: str
owner_personnel_id: int | None = None
owner_personnel_name: str | None = None
remark: str | None = None
rating_total: str | None = None
rating_grade_name: str | None = None
class CustomerCreateIn(BaseModel):
"""新增客户。
owner_personnel_id 为**初始归属**,可空(客户先建档、后派归属也合法);
归属的后续调整不走本模型,统一走 ``PUT /{id}/ownership``(见
``CustomerUpdateIn`` 的说明)。字段名蛇形直出(M1 契约:前端 api 封装
与 schema 同名对应,仅分页信封 list/pageNum 驼峰)。
"""
model_config = ConfigDict(str_strip_whitespace=True)
company_name: str = Field(min_length=1, max_length=128, description="公司名称")
owner_name: str = Field(
min_length=1,
max_length=64,
description="客户负责人姓名(客户公司的负责人人名,非系统内营销人员)",
)
contact_person: str | None = Field(default=None, max_length=64, description="联系人")
contact_phone: str | None = Field(default=None, max_length=20, description="联系电话")
address: str | None = Field(default=None, max_length=255, description="地址")
industry: str | None = Field(default=None, max_length=64, description="所属行业")
customer_type: str = Field(
min_length=1,
max_length=32,
description="客户分类:personal个人户 manager_personal经理个人户 enterprise企业户",
)
owner_personnel_id: int | None = Field(
default=None,
ge=1,
description="归属负责营销人员ID(初始归属,可空)",
)
remark: str | None = Field(default=None, max_length=255, description="备注")
class CustomerUpdateIn(BaseModel):
"""编辑客户(全量替换,前端表单全量提交)。
**不含 owner_personnel_id** —— 归属调整统一走 ``PUT /{id}/ownership``,
只有那条路径才写归属变更留痕。若在本端点放开该字段,会出现「改了归属
却无留痕」的旁路,破坏留痕的 append-only 完整性;编辑表单即使提交
ownerPersonnelId,pydantic 默认忽略未声明字段,归属以归属端点为准。
"""
model_config = ConfigDict(str_strip_whitespace=True)
company_name: str = Field(min_length=1, max_length=128, description="公司名称")
owner_name: str = Field(min_length=1, max_length=64, description="客户负责人姓名")
contact_person: str | None = Field(default=None, max_length=64, description="联系人")
contact_phone: str | None = Field(default=None, max_length=20, description="联系电话")
address: str | None = Field(default=None, max_length=255, description="地址")
industry: str | None = Field(default=None, max_length=64, description="所属行业")
customer_type: str = Field(
min_length=1,
max_length=32,
description="客户分类:personal个人户 manager_personal经理个人户 enterprise企业户",
)
remark: str | None = Field(default=None, max_length=255, description="备注")
class CustomerQuery(PageQuery):
"""客户列表查询条件(字段名蛇形直出,与前端 api 封装同名对应)。"""
keyword: str | None = Field(
default=None, max_length=64, description="公司名称或客户负责人,模糊匹配"
)
customer_type: str | None = Field(default=None, max_length=32, description="客户分类筛选")
owner_personnel_id: int | None = Field(default=None, ge=1, description="归属人员ID筛选")
# 排序白名单(全项目首个列表排序参数):非法值由 Literal 拦下、经全局
# 异常处理器转 400 —— 不把排序字段透传给 SQL,避免注入面
order_field: Literal["rating"] | None = Field(
default=None, description="排序字段白名单:rating=客户评级总分(等级由总分判定、同向)"
)
order_type: Literal["asc", "desc"] | None = Field(
default=None, description="排序方向:asc 正序 / desc 倒序(未评级恒在末尾)"
)
class CustomerOwnershipIn(BaseModel):
"""归属变更入参。
new_owner_personnel_id 可空:null 表示清空归属(客户暂无负责人也合法)。
"""
new_owner_personnel_id: int | None = Field(
default=None,
ge=1,
description="变更后归属人员ID,null=清空归属",
)
reason: str | None = Field(default=None, max_length=255, description="变更原因")
class OwnershipChangeLogOut(BaseModel):
"""归属变更留痕响应。
新旧归属人员姓名由 LEFT JOIN 带出,人员被删除后为 null;固化的
``*_personnel_id`` 始终原样返回 —— 留痕表对人员 ID 无外键固化
(见 `models/customer.py` 的论证),姓名可随人员消失,ID 不可。
"""
model_config = ConfigDict(from_attributes=True)
id: int
customer_id: int
old_owner_personnel_id: int | None = None
new_owner_personnel_id: int | None = None
old_owner_name: str | None = None
new_owner_name: str | None = None
change_reason: str | None = None
operator_id: int | None = None
operator_name: str
create_time: datetime