116 lines
3.3 KiB
Python
116 lines
3.3 KiB
Python
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass, field
|
||
from datetime import date, time
|
||
from typing import Optional
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class LeaveEvent:
|
||
"""请假/调休等工时事件,key 用于跨天重复记录去重。"""
|
||
|
||
kind: str
|
||
start_text: str
|
||
end_text: str
|
||
hours: float
|
||
|
||
@property
|
||
def key(self) -> tuple[str, str, str, float]:
|
||
return (self.kind, self.start_text, self.end_text, round(self.hours, 4))
|
||
|
||
|
||
@dataclass
|
||
class DailyAttendance:
|
||
"""单个员工某一天的考勤明细。"""
|
||
|
||
work_date: date
|
||
raw_text: str = ""
|
||
first_punch: Optional[time] = None
|
||
last_punch: Optional[time] = None
|
||
late_minutes: list[int] = field(default_factory=list)
|
||
missing_card_count: int = 0
|
||
leave_events: list[LeaveEvent] = field(default_factory=list)
|
||
punch_overtime_hours: float = 0.0
|
||
|
||
@property
|
||
def leave_hours_in_cell(self) -> float:
|
||
return sum(event.hours for event in self.leave_events)
|
||
|
||
|
||
@dataclass
|
||
class EmployeeAttendance:
|
||
"""员工月度考勤数据,包含汇总字段和每日明细。"""
|
||
|
||
name: str
|
||
attendance_group: str = ""
|
||
department: str = ""
|
||
employee_no: str = ""
|
||
position: str = ""
|
||
user_id: str = ""
|
||
summary_fields: dict[str, float | str] = field(default_factory=dict)
|
||
daily_records: list[DailyAttendance] = field(default_factory=list)
|
||
|
||
@property
|
||
def unique_leave_events(self) -> list[LeaveEvent]:
|
||
seen: set[tuple[str, str, str, float]] = set()
|
||
result: list[LeaveEvent] = []
|
||
for record in self.daily_records:
|
||
for event in record.leave_events:
|
||
if event.key not in seen:
|
||
seen.add(event.key)
|
||
result.append(event)
|
||
return result
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class EmployeePayConfig:
|
||
"""员工工资配置;计算器只关心规则值,不关心这些值来自数据库还是配置文件。"""
|
||
|
||
salary_mode: str = "monthly"
|
||
base_salary: Optional[float] = None
|
||
hourly_rate: float = 0.0
|
||
piece_quantity: float = 0.0
|
||
piece_unit_price: float = 0.0
|
||
probation_type: str = "ratio"
|
||
probation_ratio: float = 1.0
|
||
probation_salary: Optional[float] = None
|
||
commission_amount: float = 0.0
|
||
overtime_rate: Optional[float] = None
|
||
weekend_overtime_rate: Optional[float] = None
|
||
holiday_overtime_rate: Optional[float] = None
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class PayrollResult:
|
||
"""员工级工资计算结果。"""
|
||
|
||
employee: EmployeeAttendance
|
||
salary_month: str
|
||
salary_mode: str
|
||
attendance_days: float
|
||
absence_days: float
|
||
actual_work_hours: float
|
||
punch_overtime_hours: float
|
||
leave_hours: float
|
||
remaining_overtime_hours: float
|
||
workday_overtime_hours: float
|
||
weekend_overtime_hours: float
|
||
holiday_overtime_hours: float
|
||
minor_late_count: int
|
||
major_late_count: int
|
||
penalized_late_count: int
|
||
late_deduction: float
|
||
missing_card_count: int
|
||
missing_card_deduction: float
|
||
total_deduction: float
|
||
base_salary: Optional[float]
|
||
base_pay: Optional[float]
|
||
commission_amount: float
|
||
overtime_rate: float
|
||
weekend_overtime_rate: float
|
||
holiday_overtime_rate: float
|
||
overtime_pay: float
|
||
gross_salary: Optional[float]
|
||
net_salary: Optional[float]
|
||
note: str = ""
|