Compare commits
No commits in common. "main" and "master" have entirely different histories.
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
14
.idea/backend-fontend.iml
generated
Normal file
14
.idea/backend-fontend.iml
generated
Normal file
@ -0,0 +1,14 @@
|
||||
<?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
Normal file
6
.idea/encodings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?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>
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?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
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?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
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?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.
@ -16,6 +16,9 @@ from pathlib import Path
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
|
||||
|
||||
@ -25,6 +28,10 @@ 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!
|
||||
DEBUG = True
|
||||
|
||||
if DEBUG: # Убедитесь, что это только для разработки
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': [],
|
||||
}
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
CELERY_BROKER_URL = 'redis://localhost:6379/0'
|
||||
@ -44,6 +51,8 @@ INSTALLED_APPS = [
|
||||
'scan',
|
||||
'forms',
|
||||
'users',
|
||||
'issues',
|
||||
'production',
|
||||
"channels",
|
||||
'rest_framework',
|
||||
'rest_framework_simplejwt',
|
||||
@ -99,10 +108,10 @@ WSGI_APPLICATION = 'barcode.wsgi.application'
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': 'mydatabase',
|
||||
'USER': 'myuser',
|
||||
'PASSWORD': 'mypassword',
|
||||
'HOST': '31.130.144.182',
|
||||
'NAME': 'postgres',
|
||||
'USER': 'postgres',
|
||||
'PASSWORD': '1240069630Bk!',
|
||||
'HOST': 'localhost',
|
||||
'PORT': '5432',
|
||||
'OPTIONS': {
|
||||
'client_encoding': 'UTF8',
|
||||
@ -167,7 +176,12 @@ REST_FRAMEWORK = {
|
||||
),
|
||||
}
|
||||
|
||||
CORS_ALLOW_HEADERS = ['Content-Type'] # <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
CORS_ALLOW_HEADERS = [
|
||||
'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>
|
||||
# <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> localhost:3000
|
||||
|
||||
@ -30,4 +30,6 @@ urlpatterns = [
|
||||
path("inventory/", include("inventory.urls")),
|
||||
path("forms/", include("forms.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.
Binary file not shown.
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.5 on 2025-03-06 04:31
|
||||
# Generated by Django 5.1.5 on 2025-03-21 23:48
|
||||
|
||||
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.
@ -1,6 +1,7 @@
|
||||
# Generated by Django 5.1.5 on 2025-03-06 04:31
|
||||
# Generated by Django 5.1.5 on 2025-03-21 23:48
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
@ -9,6 +10,7 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@ -44,4 +46,24 @@ class Migration(migrations.Migration):
|
||||
('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')),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -1,5 +1,7 @@
|
||||
from django.db import models
|
||||
from datetime import timedelta, date
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
class Nomenclature(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
@ -57,3 +59,23 @@ class StickerMovement(models.Model):
|
||||
self.sticker.location = "склад"
|
||||
self.sticker.save()
|
||||
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()
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Nomenclature, Sticker, StickerMovement
|
||||
from datetime import date
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
class NomenclatureSerializer(serializers.ModelSerializer):
|
||||
@ -32,3 +33,35 @@ class StickerMovementSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = StickerMovement
|
||||
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
|
||||
21
inventory/serializers_transfer.py
Normal file
21
inventory/serializers_transfer.py
Normal file
@ -0,0 +1,21 @@
|
||||
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
|
||||
@ -1,13 +1,35 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import NomenclatureViewSet, StickerViewSet, StickerMovementViewSet, ExternalNomenclatureView
|
||||
from .views import (
|
||||
NomenclatureViewSet,
|
||||
StickerViewSet,
|
||||
StickerMovementViewSet,
|
||||
ExternalNomenclatureView,
|
||||
)
|
||||
from .views_transfer import StickerTransferRequestViewSet # Импортируем новый ViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'nomenclature', NomenclatureViewSet)
|
||||
router.register(r'stickers', StickerViewSet)
|
||||
router.register(r'movement', StickerMovementViewSet)
|
||||
router.register(r'transfers', StickerTransferRequestViewSet, basename='stickertransfer') # Добавляем
|
||||
|
||||
urlpatterns = [
|
||||
path('api/', include(router.urls)),
|
||||
path('api/external-nomenclature/', ExternalNomenclatureView.as_view(), name="external-nomenclature"),
|
||||
]
|
||||
"""Трансфер передачи стикеров"""
|
||||
"""{
|
||||
"to_user": 3, // <-- ID пользователя, а не имя!
|
||||
"items": [
|
||||
{
|
||||
"sticker": 12, // ID стикера
|
||||
"quantity": 10
|
||||
},
|
||||
{
|
||||
"sticker": 15,
|
||||
"quantity": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
@ -117,6 +117,11 @@ class StickerViewSet(viewsets.ModelViewSet):
|
||||
self.perform_destroy(instance)
|
||||
return Response({"message": "Стикер успешно удалён"}, status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
|
||||
"""Передача стикеров"""
|
||||
|
||||
class StickerMovementViewSet(viewsets.ModelViewSet):
|
||||
queryset = StickerMovement.objects.all()
|
||||
serializer_class = StickerMovementSerializer
|
||||
|
||||
|
||||
58
inventory/views_transfer.py
Normal file
58
inventory/views_transfer.py
Normal file
@ -0,0 +1,58 @@
|
||||
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": "Отклонена"})
|
||||
0
issues/__init__.py
Normal file
0
issues/__init__.py
Normal file
16
issues/admin.py
Normal file
16
issues/admin.py
Normal file
@ -0,0 +1,16 @@
|
||||
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",)
|
||||
6
issues/apps.py
Normal file
6
issues/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class IssuesConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'issues'
|
||||
59
issues/migrations/0001_initial.py
Normal file
59
issues/migrations/0001_initial.py
Normal file
@ -0,0 +1,59 @@
|
||||
# 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')),
|
||||
],
|
||||
),
|
||||
]
|
||||
0
issues/migrations/__init__.py
Normal file
0
issues/migrations/__init__.py
Normal file
93
issues/models.py
Normal file
93
issues/models.py
Normal file
@ -0,0 +1,93 @@
|
||||
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
|
||||
33
issues/serializers.py
Normal file
33
issues/serializers.py
Normal file
@ -0,0 +1,33 @@
|
||||
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__'
|
||||
3
issues/tests.py
Normal file
3
issues/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
16
issues/urls.py
Normal file
16
issues/urls.py
Normal file
@ -0,0 +1,16 @@
|
||||
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)),
|
||||
]
|
||||
29
issues/views.py
Normal file
29
issues/views.py
Normal file
@ -0,0 +1,29 @@
|
||||
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
|
||||
321
mobileApps/fletFormTest.py
Normal file
321
mobileApps/fletFormTest.py
Normal file
@ -0,0 +1,321 @@
|
||||
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л", "5л"],
|
||||
"Скит": ["Sipa", "Devin", "JR", "19 Литров", "5 Литров"]
|
||||
}
|
||||
|
||||
line_zone_map = {
|
||||
"Sipa": ["Сериализация", "Упаковка", "Сборка палеты", "Закрытие палеты"],
|
||||
"Devin": ["Сериализация", "Упаковка", "Сборка палеты"],
|
||||
"JR": ["Сериализация", "Упаковка", "Сборка палеты", "Закрытие палеты"],
|
||||
"19 Литров": ["Валидация", "Тонкий клиент"],
|
||||
"5 Литров": ["Валидация", "Тонкий клиент"],
|
||||
"ПЭТ": ["Сериализация", "Упаковка", "Сборка палеты", "Закрытие палеты"],
|
||||
"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)
|
||||
0
production/__init__.py
Normal file
0
production/__init__.py
Normal file
3
production/admin.py
Normal file
3
production/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
production/apps.py
Normal file
6
production/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ProductionConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'production'
|
||||
27
production/migrations/0001_initial.py
Normal file
27
production/migrations/0001_initial.py
Normal file
@ -0,0 +1,27 @@
|
||||
# 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='Создано')),
|
||||
],
|
||||
),
|
||||
]
|
||||
0
production/migrations/__init__.py
Normal file
0
production/migrations/__init__.py
Normal file
13
production/models.py
Normal file
13
production/models.py
Normal file
@ -0,0 +1,13 @@
|
||||
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}"
|
||||
7
production/serializers.py
Normal file
7
production/serializers.py
Normal file
@ -0,0 +1,7 @@
|
||||
from rest_framework import serializers
|
||||
from .models import ProductionPlan
|
||||
|
||||
class ProductionPlanSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ProductionPlan
|
||||
fields = '__all__'
|
||||
3
production/tests.py
Normal file
3
production/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
10
production/urls.py
Normal file
10
production/urls.py
Normal file
@ -0,0 +1,10 @@
|
||||
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)),
|
||||
]
|
||||
7
production/views.py
Normal file
7
production/views.py
Normal file
@ -0,0 +1,7 @@
|
||||
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.
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.5 on 2025-03-06 04:31
|
||||
# Generated by Django 5.1.5 on 2025-03-21 23:48
|
||||
|
||||
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.
Binary file not shown.
@ -5,8 +5,12 @@ from rest_framework_simplejwt.tokens import RefreshToken
|
||||
|
||||
class UserLoginSerializer(serializers.Serializer):
|
||||
username = serializers.CharField()
|
||||
profile_name = serializers.SerializerMethodField() # Добавляем поле для профиля
|
||||
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):
|
||||
from django.contrib.auth import authenticate
|
||||
|
||||
@ -18,6 +22,7 @@ class UserLoginSerializer(serializers.Serializer):
|
||||
|
||||
return {
|
||||
"username": user.username,
|
||||
"profile": self.get_profile_name(user), # Возвращаем профильное имя
|
||||
"access": str(refresh.access_token),
|
||||
"refresh": str(refresh)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user