PayrollManagementSystem/backend/app/models/customer_rating.py

78 lines
3.7 KiB
Python
Raw Permalink 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.2.5)。
评级从「纯计算器不落库」升级为**每客户一行落库**(2026-10-19 确认):
客户管理页要罗列评级列并按其排序,评分必须可回溯。设计要点:
- ``customer_id`` 唯一键 + RESTRICT 外键:一客户一份当前评级(快照语义,
非流水),删除客户由服务层先删评级行再删主行(RESTRICT 兜底并发窗口)
- ``grade_name`` 为**快照固化、无外键**:等级映射表仍允许自由增删清空
(rating_service 的既有前提),删等级不能伤及已评级客户的历史结论 ——
与归属留痕「ID/人名固化、不回溯」同一论证口径;等级未命中时为 NULL
- ``scorer_id`` 外键 SET NULL + ``scorer_name`` 冗余固化:账号删除后
记录仍可读(同 ownership_change_logs 的操作人口径)
- 分值列 DECIMAL(5,1):UI 整数 0-20,DECIMAL 留十进制余地;总分由
**服务端重算**,不采信前端回传
"""
from decimal import Decimal
from sqlalchemy import ForeignKey, Numeric, String, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import BIGINT_UNSIGNED, Base, TimestampMixin, mysql_table_args
class CustomerRating(Base, TimestampMixin):
"""客户评级表:每客户一行(uk_customer_ratings_customer_id)。
五项各 0-20 分、总分 = 五项之和(0-100),判定规则同等级映射表
(按 sort_order 升序首个 min_score ≤ 总分,见 rating_service.resolve_grade)。
"""
__tablename__ = "customer_ratings"
__table_args__ = mysql_table_args(
"客户评级表(每客户一行;等级名为快照固化无外键,删等级不伤已评级记录)",
UniqueConstraint("customer_id", name="uk_customer_ratings_customer_id"),
)
id: Mapped[int] = mapped_column(
BIGINT_UNSIGNED, primary_key=True, autoincrement=True, comment="主键ID"
)
customer_id: Mapped[int] = mapped_column(
BIGINT_UNSIGNED,
ForeignKey("customers.id", ondelete="RESTRICT"),
nullable=False,
comment="客户ID(唯一:一客户一份当前评级)",
)
score_ship_amount: Mapped[Decimal] = mapped_column(
Numeric(5, 1), nullable=False, server_default="0", comment="发货额评分(0-20)"
)
score_receipt_rate: Mapped[Decimal] = mapped_column(
Numeric(5, 1), nullable=False, server_default="0", comment="回款率评分(0-20)"
)
score_profit_rate: Mapped[Decimal] = mapped_column(
Numeric(5, 1), nullable=False, server_default="0", comment="毛利率评分(0-20)"
)
score_contract_cooperation: Mapped[Decimal] = mapped_column(
Numeric(5, 1), nullable=False, server_default="0", comment="合同履约配合能力评分(0-20)"
)
score_site_construction: Mapped[Decimal] = mapped_column(
Numeric(5, 1), nullable=False, server_default="0", comment="现场规范施工能力评分(0-20)"
)
total_score: Mapped[Decimal] = mapped_column(
Numeric(5, 1), nullable=False, comment="总分(服务端重算,五项之和 0-100)"
)
grade_name: Mapped[str | None] = mapped_column(
String(32),
comment="评级等级(保存时按等级映射快照固化,无外键;未命中/等级表空为 NULL)",
)
remark: Mapped[str | None] = mapped_column(String(255), comment="备注")
scorer_id: Mapped[int | None] = mapped_column(
BIGINT_UNSIGNED,
ForeignKey("users.id", ondelete="SET NULL"),
comment="评级操作人ID(账号删除后置空)",
)
scorer_name: Mapped[str] = mapped_column(
String(64), nullable=False, comment="评级操作人用户名(冗余固化)"
)