@@ -30,6 +30,7 @@ APPS = [
|
||||
'core.apps.orders',
|
||||
'core.apps.finance',
|
||||
'core.apps.counterparty',
|
||||
'core.apps.notifications',
|
||||
]
|
||||
|
||||
PACKAGES = [
|
||||
|
||||
@@ -35,6 +35,7 @@ urlpatterns = [
|
||||
path('orders/', include('core.apps.orders.urls')),
|
||||
path('finance/', include('core.apps.finance.urls')),
|
||||
path('counterparties/', include('core.apps.counterparty.urls')),
|
||||
path('notifications/', include('core.apps.notifications.urls')),
|
||||
]
|
||||
)),
|
||||
|
||||
|
||||
0
core/apps/notifications/__init__.py
Normal file
0
core/apps/notifications/__init__.py
Normal file
6
core/apps/notifications/apps.py
Normal file
6
core/apps/notifications/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class NotificationsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'core.apps.notifications'
|
||||
31
core/apps/notifications/migrations/0001_initial.py
Normal file
31
core/apps/notifications/migrations/0001_initial.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# Generated by Django 5.2.4 on 2025-10-28 15:45
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Notification',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('token', models.CharField(max_length=255, unique=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
0
core/apps/notifications/migrations/__init__.py
Normal file
0
core/apps/notifications/migrations/__init__.py
Normal file
1
core/apps/notifications/models/__init__.py
Normal file
1
core/apps/notifications/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .notification import *
|
||||
9
core/apps/notifications/models/notification.py
Normal file
9
core/apps/notifications/models/notification.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.db import models
|
||||
|
||||
from core.apps.shared.models import BaseModel
|
||||
from core.apps.accounts.models import User
|
||||
|
||||
class Notification(BaseModel):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='notifications')
|
||||
token = models.CharField(max_length=255, unique=True)
|
||||
|
||||
11
core/apps/notifications/serializers/notification.py
Normal file
11
core/apps/notifications/serializers/notification.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from core.apps.notifications.models import Notification
|
||||
|
||||
|
||||
class NotificationSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Notification
|
||||
fields = [
|
||||
'token'
|
||||
]
|
||||
7
core/apps/notifications/urls.py
Normal file
7
core/apps/notifications/urls.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
from core.apps.notifications.views import notification
|
||||
|
||||
urlpatterns = [
|
||||
path('device/register/', notification.RegisterExpoPushToken.as_view()),
|
||||
]
|
||||
8
core/apps/notifications/utils/notify_user.py
Normal file
8
core/apps/notifications/utils/notify_user.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from core.apps.notifications.models import Notification
|
||||
from core.apps.notifications.utils.send_notification import send_notification
|
||||
|
||||
|
||||
def notify_user(user, title, body):
|
||||
tokens = Notification.objects.filter(user=user)
|
||||
for token in tokens:
|
||||
send_notification(token.token, title, body)
|
||||
16
core/apps/notifications/utils/send_notification.py
Normal file
16
core/apps/notifications/utils/send_notification.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import requests
|
||||
|
||||
def send_notification(token, title, body, data=None):
|
||||
message = {
|
||||
"to": token,
|
||||
"sound": "default",
|
||||
"title": title,
|
||||
"body": body,
|
||||
"data": data or {},
|
||||
}
|
||||
response = requests.post(
|
||||
"https://exp.host/--/api/v2/push/send",
|
||||
json=message,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
return response.json()
|
||||
20
core/apps/notifications/views/notification.py
Normal file
20
core/apps/notifications/views/notification.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from rest_framework import generics, status
|
||||
from rest_framework.response import Response
|
||||
|
||||
from core.apps.notifications.serializers import notification as serializers
|
||||
from core.apps.notifications.models import Notification
|
||||
|
||||
|
||||
class RegisterExpoPushToken(generics.GenericAPIView):
|
||||
serializer_class = serializers.NotificationSerializer
|
||||
queryset = Notification.objects.all()
|
||||
|
||||
def post(self, request):
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
if serializer.is_valid():
|
||||
Notification.objects.update_or_create(
|
||||
user=request.user,
|
||||
token=serializer.validated_data['token']
|
||||
)
|
||||
return Response({"message": "Token saqlandi"}, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
@@ -16,6 +16,8 @@ from core.apps.projects.models import Project, ProjectFolder
|
||||
from core.apps.projects.serializers.project import ProjectListSerializer, ProjectFolderListSerializer
|
||||
# counterparty
|
||||
from core.apps.counterparty.models import Counterparty
|
||||
# notifications
|
||||
from core.apps.notifications.utils.notify_user import notify_user
|
||||
|
||||
|
||||
class OrderCreateSerializer(serializers.Serializer):
|
||||
@@ -71,7 +73,7 @@ class MultipleOrderCreateSerializer(serializers.Serializer):
|
||||
orders = []
|
||||
|
||||
for resource in resources:
|
||||
orders.append(Order(
|
||||
order = Order(
|
||||
product=resource['product'],
|
||||
unity=resource['unity'],
|
||||
wherehouse=resource['wherehouse'],
|
||||
@@ -80,8 +82,16 @@ class MultipleOrderCreateSerializer(serializers.Serializer):
|
||||
quantity=resource['quantity'],
|
||||
date=common_date,
|
||||
employee=self.context.get('user'),
|
||||
))
|
||||
)
|
||||
orders.append(order)
|
||||
body = f"""
|
||||
{user.full_name} {order.project_folder.name} uchun {order.wherehouse.name} ombor ga {order.quantity} {order.unity.value} {order.product.name} ga buyurtma berdi. Buyurtma
|
||||
yetkazish sanasi {common_date}
|
||||
"""
|
||||
notify_user(user=self.context.get("user"), title="Ta'minot",body=body)
|
||||
|
||||
created_orders = Order.objects.bulk_create(orders)
|
||||
user = self.context.get('user')
|
||||
return created_orders
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
from django.db import transaction
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from core.apps.wherehouse.models import StockMovemend, StockMovmendProduct, Inventory, WhereHouse
|
||||
from core.apps.products.models import Unity, Product
|
||||
from core.apps.projects.models import ProjectFolder, Project
|
||||
from core.apps.products.models import Product, Unity
|
||||
from core.apps.projects.models import Project, ProjectFolder
|
||||
from core.apps.wherehouse.models import (
|
||||
Inventory,
|
||||
StockMovemend,
|
||||
StockMovmendProduct,
|
||||
WhereHouse,
|
||||
)
|
||||
|
||||
|
||||
class StockMovmendProductSerializer(serializers.Serializer):
|
||||
@@ -12,10 +16,10 @@ class StockMovmendProductSerializer(serializers.Serializer):
|
||||
quantity = serializers.IntegerField()
|
||||
|
||||
def validate(self, data):
|
||||
inventory = Inventory.objects.filter(id=data['inventory_id']).first()
|
||||
inventory = Inventory.objects.filter(id=data["inventory_id"]).first()
|
||||
if not inventory:
|
||||
raise serializers.ValidationError("Inventory not found")
|
||||
data['inventory'] = inventory
|
||||
data["inventory"] = inventory
|
||||
return data
|
||||
|
||||
|
||||
@@ -29,117 +33,150 @@ class StockMovmendCreateSerializer(serializers.Serializer):
|
||||
comment = serializers.CharField(required=False)
|
||||
|
||||
def validate(self, data):
|
||||
if data.get('project_folder_id'):
|
||||
project_folder = ProjectFolder.objects.filter(id=data['project_folder_id']).first()
|
||||
if data.get("project_folder_id"):
|
||||
project_folder = ProjectFolder.objects.filter(
|
||||
id=data["project_folder_id"]
|
||||
).first()
|
||||
if not project_folder:
|
||||
raise serializers.ValidationError("Project Folder not found")
|
||||
data['project_folder'] = project_folder
|
||||
if data.get('project_id'):
|
||||
project = Project.objects.filter(id=data['project_id']).first()
|
||||
data["project_folder"] = project_folder
|
||||
if data.get("project_id"):
|
||||
project = Project.objects.filter(id=data["project_id"]).first()
|
||||
if not project:
|
||||
raise serializers.ValidationError("Project not found")
|
||||
data['project'] = project
|
||||
wherehouse_to = WhereHouse.objects.filter(id=data['wherehouse_to_id']).first()
|
||||
data["project"] = project
|
||||
wherehouse_to = WhereHouse.objects.filter(id=data["wherehouse_to_id"]).first()
|
||||
if not wherehouse_to:
|
||||
raise serializers.ValidationError("WhereHouse to not found")
|
||||
wherehouse_from = WhereHouse.objects.filter(id=data['wherehouse_from_id']).first()
|
||||
wherehouse_from = WhereHouse.objects.filter(
|
||||
id=data["wherehouse_from_id"]
|
||||
).first()
|
||||
if not wherehouse_from:
|
||||
raise serializers.ValidationError("WhereHouse from not found")
|
||||
data['wherehouse_to'] = wherehouse_to
|
||||
data['wherehouse_from'] = wherehouse_from
|
||||
data["wherehouse_to"] = wherehouse_to
|
||||
data["wherehouse_from"] = wherehouse_from
|
||||
return data
|
||||
|
||||
def create(self, validated_data):
|
||||
with transaction.atomic():
|
||||
products = validated_data.pop('products')
|
||||
products = validated_data.pop("products")
|
||||
stock_movemend = StockMovemend.objects.create(
|
||||
project_folder=validated_data.get('project_folder'),
|
||||
project=validated_data.get('project'),
|
||||
date=validated_data.get('date'),
|
||||
comment=validated_data.get('comment'),
|
||||
wherehouse_to=validated_data.get('wherehouse_to'),
|
||||
wherehouse_from=validated_data.get('wherehouse_from'),
|
||||
recipient=self.context.get('user'),
|
||||
project_folder=validated_data.get("project_folder"),
|
||||
project=validated_data.get("project"),
|
||||
date=validated_data.get("date"),
|
||||
comment=validated_data.get("comment"),
|
||||
wherehouse_to=validated_data.get("wherehouse_to"),
|
||||
wherehouse_from=validated_data.get("wherehouse_from"),
|
||||
recipient=self.context.get("user"),
|
||||
)
|
||||
movmend_products = []
|
||||
for product in products:
|
||||
movmend_products.append(StockMovmendProduct(
|
||||
inventory=product.get('inventory'),
|
||||
quantity=product.get('quantity'),
|
||||
stock_movemend=stock_movemend,
|
||||
))
|
||||
movmend_products.append(
|
||||
StockMovmendProduct(
|
||||
inventory=product.get("inventory"),
|
||||
quantity=product.get("quantity"),
|
||||
stock_movemend=stock_movemend,
|
||||
)
|
||||
)
|
||||
StockMovmendProduct.objects.bulk_create(movmend_products)
|
||||
return stock_movemend
|
||||
|
||||
|
||||
class StockMovemendProductListSerializer(serializers.ModelSerializer):
|
||||
product = serializers.SerializerMethodField(method_name='get_product')
|
||||
unity = serializers.SerializerMethodField(method_name='get_unity')
|
||||
product = serializers.SerializerMethodField(method_name="get_product")
|
||||
unity = serializers.SerializerMethodField(method_name="get_unity")
|
||||
|
||||
class Meta:
|
||||
model = StockMovmendProduct
|
||||
fields = [
|
||||
'id', 'product', 'unity', 'quantity'
|
||||
]
|
||||
fields = ["id", "product", "unity", "quantity"]
|
||||
|
||||
def get_product(self, obj):
|
||||
return {
|
||||
'id': obj.inventory.product.id,
|
||||
'type': obj.inventory.product.type,
|
||||
'name': obj.inventory.product.name,
|
||||
}
|
||||
return (
|
||||
{
|
||||
"id": obj.inventory.product.id,
|
||||
"type": obj.inventory.product.type,
|
||||
"name": obj.inventory.product.name,
|
||||
}
|
||||
if obj.inventory.product
|
||||
else None
|
||||
)
|
||||
|
||||
def get_unity(self, obj):
|
||||
return {
|
||||
'id': obj.inventory.unity.id,
|
||||
'value': obj.inventory.unity.value,
|
||||
}
|
||||
return (
|
||||
{
|
||||
"id": obj.inventory.unity.id,
|
||||
"value": obj.inventory.unity.value,
|
||||
}
|
||||
if obj.inventory.unity
|
||||
else None
|
||||
)
|
||||
|
||||
|
||||
class StockMovemendListSerializer(serializers.ModelSerializer):
|
||||
movemend_products = StockMovemendProductListSerializer(many=True)
|
||||
wherehouse_to = serializers.SerializerMethodField(method_name='get_wherehouse_to')
|
||||
wherehouse_from = serializers.SerializerMethodField(method_name='get_wherehouse_from')
|
||||
recipient = serializers.SerializerMethodField(method_name='get_recipient')
|
||||
project_folder = serializers.SerializerMethodField(method_name='get_project_folder')
|
||||
project = serializers.SerializerMethodField(method_name='get_project')
|
||||
wherehouse_to = serializers.SerializerMethodField(method_name="get_wherehouse_to")
|
||||
wherehouse_from = serializers.SerializerMethodField(
|
||||
method_name="get_wherehouse_from"
|
||||
)
|
||||
recipient = serializers.SerializerMethodField(method_name="get_recipient")
|
||||
project_folder = serializers.SerializerMethodField(method_name="get_project_folder")
|
||||
project = serializers.SerializerMethodField(method_name="get_project")
|
||||
|
||||
class Meta:
|
||||
model = StockMovemend
|
||||
fields = [
|
||||
'id', 'number', 'wherehouse_to', 'wherehouse_from', 'recipient', 'project_folder',
|
||||
'project', 'movemend_type', 'date', 'comment', 'movemend_products'
|
||||
"id",
|
||||
"number",
|
||||
"wherehouse_to",
|
||||
"wherehouse_from",
|
||||
"recipient",
|
||||
"project_folder",
|
||||
"project",
|
||||
"movemend_type",
|
||||
"date",
|
||||
"comment",
|
||||
"movemend_products",
|
||||
]
|
||||
|
||||
def get_wherehouse_to(self, obj):
|
||||
return {
|
||||
'id': obj.wherehouse_to.id,
|
||||
'name': obj.wherehouse_to.name,
|
||||
}
|
||||
return (
|
||||
{
|
||||
"id": obj.wherehouse_to.id,
|
||||
"name": obj.wherehouse_to.name,
|
||||
}
|
||||
if obj.wherehouse_to
|
||||
else None
|
||||
)
|
||||
|
||||
def get_wherehouse_from(self, obj):
|
||||
return {
|
||||
'id': obj.wherehouse_from.id,
|
||||
'name': obj.wherehouse_from.name,
|
||||
}
|
||||
return (
|
||||
{
|
||||
"id": obj.wherehouse_from.id,
|
||||
"name": obj.wherehouse_from.name,
|
||||
}
|
||||
if obj.wherehouse_from
|
||||
else None
|
||||
)
|
||||
|
||||
def get_recipient(self, obj):
|
||||
return {
|
||||
'id': obj.recipient.id,
|
||||
'full_name': obj.recipient.full_name,
|
||||
} if obj.recipient else None
|
||||
return (
|
||||
{
|
||||
"id": obj.recipient.id,
|
||||
"full_name": obj.recipient.full_name,
|
||||
}
|
||||
if obj.recipient
|
||||
else None
|
||||
)
|
||||
|
||||
def get_project_folder(self, obj):
|
||||
return {
|
||||
'id': obj.project_folder.id,
|
||||
'name': obj.project_folder.name
|
||||
}
|
||||
return (
|
||||
{"id": obj.project_folder.id, "name": obj.project_folder.name}
|
||||
if obj.project_folder
|
||||
else None
|
||||
)
|
||||
|
||||
def get_project(self, obj):
|
||||
return {
|
||||
'id': obj.project.id,
|
||||
'name': obj.project.name
|
||||
} if obj.project else None
|
||||
return {"id": obj.project.id, "name": obj.project.name} if obj.project else None
|
||||
|
||||
|
||||
class StockMovemendProductUpdateSerializer(serializers.Serializer):
|
||||
@@ -147,12 +184,16 @@ class StockMovemendProductUpdateSerializer(serializers.Serializer):
|
||||
quantity = serializers.IntegerField()
|
||||
|
||||
def validate(self, data):
|
||||
movemend_product = StockMovmendProduct.objects.filter(id=data['movemend_product_id']).first()
|
||||
movemend_product = StockMovmendProduct.objects.filter(
|
||||
id=data["movemend_product_id"]
|
||||
).first()
|
||||
if not movemend_product:
|
||||
raise serializers.ValidationError("Stock Movemend Product not found")
|
||||
if movemend_product.inventory.quantity < data['quantity']:
|
||||
raise serializers.ValidationError("invalid quantity, quantity must les than product quantity")
|
||||
data['movemend_product'] = movemend_product
|
||||
if movemend_product.inventory.quantity < data["quantity"]:
|
||||
raise serializers.ValidationError(
|
||||
"invalid quantity, quantity must les than product quantity"
|
||||
)
|
||||
data["movemend_product"] = movemend_product
|
||||
return data
|
||||
|
||||
|
||||
@@ -162,31 +203,38 @@ class StockMovemendUpdateSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = StockMovemend
|
||||
fields = [
|
||||
'wherehouse_to', 'project_folder', 'project', 'date',
|
||||
'comment', 'movemend_products'
|
||||
"wherehouse_to",
|
||||
"project_folder",
|
||||
"project",
|
||||
"date",
|
||||
"comment",
|
||||
"movemend_products",
|
||||
]
|
||||
extra_kwargs = {'wherehouse_to': {'required': False}}
|
||||
extra_kwargs = {"wherehouse_to": {"required": False}}
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
with transaction.atomic():
|
||||
instance.wherehouse_to = validated_data.get('wherehouse_to', instance.wherehouse_to)
|
||||
instance.project_folder = validated_data.get('project_folder', instance.project_folder)
|
||||
instance.project = validated_data.get('project', instance.project)
|
||||
instance.date = validated_data.get('date', instance.date)
|
||||
instance.comment = validated_data.get('comment', instance.comment)
|
||||
instance.wherehouse_to = validated_data.get(
|
||||
"wherehouse_to", instance.wherehouse_to
|
||||
)
|
||||
instance.project_folder = validated_data.get(
|
||||
"project_folder", instance.project_folder
|
||||
)
|
||||
instance.project = validated_data.get("project", instance.project)
|
||||
instance.date = validated_data.get("date", instance.date)
|
||||
instance.comment = validated_data.get("comment", instance.comment)
|
||||
instance.save()
|
||||
|
||||
movemend_products = validated_data.pop('movemend_products', [])
|
||||
movemend_products = validated_data.pop("movemend_products", [])
|
||||
if movemend_products:
|
||||
updated_products = []
|
||||
for product_data in movemend_products:
|
||||
product = product_data['movemend_product']
|
||||
product.quantity = product_data['quantity']
|
||||
product = product_data["movemend_product"]
|
||||
product.quantity = product_data["quantity"]
|
||||
updated_products.append(product)
|
||||
|
||||
StockMovmendProduct.objects.bulk_update(
|
||||
updated_products,
|
||||
fields=['quantity']
|
||||
updated_products, fields=["quantity"]
|
||||
)
|
||||
|
||||
return instance
|
||||
Reference in New Issue
Block a user