"""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