This commit is contained in:
2
.env
2
.env
@@ -4,7 +4,7 @@ DB_USER=prodman_user
|
|||||||
DB_PASS=prodman_password_zwE45t!
|
DB_PASS=prodman_password_zwE45t!
|
||||||
|
|
||||||
# Настройки Django
|
# Настройки Django
|
||||||
ENV_TYPE=dev
|
# ENV_TYPE=dev
|
||||||
DB_HOST=db
|
DB_HOST=db
|
||||||
SECRET_KEY='django-insecure-9p*t_(vtwo144=u)ie8qzb31a8%i(&1w4$0vq#udautexj^vms'
|
SECRET_KEY='django-insecure-9p*t_(vtwo144=u)ie8qzb31a8%i(&1w4$0vq#udautexj^vms'
|
||||||
# todo потом установить домен для продакшена
|
# todo потом установить домен для продакшена
|
||||||
|
|||||||
@@ -7,11 +7,12 @@
|
|||||||
Если внес изменения в проект и готов отправить их на сервер `ProdServ`:
|
Если внес изменения в проект и готов отправить их на сервер `ProdServ`:
|
||||||
|
|
||||||
1. **Подготовь файлы**:
|
1. **Подготовь файлы**:
|
||||||
`git add .`
|
git add .
|
||||||
|
|
||||||
2. **Запечатай изменения**:
|
2. **Запечатай изменения**:
|
||||||
`git commit -m "Подправил докерфайл сборки контейнера"`
|
git commit -m "Подправил докерфайл сборки контейнера"
|
||||||
3. **Отправляй в полет**:
|
3. **Отправляй в полет**:
|
||||||
`git push origin main`
|
git push origin main
|
||||||
|
|
||||||
### 💊 Таблетка: Если не пушится (сброс авторизации)
|
### 💊 Таблетка: Если не пушится (сброс авторизации)
|
||||||
Если Git "забыл" пароль или выдает ошибку Permission Denied:
|
Если Git "забыл" пароль или выдает ошибку Permission Denied:
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ TEMPLATES = [
|
|||||||
'django.template.context_processors.request',
|
'django.template.context_processors.request',
|
||||||
'django.contrib.auth.context_processors.auth',
|
'django.contrib.auth.context_processors.auth',
|
||||||
'django.contrib.messages.context_processors.messages',
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
'shiftflow.context_processors.env_info', # Правильный путь
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
env_file:
|
env_file:
|
||||||
- .env # Прокидывает все секреты и настройки внутрь Python
|
- .env # Прокидывает все секреты и настройки внутрь Python
|
||||||
|
- ENV_TYPE=server
|
||||||
volumes:
|
volumes:
|
||||||
# Общие папки для статики и картинок. Сюда Django их складывает.
|
# Общие папки для статики и картинок. Сюда Django их складывает.
|
||||||
- staticfiles:/app/staticfiles
|
- staticfiles:/app/staticfiles
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import Company, EmployeeProfile, Machine, Deal, Material, Item
|
from .models import Company, EmployeeProfile, Machine, Deal, Material, ProductionTask, Item
|
||||||
|
|
||||||
# --- Настройка отображения Компаний ---
|
# --- Настройка отображения Компаний ---
|
||||||
@admin.register(Company)
|
@admin.register(Company)
|
||||||
@@ -20,52 +20,41 @@ class DealAdmin(admin.ModelAdmin):
|
|||||||
class MaterialAdmin(admin.ModelAdmin):
|
class MaterialAdmin(admin.ModelAdmin):
|
||||||
search_fields = ('name',)
|
search_fields = ('name',)
|
||||||
|
|
||||||
# --- ГЛАВНАЯ МАГИЯ: Сменные задания ---
|
# --- Задания на производство (База) ---
|
||||||
|
@admin.register(ProductionTask)
|
||||||
|
class ProductionTaskAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('drawing_name', 'deal', 'material', 'quantity_ordered', 'created_at')
|
||||||
|
search_fields = ('drawing_name', 'deal__number')
|
||||||
|
list_filter = ('deal', 'material', 'is_bend')
|
||||||
|
|
||||||
|
# --- Сменные задания (Выполнение) ---
|
||||||
@admin.register(Item)
|
@admin.register(Item)
|
||||||
class ItemAdmin(admin.ModelAdmin):
|
class ItemAdmin(admin.ModelAdmin):
|
||||||
# Что видим в общем списке
|
# Что видим в общем списке (используем task__ для доступа к полям базы)
|
||||||
list_display = ('date', 'machine', 'deal', 'drawing_name', 'quantity_plan', 'quantity_fact', 'status', 'is_synced_1c')
|
list_display = ('date', 'machine', 'get_deal', 'get_drawing', 'quantity_plan', 'quantity_fact', 'status', 'is_synced_1c')
|
||||||
# Фильтры справа
|
# Фильтры справа
|
||||||
list_filter = ('date', 'machine', 'status', 'is_synced_1c', 'deal')
|
list_filter = ('date', 'machine', 'status', 'is_synced_1c', 'task__deal')
|
||||||
# Поиск по номеру сделки и названию детали
|
# Поиск по номеру сделки и названию детали через связь task
|
||||||
search_fields = ('drawing_name', 'deal__number')
|
search_fields = ('task__drawing_name', 'task__deal__number')
|
||||||
|
|
||||||
# Группируем поля в форме редактирования для удобства
|
# Группируем поля в форме редактирования
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Основная информация', {
|
('Связь с заданием', {
|
||||||
'fields': ('date', 'machine', 'deal', 'status')
|
'fields': ('task', 'date', 'machine')
|
||||||
}),
|
}),
|
||||||
('Чертеж и параметры', {
|
('Исполнение', {
|
||||||
'fields': ('drawing_file', 'drawing_name', 'size_value', 'extra_drawing', 'material', 'quantity_plan', 'is_bend')
|
'fields': ('quantity_plan', 'quantity_fact', 'status', 'is_synced_1c')
|
||||||
}),
|
}),
|
||||||
('Исполнение (Оператор)', {
|
('Отходы и материалы', {
|
||||||
'fields': ('operator', 'quantity_fact', 'material_taken', 'usable_waste', 'scrap_weight', 'is_synced_1c')
|
'fields': ('material_taken', 'usable_waste', 'scrap_weight')
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
def save_model(self, request, obj, form, change):
|
def get_deal(self, obj): return obj.task.deal
|
||||||
"""
|
get_deal.short_description = 'Сделка'
|
||||||
Переопределяем сохранение, чтобы автоматизировать заполнение полей.
|
|
||||||
"""
|
|
||||||
# 1. Если имя детали "Б/ч" или пустое, а файл загружен — берем имя из файла
|
|
||||||
if (obj.drawing_name == "Б/ч" or not obj.drawing_name) and obj.drawing_file:
|
|
||||||
filename = os.path.basename(obj.drawing_file.name)
|
|
||||||
obj.drawing_name = os.path.splitext(filename)[0] # Отрезаем .dxf или .step
|
|
||||||
|
|
||||||
# 2. Логика запоминания последней сделки (через сессию браузера)
|
def get_drawing(self, obj): return obj.task.drawing_name
|
||||||
if obj.deal:
|
get_drawing.short_description = 'Деталь'
|
||||||
request.session['last_deal_id'] = obj.deal.id
|
|
||||||
|
|
||||||
super().save_model(request, obj, form, change)
|
|
||||||
|
|
||||||
def get_changeform_initial_data(self, request):
|
|
||||||
"""
|
|
||||||
Подставляем последнюю выбранную сделку в новую форму.
|
|
||||||
"""
|
|
||||||
initial = super().get_changeform_initial_data(request)
|
|
||||||
if 'last_deal_id' in request.session:
|
|
||||||
initial['deal'] = request.session['last_deal_id']
|
|
||||||
return initial
|
|
||||||
|
|
||||||
# Регистрация станков просто списком
|
# Регистрация станков просто списком
|
||||||
admin.site.register(Machine)
|
admin.site.register(Machine)
|
||||||
|
|||||||
9
shiftflow/context_processors.py
Normal file
9
shiftflow/context_processors.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
def env_info(request):
|
||||||
|
"""
|
||||||
|
Прокидываем ENV_TYPE во все шаблоны.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'ENV_TYPE': getattr(settings, 'ENV_TYPE', 'local')
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# Generated by Django 6.0.3 on 2026-03-29 12:51
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('shiftflow', '0003_employeeprofile'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='item',
|
||||||
|
options={'ordering': ['-date', 'task__deal'], 'verbose_name': 'Позиция сменки', 'verbose_name_plural': 'Реестр сменных заданий'},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='item',
|
||||||
|
name='deal',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='item',
|
||||||
|
name='drawing_file',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='item',
|
||||||
|
name='drawing_name',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='item',
|
||||||
|
name='extra_drawing',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='item',
|
||||||
|
name='is_bend',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='item',
|
||||||
|
name='material',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='item',
|
||||||
|
name='operator',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='item',
|
||||||
|
name='size_value',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='item',
|
||||||
|
name='date',
|
||||||
|
field=models.DateField(default=django.utils.timezone.now, verbose_name='Дата смены'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='item',
|
||||||
|
name='quantity_plan',
|
||||||
|
field=models.PositiveIntegerField(verbose_name='План на смену, шт'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ProductionTask',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('drawing_name', models.CharField(blank=True, default='Б/ч', max_length=255, verbose_name='Название детали')),
|
||||||
|
('size_value', models.FloatField(help_text='Длина (мм) или Толщина (мм)', verbose_name='Размер детали')),
|
||||||
|
('drawing_file', models.FileField(blank=True, null=True, upload_to='drawings/%Y/%m/', verbose_name='Исходник (DXF/IGES)')),
|
||||||
|
('extra_drawing', models.FileField(blank=True, null=True, upload_to='extra_drawings/%Y/%m/', verbose_name='Доп. чертеж (PDF)')),
|
||||||
|
('quantity_ordered', models.PositiveIntegerField(verbose_name='Заказано всего, шт')),
|
||||||
|
('is_bend', models.BooleanField(default=False, verbose_name='Гибка')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||||
|
('deal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shiftflow.deal', verbose_name='Сделка')),
|
||||||
|
('material', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='shiftflow.material', verbose_name='Материал')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Задание на деталь',
|
||||||
|
'verbose_name_plural': 'Задания на детали',
|
||||||
|
'ordering': ['-created_at'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='item',
|
||||||
|
name='task',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='items', to='shiftflow.productiontask', verbose_name='Задание'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -49,10 +49,35 @@ class Material(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Материал"; verbose_name_plural = "Материалы"
|
verbose_name = "Материал"; verbose_name_plural = "Материалы"
|
||||||
|
|
||||||
|
class ProductionTask(models.Model):
|
||||||
|
"""
|
||||||
|
Основание для производства. Определяет ЧТО делать.
|
||||||
|
Создается технологом или мастером на основе заказа.
|
||||||
|
"""
|
||||||
|
deal = models.ForeignKey(Deal, on_delete=models.CASCADE, verbose_name="Сделка")
|
||||||
|
|
||||||
|
drawing_name = models.CharField("Название детали", max_length=255, blank=True, default="Б/ч")
|
||||||
|
size_value = models.FloatField("Размер детали", help_text="Длина (мм) или Толщина (мм)")
|
||||||
|
|
||||||
|
drawing_file = models.FileField("Исходник (DXF/IGES)", upload_to="drawings/%Y/%m/", blank=True, null=True)
|
||||||
|
extra_drawing = models.FileField("Доп. чертеж (PDF)", upload_to="extra_drawings/%Y/%m/", blank=True, null=True)
|
||||||
|
|
||||||
|
material = models.ForeignKey(Material, on_delete=models.PROTECT, verbose_name="Материал")
|
||||||
|
quantity_ordered = models.PositiveIntegerField("Заказано всего, шт")
|
||||||
|
is_bend = models.BooleanField("Гибка", default=False)
|
||||||
|
|
||||||
|
created_at = models.DateTimeField("Дата создания", auto_now_add=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Задание на деталь"; verbose_name_plural = "Задания на детали"
|
||||||
|
ordering = ['-created_at']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.drawing_name} (Заказ {self.deal.number})"
|
||||||
|
|
||||||
class Item(models.Model):
|
class Item(models.Model):
|
||||||
"""
|
"""
|
||||||
Единица сменного задания. Основная рабочая сущность.
|
Единица сменного задания. Определяет КТО, КОГДА и СКОЛЬКО сделал.
|
||||||
Статус по умолчанию 'work' (В работе).
|
|
||||||
"""
|
"""
|
||||||
STATUS_CHOICES = [
|
STATUS_CHOICES = [
|
||||||
('work', 'В работе'),
|
('work', 'В работе'),
|
||||||
@@ -61,23 +86,15 @@ class Item(models.Model):
|
|||||||
('leftover', 'Недодел'),
|
('leftover', 'Недодел'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# --- База (заполняет начальник) ---
|
# --- Ссылка на основу (временно null=True для миграции старых данных) ---
|
||||||
date = models.DateField("Дата задания", default=timezone.now)
|
task = models.ForeignKey(ProductionTask, on_delete=models.CASCADE, related_name='items', verbose_name="Задание", null=True, blank=True)
|
||||||
|
|
||||||
|
# --- Смена (заполняет мастер) ---
|
||||||
|
date = models.DateField("Дата смены", default=timezone.now)
|
||||||
machine = models.ForeignKey(Machine, on_delete=models.PROTECT, verbose_name="Станок")
|
machine = models.ForeignKey(Machine, on_delete=models.PROTECT, verbose_name="Станок")
|
||||||
deal = models.ForeignKey(Deal, on_delete=models.CASCADE, verbose_name="Сделка")
|
quantity_plan = models.PositiveIntegerField("План на смену, шт")
|
||||||
|
|
||||||
drawing_name = models.CharField("Название детали", max_length=255, blank=True, default="Б/ч")
|
# --- Исполнение (заполняет оператор) ---
|
||||||
size_value = models.FloatField("Размер детали", help_text="Длина (мм) или Толщина (мм)")
|
|
||||||
|
|
||||||
drawing_file = models.FileField("Исходник (DXF/STEP)", upload_to="drawings/%Y/%m/", blank=True, null=True)
|
|
||||||
extra_drawing = models.FileField("Доп. чертеж (PDF)", upload_to="extra_drawings/%Y/%m/", blank=True, null=True)
|
|
||||||
|
|
||||||
material = models.ForeignKey(Material, on_delete=models.PROTECT, verbose_name="Материал")
|
|
||||||
quantity_plan = models.PositiveIntegerField("План, шт")
|
|
||||||
is_bend = models.BooleanField("Гибка", default=False)
|
|
||||||
|
|
||||||
# --- Исполнение (заполняет оператор/мастер) ---
|
|
||||||
operator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Исполнитель")
|
|
||||||
quantity_fact = models.PositiveIntegerField("Факт, шт", default=0)
|
quantity_fact = models.PositiveIntegerField("Факт, шт", default=0)
|
||||||
|
|
||||||
material_taken = models.TextField("Взятый материал", blank=True, help_text="Напр: 3 трубы по 12м")
|
material_taken = models.TextField("Взятый материал", blank=True, help_text="Напр: 3 трубы по 12м")
|
||||||
@@ -89,11 +106,11 @@ class Item(models.Model):
|
|||||||
is_synced_1c = models.BooleanField("Учтено в 1С", default=False)
|
is_synced_1c = models.BooleanField("Учтено в 1С", default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Позиция"; verbose_name_plural = "Сменное задание"
|
verbose_name = "Позиция сменки"; verbose_name_plural = "Реестр сменных заданий"
|
||||||
ordering = ['-date', 'deal']
|
ordering = ['-date', 'task__deal']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.drawing_name} ({self.quantity_plan} шт.)"
|
return f"{self.task.drawing_name} - {self.date}"
|
||||||
|
|
||||||
|
|
||||||
class EmployeeProfile(models.Model):
|
class EmployeeProfile(models.Model):
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<div class="card shadow-sm border-secondary mb-4">
|
<div class="card shadow-sm border-secondary mb-4">
|
||||||
|
|
||||||
<div class="card-header border-secondary d-flex justify-content-between align-items-center py-3">
|
<div class="card-header border-secondary d-flex justify-content-between align-items-center py-3">
|
||||||
<h3 class="text-accent mb-0"><i class="bi bi-info-circle me-2"></i>{{ item.drawing_name|default:"Без названия" }}</h3>
|
<h3 class="text-accent mb-0"><i class="bi bi-info-circle me-2"></i>{{ item.task.drawing_name|default:"Без названия" }}</h3>
|
||||||
<span class="badge bg-secondary">Сделка № {{ item.deal.number }}</span>
|
<span class="badge bg-secondary">Сделка № {{ item.task.deal.number }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="post" id="mainForm" class="card-body p-4">
|
<form method="post" id="mainForm" class="card-body p-4">
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<small class="text-muted d-block">Материал</small>
|
<small class="text-muted d-block">Материал</small>
|
||||||
<strong>{{ item.material.name }}</strong>
|
<strong>{{ item.task.material.name }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<small class="text-muted d-block">План</small>
|
<small class="text-muted d-block">План</small>
|
||||||
@@ -29,8 +29,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4 d-flex gap-2">
|
<div class="mb-4 d-flex gap-2">
|
||||||
{% if item.drawing_file %}<a href="{{ item.drawing_file.url }}" target="_blank" class="btn btn-outline-info btn-sm">DXF</a>{% endif %}
|
{% if item.task.drawing_file %}<a href="{{ item.task.drawing_file.url }}" target="_blank" class="btn btn-outline-info btn-sm">DXF</a>{% endif %}
|
||||||
{% if item.extra_drawing %}<a href="{{ item.extra_drawing.url }}" target="_blank" class="btn btn-outline-danger btn-sm">PDF</a>{% endif %}
|
{% if item.task.extra_drawing %}<a href="{{ item.task.extra_drawing.url }}" target="_blank" class="btn btn-outline-danger btn-sm">PDF</a>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="hidden" name="status" id="id_status" value="{{ item.status }}">
|
<input type="hidden" name="status" id="id_status" value="{{ item.status }}">
|
||||||
|
|||||||
@@ -22,10 +22,16 @@ class RegistryView(LoginRequiredMixin, ListView):
|
|||||||
context_object_name = 'items'
|
context_object_name = 'items'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
# Позже здесь добавим: .filter(machine__in=request.user.profile.machines.all())
|
# Оптимизируем запросы, подгружая связанные данные сразу
|
||||||
# Сортируем: сначала статус (по алфавиту или логике choices),
|
queryset = Item.objects.select_related('task', 'task__deal', 'task__material', 'machine').all()
|
||||||
# затем по дате (свежие сверху), по станку и по номеру сделки
|
|
||||||
return Item.objects.all().order_by('status', '-date', 'machine__name', 'deal__number')
|
# Если это оператор, показываем только задания для его станков
|
||||||
|
if hasattr(self.request.user, 'profile') and self.request.user.profile.role == 'operator':
|
||||||
|
user_machines = self.request.user.profile.machines.all()
|
||||||
|
if user_machines.exists():
|
||||||
|
queryset = queryset.filter(machine__in=user_machines)
|
||||||
|
|
||||||
|
return queryset.order_by('status', '-date', 'machine__name', 'task__deal__number')
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
@@ -38,10 +44,11 @@ class RegistryView(LoginRequiredMixin, ListView):
|
|||||||
class ItemUpdateView(LoginRequiredMixin, UpdateView):
|
class ItemUpdateView(LoginRequiredMixin, UpdateView):
|
||||||
model = Item
|
model = Item
|
||||||
template_name = 'shiftflow/item_detail.html'
|
template_name = 'shiftflow/item_detail.html'
|
||||||
# Перечисляем поля, которые можно редактировать (укажи нужные)
|
# Перечисляем поля, которые можно редактировать в сменке
|
||||||
fields = [
|
fields = [
|
||||||
'drawing_name', 'machine', 'quantity_plan', 'quantity_fact',
|
'machine', 'quantity_plan', 'quantity_fact',
|
||||||
'material', 'size_value', 'status', 'is_synced_1c', 'extra_drawing'
|
'status', 'is_synced_1c',
|
||||||
|
'material_taken', 'usable_waste', 'scrap_weight'
|
||||||
]
|
]
|
||||||
context_object_name = 'item'
|
context_object_name = 'item'
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
<footer class="footer-custom mt-auto py-3">
|
<footer class="footer-custom mt-auto py-3 border-top border-secondary">
|
||||||
<div class="container-fluid text-center">
|
<div class="container-fluid text-center">
|
||||||
<span class="text-muted small">
|
<div class="text-muted small">Система учета сменных заданий</div>
|
||||||
Система учета сменных заданий, разработана <strong>ACK</strong> © 2026
|
<div class="text-muted small">
|
||||||
|
разработана <strong>ACK</strong> © 2026
|
||||||
<i class="bi bi-cpu ms-2 text-accent"></i>
|
<i class="bi bi-cpu ms-2 text-accent"></i>
|
||||||
</span>
|
{% if user.is_superuser %}
|
||||||
|
<span class="badge bg-dark border border-secondary text-muted fw-normal ms-1" style="font-size: 0.65rem; vertical-align: middle;">
|
||||||
|
{{ ENV_TYPE|upper }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
Reference in New Issue
Block a user