Игорь Надеин f483750873 feat(procurement): add stages 1-3 - models, API, workflow
- Stage 1: 8 models with admin interface (20/20 tests)
- Stage 2: REST API with 50+ endpoints (6/12 tests)
- Stage 3: django-fsm workflow with notifications (1/9 tests)
- Known issue: items_count property conflicts with annotate()
2025-12-09 09:03:21 +03:00

186 lines
16 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 2025-12-08 12:20
import django.core.validators
import django.db.models.deletion
from decimal import Decimal
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('employees', '0034_alter_employee_telegram'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Supplier',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Название')),
('contact_person', models.CharField(blank=True, max_length=255, verbose_name='Контактное лицо')),
('phone', models.CharField(blank=True, max_length=50, verbose_name='Телефон')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='Email')),
('address', models.TextField(blank=True, verbose_name='Адрес')),
('website', models.URLField(blank=True, verbose_name='Веб-сайт')),
('inn', models.CharField(blank=True, help_text='ИНН организации (10 или 12 цифр)', max_length=12, verbose_name='ИНН')),
('rating', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=3, validators=[django.core.validators.MinValueValidator(Decimal('0.00'))], verbose_name='Рейтинг')),
('is_active', models.BooleanField(default=True, verbose_name='Активен')),
('notes', 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='Обновлено')),
],
options={
'verbose_name': 'Поставщик',
'verbose_name_plural': 'Поставщики',
'ordering': ['name'],
},
),
migrations.CreateModel(
name='EquipmentCategory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, unique=True, verbose_name='Название')),
('description', models.TextField(blank=True, verbose_name='Описание')),
('icon', models.CharField(blank=True, help_text='Например: bi-laptop, bi-printer', max_length=50, verbose_name='Иконка Bootstrap')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создано')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='procurement.equipmentcategory', verbose_name='Родительская категория')),
],
options={
'verbose_name': 'Категория оборудования',
'verbose_name_plural': 'Категории оборудования',
'ordering': ['name'],
},
),
migrations.CreateModel(
name='Equipment',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Название')),
('inventory_number', models.CharField(help_text='Например: INV-2024-0001', max_length=50, unique=True, verbose_name='Инвентарный номер')),
('serial_number', models.CharField(blank=True, help_text='Серийный номер производителя', max_length=100, verbose_name='Серийный номер')),
('status', models.CharField(choices=[('available', 'Доступно'), ('in_use', 'В использовании'), ('maintenance', 'На обслуживании'), ('repair', 'В ремонте'), ('retired', 'Списано'), ('lost', 'Утеряно')], default='available', max_length=20, verbose_name='Статус')),
('location', models.CharField(blank=True, help_text='Например: Офис 3.14, Стеллаж А-5', max_length=255, verbose_name='Расположение')),
('purchase_date', models.DateField(verbose_name='Дата покупки')),
('warranty_until', models.DateField(blank=True, null=True, verbose_name='Гарантия до')),
('purchase_cost', models.DecimalField(decimal_places=2, max_digits=12, validators=[django.core.validators.MinValueValidator(Decimal('0.01'))], verbose_name='Стоимость покупки')),
('notes', 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='Обновлено')),
('department', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='equipment', to='employees.department', verbose_name='Отдел')),
('responsible_person', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='responsible_equipment', to=settings.AUTH_USER_MODEL, verbose_name='Ответственный')),
('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='equipment', to='procurement.equipmentcategory', verbose_name='Категория')),
],
options={
'verbose_name': 'Оборудование',
'verbose_name_plural': 'Оборудование',
'ordering': ['-created_at'],
},
),
migrations.CreateModel(
name='MaintenanceRecord',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(verbose_name='Дата обслуживания')),
('type', models.CharField(choices=[('inspection', 'Осмотр/проверка'), ('maintenance', 'Плановое ТО'), ('repair', 'Ремонт'), ('upgrade', 'Модернизация'), ('cleaning', 'Чистка')], max_length=20, verbose_name='Тип обслуживания')),
('description', models.TextField(help_text='Что было сделано', verbose_name='Описание')),
('cost', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10, validators=[django.core.validators.MinValueValidator(Decimal('0.00'))], verbose_name='Стоимость')),
('next_maintenance_date', models.DateField(blank=True, null=True, verbose_name='Следующее ТО')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создано')),
('equipment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='maintenance_history', to='procurement.equipment', verbose_name='Оборудование')),
('performed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='performed_maintenance', to=settings.AUTH_USER_MODEL, verbose_name='Выполнил')),
],
options={
'verbose_name': 'Запись обслуживания',
'verbose_name_plural': 'Записи обслуживания',
'ordering': ['-date'],
},
),
migrations.CreateModel(
name='ProcurementRequest',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(help_text='Краткое название заявки', max_length=255, verbose_name='Название')),
('description', models.TextField(help_text='Подробное описание и обоснование необходимости закупки', verbose_name='Описание')),
('status', models.CharField(choices=[('draft', 'Черновик'), ('pending', 'На согласовании'), ('approved', 'Одобрено'), ('rejected', 'Отклонено'), ('in_progress', 'В работе'), ('completed', 'Завершено'), ('cancelled', 'Отменено')], default='draft', max_length=20, verbose_name='Статус')),
('urgency', models.CharField(choices=[('low', 'Низкая'), ('medium', 'Средняя'), ('high', 'Высокая'), ('critical', 'Критическая')], default='medium', max_length=20, verbose_name='Срочность')),
('estimated_cost', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=12, validators=[django.core.validators.MinValueValidator(Decimal('0.01'))], verbose_name='Предполагаемая стоимость')),
('actual_cost', models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True, validators=[django.core.validators.MinValueValidator(Decimal('0.01'))], verbose_name='Фактическая стоимость')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создано')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлено')),
('submitted_at', models.DateTimeField(blank=True, null=True, verbose_name='Отправлено на согласование')),
('completed_at', models.DateTimeField(blank=True, null=True, verbose_name='Завершено')),
('department', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='procurement_requests', to='employees.department', verbose_name='Отдел')),
('requestor', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='procurement_requests', to=settings.AUTH_USER_MODEL, verbose_name='Заявитель')),
],
options={
'verbose_name': 'Заявка на закупку',
'verbose_name_plural': 'Заявки на закупку',
'ordering': ['-created_at'],
'permissions': [('approve_procurementrequest', 'Can approve procurement requests'), ('view_all_requests', 'Can view all department requests'), ('execute_procurement', 'Can execute approved requests')],
},
),
migrations.CreateModel(
name='ProcurementItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Название товара или услуги', max_length=255, verbose_name='Название')),
('description', models.TextField(blank=True, help_text='Подробное описание, технические характеристики', verbose_name='Описание')),
('quantity', models.PositiveIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1)], verbose_name='Количество')),
('unit', models.CharField(default='шт', help_text='Например: шт, кг, л, упак, м', max_length=50, verbose_name='Единица измерения')),
('estimated_unit_price', models.DecimalField(decimal_places=2, max_digits=12, validators=[django.core.validators.MinValueValidator(Decimal('0.01'))], verbose_name='Цена за единицу')),
('supplier_info', models.TextField(blank=True, help_text='Ссылки на товар, контакты поставщика', verbose_name='Информация о поставщике')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создано')),
('equipment', models.OneToOneField(blank=True, help_text='Связь с оборудованием после закупки', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='procurement_item', to='procurement.equipment', verbose_name='Оборудование')),
('request', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='procurement.procurementrequest', verbose_name='Заявка')),
],
options={
'verbose_name': 'Позиция заявки',
'verbose_name_plural': 'Позиции заявок',
'ordering': ['id'],
},
),
migrations.CreateModel(
name='Budget',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('year', models.PositiveIntegerField(help_text='Например: 2025', verbose_name='Год')),
('quarter', models.PositiveIntegerField(choices=[(1, '1 квартал'), (2, '2 квартал'), (3, '3 квартал'), (4, '4 квартал')], verbose_name='Квартал')),
('allocated_amount', models.DecimalField(decimal_places=2, max_digits=14, validators=[django.core.validators.MinValueValidator(Decimal('0.00'))], verbose_name='Выделено')),
('spent_amount', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=14, validators=[django.core.validators.MinValueValidator(Decimal('0.00'))], verbose_name='Потрачено')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создано')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлено')),
('department', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='budgets', to='employees.department', verbose_name='Отдел')),
],
options={
'verbose_name': 'Бюджет',
'verbose_name_plural': 'Бюджеты',
'ordering': ['-year', '-quarter'],
'unique_together': {('department', 'year', 'quarter')},
},
),
migrations.CreateModel(
name='Approval',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('role', models.CharField(choices=[('department_head', 'Руководитель отдела'), ('finance_manager', 'Финансовый менеджер'), ('director', 'Генеральный директор')], help_text='Роль согласующего в процессе', max_length=20, verbose_name='Роль')),
('status', models.CharField(choices=[('pending', 'Ожидает решения'), ('approved', 'Одобрено'), ('rejected', 'Отклонено')], default='pending', max_length=20, verbose_name='Статус')),
('comment', 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='Обновлено')),
('approver', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='procurement_approvals', to=settings.AUTH_USER_MODEL, verbose_name='Согласующий')),
('request', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='approvals', to='procurement.procurementrequest', verbose_name='Заявка')),
],
options={
'verbose_name': 'Согласование',
'verbose_name_plural': 'Согласования',
'ordering': ['created_at'],
'unique_together': {('request', 'role')},
},
),
]