补齐源快照、资料预演、正式导入及工资核算的依赖链路。 新增版本化输入、逐段 FIFO、生成锁和结构化当月调整, 并提供默认关闭的周期采集、有限重试及 Windows 托管。 原始客户资料、私有配置及首页改动不纳入本次提交。 BREAKING CHANGE: 升级前需应用新增迁移,并通过 PAYROLL_MAKER_USER_ID 绑定实际工资制作人,否则业务只读。 旧单批次核算默认关闭;历史兼容需显式启用 PAYROLL_ALLOW_LEGACY_CALCULATION。生产启用和真实金额待验收。
60 lines
1.8 KiB
Python
60 lines
1.8 KiB
Python
"""正式工资核算的最小输入契约;金额使用字符串,资料版本显式校验。"""
|
|
|
|
from typing import Literal
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
|
|
from app.core.month import validate_month
|
|
from app.schemas.tplus_workbench import HandoverFact, _decimal
|
|
|
|
|
|
class PayrollMonthIn(BaseModel):
|
|
month: str
|
|
reason: str | None = Field(default=None, min_length=1, max_length=255)
|
|
|
|
@field_validator("month")
|
|
@classmethod
|
|
def check_month(cls, value):
|
|
return validate_month(value)
|
|
|
|
|
|
class ShipmentOwnershipIn(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
shipment_item_id: int = Field(ge=1)
|
|
personnel_id: int = Field(ge=1)
|
|
manager_id: int | None = Field(default=None, ge=1)
|
|
unit_cost: str
|
|
|
|
@field_validator("unit_cost")
|
|
@classmethod
|
|
def check_cost(cls, value):
|
|
_decimal(value, 4)
|
|
return value
|
|
|
|
|
|
class PayrollSetupIn(PayrollMonthIn):
|
|
model_config = ConfigDict(extra="forbid")
|
|
expected_version: int = Field(default=0, ge=0)
|
|
evidence: str = Field(min_length=1, max_length=500)
|
|
opening_balance: str
|
|
history_confirmed: Literal[True]
|
|
guidance_finalized: Literal[True]
|
|
shipment_ownerships: list[ShipmentOwnershipIn] = Field(min_length=1, max_length=10000)
|
|
handovers: list[HandoverFact] = Field(default_factory=list, max_length=1000)
|
|
|
|
@field_validator("opening_balance")
|
|
@classmethod
|
|
def check_opening(cls, value):
|
|
_decimal(value, 2)
|
|
return value
|
|
|
|
|
|
class AllocationAdjustmentIn(BaseModel):
|
|
"""分配差额仅允许归生成当月;关联已结算原台账留痕。"""
|
|
|
|
ledger_id: int = Field(ge=1)
|
|
personnel_id: int = Field(ge=1)
|
|
direction: Literal[1, 2]
|
|
amount: str = Field(pattern=r"^\d{1,12}(\.\d{1,2})?$")
|
|
reason: str = Field(min_length=1, max_length=255)
|