financial_system/financial_system/services/salary_config_service.py
2026-06-18 13:21:20 +08:00

229 lines
8.7 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.

from __future__ import annotations
import json
from dataclasses import dataclass
from ..core.config import AppConfig, AttendanceRules, PayrollRules
from ..core.logger import AppLogger
from ..database.orm import SalaryConfigORM
from ..repositories import SalaryConfigRepository
logger = AppLogger.get_logger(__name__)
DEFAULT_CONFIGS = [
{
"config_key": "attendance.on_work_time",
"config_name": "上班时间",
"config_group": "attendance",
"config_value": "08:30",
"value_type": "string",
"remark": "用于迟到识别格式HH:MM",
},
{
"config_key": "attendance.off_work_time",
"config_name": "下班时间",
"config_group": "attendance",
"config_value": "17:00",
"value_type": "string",
"remark": "加班按下班打卡超过该时间后向下取整",
},
{
"config_key": "attendance.overtime_round_minutes",
"config_name": "加班取整分钟",
"config_group": "attendance",
"config_value": "60",
"value_type": "integer",
"remark": "例如60表示20:30按3小时计算",
},
{
"config_key": "attendance.standard_daily_hours",
"config_name": "标准日工时",
"config_group": "attendance",
"config_value": "8",
"value_type": "number",
"remark": "计时工资按出勤天数折算工时",
},
{
"config_key": "attendance.minor_late_minutes",
"config_name": "轻微迟到分钟",
"config_group": "attendance",
"config_value": "5",
"value_type": "integer",
"remark": "小于等于该分钟数按轻微迟到统计",
},
{
"config_key": "attendance.minor_late_free_times",
"config_name": "轻微迟到免扣次数",
"config_group": "attendance",
"config_value": "3",
"value_type": "integer",
"remark": "5分钟内迟到3次内合格超过后按次扣款",
},
{
"config_key": "attendance.late_penalty",
"config_name": "迟到扣款",
"config_group": "attendance",
"config_value": "30",
"value_type": "number",
"remark": "每次计扣迟到扣款金额",
},
{
"config_key": "attendance.missing_card_penalty",
"config_name": "缺卡扣款",
"config_group": "attendance",
"config_value": "20",
"value_type": "number",
"remark": "每次缺卡扣款金额",
},
{
"config_key": "attendance.weekend_days",
"config_name": "周末星期",
"config_group": "attendance",
"config_value": "[5, 6]",
"value_type": "json",
"remark": "Python weekday周一0周六5周日6",
},
{
"config_key": "attendance.legal_holidays",
"config_name": "法定节假日",
"config_group": "attendance",
"config_value": "[]",
"value_type": "json",
"remark": "日期数组,例如[\"2026-10-01\"]",
},
{
"config_key": "attendance.leave_keywords",
"config_name": "请假关键字",
"config_group": "attendance",
"config_value": json.dumps(list(AttendanceRules.leave_keywords), ensure_ascii=False),
"value_type": "json",
"remark": "从钉钉单元格识别请假/调休类型",
},
{
"config_key": "payroll.default_overtime_rate",
"config_name": "工作日加班单价",
"config_group": "payroll",
"config_value": "0",
"value_type": "number",
"remark": "未维护员工单价时使用",
},
{
"config_key": "payroll.default_weekend_overtime_rate",
"config_name": "周末加班单价",
"config_group": "payroll",
"config_value": "0",
"value_type": "number",
"remark": "未维护员工周末单价时使用",
},
{
"config_key": "payroll.default_holiday_overtime_rate",
"config_name": "节假日加班单价",
"config_group": "payroll",
"config_value": "0",
"value_type": "number",
"remark": "未维护员工节假日单价时使用",
},
]
@dataclass(frozen=True)
class SalaryConfigUpdate:
config_name: str
config_group: str
config_value: str
value_type: str
is_enabled: bool
remark: str
class SalaryConfigService:
"""系统配置服务:维护规则配置,并把数据库配置合成为 AppConfig。"""
def __init__(self, repository: SalaryConfigRepository):
self.repository = repository
def ensure_defaults(self) -> int:
created = self.repository.create_defaults(DEFAULT_CONFIGS)
logger.info("系统默认配置初始化完成 created=%s", created)
return created
def list_configs(self, *, config_group: str | None = None) -> list[SalaryConfigORM]:
configs = self.repository.list_configs(config_group=_clean_optional(config_group))
logger.info("查询系统配置成功 count=%s group=%s", len(configs), config_group)
return configs
def update_config(self, config_key: str, values: SalaryConfigUpdate) -> SalaryConfigORM:
self._parse_value(values.config_value, values.value_type)
config = self.repository.upsert_config(
config_key=config_key,
config_name=values.config_name.strip(),
config_group=values.config_group.strip(),
config_value=values.config_value.strip(),
value_type=values.value_type.strip(),
is_enabled=values.is_enabled,
remark=values.remark.strip(),
)
logger.info("更新系统配置成功 config_key=%s", config_key)
return config
def resolve_app_config(self, base_config: AppConfig) -> AppConfig:
values = {
config.config_key: self._parse_value(config.config_value, config.value_type)
for config in self.repository.list_configs()
if config.is_enabled
}
attendance = AttendanceRules(
on_work_time=str(values.get("attendance.on_work_time", base_config.attendance.on_work_time)),
off_work_time=str(values.get("attendance.off_work_time", base_config.attendance.off_work_time)),
overtime_round_minutes=int(
values.get("attendance.overtime_round_minutes", base_config.attendance.overtime_round_minutes)
),
standard_daily_hours=float(
values.get("attendance.standard_daily_hours", base_config.attendance.standard_daily_hours)
),
minor_late_minutes=int(values.get("attendance.minor_late_minutes", base_config.attendance.minor_late_minutes)),
minor_late_free_times=int(
values.get("attendance.minor_late_free_times", base_config.attendance.minor_late_free_times)
),
late_penalty=float(values.get("attendance.late_penalty", base_config.attendance.late_penalty)),
missing_card_penalty=float(
values.get("attendance.missing_card_penalty", base_config.attendance.missing_card_penalty)
),
weekend_days=tuple(int(item) for item in values.get("attendance.weekend_days", base_config.attendance.weekend_days)),
legal_holidays=tuple(str(item) for item in values.get("attendance.legal_holidays", base_config.attendance.legal_holidays)),
leave_keywords=tuple(str(item) for item in values.get("attendance.leave_keywords", base_config.attendance.leave_keywords)),
)
payroll = PayrollRules(
default_overtime_rate=float(
values.get("payroll.default_overtime_rate", base_config.payroll.default_overtime_rate)
),
default_weekend_overtime_rate=float(
values.get("payroll.default_weekend_overtime_rate", base_config.payroll.default_weekend_overtime_rate)
),
default_holiday_overtime_rate=float(
values.get("payroll.default_holiday_overtime_rate", base_config.payroll.default_holiday_overtime_rate)
),
employees=base_config.payroll.employees,
)
logger.info("已合并数据库规则配置到运行时计算配置")
return AppConfig(attendance=attendance, payroll=payroll)
def _parse_value(self, raw_value: str, value_type: str):
if value_type == "integer":
return int(raw_value)
if value_type == "number":
return float(raw_value)
if value_type == "boolean":
return str(raw_value).lower() in {"1", "true", "yes", "on"}
if value_type == "json":
return json.loads(raw_value or "null")
return raw_value
def _clean_optional(value: str | None) -> str | None:
if value is None:
return None
stripped = value.strip()
return stripped or None