@@ -72,7 +72,8 @@ class ExpenceCreateSerializer(serializers.ModelSerializer):
|
|||||||
expence.counterparty.save()
|
expence.counterparty.save()
|
||||||
body = f"""{user.full_name} {expence.price} {expence.currency.upper()}... \n screen: /monitoring"""
|
body = f"""{user.full_name} {expence.price} {expence.currency.upper()}... \n screen: /monitoring"""
|
||||||
data = {
|
data = {
|
||||||
"screen": "/monitoring"
|
"screen": "/monitoring",
|
||||||
|
"type": "expence",
|
||||||
}
|
}
|
||||||
notify_user(user=user, title="Tasdiqlang yoki rad eting", body=body, data=data)
|
notify_user(user=user, title="Tasdiqlang yoki rad eting", body=body, data=data)
|
||||||
cash_transaction.save()
|
cash_transaction.save()
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
from .notification import *
|
from .notification import *
|
||||||
|
from .notification_history import *
|
||||||
6
core/apps/notifications/admin/notification_history.py
Normal file
6
core/apps/notifications/admin/notification_history.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from core.apps.notifications.models.notification_history import NotificationHistory
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(NotificationHistory)
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-10-30 16:16
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('notifications', '0002_notification_type_alter_notification_unique_together'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='NotificationHistory',
|
||||||
|
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)),
|
||||||
|
('title', models.CharField(max_length=200)),
|
||||||
|
('body', models.TextField()),
|
||||||
|
('is_read', models.BooleanField(default=False)),
|
||||||
|
('data', models.JSONField(blank=True, null=True)),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notification_histories', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1 +1,2 @@
|
|||||||
from .notification import *
|
from .notification import *
|
||||||
|
from .notification_history import *
|
||||||
17
core/apps/notifications/models/notification_history.py
Normal file
17
core/apps/notifications/models/notification_history.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from core.apps.shared.models import BaseModel
|
||||||
|
from core.apps.accounts.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationHistory(BaseModel):
|
||||||
|
title = models.CharField(max_length=200)
|
||||||
|
body = models.TextField()
|
||||||
|
user = models.ForeignKey(
|
||||||
|
User, on_delete=models.CASCADE, related_name='notification_histories'
|
||||||
|
)
|
||||||
|
is_read = models.BooleanField(default=False)
|
||||||
|
data = models.JSONField(null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'Notification to {self.user}: {self.title}'
|
||||||
11
core/apps/notifications/serializers/notification_history.py
Normal file
11
core/apps/notifications/serializers/notification_history.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from core.apps.notifications.models import NotificationHistory
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationHistorySerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = NotificationHistory
|
||||||
|
fields = [
|
||||||
|
'id', 'title', 'body', 'data', 'is_read', 'created_at'
|
||||||
|
]
|
||||||
22
core/apps/notifications/tasks/create_notification_history.py
Normal file
22
core/apps/notifications/tasks/create_notification_history.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from celery import shared_task
|
||||||
|
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
|
from core.apps.notifications.models import NotificationHistory
|
||||||
|
from core.apps.accounts.models import User
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def create_history(user_ids, title, body, data=None):
|
||||||
|
histories = []
|
||||||
|
for user_id in user_ids:
|
||||||
|
user = get_object_or_404(User, id=user_id)
|
||||||
|
histories.append(NotificationHistory(
|
||||||
|
title=title,
|
||||||
|
user=user,
|
||||||
|
body=body,
|
||||||
|
data=data,
|
||||||
|
is_read=False
|
||||||
|
))
|
||||||
|
|
||||||
|
NotificationHistory.objects.bulk_create(histories)
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from core.apps.notifications.views import notification
|
from core.apps.notifications.views import notification
|
||||||
|
from core.apps.notifications.views import notification_history
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('device/register/', notification.RegisterExpoPushToken.as_view()),
|
path('device/register/', notification.RegisterExpoPushToken.as_view()),
|
||||||
|
path('history/', notification_history.NotificationHistoryListApiView.as_view()),
|
||||||
|
path('history/<uuid:id>/read/', notification_history.NotificationHistoryUpdateApiView.as_view()),
|
||||||
]
|
]
|
||||||
@@ -1,18 +1,20 @@
|
|||||||
import requests
|
import requests
|
||||||
from firebase_admin import messaging
|
from firebase_admin import messaging
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from core.apps.notifications.models import Notification
|
from core.apps.notifications.models import Notification
|
||||||
|
from core.apps.notifications.tasks.create_notification_history import create_history
|
||||||
|
|
||||||
|
|
||||||
EXPO_API_URL = "https://exp.host/--/api/v2/push/send"
|
EXPO_API_URL = "https://exp.host/--/api/v2/push/send"
|
||||||
|
|
||||||
|
|
||||||
def send_notification(token, title, body, data=None):
|
def send_notification(token, title, body, data=None):
|
||||||
tokens = list(Notification.objects.exclude(token=token).values_list("token", flat=True))
|
tokens = list(Notification.objects.exclude(token=token).values_list("token", flat=True))
|
||||||
|
users = list(Notification.objects.exclude(token=token).values_list("user", flat=True))
|
||||||
|
create_history.delay(users, title, body, data)
|
||||||
if not tokens:
|
if not tokens:
|
||||||
return {"error": "No tokens found"}
|
return {"error": "No tokens found"}
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
"to": token,
|
"to": token,
|
||||||
@@ -38,6 +40,8 @@ def send_notification(token, title, body, data=None):
|
|||||||
|
|
||||||
def send_web_notification(token, title, body, data=None):
|
def send_web_notification(token, title, body, data=None):
|
||||||
tokens = list(Notification.objects.exclude(token=token).values_list('token', flat=True))
|
tokens = list(Notification.objects.exclude(token=token).values_list('token', flat=True))
|
||||||
|
users = list(Notification.objects.exclude(token=token).values_list("user", flat=True))
|
||||||
|
create_history.delay(users, title, body, data)
|
||||||
if not tokens:
|
if not tokens:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
43
core/apps/notifications/views/notification_history.py
Normal file
43
core/apps/notifications/views/notification_history.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
|
from rest_framework import generics, views
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from core.apps.accounts.permissions.permissions import HasRolePermission
|
||||||
|
from core.apps.notifications.models import NotificationHistory
|
||||||
|
from core.apps.notifications.serializers.notification_history import NotificationHistorySerializer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationHistoryListApiView(generics.GenericAPIView):
|
||||||
|
serializer_class = NotificationHistorySerializer
|
||||||
|
permission_classes = [HasRolePermission]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return NotificationHistory.objects.filter(user=self.request.user).order_by('-created_at')
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
is_read = request.query_params.get('is_read')
|
||||||
|
if is_read:
|
||||||
|
if is_read.lower() == 'true':
|
||||||
|
queryset = queryset.filter(is_read=True)
|
||||||
|
if is_read.lower() == 'false':
|
||||||
|
queryset = queryset.filter(is_read=False)
|
||||||
|
page = self.paginate_queryset(queryset)
|
||||||
|
if page is not None:
|
||||||
|
serializer = self.serializer_class(page, many=True)
|
||||||
|
return self.get_paginated_response(serializer.data)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationHistoryUpdateApiView(views.APIView):
|
||||||
|
permission_classes = [HasRolePermission]
|
||||||
|
|
||||||
|
def post(self, request, id):
|
||||||
|
obj = get_object_or_404(NotificationHistory, id=id)
|
||||||
|
if obj.is_read:
|
||||||
|
return Response({'success': False, "message": 'already readed'}, status=400)
|
||||||
|
obj.is_read = True
|
||||||
|
obj.save()
|
||||||
|
return Response({'success': True, "message": 'readed'}, status=200)
|
||||||
@@ -88,7 +88,8 @@ class MultipleOrderCreateSerializer(serializers.Serializer):
|
|||||||
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}
|
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}
|
||||||
"""
|
"""
|
||||||
data = {
|
data = {
|
||||||
"screen": "/supply"
|
"screen": "/supply",
|
||||||
|
"type": "order"
|
||||||
}
|
}
|
||||||
notify_user(user=user, title="Ta'minot",body=body, data=data)
|
notify_user(user=user, title="Ta'minot",body=body, data=data)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-10-30 16:19
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('projects', '0016_estimatework_employee_estimatework_end_date_and_more'),
|
||||||
|
('wherehouse', '0017_wherehouse_users'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='invalidproduct',
|
||||||
|
name='project_folder',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='invalid_products', to='projects.projectfolder'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -26,7 +26,8 @@ class InvalidProduct(BaseModel):
|
|||||||
# relationship
|
# relationship
|
||||||
inventory = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='invalid_products')
|
inventory = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='invalid_products')
|
||||||
project_folder = models.ForeignKey(
|
project_folder = models.ForeignKey(
|
||||||
ProjectFolder, on_delete=models.CASCADE, related_name='invalid_products'
|
ProjectFolder, on_delete=models.CASCADE, related_name='invalid_products',
|
||||||
|
null=True, blank=True
|
||||||
)
|
)
|
||||||
witnesses = models.ManyToManyField(User, related_name='invalid_products')
|
witnesses = models.ManyToManyField(User, related_name='invalid_products')
|
||||||
work = models.ForeignKey(
|
work = models.ForeignKey(
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from core.apps.accounts.serializers.user import UserListSerializer
|
|||||||
|
|
||||||
class InvalidProductCreateSerializer(serializers.Serializer):
|
class InvalidProductCreateSerializer(serializers.Serializer):
|
||||||
inventory_id = serializers.UUIDField()
|
inventory_id = serializers.UUIDField()
|
||||||
project_folder_id = serializers.UUIDField()
|
project_folder_id = serializers.UUIDField(required=False)
|
||||||
witnesses_ids = serializers.ListField(child=serializers.UUIDField())
|
witnesses_ids = serializers.ListField(child=serializers.UUIDField())
|
||||||
work_id = serializers.UUIDField(required=False)
|
work_id = serializers.UUIDField(required=False)
|
||||||
amount = serializers.IntegerField()
|
amount = serializers.IntegerField()
|
||||||
@@ -24,16 +24,17 @@ class InvalidProductCreateSerializer(serializers.Serializer):
|
|||||||
inventory = Inventory.objects.filter(id=attrs['inventory_id']).first()
|
inventory = Inventory.objects.filter(id=attrs['inventory_id']).first()
|
||||||
if not inventory:
|
if not inventory:
|
||||||
raise serializers.ValidationError("Inventory not found")
|
raise serializers.ValidationError("Inventory not found")
|
||||||
project_folder = ProjectFolder.objects.filter(id=attrs['project_folder_id']).first()
|
if attrs.get('project_folder_id'):
|
||||||
if not project_folder:
|
project_folder = ProjectFolder.objects.filter(id=attrs['project_folder_id']).first()
|
||||||
raise serializers.ValidationError("Project Folder not found")
|
if not project_folder:
|
||||||
|
raise serializers.ValidationError("Project Folder not found")
|
||||||
|
attrs['project_folder'] = project_folder
|
||||||
if attrs.get('work_id'):
|
if attrs.get('work_id'):
|
||||||
work = EstimateWork.objects.filter(id=attrs['work_id']).first()
|
work = EstimateWork.objects.filter(id=attrs['work_id']).first()
|
||||||
if not work:
|
if not work:
|
||||||
raise serializers.ValidationError("Work not found")
|
raise serializers.ValidationError("Work not found")
|
||||||
attrs['work'] = work
|
attrs['work'] = work
|
||||||
attrs['inventory'] = inventory
|
attrs['inventory'] = inventory
|
||||||
attrs['project_folder'] = project_folder
|
|
||||||
return super().validate(attrs)
|
return super().validate(attrs)
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
@@ -75,7 +76,7 @@ class InvliadProductListSerializer(serializers.ModelSerializer):
|
|||||||
return {
|
return {
|
||||||
'id': obj.project_folder.id,
|
'id': obj.project_folder.id,
|
||||||
'name': obj.project_folder.name,
|
'name': obj.project_folder.name,
|
||||||
}
|
} if obj.project_folder else None
|
||||||
|
|
||||||
def get_wherehouse(self, obj):
|
def get_wherehouse(self, obj):
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user