"""Code-owned payroll input component definitions.""" from dataclasses import dataclass from finance.enums import PayrollComponentKind from finance.enums import PayrollComponentPeriodScope @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 @property def period_scope(self) -> str: return PayrollComponentPeriodScope.ALL @property def scope_period_id(self) -> None: return None 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.""" class _Manager: def all(self): return get_payroll_components() def get(self, **criteria): return self.all().get(**criteria) def filter(self, **criteria): return self.all().filter(**criteria) def exclude(self, **criteria): return self.all().exclude(**criteria) def order_by(self, *fields): return self.all().order_by(*fields) objects = _Manager() def get_payroll_components(period=None) -> _PayrollComponentCollection: """Return built-ins plus active user-created component columns.""" from finance.models import PayrollCustomComponent custom = list( PayrollCustomComponent.objects.filter(is_active=True) .select_related("scope_period") .order_by("display_order", "name", "id") ) if period is not None: custom = [component for component in custom if component.applies_to_period(period)] return _PayrollComponentCollection((*PAYROLL_COMPONENTS, *custom)) def get_payroll_component(code: str, *, period=None) -> PayrollComponentDefinition: component = PAYROLL_COMPONENTS_BY_CODE.get(code) if component is not None: return component from finance.models import PayrollCustomComponent component = ( PayrollCustomComponent.objects.filter(code=code, is_active=True) .select_related("scope_period") .first() ) if component is not None and ( period is None or component.applies_to_period(period) ): return component raise ValueError(f"Неизвестный вид зарплатной операции: {code}")