Corp/backend/finance/migrations/0001_initial.py

1543 lines
60 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.

# Generated by Django 5.2.4 on 2026-07-17 12:13
import django.core.validators
import django.db.models.deletion
import uuid
from decimal import Decimal
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="PayrollComponent",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"code",
models.CharField(
max_length=64,
unique=True,
validators=[
django.core.validators.RegexValidator(
message="Код должен начинаться с A-Z и содержать только A-Z, 0-9, _.-",
regex="^[A-Z][A-Z0-9_.-]{0,63}$",
)
],
verbose_name="Код",
),
),
("name", models.CharField(max_length=160, verbose_name="Название")),
(
"kind",
models.CharField(
choices=[
("earning", "Начисление"),
("adjustment_credit", "Коррекция +"),
("adjustment_debit", "Коррекция −"),
("deduction", "Удержание"),
("payment", "Выплата"),
],
max_length=24,
verbose_name="Тип",
),
),
(
"requires_reason",
models.BooleanField(
default=False, verbose_name="Требует основание"
),
),
(
"is_active",
models.BooleanField(default=True, verbose_name="Активен"),
),
(
"display_order",
models.PositiveIntegerField(default=100, verbose_name="Порядок"),
),
],
options={
"verbose_name": "Компонент зарплаты",
"verbose_name_plural": "Компоненты зарплаты",
"ordering": ["display_order", "code"],
},
),
migrations.CreateModel(
name="PayrollPeriod",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"code",
models.CharField(max_length=32, unique=True, verbose_name="Код"),
),
(
"name",
models.CharField(
blank=True, max_length=120, verbose_name="Название"
),
),
("date_from", models.DateField(verbose_name="Начало периода")),
("date_to", models.DateField(verbose_name="Конец периода")),
(
"pay_date",
models.DateField(
blank=True, null=True, verbose_name="Дата выплаты"
),
),
(
"currency",
models.CharField(
default="RUB",
max_length=3,
validators=[
django.core.validators.RegexValidator(
message="Валюта должна быть трёхбуквенным кодом в верхнем регистре.",
regex="^[A-Z]{3}$",
)
],
verbose_name="Валюта",
),
),
(
"status",
models.CharField(
choices=[
("open", "Открыт"),
("calculated", "Рассчитан"),
("review", "На проверке"),
("approved", "Утверждён"),
("published", "Опубликован"),
("closed", "Закрыт"),
],
default="open",
max_length=20,
verbose_name="Статус",
),
),
(
"lock_version",
models.PositiveIntegerField(
default=0, verbose_name="Версия блокировки"
),
),
(
"created_at",
models.DateTimeField(auto_now_add=True, verbose_name="Создан"),
),
(
"updated_at",
models.DateTimeField(auto_now=True, verbose_name="Обновлён"),
),
(
"created_by",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="created_payroll_periods",
to=settings.AUTH_USER_MODEL,
verbose_name="Создал",
),
),
],
options={
"verbose_name": "Расчётный период",
"verbose_name_plural": "Расчётные периоды",
"ordering": ["-date_from", "-id"],
"permissions": [
("manage_payroll_inputs", "Может управлять данными для расчёта"),
("approve_payroll_inputs", "Может утверждать данные для расчёта"),
("calculate_payroll", "Может запускать расчёт зарплаты"),
("approve_payroll", "Может утверждать расчёт зарплаты"),
("publish_payroll", "Может публиковать расчётные листки"),
("view_all_payroll", "Может просматривать все расчёты"),
("audit_payroll", "Может просматривать аудит расчётов"),
],
},
),
migrations.CreateModel(
name="PayrollRun",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("revision", models.PositiveIntegerField(verbose_name="Ревизия")),
(
"status",
models.CharField(
choices=[
("calculated", "Рассчитан"),
("review", "На проверке"),
("approved", "Утверждён"),
("published", "Опубликован"),
("superseded", "Заменён новой ревизией"),
],
default="calculated",
max_length=20,
verbose_name="Статус",
),
),
(
"idempotency_key",
models.UUIDField(
default=uuid.uuid4,
editable=False,
unique=True,
verbose_name="Ключ идемпотентности",
),
),
(
"ruleset_id",
models.CharField(max_length=120, verbose_name="Набор правил"),
),
(
"ruleset_version",
models.CharField(max_length=64, verbose_name="Версия правил"),
),
(
"ruleset_hash",
models.CharField(max_length=64, verbose_name="Хэш правил"),
),
(
"calculator_version",
models.CharField(max_length=64, verbose_name="Версия калькулятора"),
),
(
"input_hash",
models.CharField(max_length=64, verbose_name="Хэш входных данных"),
),
(
"result_hash",
models.CharField(max_length=64, verbose_name="Хэш результата"),
),
(
"employee_count",
models.PositiveIntegerField(default=0, verbose_name="Сотрудников"),
),
(
"gross_total",
models.DecimalField(
decimal_places=2,
default=Decimal("0"),
max_digits=21,
verbose_name="Всего начислено",
),
),
(
"deduction_total",
models.DecimalField(
decimal_places=2,
default=Decimal("0"),
max_digits=21,
verbose_name="Всего удержано",
),
),
(
"payable_total",
models.DecimalField(
decimal_places=2,
default=Decimal("0"),
max_digits=21,
verbose_name="Всего к выплате",
),
),
(
"requested_at",
models.DateTimeField(auto_now_add=True, verbose_name="Рассчитан"),
),
(
"approved_at",
models.DateTimeField(
blank=True, null=True, verbose_name="Утверждён"
),
),
(
"published_at",
models.DateTimeField(
blank=True, null=True, verbose_name="Опубликован"
),
),
(
"approved_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="approved_payroll_runs",
to=settings.AUTH_USER_MODEL,
verbose_name="Утвердил",
),
),
(
"period",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="runs",
to="finance.payrollperiod",
verbose_name="Период",
),
),
(
"published_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="published_payroll_runs",
to=settings.AUTH_USER_MODEL,
verbose_name="Опубликовал",
),
),
(
"requested_by",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="requested_payroll_runs",
to=settings.AUTH_USER_MODEL,
verbose_name="Запустил",
),
),
(
"supersedes",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="superseded_by_runs",
to="finance.payrollrun",
verbose_name="Пересчитывает ревизию",
),
),
],
options={
"verbose_name": "Запуск расчёта",
"verbose_name_plural": "Запуски расчёта",
"ordering": ["-period__date_from", "-revision"],
},
),
migrations.AddField(
model_name="payrollperiod",
name="current_run",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="active_for_periods",
to="finance.payrollrun",
verbose_name="Текущая ревизия расчёта",
),
),
migrations.CreateModel(
name="PayrollStatement",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"employee_snapshot",
models.JSONField(default=dict, verbose_name="Снимок сотрудника"),
),
(
"currency",
models.CharField(
max_length=3,
validators=[
django.core.validators.RegexValidator(
message="Валюта должна быть трёхбуквенным кодом в верхнем регистре.",
regex="^[A-Z]{3}$",
)
],
verbose_name="Валюта",
),
),
(
"point_delta",
models.DecimalField(
blank=True,
decimal_places=4,
max_digits=19,
null=True,
verbose_name="Отклонение баллов",
),
),
(
"gross_before_adjustments",
models.DecimalField(
decimal_places=2,
max_digits=21,
verbose_name="Начислено до корректировок",
),
),
(
"adjustment_total",
models.DecimalField(
decimal_places=2, max_digits=21, verbose_name="Корректировки"
),
),
(
"gross_total",
models.DecimalField(
decimal_places=2, max_digits=21, verbose_name="Итого начислено"
),
),
(
"deduction_total",
models.DecimalField(
decimal_places=2, max_digits=21, verbose_name="Удержания"
),
),
(
"net_pay",
models.DecimalField(
decimal_places=2, max_digits=21, verbose_name="После удержаний"
),
),
(
"payment_total",
models.DecimalField(
decimal_places=2, max_digits=21, verbose_name="Уже выплачено"
),
),
(
"payable",
models.DecimalField(
decimal_places=2, max_digits=21, verbose_name="К выплате"
),
),
(
"input_hash",
models.CharField(max_length=64, verbose_name="Хэш входных данных"),
),
(
"result_hash",
models.CharField(max_length=64, verbose_name="Хэш результата"),
),
(
"input_snapshot",
models.JSONField(default=dict, verbose_name="Входной снимок"),
),
(
"result_snapshot",
models.JSONField(default=dict, verbose_name="Результат расчёта"),
),
(
"created_at",
models.DateTimeField(auto_now_add=True, verbose_name="Создан"),
),
(
"employee",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="payroll_statements",
to=settings.AUTH_USER_MODEL,
verbose_name="Сотрудник",
),
),
(
"run",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="statements",
to="finance.payrollrun",
verbose_name="Запуск",
),
),
(
"supersedes",
models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="superseded_by_statement",
to="finance.payrollstatement",
verbose_name="Заменяет листок",
),
),
],
options={
"verbose_name": "Расчётный листок",
"verbose_name_plural": "Расчётные листки",
"ordering": ["-run__period__date_from", "employee_id"],
},
),
migrations.CreateModel(
name="PayrollStatementAcknowledgement",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"content_hash",
models.CharField(
max_length=64, verbose_name="Хэш подтверждённого листка"
),
),
(
"idempotency_key",
models.UUIDField(
default=uuid.uuid4,
editable=False,
unique=True,
verbose_name="Ключ идемпотентности",
),
),
(
"viewed_at",
models.DateTimeField(
blank=True, null=True, verbose_name="Просмотрен"
),
),
(
"acknowledged_at",
models.DateTimeField(
blank=True, null=True, verbose_name="Получение подтверждено"
),
),
(
"disputed_at",
models.DateTimeField(blank=True, null=True, verbose_name="Оспорен"),
),
(
"dispute_reason",
models.TextField(blank=True, verbose_name="Причина оспаривания"),
),
(
"created_at",
models.DateTimeField(auto_now_add=True, verbose_name="Создано"),
),
(
"updated_at",
models.DateTimeField(auto_now=True, verbose_name="Обновлено"),
),
(
"employee",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="payroll_acknowledgements",
to=settings.AUTH_USER_MODEL,
verbose_name="Сотрудник",
),
),
(
"statement",
models.OneToOneField(
on_delete=django.db.models.deletion.PROTECT,
related_name="acknowledgement",
to="finance.payrollstatement",
verbose_name="Расчётный листок",
),
),
],
options={
"verbose_name": "Подтверждение расчётного листка",
"verbose_name_plural": "Подтверждения расчётных листков",
},
),
migrations.CreateModel(
name="PayrollStatementLine",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("position", models.PositiveIntegerField(verbose_name="Позиция")),
(
"line_id",
models.CharField(
max_length=160, verbose_name="Идентификатор строки"
),
),
("code", models.CharField(max_length=64, verbose_name="Код")),
("label", models.CharField(max_length=160, verbose_name="Название")),
(
"kind",
models.CharField(
choices=[
("earning", "Начисление"),
("adjustment_credit", "Коррекция +"),
("adjustment_debit", "Коррекция −"),
("deduction", "Удержание"),
("payment", "Выплата"),
],
max_length=24,
verbose_name="Тип",
),
),
(
"amount",
models.DecimalField(
decimal_places=2, max_digits=21, verbose_name="Сумма"
),
),
(
"source_ref",
models.CharField(max_length=255, verbose_name="Источник"),
),
("reason", models.TextField(blank=True, verbose_name="Основание")),
(
"source_period_from",
models.DateField(
blank=True, null=True, verbose_name="Период-источник: начало"
),
),
(
"source_period_to",
models.DateField(
blank=True, null=True, verbose_name="Период-источник: конец"
),
),
(
"calculated",
models.BooleanField(default=False, verbose_name="Рассчитана ядром"),
),
(
"statement",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="lines",
to="finance.payrollstatement",
verbose_name="Расчётный листок",
),
),
],
options={
"verbose_name": "Строка расчётного листка",
"verbose_name_plural": "Строки расчётных листков",
"ordering": ["statement_id", "position"],
},
),
migrations.CreateModel(
name="PayrollWorkRecord",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"target_points",
models.DecimalField(
decimal_places=4,
max_digits=19,
validators=[
django.core.validators.MinValueValidator(Decimal("0.0001"))
],
verbose_name="Норма баллов",
),
),
(
"actual_points",
models.DecimalField(
decimal_places=4,
max_digits=19,
validators=[
django.core.validators.MinValueValidator(Decimal("0"))
],
verbose_name="Фактические баллы",
),
),
(
"expected_point_amount",
models.DecimalField(
blank=True,
decimal_places=2,
max_digits=19,
null=True,
verbose_name="Контроль: выработка по баллам",
),
),
(
"expected_gross",
models.DecimalField(
blank=True,
decimal_places=2,
max_digits=19,
null=True,
verbose_name="Контроль: итого начислено",
),
),
(
"expected_recalculated_gross",
models.DecimalField(
blank=True,
decimal_places=2,
max_digits=19,
null=True,
verbose_name="Контроль: перерасчёт",
),
),
(
"expected_payable",
models.DecimalField(
blank=True,
decimal_places=2,
max_digits=19,
null=True,
verbose_name="Контроль: к выплате",
),
),
(
"revision",
models.PositiveIntegerField(default=1, verbose_name="Ревизия"),
),
(
"status",
models.CharField(
choices=[
("draft", "Черновик"),
("approved", "Утверждено"),
("voided", "Аннулировано"),
],
default="draft",
max_length=16,
verbose_name="Статус",
),
),
(
"source",
models.CharField(
choices=[
("manual", "Вручную"),
("excel", "Excel"),
("api", "API"),
],
default="manual",
max_length=16,
verbose_name="Источник",
),
),
(
"source_ref",
models.CharField(
blank=True,
max_length=255,
verbose_name="Идентификатор в источнике",
),
),
(
"created_at",
models.DateTimeField(auto_now_add=True, verbose_name="Создана"),
),
(
"updated_at",
models.DateTimeField(auto_now=True, verbose_name="Обновлена"),
),
(
"approved_at",
models.DateTimeField(
blank=True, null=True, verbose_name="Утверждена"
),
),
(
"voided_at",
models.DateTimeField(
blank=True, null=True, verbose_name="Аннулирована"
),
),
(
"approved_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="approved_payroll_work_records",
to=settings.AUTH_USER_MODEL,
verbose_name="Утвердил",
),
),
(
"created_by",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="created_payroll_work_records",
to=settings.AUTH_USER_MODEL,
verbose_name="Создал",
),
),
(
"employee",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="payroll_work_records",
to=settings.AUTH_USER_MODEL,
verbose_name="Сотрудник",
),
),
(
"period",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="work_records",
to="finance.payrollperiod",
verbose_name="Период",
),
),
(
"replaces",
models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="replaced_by",
to="finance.payrollworkrecord",
verbose_name="Заменяет запись",
),
),
(
"voided_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="voided_payroll_work_records",
to=settings.AUTH_USER_MODEL,
verbose_name="Аннулировал",
),
),
],
options={
"verbose_name": "Выработка за период",
"verbose_name_plural": "Выработка за периоды",
"ordering": ["period_id", "employee_id", "-revision"],
},
),
migrations.CreateModel(
name="EmployeePayRate",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"rate_code",
models.CharField(
default="BASE",
max_length=64,
validators=[
django.core.validators.RegexValidator(
message="Код должен начинаться с A-Z и содержать только A-Z, 0-9, _.-",
regex="^[A-Z][A-Z0-9_.-]{0,63}$",
)
],
verbose_name="Код ставки",
),
),
(
"amount",
models.DecimalField(
decimal_places=4,
max_digits=19,
validators=[
django.core.validators.MinValueValidator(Decimal("0.0001"))
],
verbose_name="Сумма ставки",
),
),
(
"point_rate",
models.DecimalField(
decimal_places=4,
default=Decimal("0"),
max_digits=19,
validators=[
django.core.validators.MinValueValidator(Decimal("0"))
],
verbose_name="Цена балла сверх нормы",
),
),
(
"currency",
models.CharField(
default="RUB",
max_length=3,
validators=[
django.core.validators.RegexValidator(
message="Валюта должна быть трёхбуквенным кодом в верхнем регистре.",
regex="^[A-Z]{3}$",
)
],
verbose_name="Валюта",
),
),
("effective_from", models.DateField(verbose_name="Действует с")),
(
"revision",
models.PositiveIntegerField(default=1, verbose_name="Ревизия"),
),
(
"status",
models.CharField(
choices=[
("draft", "Черновик"),
("approved", "Утверждено"),
("voided", "Аннулировано"),
],
default="draft",
max_length=16,
verbose_name="Статус",
),
),
("reason", models.TextField(blank=True, verbose_name="Основание")),
(
"source",
models.CharField(
choices=[
("manual", "Вручную"),
("excel", "Excel"),
("api", "API"),
],
default="manual",
max_length=16,
verbose_name="Источник",
),
),
(
"source_ref",
models.CharField(
blank=True,
max_length=255,
verbose_name="Идентификатор в источнике",
),
),
(
"created_at",
models.DateTimeField(auto_now_add=True, verbose_name="Создана"),
),
(
"approved_at",
models.DateTimeField(
blank=True, null=True, verbose_name="Утверждена"
),
),
(
"voided_at",
models.DateTimeField(
blank=True, null=True, verbose_name="Аннулирована"
),
),
(
"void_reason",
models.TextField(blank=True, verbose_name="Причина аннулирования"),
),
(
"approved_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="approved_pay_rates",
to=settings.AUTH_USER_MODEL,
verbose_name="Утвердил",
),
),
(
"created_by",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="created_pay_rates",
to=settings.AUTH_USER_MODEL,
verbose_name="Создал",
),
),
(
"employee",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="pay_rates",
to=settings.AUTH_USER_MODEL,
verbose_name="Сотрудник",
),
),
(
"replaces",
models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="replaced_by",
to="finance.employeepayrate",
verbose_name="Заменяет ставку",
),
),
(
"voided_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="voided_pay_rates",
to=settings.AUTH_USER_MODEL,
verbose_name="Аннулировал",
),
),
],
options={
"verbose_name": "Ставка сотрудника",
"verbose_name_plural": "Ставки сотрудников",
"ordering": ["employee_id", "-effective_from", "-revision"],
"indexes": [
models.Index(
fields=["employee", "status", "effective_from"],
name="pay_rate_employee_date_idx",
)
],
"constraints": [
models.CheckConstraint(
condition=models.Q(("amount__gt", 0)),
name="pay_rate_amount_positive",
),
models.CheckConstraint(
condition=models.Q(("point_rate__gte", 0)),
name="pay_rate_point_nonnegative",
),
models.CheckConstraint(
condition=models.Q(
("approved_by__isnull", True),
models.Q(
("created_by", models.F("approved_by")), _negated=True
),
_connector="OR",
),
name="pay_rate_maker_not_approver",
),
models.UniqueConstraint(
fields=("employee", "rate_code", "effective_from", "revision"),
name="pay_rate_revision_unique",
),
models.UniqueConstraint(
condition=models.Q(("status", "approved")),
fields=("employee", "rate_code", "effective_from"),
name="pay_rate_one_approved_event",
),
models.UniqueConstraint(
condition=models.Q(("source_ref", ""), _negated=True),
fields=("source", "source_ref"),
name="pay_rate_source_ref_unique",
),
],
},
),
migrations.CreateModel(
name="PayrollInputLine",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"amount",
models.DecimalField(
decimal_places=2,
max_digits=19,
validators=[
django.core.validators.MinValueValidator(Decimal("0.01"))
],
verbose_name="Сумма",
),
),
("reason", models.TextField(blank=True, verbose_name="Основание")),
(
"status",
models.CharField(
choices=[
("draft", "Черновик"),
("approved", "Утверждено"),
("voided", "Аннулировано"),
],
default="draft",
max_length=16,
verbose_name="Статус",
),
),
(
"source",
models.CharField(
choices=[
("manual", "Вручную"),
("excel", "Excel"),
("api", "API"),
],
default="manual",
max_length=16,
verbose_name="Источник",
),
),
(
"source_ref",
models.CharField(
blank=True,
max_length=255,
verbose_name="Идентификатор в источнике",
),
),
(
"idempotency_key",
models.UUIDField(
default=uuid.uuid4,
editable=False,
unique=True,
verbose_name="Ключ идемпотентности",
),
),
(
"created_at",
models.DateTimeField(auto_now_add=True, verbose_name="Создана"),
),
(
"approved_at",
models.DateTimeField(
blank=True, null=True, verbose_name="Утверждена"
),
),
(
"voided_at",
models.DateTimeField(
blank=True, null=True, verbose_name="Аннулирована"
),
),
(
"void_reason",
models.TextField(blank=True, verbose_name="Причина аннулирования"),
),
(
"approved_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="approved_payroll_input_lines",
to=settings.AUTH_USER_MODEL,
verbose_name="Утвердил",
),
),
(
"component",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="input_lines",
to="finance.payrollcomponent",
verbose_name="Компонент",
),
),
(
"created_by",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="created_payroll_input_lines",
to=settings.AUTH_USER_MODEL,
verbose_name="Создал",
),
),
(
"employee",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="payroll_input_lines",
to=settings.AUTH_USER_MODEL,
verbose_name="Сотрудник",
),
),
(
"reversal_of",
models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="reversed_by",
to="finance.payrollinputline",
verbose_name="Сторнирует строку",
),
),
(
"voided_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="voided_payroll_input_lines",
to=settings.AUTH_USER_MODEL,
verbose_name="Аннулировал",
),
),
(
"period",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="input_lines",
to="finance.payrollperiod",
verbose_name="Период выплаты",
),
),
(
"relates_to_period",
models.ForeignKey(
blank=True,
help_text="Заполняется для перерасчёта прошлого периода.",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="retro_input_lines",
to="finance.payrollperiod",
verbose_name="Относится к периоду",
),
),
],
options={
"verbose_name": "Строка начисления/удержания",
"verbose_name_plural": "Строки начислений/удержаний",
"ordering": [
"period_id",
"employee_id",
"component__display_order",
"id",
],
"indexes": [
models.Index(
fields=["period", "employee", "status"],
name="pay_input_period_employee_idx",
)
],
"constraints": [
models.CheckConstraint(
condition=models.Q(("amount__gt", 0)),
name="pay_input_amount_positive",
),
models.CheckConstraint(
condition=models.Q(
("approved_by__isnull", True),
models.Q(
("created_by", models.F("approved_by")), _negated=True
),
_connector="OR",
),
name="pay_input_maker_not_approver",
),
models.UniqueConstraint(
condition=models.Q(("source_ref", ""), _negated=True),
fields=("source", "source_ref"),
name="pay_input_source_ref_unique",
),
],
},
),
migrations.CreateModel(
name="PayrollAuditEvent",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("action", models.CharField(max_length=80, verbose_name="Действие")),
(
"object_type",
models.CharField(max_length=80, verbose_name="Тип объекта"),
),
(
"object_id",
models.CharField(
max_length=80, verbose_name="Идентификатор объекта"
),
),
(
"before_hash",
models.CharField(blank=True, max_length=64, verbose_name="Хэш до"),
),
(
"after_hash",
models.CharField(
blank=True, max_length=64, verbose_name="Хэш после"
),
),
(
"metadata",
models.JSONField(
blank=True, default=dict, verbose_name="Безопасные метаданные"
),
),
(
"created_at",
models.DateTimeField(auto_now_add=True, verbose_name="Создано"),
),
(
"actor",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="payroll_audit_events",
to=settings.AUTH_USER_MODEL,
verbose_name="Инициатор",
),
),
(
"period",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="audit_events",
to="finance.payrollperiod",
verbose_name="Период",
),
),
],
options={
"verbose_name": "Событие аудита зарплаты",
"verbose_name_plural": "События аудита зарплаты",
"ordering": ["-created_at", "-id"],
"indexes": [
models.Index(
fields=["object_type", "object_id", "created_at"],
name="pay_audit_object_time_idx",
),
models.Index(
fields=["period", "created_at"],
name="pay_audit_period_time_idx",
),
],
},
),
migrations.AddIndex(
model_name="payrollrun",
index=models.Index(
fields=["period", "status", "revision"],
name="pay_run_period_status_idx",
),
),
migrations.AddConstraint(
model_name="payrollrun",
constraint=models.UniqueConstraint(
fields=("period", "revision"), name="pay_run_revision_unique"
),
),
migrations.AddConstraint(
model_name="payrollrun",
constraint=models.CheckConstraint(
condition=models.Q(
("approved_by__isnull", True),
models.Q(("requested_by", models.F("approved_by")), _negated=True),
_connector="OR",
),
name="pay_run_maker_not_approver",
),
),
migrations.AddConstraint(
model_name="payrollrun",
constraint=models.CheckConstraint(
condition=models.Q(("gross_total__gte", 0)),
name="pay_run_gross_nonnegative",
),
),
migrations.AddConstraint(
model_name="payrollrun",
constraint=models.CheckConstraint(
condition=models.Q(("deduction_total__gte", 0)),
name="pay_run_deduction_nonnegative",
),
),
migrations.AddConstraint(
model_name="payrollrun",
constraint=models.CheckConstraint(
condition=models.Q(("payable_total__gte", 0)),
name="pay_run_payable_nonnegative",
),
),
migrations.AddIndex(
model_name="payrollperiod",
index=models.Index(
fields=["status", "date_from"], name="pay_period_status_date_idx"
),
),
migrations.AddConstraint(
model_name="payrollperiod",
constraint=models.CheckConstraint(
condition=models.Q(("date_from__lte", models.F("date_to"))),
name="pay_period_dates_valid",
),
),
migrations.AddConstraint(
model_name="payrollperiod",
constraint=models.UniqueConstraint(
fields=("date_from", "date_to"), name="pay_period_dates_unique"
),
),
migrations.AddIndex(
model_name="payrollstatement",
index=models.Index(
fields=["employee", "run"], name="pay_statement_employee_idx"
),
),
migrations.AddConstraint(
model_name="payrollstatement",
constraint=models.UniqueConstraint(
fields=("run", "employee"), name="pay_statement_run_employee_unique"
),
),
migrations.AddConstraint(
model_name="payrollstatement",
constraint=models.CheckConstraint(
condition=models.Q(("gross_before_adjustments__gte", 0)),
name="pay_statement_pre_gross_nonnegative",
),
),
migrations.AddConstraint(
model_name="payrollstatement",
constraint=models.CheckConstraint(
condition=models.Q(("gross_total__gte", 0)),
name="pay_statement_gross_nonnegative",
),
),
migrations.AddConstraint(
model_name="payrollstatement",
constraint=models.CheckConstraint(
condition=models.Q(("deduction_total__gte", 0)),
name="pay_statement_deduction_nonnegative",
),
),
migrations.AddConstraint(
model_name="payrollstatement",
constraint=models.CheckConstraint(
condition=models.Q(("payment_total__gte", 0)),
name="pay_statement_payment_nonnegative",
),
),
migrations.AddConstraint(
model_name="payrollstatement",
constraint=models.CheckConstraint(
condition=models.Q(("payable__gte", 0)),
name="pay_statement_payable_nonnegative",
),
),
migrations.AddConstraint(
model_name="payrollstatementline",
constraint=models.UniqueConstraint(
fields=("statement", "position"),
name="pay_statement_line_position_unique",
),
),
migrations.AddConstraint(
model_name="payrollstatementline",
constraint=models.UniqueConstraint(
fields=("statement", "line_id"), name="pay_statement_line_id_unique"
),
),
migrations.AddConstraint(
model_name="payrollstatementline",
constraint=models.CheckConstraint(
condition=models.Q(("amount__gte", 0)),
name="pay_statement_line_amount_nonnegative",
),
),
migrations.AddConstraint(
model_name="payrollworkrecord",
constraint=models.CheckConstraint(
condition=models.Q(("target_points__gt", 0)),
name="pay_work_target_positive",
),
),
migrations.AddConstraint(
model_name="payrollworkrecord",
constraint=models.CheckConstraint(
condition=models.Q(("actual_points__gte", 0)),
name="pay_work_actual_nonnegative",
),
),
migrations.AddConstraint(
model_name="payrollworkrecord",
constraint=models.CheckConstraint(
condition=models.Q(
("approved_by__isnull", True),
models.Q(("created_by", models.F("approved_by")), _negated=True),
_connector="OR",
),
name="pay_work_maker_not_approver",
),
),
migrations.AddConstraint(
model_name="payrollworkrecord",
constraint=models.UniqueConstraint(
fields=("period", "employee", "revision"),
name="pay_work_revision_unique",
),
),
migrations.AddConstraint(
model_name="payrollworkrecord",
constraint=models.UniqueConstraint(
condition=models.Q(("status", "approved")),
fields=("period", "employee"),
name="pay_work_one_approved",
),
),
migrations.AddConstraint(
model_name="payrollworkrecord",
constraint=models.UniqueConstraint(
condition=models.Q(("source_ref", ""), _negated=True),
fields=("source", "source_ref"),
name="pay_work_source_ref_unique",
),
),
]