103 lines
3.8 KiB
Python
103 lines
3.8 KiB
Python
"""Code-owned payroll input component definitions."""
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from finance.enums import PayrollComponentKind
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class PayrollComponentDefinition:
|
|
code: str
|
|
name: str
|
|
kind: str
|
|
requires_reason: bool
|
|
display_order: int
|
|
|
|
@property
|
|
def id(self) -> str:
|
|
return self.code
|
|
|
|
@property
|
|
def pk(self) -> str:
|
|
return self.code
|
|
|
|
@property
|
|
def is_active(self) -> bool:
|
|
return True
|
|
|
|
def get_kind_display(self) -> str:
|
|
return PayrollComponentKind(self.kind).label
|
|
|
|
|
|
PAYROLL_COMPONENTS = (
|
|
PayrollComponentDefinition("VACATION_PAY", "Отпускные", PayrollComponentKind.EARNING, False, 20),
|
|
PayrollComponentDefinition("BONUS", "Премия", PayrollComponentKind.EARNING, False, 30),
|
|
PayrollComponentDefinition("CORRECTION_CREDIT", "Положительная корректировка", PayrollComponentKind.ADJUSTMENT_CREDIT, True, 40),
|
|
PayrollComponentDefinition("CORRECTION_DEBIT", "Отрицательная корректировка", PayrollComponentKind.ADJUSTMENT_DEBIT, True, 41),
|
|
PayrollComponentDefinition("ONE_TIME_PAYMENT", "Разовое начисление", PayrollComponentKind.EARNING, True, 50),
|
|
PayrollComponentDefinition("DEDUCTION", "Удержание", PayrollComponentKind.DEDUCTION, True, 60),
|
|
PayrollComponentDefinition("ADVANCE", "Аванс", PayrollComponentKind.PAYMENT, False, 70),
|
|
PayrollComponentDefinition("SALARY_PAYMENT", "Фактическая выплата", PayrollComponentKind.PAYMENT, True, 80),
|
|
)
|
|
PAYROLL_COMPONENTS_BY_CODE = {component.code: component for component in PAYROLL_COMPONENTS}
|
|
PAYROLL_COMPONENT_CODE_CHOICES = tuple(
|
|
(component.code, component.name) for component in PAYROLL_COMPONENTS
|
|
)
|
|
|
|
|
|
class _PayrollComponentCollection(tuple):
|
|
def get(self, **criteria):
|
|
matches = self.filter(**criteria)
|
|
if len(matches) != 1:
|
|
raise LookupError(f"Ожидался один вид операции, найдено: {len(matches)}")
|
|
return matches[0]
|
|
|
|
def filter(self, **criteria):
|
|
return _PayrollComponentCollection(
|
|
component
|
|
for component in self
|
|
if all(getattr(component, key) == value for key, value in criteria.items())
|
|
)
|
|
|
|
def exclude(self, **criteria):
|
|
normalized = {
|
|
key.removesuffix("__in"): ("in" if key.endswith("__in") else "exact", value)
|
|
for key, value in criteria.items()
|
|
}
|
|
return _PayrollComponentCollection(
|
|
component
|
|
for component in self
|
|
if not all(
|
|
getattr(component, key) in value if lookup == "in" else getattr(component, key) == value
|
|
for key, (lookup, value) in normalized.items()
|
|
)
|
|
)
|
|
|
|
def all(self):
|
|
return self
|
|
|
|
def order_by(self, *fields):
|
|
result = list(self)
|
|
for field in reversed(fields):
|
|
reverse = field.startswith("-")
|
|
name = field.removeprefix("-")
|
|
result.sort(key=lambda component: getattr(component, name), reverse=reverse)
|
|
return _PayrollComponentCollection(result)
|
|
|
|
def values_list(self, field, *, flat=False):
|
|
values = [getattr(component, field) for component in self]
|
|
return values if flat else [(value,) for value in values]
|
|
|
|
|
|
class PayrollComponent:
|
|
"""Compatibility facade over the code registry; this is not a database model."""
|
|
|
|
objects = _PayrollComponentCollection(PAYROLL_COMPONENTS)
|
|
|
|
|
|
def get_payroll_component(code: str) -> PayrollComponentDefinition:
|
|
try:
|
|
return PAYROLL_COMPONENTS_BY_CODE[code]
|
|
except KeyError as exc:
|
|
raise ValueError(f"Неизвестный вид зарплатной операции: {code}") from exc
|