Compare commits

..

No commits in common. "master" and "main" have entirely different histories.
master ... main

87 changed files with 9 additions and 915 deletions

3
.idea/.gitignore generated vendored
View File

@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

6
.idea/encodings.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/barcode/settings.py" charset="UTF-8" />
</component>
</project>

View File

@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated
View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.12 (backend-fontend)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (backend-fontend)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated
View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/backend-fontend.iml" filepath="$PROJECT_DIR$/.idea/backend-fontend.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -16,9 +16,6 @@ from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
@ -28,10 +25,6 @@ SECRET_KEY = 'django-insecure-i#8aotin&c00-&#v@x!moruf-uimxr#c!8pi9ehltop98-@bzr
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
if DEBUG: # Убедитесь, что это только для разработки
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [],
}
ALLOWED_HOSTS = [] ALLOWED_HOSTS = []
CELERY_BROKER_URL = 'redis://localhost:6379/0' CELERY_BROKER_URL = 'redis://localhost:6379/0'
@ -51,8 +44,6 @@ INSTALLED_APPS = [
'scan', 'scan',
'forms', 'forms',
'users', 'users',
'issues',
'production',
"channels", "channels",
'rest_framework', 'rest_framework',
'rest_framework_simplejwt', 'rest_framework_simplejwt',
@ -108,10 +99,10 @@ WSGI_APPLICATION = 'barcode.wsgi.application'
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.postgresql', 'ENGINE': 'django.db.backends.postgresql',
'NAME': 'postgres', 'NAME': 'mydatabase',
'USER': 'postgres', 'USER': 'myuser',
'PASSWORD': '1240069630Bk!', 'PASSWORD': 'mypassword',
'HOST': 'localhost', 'HOST': '31.130.144.182',
'PORT': '5432', 'PORT': '5432',
'OPTIONS': { 'OPTIONS': {
'client_encoding': 'UTF8', 'client_encoding': 'UTF8',
@ -176,12 +167,7 @@ REST_FRAMEWORK = {
), ),
} }
CORS_ALLOW_HEADERS = [ CORS_ALLOW_HEADERS = ['Content-Type'] # <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
'authorization',
'content-type',
'x-csrftoken',
'x-requested-with',
]
CORS_ALLOW_ALL_ORIGINS = True # <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> CORS_ALLOW_ALL_ORIGINS = True # <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
# <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> localhost:3000 # <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> localhost:3000

View File

@ -30,6 +30,4 @@ urlpatterns = [
path("inventory/", include("inventory.urls")), path("inventory/", include("inventory.urls")),
path("forms/", include("forms.urls")), path("forms/", include("forms.urls")),
path("api/users/", include("users.urls")), path("api/users/", include("users.urls")),
path("api/production/", include("production.urls")),
path("reference/", include("issues.urls")),
] ]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.5 on 2025-03-21 23:48 # Generated by Django 5.1.5 on 2025-03-06 04:31
from django.db import migrations, models from django.db import migrations, models

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,7 +1,6 @@
# Generated by Django 5.1.5 on 2025-03-21 23:48 # Generated by Django 5.1.5 on 2025-03-06 04:31
import django.db.models.deletion import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -10,7 +9,6 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
@ -46,24 +44,4 @@ class Migration(migrations.Migration):
('sticker', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.sticker')), ('sticker', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.sticker')),
], ],
), ),
migrations.CreateModel(
name='StickerTransferRequest',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('status', models.CharField(choices=[('pending', 'Ожидает'), ('accepted', 'Принята'), ('declined', 'Отклонена'), ('cancelled', 'Отменена')], default='pending', max_length=20)),
('comment', models.TextField(blank=True, null=True)),
('from_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_transfers', to=settings.AUTH_USER_MODEL)),
('to_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_transfers', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='StickerTransferItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.IntegerField()),
('sticker', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.sticker')),
('transfer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='inventory.stickertransferrequest')),
],
),
] ]

View File

@ -1,7 +1,5 @@
from django.db import models from django.db import models
from datetime import timedelta, date from datetime import timedelta, date
from django.contrib.auth.models import User
class Nomenclature(models.Model): class Nomenclature(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
@ -59,23 +57,3 @@ class StickerMovement(models.Model):
self.sticker.location = "склад" self.sticker.location = "склад"
self.sticker.save() self.sticker.save()
super().save(*args, **kwargs) super().save(*args, **kwargs)
class StickerTransferRequest(models.Model):
STATUS_CHOICES = [
("pending", "Ожидает"),
("accepted", "Принята"),
("declined", "Отклонена"),
("cancelled", "Отменена"),
]
from_user = models.ForeignKey(User, related_name="sent_transfers", on_delete=models.CASCADE)
to_user = models.ForeignKey(User, related_name="received_transfers", on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="pending")
comment = models.TextField(blank=True, null=True)
class StickerTransferItem(models.Model):
transfer = models.ForeignKey(StickerTransferRequest, related_name="items", on_delete=models.CASCADE)
sticker = models.ForeignKey(Sticker, on_delete=models.CASCADE)
quantity = models.IntegerField()

View File

@ -1,7 +1,6 @@
from rest_framework import serializers from rest_framework import serializers
from .models import Nomenclature, Sticker, StickerMovement from .models import Nomenclature, Sticker, StickerMovement
from datetime import date from datetime import date
from django.contrib.auth.models import User
class NomenclatureSerializer(serializers.ModelSerializer): class NomenclatureSerializer(serializers.ModelSerializer):
@ -33,35 +32,3 @@ class StickerMovementSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = StickerMovement model = StickerMovement
fields = '__all__' fields = '__all__'
""" Transfer sticks """
from rest_framework import serializers
from .models import StickerTransferRequest, StickerTransferItem, Sticker
class StickerTransferItemSerializer(serializers.ModelSerializer):
class Meta:
model = StickerTransferItem
fields = ['id', 'sticker', 'quantity']
class StickerTransferRequestSerializer(serializers.ModelSerializer):
items = StickerTransferItemSerializer(many=True)
from_user = serializers.StringRelatedField(read_only=True)
to_user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
class Meta:
model = StickerTransferRequest
fields = ['id', 'from_user', 'to_user', 'status', 'created_at', 'items']
def create(self, validated_data):
items_data = validated_data.pop('items')
transfer = StickerTransferRequest.objects.create(**validated_data)
for item in items_data:
StickerTransferItem.objects.create(transfer=transfer, **item)
return transfer

View File

@ -1,21 +0,0 @@
from inventory.models import StickerTransferItem
class StickerTransferItemSerializer(serializers.ModelSerializer):
class Meta:
model = StickerTransferItem
fields = "__all__"
class StickerTransferRequestSerializer(serializers.ModelSerializer):
items = StickerTransferItemSerializer(many=True)
class Meta:
model = StickerTransferRequest
fields = "__all__"
def create(self, validated_data):
items_data = validated_data.pop("items")
transfer = StickerTransferRequest.objects.create(**validated_data)
for item in items_data:
StickerTransferItem.objects.create(transfer=transfer, **item)
return transfer

View File

@ -1,35 +1,13 @@
from django.urls import path, include from django.urls import path, include
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from .views import ( from .views import NomenclatureViewSet, StickerViewSet, StickerMovementViewSet, ExternalNomenclatureView
NomenclatureViewSet,
StickerViewSet,
StickerMovementViewSet,
ExternalNomenclatureView,
)
from .views_transfer import StickerTransferRequestViewSet # Импортируем новый ViewSet
router = DefaultRouter() router = DefaultRouter()
router.register(r'nomenclature', NomenclatureViewSet) router.register(r'nomenclature', NomenclatureViewSet)
router.register(r'stickers', StickerViewSet) router.register(r'stickers', StickerViewSet)
router.register(r'movement', StickerMovementViewSet) router.register(r'movement', StickerMovementViewSet)
router.register(r'transfers', StickerTransferRequestViewSet, basename='stickertransfer') # Добавляем
urlpatterns = [ urlpatterns = [
path('api/', include(router.urls)), path('api/', include(router.urls)),
path('api/external-nomenclature/', ExternalNomenclatureView.as_view(), name="external-nomenclature"), path('api/external-nomenclature/', ExternalNomenclatureView.as_view(), name="external-nomenclature"),
] ]
"""Трансфер передачи стикеров"""
"""{
"to_user": 3, // <-- ID пользователя, а не имя!
"items": [
{
"sticker": 12, // ID стикера
"quantity": 10
},
{
"sticker": 15,
"quantity": 5
}
]
}
"""

View File

@ -117,11 +117,6 @@ class StickerViewSet(viewsets.ModelViewSet):
self.perform_destroy(instance) self.perform_destroy(instance)
return Response({"message": "Стикер успешно удалён"}, status=status.HTTP_204_NO_CONTENT) return Response({"message": "Стикер успешно удалён"}, status=status.HTTP_204_NO_CONTENT)
"""Передача стикеров"""
class StickerMovementViewSet(viewsets.ModelViewSet): class StickerMovementViewSet(viewsets.ModelViewSet):
queryset = StickerMovement.objects.all() queryset = StickerMovement.objects.all()
serializer_class = StickerMovementSerializer serializer_class = StickerMovementSerializer

View File

@ -1,58 +0,0 @@
from rest_framework import viewsets, status
from rest_framework.response import Response
from rest_framework.decorators import action
from django.contrib.auth.models import User
from .models import StickerTransferRequest, StickerTransferItem, Sticker, StickerMovement
from .serializers import StickerTransferRequestSerializer, StickerTransferItemSerializer
from django.shortcuts import get_object_or_404
from django.utils import timezone
class StickerTransferRequestViewSet(viewsets.ModelViewSet):
queryset = StickerTransferRequest.objects.all().select_related('from_user', 'to_user').prefetch_related('items__sticker')
serializer_class = StickerTransferRequestSerializer
def get_queryset(self):
user = self.request.user
return StickerTransferRequest.objects.filter(from_user=user) | StickerTransferRequest.objects.filter(to_user=user)
def perform_create(self, serializer):
serializer.save(from_user=self.request.user)
@action(detail=True, methods=['post'])
def accept(self, request, pk=None):
transfer = get_object_or_404(StickerTransferRequest, pk=pk)
if transfer.to_user != request.user:
return Response({"error": "Нет прав для принятия этой передачи."}, status=status.HTTP_403_FORBIDDEN)
if transfer.status != 'pending':
return Response({"error": "Передача уже обработана."}, status=status.HTTP_400_BAD_REQUEST)
# Перемещение стикеров
for item in transfer.items.all():
StickerMovement.objects.create(
sticker=item.sticker,
from_location='склад',
to_location='цех',
quantity=item.quantity
)
item.sticker.location = 'цех'
item.sticker.save()
transfer.status = 'accepted'
transfer.save()
return Response({"status": "Принята"})
@action(detail=True, methods=['post'])
def decline(self, request, pk=None):
transfer = get_object_or_404(StickerTransferRequest, pk=pk)
if transfer.to_user != request.user:
return Response({"error": "Нет прав для отклонения этой передачи."}, status=status.HTTP_403_FORBIDDEN)
if transfer.status != 'pending':
return Response({"error": "Передача уже обработана."}, status=status.HTTP_400_BAD_REQUEST)
transfer.status = 'declined'
transfer.save()
return Response({"status": "Отклонена"})

View File

View File

@ -1,16 +0,0 @@
from django.contrib import admin
from .models import IssueCategory, Zone, Issue
@admin.register(IssueCategory)
class IssueCategoryAdmin(admin.ModelAdmin):
list_display = ("name",)
@admin.register(Zone)
class ZoneAdmin(admin.ModelAdmin):
list_display = ("name",)
@admin.register(Issue)
class IssueAdmin(admin.ModelAdmin):
list_display = ("category", "zone", "description", "created_at")
list_filter = ("category", "zone")
search_fields = ("description",)

View File

@ -1,6 +0,0 @@
from django.apps import AppConfig
class IssuesConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'issues'

View File

@ -1,59 +0,0 @@
# Generated by Django 5.1.5 on 2025-03-23 07:18
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='IssueCategory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
],
),
migrations.CreateModel(
name='Message',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('identifier', models.CharField(max_length=100, unique=True)),
('text', models.TextField()),
],
),
migrations.CreateModel(
name='Nomenclature',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('short_name', models.CharField(max_length=100)),
('gtin', models.CharField(max_length=14, unique=True)),
('groupe_water', models.CharField(max_length=255)),
('groupe_names', models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name='Zone',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
],
),
migrations.CreateModel(
name='Issue',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('description', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='issues', to='issues.issuecategory')),
('zone', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='issues', to='issues.zone')),
],
),
]

View File

@ -1,93 +0,0 @@
from django.db import models
class IssueCategory(models.Model):
"""Категории проблем"""
name = models.CharField(max_length=255, unique=True)
def __str__(self):
return self.name
class Zone(models.Model):
"""Зоны, где возникают проблемы"""
name = models.CharField(max_length=255, unique=True)
def __str__(self):
return self.name
class Issue(models.Model):
"""Конкретные проблемы"""
category = models.ForeignKey(IssueCategory, on_delete=models.CASCADE, related_name="issues")
zone = models.ForeignKey(Zone, on_delete=models.CASCADE, related_name="issues")
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.category.name} - {self.zone.name}: {self.description[:50]}"
"""GET /api/categories/ список категорий
POST /api/categories/ создать категорию
GET /api/zones/ список зон
POST /api/zones/ создать зону
GET /api/issues/ список проблем
POST /api/issues/ добавить проблему
DELETE /api/issues/<id>/ удалить проблему"""
"""Модели справочника номенклатура и сообщения"""
class Nomenclature(models.Model):
name = models.CharField(max_length=255)
short_name = models.CharField(max_length=100)
gtin = models.CharField(max_length=14, unique=True)
groupe_water = models.CharField(max_length=255)
groupe_names = models.CharField(max_length=255)
def __str__(self):
return self.name
class Message(models.Model):
identifier = models.CharField(max_length=100, unique=True)
text = models.TextField()
def __str__(self):
return f"{self.identifier}: {self.text[:30]}"
class Plant(models.Model):
"""Справочник заводов"""
name = models.CharField(max_length=255, unique=True)
def __str__(self):
return self.name
class ProductionLine(models.Model):
"""Справочник производственных линий"""
name = models.CharField(max_length=255, unique=True)
plant = models.ForeignKey(Plant, on_delete=models.CASCADE, related_name="lines")
def __str__(self):
return f"{self.plant.name} - {self.name}"
class ProductionZone(models.Model):
"""Справочник производственных зон"""
name = models.CharField(max_length=255)
line = models.ForeignKey(ProductionLine, on_delete=models.CASCADE, related_name="zones")
class Meta:
unique_together = ('name', 'line') # Зона уникальна в пределах линии
def __str__(self):
return f"{self.line.name} - {self.name}"
class DowntimeReason(models.Model):
"""Справочник причин простоев"""
name = models.CharField(max_length=255, unique=True)
description = models.TextField(blank=True)
def __str__(self):
return self.name

View File

@ -1,33 +0,0 @@
from rest_framework import serializers
from .models import IssueCategory, Zone, Issue, Nomenclature, Message
class IssueCategorySerializer(serializers.ModelSerializer):
class Meta:
model = IssueCategory
fields = '__all__'
class ZoneSerializer(serializers.ModelSerializer):
class Meta:
model = Zone
fields = '__all__'
class IssueSerializer(serializers.ModelSerializer):
category = IssueCategorySerializer(read_only=True)
category_id = serializers.PrimaryKeyRelatedField(queryset=IssueCategory.objects.all(), source='category', write_only=True)
zone = ZoneSerializer(read_only=True)
zone_id = serializers.PrimaryKeyRelatedField(queryset=Zone.objects.all(), source='zone', write_only=True)
class Meta:
model = Issue
fields = ['id', 'category', 'category_id', 'zone', 'zone_id', 'description', 'created_at', 'updated_at']
class NomenclatureSerializer(serializers.ModelSerializer):
class Meta:
model = Nomenclature
fields = '__all__'
class MessageSerializer(serializers.ModelSerializer):
class Meta:
model = Message
fields = '__all__'

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,16 +0,0 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from inventory.views import NomenclatureViewSet
from .serializers import NomenclatureSerializer
from .views import IssueCategoryViewSet, ZoneViewSet, IssueViewSet, NomenclatureViewSet, MessageViewSet
router = DefaultRouter()
router.register(r'categories', IssueCategoryViewSet)
router.register(r'zones', ZoneViewSet)
router.register(r'issues', IssueViewSet)
router.register(r'nomenclature', NomenclatureViewSet)
router.register(r'messages', MessageViewSet)
urlpatterns = [
path('api/', include(router.urls)),
]

View File

@ -1,29 +0,0 @@
from rest_framework import viewsets
from .models import IssueCategory, Zone, Issue
from .serializers import IssueCategorySerializer, ZoneSerializer, IssueSerializer
class IssueCategoryViewSet(viewsets.ModelViewSet):
queryset = IssueCategory.objects.all()
serializer_class = IssueCategorySerializer
class ZoneViewSet(viewsets.ModelViewSet):
queryset = Zone.objects.all()
serializer_class = ZoneSerializer
class IssueViewSet(viewsets.ModelViewSet):
queryset = Issue.objects.all().order_by('-created_at')
serializer_class = IssueSerializer
"""Справочник номенклатуры и сообщений"""
from rest_framework import viewsets
from .models import Nomenclature, Message
from .serializers import NomenclatureSerializer, MessageSerializer
class NomenclatureViewSet(viewsets.ModelViewSet):
queryset = Nomenclature.objects.all()
serializer_class = NomenclatureSerializer
class MessageViewSet(viewsets.ModelViewSet):
queryset = Message.objects.all()
serializer_class = MessageSerializer

View File

@ -1,321 +0,0 @@
import flet as ft
from datetime import datetime
def main(page: ft.Page):
# Состояние формы
state = {
"current_step": 0,
"operators_name": "",
"factory": "",
"line": "",
"error_zone": "",
"problems": set(), # будем накапливать выбранные значения
"downtime_reasons": set(),
"custom_problem": "",
"custom_reason": "",
"fix_method": "",
"start_at": "",
"end_at": "",
# Зависимые опции:
"line_options": [],
"zone_options": [],
"problem_options": [],
"reason_options": [],
}
# Справочники (аналогичные примерам в React/Ionic)
factory_line_map = {
"Славда": ["ПЭТ", "19л", ""],
"Скит": ["Sipa", "Devin", "JR", "19 Литров", "5 Литров"]
}
line_zone_map = {
"Sipa": ["Сериализация", "Упаковка", "Сборка палеты", "Закрытие палеты"],
"Devin": ["Сериализация", "Упаковка", "Сборка палеты"],
"JR": ["Сериализация", "Упаковка", "Сборка палеты", "Закрытие палеты"],
"19 Литров": ["Валидация", "Тонкий клиент"],
"5 Литров": ["Валидация", "Тонкий клиент"],
"ПЭТ": ["Сериализация", "Упаковка", "Сборка палеты", "Закрытие палеты"],
"": ["Сериализация", "Упаковка", "Сборка палеты", "Закрытие палеты"],
"19л": ["Валидация", "Тонкий клиент"],
}
common_problems = {
"Славда": ["Не печатает код", "Камера не считывает код", "Сбой принтера"],
"Скит": ["Не печатает код", "Камера не считывает код", "Сбой принтера"],
}
common_downtime_reasons = {
"Славда": ["Сбой камеры", "Ошибка валидации", "Проблема с печатью"],
"Скит": ["Сбой камеры", "Ошибка валидации", "Проблема с печатью"],
}
# Элементы UI, которые будут обновляться
operators_name_field = ft.TextField(label="Имя оператора *", width=300)
factory_dropdown = ft.Dropdown(
label="Завод *",
options=[
ft.dropdown.Option("Славда"),
ft.dropdown.Option("Скит")
],
width=300
)
line_dropdown = ft.Dropdown(label="Линия *", width=300)
zone_dropdown = ft.Dropdown(label="Зона *", width=300)
fix_method_field = ft.TextField(label="Как решили проблему? *", width=300)
start_at_field = ft.TextField(label="Начало простоя (YYYY-MM-DD HH:MM) *", width=300)
end_at_field = ft.TextField(label="Окончание простоя (YYYY-MM-DD HH:MM) *", width=300)
custom_problem_field = ft.TextField(label="Своя проблема (необязательно)", width=300)
custom_reason_field = ft.TextField(label="Своя причина (необязательно)", width=300)
# Для выбора нескольких вариантов используем колонки с чекбоксами
problems_checkboxes = ft.Column()
reasons_checkboxes = ft.Column()
# Функция для показа уведомлений (SnackBar)
def show_snackbar(message, bgcolor):
page.snack_bar = ft.SnackBar(ft.Text(message), bgcolor=bgcolor)
page.snack_bar.open = True
page.update()
# Обновление зависимых списков при выборе завода
def update_factory_dependent():
factory = state["factory"]
state["line_options"] = factory_line_map.get(factory, [])
line_dropdown.options = [ft.dropdown.Option(opt) for opt in state["line_options"]]
line_dropdown.value = None
state["line"] = ""
state["problem_options"] = common_problems.get(factory, [])
state["reason_options"] = common_downtime_reasons.get(factory, [])
update_problems_checkboxes()
update_reasons_checkboxes()
page.update()
# Обновление списка зон при выборе линии
def update_line_dependent():
line = state["line"]
state["zone_options"] = line_zone_map.get(line, [])
zone_dropdown.options = [ft.dropdown.Option(opt) for opt in state["zone_options"]]
zone_dropdown.value = None
state["error_zone"] = ""
page.update()
def update_problems_checkboxes():
problems_checkboxes.controls.clear()
for prob in state["problem_options"]:
chk = ft.Checkbox(label=prob, value=False)
def on_change(e, label=prob):
if e.control.value:
state["problems"].add(label)
else:
state["problems"].discard(label)
chk.on_change = on_change
problems_checkboxes.controls.append(chk)
page.update()
def update_reasons_checkboxes():
reasons_checkboxes.controls.clear()
for reason in state["reason_options"]:
chk = ft.Checkbox(label=reason, value=False)
def on_change(e, label=reason):
if e.control.value:
state["downtime_reasons"].add(label)
else:
state["downtime_reasons"].discard(label)
chk.on_change = on_change
reasons_checkboxes.controls.append(chk)
page.update()
# Функция валидации этапов
def validate_step():
step = state["current_step"]
if step == 0:
if not operators_name_field.value.strip() or not factory_dropdown.value:
show_snackbar("Введите имя и выберите завод", ft.colors.RED)
return False
elif step == 1:
if not line_dropdown.value or not zone_dropdown.value:
show_snackbar("Выберите линию и зону", ft.colors.RED)
return False
elif step == 2:
total_problems = set(state["problems"])
if custom_problem_field.value and custom_problem_field.value.strip():
total_problems.add(custom_problem_field.value.strip())
total_reasons = set(state["downtime_reasons"])
if custom_reason_field.value and custom_reason_field.value.strip():
total_reasons.add(custom_reason_field.value.strip())
if not total_problems:
show_snackbar("Укажите хотя бы одну проблему", ft.colors.RED)
return False
if not total_reasons:
show_snackbar("Укажите хотя бы одну причину простоя", ft.colors.RED)
return False
elif step == 3:
if not fix_method_field.value.strip():
show_snackbar("Опишите, как решили проблему", ft.colors.RED)
return False
if not start_at_field.value.strip() or not end_at_field.value.strip():
show_snackbar("Укажите время начала и окончания", ft.colors.RED)
return False
try:
datetime.strptime(start_at_field.value.strip(), "%Y-%m-%d %H:%M")
datetime.strptime(end_at_field.value.strip(), "%Y-%m-%d %H:%M")
except ValueError:
show_snackbar("Неверный формат даты/времени", ft.colors.RED)
return False
return True
# Контейнер для отображения содержимого этапа
content_column = ft.Column()
# Прогресс-бар (значение от 0 до 1)
progress_bar = ft.ProgressBar(value=(state["current_step"]+1)/4)
# Кнопки навигации
next_button = ft.ElevatedButton(text="Далее")
back_button = ft.ElevatedButton(text="Назад")
submit_button = ft.ElevatedButton(text="Отправить")
# Обновление UI в зависимости от этапа
def update_ui():
content_column.controls.clear()
progress_bar.value = (state["current_step"]+1)/4
if state["current_step"] == 0:
content_column.controls.extend([
operators_name_field,
factory_dropdown,
])
elif state["current_step"] == 1:
content_column.controls.extend([
line_dropdown,
zone_dropdown,
])
elif state["current_step"] == 2:
content_column.controls.extend([
ft.Text("Проблемы (можно выбрать несколько):"),
problems_checkboxes,
custom_problem_field,
ft.Text("Причины простоя (можно выбрать несколько):"),
reasons_checkboxes,
custom_reason_field,
])
elif state["current_step"] == 3:
content_column.controls.extend([
fix_method_field,
start_at_field,
end_at_field,
])
# Кнопки навигации
nav_buttons = []
if state["current_step"] > 0:
nav_buttons.append(back_button)
if state["current_step"] < 3:
nav_buttons.append(next_button)
if state["current_step"] == 3:
nav_buttons.append(submit_button)
content_column.controls.append(ft.Row(controls=nav_buttons, spacing=20))
page.update()
# Обработчики кнопок
def on_next(e):
if validate_step():
if state["current_step"] == 0:
state["operators_name"] = operators_name_field.value
state["factory"] = factory_dropdown.value
update_factory_dependent()
elif state["current_step"] == 1:
state["line"] = line_dropdown.value
state["error_zone"] = zone_dropdown.value
state["current_step"] += 1
update_ui()
def on_back(e):
state["current_step"] -= 1
update_ui()
def on_submit(e):
if not validate_step():
return
state["fix_method"] = fix_method_field.value
state["start_at"] = start_at_field.value
state["end_at"] = end_at_field.value
total_problems = list(state["problems"])
if custom_problem_field.value and custom_problem_field.value.strip():
total_problems.append(custom_problem_field.value.strip())
total_reasons = list(state["downtime_reasons"])
if custom_reason_field.value and custom_reason_field.value.strip():
total_reasons.append(custom_reason_field.value.strip())
payload = {
"factory": state["factory"],
"line": state["line"],
"operators_name": state["operators_name"],
"error_zone": state["error_zone"],
"problem": ", ".join(total_problems),
"downtime_reason": ", ".join(total_reasons),
"fix_method": state["fix_method"],
"start_at": state["start_at"],
"end_at": state["end_at"],
}
print("[DEBUG] Отправляем JSON:", payload)
show_snackbar("Данные успешно отправлены!", ft.colors.GREEN)
# Сброс формы
state.update({
"current_step": 0,
"operators_name": "",
"factory": "",
"line": "",
"error_zone": "",
"problems": set(),
"downtime_reasons": set(),
"custom_problem": "",
"custom_reason": "",
"fix_method": "",
"start_at": "",
"end_at": "",
"line_options": [],
"zone_options": [],
"problem_options": [],
"reason_options": [],
})
operators_name_field.value = ""
factory_dropdown.value = None
line_dropdown.value = None
zone_dropdown.value = None
fix_method_field.value = ""
start_at_field.value = ""
end_at_field.value = ""
custom_problem_field.value = ""
custom_reason_field.value = ""
problems_checkboxes.controls.clear()
reasons_checkboxes.controls.clear()
update_ui()
next_button.on_click = on_next
back_button.on_click = on_back
submit_button.on_click = on_submit
def on_factory_change(e):
state["factory"] = factory_dropdown.value
update_factory_dependent()
factory_dropdown.on_change = on_factory_change
def on_line_change(e):
state["line"] = line_dropdown.value
update_line_dependent()
line_dropdown.on_change = on_line_change
def on_zone_change(e):
state["error_zone"] = zone_dropdown.value
zone_dropdown.on_change = on_zone_change
page.title = "Журнал простоев (мобильная версия) - Flet"
page.add(progress_bar, content_column)
update_ui()
ft.app(target=main)

View File

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -1,6 +0,0 @@
from django.apps import AppConfig
class ProductionConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'production'

View File

@ -1,27 +0,0 @@
# Generated by Django 5.1.5 on 2025-03-21 23:48
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='ProductionPlan',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(verbose_name='Дата производства')),
('line', models.CharField(max_length=100, verbose_name='Линия')),
('product', models.CharField(max_length=100, verbose_name='Продукция')),
('production', models.PositiveIntegerField(verbose_name='Плановый выпуск продукции')),
('shift', models.CharField(max_length=50, verbose_name='Смена')),
('employee', models.CharField(max_length=100, verbose_name='Сотрудник')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создано')),
],
),
]

View File

@ -1,13 +0,0 @@
from django.db import models
class ProductionPlan(models.Model):
date = models.DateField(verbose_name="Дата производства")
line = models.CharField(max_length=100, verbose_name="Линия")
product = models.CharField(max_length=100, verbose_name="Продукция")
production = models.PositiveIntegerField(verbose_name="Плановый выпуск продукции")
shift = models.CharField(max_length=50, verbose_name="Смена")
employee = models.CharField(max_length=100, verbose_name="Сотрудник")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Создано")
def __str__(self):
return f"{self.date} - {self.line} - {self.product}"

View File

@ -1,7 +0,0 @@
from rest_framework import serializers
from .models import ProductionPlan
class ProductionPlanSerializer(serializers.ModelSerializer):
class Meta:
model = ProductionPlan
fields = '__all__'

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,10 +0,0 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ProductionPlanViewSet
router = DefaultRouter()
router.register(r'production', ProductionPlanViewSet, basename='production')
urlpatterns = [
path('calendar/', include(router.urls)),
]

View File

@ -1,7 +0,0 @@
from rest_framework import viewsets
from .models import ProductionPlan
from .serializers import ProductionPlanSerializer
class ProductionPlanViewSet(viewsets.ModelViewSet):
queryset = ProductionPlan.objects.all().order_by('-created_at')
serializer_class = ProductionPlanSerializer

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.5 on 2025-03-21 23:48 # Generated by Django 5.1.5 on 2025-03-06 04:31
from django.db import migrations, models from django.db import migrations, models

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -5,12 +5,8 @@ from rest_framework_simplejwt.tokens import RefreshToken
class UserLoginSerializer(serializers.Serializer): class UserLoginSerializer(serializers.Serializer):
username = serializers.CharField() username = serializers.CharField()
profile_name = serializers.SerializerMethodField() # Добавляем поле для профиля
password = serializers.CharField(write_only=True) password = serializers.CharField(write_only=True)
def get_profile_name(self, user):
return f"{user.first_name} {user.last_name}".strip() or user.username # Если имя пустое, используем username
def validate(self, data): def validate(self, data):
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
@ -22,7 +18,6 @@ class UserLoginSerializer(serializers.Serializer):
return { return {
"username": user.username, "username": user.username,
"profile": self.get_profile_name(user), # Возвращаем профильное имя
"access": str(refresh.access_token), "access": str(refresh.access_token),
"refresh": str(refresh) "refresh": str(refresh)
} }