fix the notificatin and notification counter
This commit is contained in:
parent
473d7e1990
commit
73ae641ddb
@ -1,21 +1,7 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.contrib.auth import get_user_model
|
||||
from inventory.tasks import create_coa_accounts
|
||||
from inventory.models import Dealer
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
from inventory.tasks import long_running_task
|
||||
from django_q.tasks import async_task
|
||||
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *args, **kwargs):
|
||||
# user = User.objects.last()
|
||||
# print(user.email)
|
||||
# # 2. Force email confirmation
|
||||
# # email = user.emailaddress_set.first()
|
||||
# confirmation = EmailConfirmation.create(user.email)
|
||||
# confirmation.send()
|
||||
|
||||
# result = re.match(r'^05\d{8}$', '0625252522')
|
||||
# print(result)
|
||||
dealer = Dealer.objects.last()
|
||||
create_coa_accounts(dealer.pk)
|
||||
async_task(long_running_task, 20)
|
||||
@ -126,7 +126,9 @@ class DealerSlugMiddleware:
|
||||
request.path_info.startswith('/en/login/') or \
|
||||
request.path_info.startswith('/en/logout/') or \
|
||||
request.path_info.startswith('/en/ledger/') or \
|
||||
request.path_info.startswith('/ar/ledger/'):
|
||||
request.path_info.startswith('/en/ledger/') or \
|
||||
request.path_info.startswith('/en/notifications/') or \
|
||||
request.path_info.startswith('/ar/notifications/'):
|
||||
return None
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
@ -141,6 +143,7 @@ class DealerSlugMiddleware:
|
||||
return None
|
||||
|
||||
if dealer_slug.lower() != request.dealer.slug.lower():
|
||||
print(dealer_slug)
|
||||
logger.warning(f"Dealer slug mismatch: {dealer_slug} != {request.dealer.slug}")
|
||||
raise Http404("Dealer slug mismatch")
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ from datetime import datetime
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import Permission
|
||||
from decimal import Decimal
|
||||
from django.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
from django.utils import timezone
|
||||
from django.core.validators import MinValueValidator
|
||||
@ -600,6 +601,8 @@ class Car(Base):
|
||||
)
|
||||
# history = HistoricalRecords()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("car_detail", kwargs={"dealer_slug": self.dealer.slug,"slug": self.slug})
|
||||
def save(self, *args, **kwargs):
|
||||
self.slug = slugify(self.vin)
|
||||
self.hash = self.get_hash
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from django.urls import reverse
|
||||
from inventory.tasks import create_coa_accounts, create_make_accounts
|
||||
from django.contrib.auth.models import Group
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
@ -18,7 +20,7 @@ from django_ledger.models import (
|
||||
from . import models
|
||||
from django.utils.timezone import now
|
||||
from django.db import transaction
|
||||
|
||||
from django_q.tasks import async_task
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@ -143,7 +145,7 @@ def create_ledger_entity(sender, instance, created, **kwargs):
|
||||
entity.create_uom(name=u[1], unit_abbr=u[0])
|
||||
|
||||
# Create COA accounts, background task
|
||||
create_coa_accounts(instance.pk)
|
||||
async_task(create_coa_accounts,instance.pk)
|
||||
|
||||
# create_settings(instance.pk)
|
||||
# create_accounts_for_make(instance.pk)
|
||||
@ -164,12 +166,12 @@ def create_dealer_groups(sender, instance, created, **kwargs):
|
||||
:param kwargs: Additional keyword arguments passed by the signal.
|
||||
:type kwargs: dict
|
||||
"""
|
||||
group_names = ["Inventory", "Accountant", "Sales"]
|
||||
group_names = ["Inventory", "Accountant", "Sales","Manager"]
|
||||
|
||||
def create_groups():
|
||||
for group_name in group_names:
|
||||
group, created = Group.objects.get_or_create(
|
||||
name=f"{instance.pk}_{group_name}"
|
||||
name=f"{instance.slug}_{group_name}"
|
||||
)
|
||||
group_manager, created = models.CustomGroup.objects.get_or_create(
|
||||
name=group_name, dealer=instance, group=group
|
||||
@ -896,7 +898,26 @@ def update_finance_cost(sender, instance, created, **kwargs):
|
||||
|
||||
@receiver(post_save, sender=PurchaseOrderModel)
|
||||
def create_po_item_upload(sender,instance,created,**kwargs):
|
||||
if instance.po_status == "fulfilled":
|
||||
for item in instance.get_itemtxs_data()[0]:
|
||||
if instance.po_status == "fulfilled":
|
||||
for item in instance.get_itemtxs_data()[0]:
|
||||
dealer = models.Dealer.objects.get(entity=instance.entity)
|
||||
models.PoItemsUploaded.objects.create(dealer=dealer,po=instance, item=item, status="fulfilled")
|
||||
|
||||
|
||||
|
||||
##########################################################
|
||||
######################Notification########################
|
||||
##########################################################
|
||||
|
||||
@receiver(post_save, sender=models.Car)
|
||||
def car_created_notification(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
accountants = models.CustomGroup.objects.filter(dealer=instance.dealer,name="Accountant").first().group.user_set.exclude(email=instance.dealer.user.email)
|
||||
for accountant in accountants:
|
||||
models.Notification.objects.create(
|
||||
user=accountant,
|
||||
message=f"""
|
||||
New Car {instance.vin} has been added to dealer {instance.dealer.name}.
|
||||
<a href="{instance.get_absolute_url()}" target="_blank">View</a>
|
||||
""",
|
||||
)
|
||||
@ -1,13 +1,11 @@
|
||||
from datetime import datetime
|
||||
from django.db import transaction
|
||||
from django_ledger.io import roles
|
||||
from django.core.mail import send_mail
|
||||
from background_task import background
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from inventory.models import DealerSettings, Dealer
|
||||
from django_q.tasks import async_task
|
||||
|
||||
|
||||
# @background
|
||||
def create_settings(pk):
|
||||
instance = Dealer.objects.get(pk=pk)
|
||||
|
||||
@ -34,7 +32,6 @@ def create_settings(pk):
|
||||
)
|
||||
|
||||
|
||||
@background
|
||||
def create_coa_accounts(pk):
|
||||
with transaction.atomic():
|
||||
instance = Dealer.objects.select_for_update().get(pk=pk)
|
||||
@ -772,8 +769,6 @@ def create_coa_accounts(pk):
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
@background
|
||||
def create_coa_accounts1(pk):
|
||||
with transaction.atomic():
|
||||
instance = Dealer.objects.select_for_update().get(pk=pk)
|
||||
@ -1456,25 +1451,16 @@ def create_make_accounts(entity, coa, makes, name, role, balance_type):
|
||||
)
|
||||
return acc
|
||||
|
||||
|
||||
@background
|
||||
def send_email(from_, to_, subject, message):
|
||||
subject = subject
|
||||
message = message
|
||||
from_email = from_
|
||||
recipient_list = [to_]
|
||||
send_mail(subject, message, from_email, recipient_list)
|
||||
async_task(send_mail,subject, message, from_email, recipient_list)
|
||||
|
||||
|
||||
@background
|
||||
def long_running_task(task_id, *args, **kwargs):
|
||||
# @background
|
||||
def long_running_task(duration):
|
||||
"""Example background task"""
|
||||
print(f"Starting task {task_id} with args: {args}, kwargs: {kwargs}")
|
||||
|
||||
# Simulate work
|
||||
for i in range(5):
|
||||
print(f"Task {task_id} progress: {i + 1}/5")
|
||||
|
||||
result = f"Task {task_id} completed at {datetime.now()}"
|
||||
print(result)
|
||||
return result
|
||||
print("Task completed")
|
||||
return True
|
||||
|
||||
@ -644,4 +644,4 @@ def inventory_table(context, queryset):
|
||||
"inventory_list": queryset,
|
||||
}
|
||||
ctx.update(queryset.aggregate(inventory_total_value=Sum("total_value")))
|
||||
return ctx
|
||||
return ctx
|
||||
@ -14,7 +14,6 @@ urlpatterns = [
|
||||
path("<slug:dealer_slug>/", views.HomeView.as_view(), name="home"),
|
||||
|
||||
# Tasks
|
||||
path("<slug:dealer_slug>/tasks/", views.task_list, name="task_list"),
|
||||
path("legal/", views.terms_and_privacy, name="terms_and_privacy"),
|
||||
# path('tasks/<int:task_id>/detail/', views.task_detail, name='task_detail'),
|
||||
# Dashboards
|
||||
@ -203,38 +202,29 @@ urlpatterns = [
|
||||
),
|
||||
# path('crm/opportunities/<int:pk>/logs/', views.OpportunityLogsView.as_view(), name='opportunity_logs'),
|
||||
# #######################
|
||||
path("stream/", views.sse_stream, name="sse_stream"),
|
||||
path("fetch/", views.fetch_notifications, name="fetch_notifications"),
|
||||
# Mark single notification as read
|
||||
path(
|
||||
"<int:notification_id>/mark-read/",
|
||||
views.mark_notification_as_read,
|
||||
name="mark_notification_as_read",
|
||||
),
|
||||
# Mark all notifications as read
|
||||
path(
|
||||
"mark-all-read/",
|
||||
views.mark_all_notifications_as_read,
|
||||
name="mark_all_notifications_as_read",
|
||||
),
|
||||
# Notification history
|
||||
path("history/", views.notifications_history, name="notifications_history"),
|
||||
# #######################
|
||||
# Notifications
|
||||
path("notifications/stream/", views.sse_stream, name="sse_stream"),
|
||||
path("notifications/fetch/", views.fetch_notifications, name="fetch_notifications"),
|
||||
|
||||
path(
|
||||
"crm/notifications/",
|
||||
"/notifications/",
|
||||
views.NotificationListView.as_view(),
|
||||
name="notifications_history",
|
||||
),
|
||||
|
||||
path(
|
||||
"crm/fetch_notifications/",
|
||||
views.fetch_notifications,
|
||||
name="fetch_notifications",
|
||||
),
|
||||
path(
|
||||
"crm/notifications/<int:notification_id>/mark_as_read/",
|
||||
"notifications/<int:notification_id>/mark_as_read/",
|
||||
views.mark_notification_as_read,
|
||||
name="mark_notification_as_read",
|
||||
),
|
||||
path(
|
||||
"notifications/mark_all_notifications_as_read/",
|
||||
views.mark_all_notifications_as_read,
|
||||
name="mark_all_notifications_as_read",
|
||||
),
|
||||
# #######################
|
||||
# #######################
|
||||
path("crm/calender/", views.EmployeeCalendarView.as_view(), name="calendar_list"),
|
||||
#######################################################
|
||||
# Vendor URLs
|
||||
|
||||
@ -26,7 +26,7 @@ from django_ledger.models import (
|
||||
from django.utils.translation import get_language
|
||||
from appointment.models import StaffMember
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from django_q.tasks import async_task
|
||||
import secrets
|
||||
|
||||
|
||||
@ -152,7 +152,7 @@ def send_email(from_, to_, subject, message):
|
||||
message = message
|
||||
from_email = from_
|
||||
recipient_list = [to_]
|
||||
send_mail(subject, message, from_email, recipient_list)
|
||||
async_task(send_mail,subject, message, from_email, recipient_list)
|
||||
|
||||
|
||||
def get_user_type(request):
|
||||
|
||||
@ -23,7 +23,6 @@ from urllib.parse import urlparse, urlunparse
|
||||
from inventory.mixins import DealerSlugMixin
|
||||
from inventory.models import Status as LeadStatus
|
||||
from django.db import IntegrityError
|
||||
from background_task.models import Task
|
||||
from django.views.generic import FormView
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.db.models.deletion import RestrictedError
|
||||
@ -2670,9 +2669,17 @@ class GroupCreateView(
|
||||
def form_valid(self, form):
|
||||
dealer = get_user_type(self.request)
|
||||
instance = form.save(commit=False)
|
||||
group = Group.objects.create(name=f"{dealer.slug}_{instance.name}")
|
||||
instance.dealer = dealer
|
||||
instance.group = group
|
||||
group_name = f"{dealer.slug}_{instance.name}"
|
||||
group,created = Group.objects.get_or_create(name=group_name)
|
||||
if created:
|
||||
group_manager, created = models.CustomGroup.objects.get_or_create(
|
||||
name=group_name, dealer=dealer, group=group
|
||||
)
|
||||
group_manager.set_default_permissions()
|
||||
dealer.user.groups.add(group)
|
||||
else:
|
||||
instance.dealer = dealer
|
||||
instance.group = group
|
||||
instance.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
@ -2951,10 +2958,10 @@ class UserCreateView(
|
||||
staff.staff_member = staff_member
|
||||
staff.dealer = dealer
|
||||
staff.add_as_manager()
|
||||
group = Group.objects.filter(customgroup__name__iexact=staff.staff_type).first()
|
||||
group = models.CustomGroup.objects.filter(dealer=dealer,name__iexact=staff.staff_type).first()
|
||||
staff.save()
|
||||
if group:
|
||||
staff.add_group(group)
|
||||
staff.add_group(group.group)
|
||||
return super().form_valid(form)
|
||||
def get_success_url(self):
|
||||
return reverse_lazy("user_list", args=[self.request.dealer.slug])
|
||||
@ -8777,20 +8784,8 @@ def payment_callback(request,dealer_slug):
|
||||
return render(request, "payment_failed.html", {"message": message})
|
||||
|
||||
|
||||
# Background Tasks
|
||||
def task_list(request):
|
||||
# Get all tasks ordered by creation time
|
||||
tasks = Task.objects.all()
|
||||
|
||||
# Add pagination
|
||||
paginator = Paginator(tasks, 10) # Show 10 tasks per page
|
||||
page_number = request.GET.get("page")
|
||||
page_obj = paginator.get_page(page_number)
|
||||
|
||||
return render(request, "tasks/task_list.html", {"page_obj": page_obj})
|
||||
|
||||
|
||||
def sse_stream(request):
|
||||
print("hi")
|
||||
def event_stream():
|
||||
last_id = request.GET.get("last_id", 0)
|
||||
while True:
|
||||
@ -8833,7 +8828,7 @@ def mark_notification_as_read(request, notification_id):
|
||||
notification = get_object_or_404(
|
||||
models.Notification, id=notification_id, user=request.user
|
||||
)
|
||||
notification.read = True
|
||||
notification.is_read = True
|
||||
notification.save()
|
||||
return JsonResponse({"status": "success"})
|
||||
|
||||
@ -8841,9 +8836,9 @@ def mark_notification_as_read(request, notification_id):
|
||||
@login_required
|
||||
def mark_all_notifications_as_read(request):
|
||||
models.Notification.objects.filter(user=request.user, is_read=False).update(
|
||||
read=True
|
||||
is_read=True
|
||||
)
|
||||
return JsonResponse({"status": "success"})
|
||||
return redirect(request.META.get("HTTP_REFERER"))
|
||||
|
||||
|
||||
@login_required
|
||||
@ -9669,7 +9664,7 @@ def upload_cars(request, dealer_slug, pk=None):
|
||||
|
||||
if not csv_file.name.endswith(".csv"):
|
||||
messages.error(request, "Please upload a CSV file")
|
||||
return redirect("upload_cars", dealer_slug=dealer_slug)
|
||||
return response
|
||||
try:
|
||||
# Read the file content
|
||||
file_content = csv_file.read().decode("utf-8")
|
||||
@ -9715,10 +9710,16 @@ def upload_cars(request, dealer_slug, pk=None):
|
||||
po_item.save()
|
||||
|
||||
messages.success(request, f"Successfully imported {cars_created} cars")
|
||||
return response
|
||||
return redirect(
|
||||
"view_items_inventory",
|
||||
dealer_slug=dealer_slug,
|
||||
slug_entity=dealer.entity.slug,
|
||||
po_pk=item.po_model.pk,)
|
||||
|
||||
except Exception as e:
|
||||
messages.error(request, f"Error processing CSV: {str(e)}")
|
||||
return response
|
||||
|
||||
form = forms.CSVUploadForm()
|
||||
form.fields["vendor"].queryset = dealer.vendors.all()
|
||||
|
||||
|
||||
@ -4,18 +4,21 @@ arrow==1.3.0
|
||||
asgiref==3.8.1
|
||||
attrs==25.3.0
|
||||
Babel==2.15.0
|
||||
beautifulsoup4==4.13.4
|
||||
blessed==1.21.0
|
||||
cattrs==24.1.3
|
||||
certifi==2025.1.31
|
||||
cffi==1.17.1
|
||||
charset-normalizer==3.4.1
|
||||
click==8.2.1
|
||||
colorama==0.4.6
|
||||
crispy-bootstrap5==2024.10
|
||||
cryptography==44.0.2
|
||||
cssbeautifier==1.15.4
|
||||
defusedxml==0.7.1
|
||||
diff-match-patch==20241021
|
||||
distro==1.9.0
|
||||
Django==5.1.7
|
||||
Django==5.2.3
|
||||
django-allauth==65.6.0
|
||||
django-appointment==3.8.0
|
||||
django-background-tasks==1.2.8
|
||||
@ -24,17 +27,20 @@ django-ckeditor==6.7.2
|
||||
django-cors-headers==4.7.0
|
||||
django-countries==7.6.1
|
||||
django-crispy-forms==2.3
|
||||
django-easy-audit==1.3.7
|
||||
django-extensions==3.2.3
|
||||
django-filter==25.1
|
||||
django-import-export==4.3.7
|
||||
django-js-asset==3.1.2
|
||||
django-ledger==0.7.6.1
|
||||
django-ledger==0.7.7
|
||||
django-manager-utils==3.1.5
|
||||
django-next-url-mixin==0.4.0
|
||||
django-ordered-model==3.7.4
|
||||
django-phonenumber-field==8.0.0
|
||||
django-picklefield==3.3
|
||||
django-plans==2.0.0
|
||||
django-q==1.3.9
|
||||
django-q2==1.8.0
|
||||
django-query-builder==3.2.0
|
||||
django-schema-graph==3.1.0
|
||||
django-sequences==3.0
|
||||
django-tables2==2.7.5
|
||||
@ -42,29 +48,47 @@ django-treebeard==4.7.1
|
||||
django-widget-tweaks==1.5.0
|
||||
djangorestframework==3.15.2
|
||||
djhtml==3.0.7
|
||||
djlint==1.36.4
|
||||
docopt==0.6.2
|
||||
Faker==37.1.0
|
||||
EditorConfig==0.17.0
|
||||
Faker==37.3.0
|
||||
fleming==0.7.0
|
||||
fonttools==4.57.0
|
||||
fpdf==1.7.2
|
||||
fpdf2==2.8.3
|
||||
greenlet==3.2.2
|
||||
h11==0.14.0
|
||||
httpcore==1.0.7
|
||||
httpx==0.28.1
|
||||
icalendar==6.1.2
|
||||
idna==3.10
|
||||
jiter==0.9.0
|
||||
jsbeautifier==1.15.4
|
||||
json5==0.12.0
|
||||
jsonpatch==1.33
|
||||
jsonpointer==3.0.0
|
||||
jwt==1.3.1
|
||||
langchain==0.3.25
|
||||
langchain-core==0.3.61
|
||||
langchain-ollama==0.3.3
|
||||
langchain-text-splitters==0.3.8
|
||||
langsmith==0.3.42
|
||||
luhnchecker==0.0.12
|
||||
Markdown==3.7
|
||||
Markdown==3.8
|
||||
markdown-it-py==3.0.0
|
||||
mdurl==0.1.2
|
||||
num2words==0.5.14
|
||||
numpy==2.2.4
|
||||
ofxtools==0.9.5
|
||||
ollama==0.4.8
|
||||
openai==1.68.2
|
||||
opencv-python==4.11.0.86
|
||||
orjson==3.10.18
|
||||
packaging==24.2
|
||||
pandas==2.2.3
|
||||
pathspec==0.12.1
|
||||
phonenumbers==8.13.42
|
||||
pillow==10.4.0
|
||||
pillow==11.2.1
|
||||
pycparser==2.22
|
||||
pydantic==2.10.6
|
||||
pydantic_core==2.27.2
|
||||
@ -74,24 +98,29 @@ python-slugify==8.0.4
|
||||
python-stdnum==1.20
|
||||
pytz==2025.2
|
||||
pyvin==0.0.2
|
||||
PyYAML==6.0.2
|
||||
pyzbar==0.1.9
|
||||
redis==3.5.3
|
||||
regex==2024.11.6
|
||||
requests==2.32.3
|
||||
requests-toolbelt==1.0.0
|
||||
rich==14.0.0
|
||||
ruff==0.11.10
|
||||
setuptools==80.3.0
|
||||
six==1.17.0
|
||||
sniffio==1.3.1
|
||||
soupsieve==2.7
|
||||
SQLAlchemy==2.0.41
|
||||
sqlparse==0.5.3
|
||||
suds==1.2.0
|
||||
swapper==1.3.0
|
||||
tablib==3.8.0
|
||||
tenacity==9.1.2
|
||||
text-unidecode==1.3
|
||||
tqdm==4.67.1
|
||||
types-python-dateutil==2.9.0.20241206
|
||||
types-python-dateutil==2.9.0.20250516
|
||||
typing_extensions==4.13.0
|
||||
tzdata==2025.2
|
||||
urllib3==2.3.0
|
||||
wcwidth==0.2.13
|
||||
langchain
|
||||
langchain_ollama
|
||||
django-easy-audit==1.3.7
|
||||
zstandard==0.23.0
|
||||
|
||||
@ -243,7 +243,7 @@
|
||||
<span class="fas fa-ellipsis-h fs-10"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-end py-2">
|
||||
<a href="{% url 'activate_account' 'staff' obj.slug %}"><button class="dropdown-item text-primary">{% trans "Activate" %}</button></a>
|
||||
<a href="{% url 'activate_account' request.dealer.slug 'staff' obj.slug %}"><button class="dropdown-item text-primary">{% trans "Activate" %}</button></a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="{% url 'permenant_delete_account' request.dealer.slug 'staff' obj.slug %}"><button class="dropdown-item text-danger">{% trans "Permenantly Delete" %}</button></a>
|
||||
</div>
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
|
||||
<div class="content">
|
||||
<h2 class="mb-5">{{ _("Notifications") }}</h2>
|
||||
|
||||
<div class="d-flex justify-content-end mb-3">
|
||||
<a href="{% url 'mark_all_notifications_as_read' %}" class="btn btn-primary"><i class="far fa-envelope fs-8 me-2"></i>{{ _("Mark all as read") }}</a>
|
||||
</div>
|
||||
{% if notifications %}
|
||||
<div class="mx-n4 mx-lg-n6 mb-5 border-bottom">
|
||||
{% for notification in notifications %}
|
||||
@ -20,10 +22,7 @@
|
||||
<p class="text-body-secondary fs-9 mb-0"><span class="me-1 far fa-clock"></span>{{ notification.created }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<button class="btn fs-10 btn-sm dropdown-toggle dropdown-caret-none transition-none notification-dropdown-toggle" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10 text-body"></span></button>
|
||||
<div class="dropdown-menu dropdown-menu-end py-2"><a class="dropdown-item" href="{% url 'mark_notification_as_read' notification.id %}">{{ _("Mark as Read")}}</a></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
<div class="card-header p-2">
|
||||
<div class="d-flex justify-content-between">
|
||||
<h5 class="text-body-emphasis mb-0">{{ _("Notifications") }}</h5>
|
||||
<button class="btn btn-link p-0 fs-9 fw-normal" type="button" id="mark-all-read">{{ _("Mark all as read")}}</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@ -72,7 +72,7 @@
|
||||
|
||||
data.notifications.forEach(notification => {
|
||||
seenNotificationIds.add(notification.id);
|
||||
if (notification.unread) {
|
||||
if (!notification.is_read) {
|
||||
unreadCount++;
|
||||
}
|
||||
});
|
||||
@ -136,7 +136,7 @@
|
||||
}
|
||||
|
||||
function createNotificationElement(data) {
|
||||
const isRead = data.read ? 'read' : 'unread';
|
||||
const isRead = data.is_read ? 'read' : 'unread';
|
||||
return `
|
||||
<div class="px-2 px-sm-3 py-3 notification-card position-relative ${isRead} border-bottom"
|
||||
data-notification-id="${data.id}">
|
||||
@ -223,7 +223,7 @@
|
||||
if (e.target.classList.contains('mark-as-read')) {
|
||||
e.preventDefault();
|
||||
const notificationId = e.target.getAttribute('data-notification-id');
|
||||
fetch(`/notifications/${notificationId}/mark-read/`, {
|
||||
fetch(`/notifications/${notificationId}/mark_as_read/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRFToken': '{{ csrf_token }}',
|
||||
|
||||
@ -82,7 +82,7 @@
|
||||
<div class="card border-light h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="h6 text-muted mb-2">{% trans 'Purchase Order Amount' %}</h5>
|
||||
<p class="h5">{% currency_symbol %}{{ po_model.po_amount|currency_format }}</p>
|
||||
<p class="h5"><span class="currency">{{CURRENCY}}</span>{{ po_model.po_amount|currency_format }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -101,7 +101,7 @@
|
||||
<div class="card border-light h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="h6 text-muted mb-2">{% trans 'Purchase Order Amount' %}</h5>
|
||||
<p class="h5">{% currency_symbol %}{{ po_model.po_amount|currency_format }}</p>
|
||||
<p class="h5"><span class="currency">{{CURRENCY}}</span>{{ po_model.po_amount|currency_format }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -120,7 +120,7 @@
|
||||
<div class="card border-light h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="h6 text-muted mb-2">{% trans 'PO Amount' %}</h5>
|
||||
<p class="h5">{{ po_model.po_amount|currency_format }}{% currency_symbol %}</p>
|
||||
<p class="h5">{{ po_model.po_amount|currency_format }}<span class="currency">{{CURRENCY}}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -128,7 +128,7 @@
|
||||
<div class="card border-light h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="h6 text-muted mb-2">{% trans 'Received Amount' %}</h5>
|
||||
<p class="h5 text-success">{{ po_model.po_amount_received|currency_format }}{% currency_symbol %}</p>
|
||||
<p class="h5 text-success">{{ po_model.po_amount_received|currency_format }}<span class="currency">{{CURRENCY}}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -148,7 +148,7 @@
|
||||
<div class="card-body">
|
||||
<h5 class="h6 text-muted mb-2">{% trans 'PO Amount' %}</h5>
|
||||
<div class="d-flex align-items-center">
|
||||
<p class="h5 mb-0 me-2">{% currency_symbol %}{{ po_model.po_amount|currency_format }}</p>
|
||||
<p class="h5 mb-0 me-2"><span class="currency">{{CURRENCY}}</span>{{ po_model.po_amount|currency_format }}</p>
|
||||
<span class="badge bg-success">
|
||||
<i class="fas fa-check-circle me-1"></i>{% trans 'Fulfilled' %}
|
||||
</span>
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
<table class="table table-hover table-bordered">
|
||||
<thead class="">
|
||||
<tr>
|
||||
<th class="d-flex justify-content-between align-items-center">
|
||||
<th style="min-width: 600px;" class="d-flex justify-content-between align-items-center">
|
||||
{% trans 'Item' %}
|
||||
{% if po_model.is_draft %}
|
||||
<button type="button"
|
||||
@ -62,7 +62,7 @@
|
||||
<td id="{{ f.instance.html_id_quantity }}">{{ f.po_quantity|add_class:"form-control" }}</td>
|
||||
<td>{{ f.entity_unit|add_class:"form-control" }}</td>
|
||||
<td class="text-end" id="{{ f.instance.html_id_total_amount }}">
|
||||
{% currency_symbol %}{{ f.instance.po_total_amount | currency_format }}</td>
|
||||
<span class="currency">{{CURRENCY}}</span>{{ f.instance.po_total_amount | currency_format }}</td>
|
||||
<td>{{ f.po_item_status|add_class:"form-control" }}</td>
|
||||
{% if itemtxs_formset.can_delete %}
|
||||
<td class="text-center">
|
||||
@ -98,7 +98,7 @@
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th class="text-end">{% trans 'Total' %}</th>
|
||||
<th class="text-end">{% currency_symbol %}{{ po_model.po_amount | currency_format }}</th>
|
||||
<th class="text-end"><span class="currency">{{CURRENCY}}</span>{{ po_model.po_amount | currency_format }}</th>
|
||||
<th></th>
|
||||
{% if itemtxs_formset.can_delete %}
|
||||
<th></th>
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
<td>{{ po.po_title }}</td>
|
||||
<td>{{ po.get_status_action_date }}</td>
|
||||
<td>{{ po.get_po_status_display }}</td>
|
||||
<td>{% currency_symbol %}{{ po.po_amount | currency_format }}</td>
|
||||
<td><span class="currency">{{CURRENCY}}</span>{{ po.po_amount | currency_format }}</td>
|
||||
<td class="has-text-centered">
|
||||
<div class="dropdown is-right is-hoverable" id="bill-action-{{ po.uuid }}">
|
||||
<div class="dropdown-trigger">
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load trans from i18n %}
|
||||
{% load static %}
|
||||
{% load custom_filters %}
|
||||
{% load django_ledger %}
|
||||
{% load custom_filters %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid mt-4">
|
||||
@ -23,7 +23,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-12">
|
||||
<div class="col-lg-12">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="row text-center">
|
||||
@ -31,7 +31,7 @@
|
||||
<div class="p-2">
|
||||
<h6 class="text-muted mb-2">{% trans 'PO Amount' %}</h6>
|
||||
<h3 class="fw-light mb-0">
|
||||
{% currency_symbol %}{{ po_model.po_amount | absolute | currency_format }}
|
||||
<span class="currency">{{CURRENCY}}</span>{{ po_model.po_amount | absolute | currency_format }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
@ -39,24 +39,24 @@
|
||||
<div class="p-2">
|
||||
<h6 class="text-muted mb-2">{% trans 'Amount Received' %}</h6>
|
||||
<h3 class="fw-light mb-0 text-success">
|
||||
{% currency_symbol %}{{ po_model.po_amount_received | currency_format }}
|
||||
<span class="currency">{{CURRENCY}}</span>{{ po_model.po_amount_received | currency_format }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-12">
|
||||
<div class="col-lg-12">
|
||||
<div class="table-responsive">
|
||||
{% po_item_table1 po_items %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include "purchase_orders/includes/mark_as.html" %}
|
||||
|
||||
@ -61,7 +61,7 @@
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th>{% currency_symbol %}{{ ce_cost_estimate__sum | currency_format }}</th>
|
||||
<th><span class="currency">{{CURRENCY}}</span>{{ ce_cost_estimate__sum | currency_format }}</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
<tbody>
|
||||
@ -69,8 +69,8 @@
|
||||
<tr>
|
||||
<td>{{ i.item_model__name }}</td>
|
||||
<td>{{ i.ce_quantity__sum }}</td>
|
||||
<td>{% currency_symbol %}{{ i.avg_unit_cost | currency_format }}</td>
|
||||
<td>{% currency_symbol %}{{ i.ce_cost_estimate__sum | currency_format }}</td>
|
||||
<td><span class="currency">{{CURRENCY}}</span>{{ i.avg_unit_cost | currency_format }}</td>
|
||||
<td><span class="currency">{{CURRENCY}}</span>{{ i.ce_cost_estimate__sum | currency_format }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
<td class="has-text-centered">{{ item.po_unit_cost }}</td>
|
||||
<td class="has-text-centered">{{ item.po_quantity }}</td>
|
||||
<td class="{% if item.is_cancelled %}djl-is-strikethrough{% endif %} has-text-centered">
|
||||
{% currency_symbol %}{{ item.po_total_amount | currency_format }}</td>
|
||||
<span class="currency">{{CURRENCY}}</span>{{ item.po_total_amount | currency_format }}</td>
|
||||
<td class="has-text-weight-bold has-text-centered {% if item.is_cancelled %}has-text-danger{% endif %}">
|
||||
{% if item.po_item_status %}
|
||||
{{ item.get_po_item_status_display }}
|
||||
@ -40,7 +40,7 @@
|
||||
<td></td>
|
||||
<td class="has-text-right">{% trans 'Total PO Amount' %}</td>
|
||||
<td class="has-text-weight-bold has-text-centered">
|
||||
{% currency_symbol %}{{ po_model.po_amount | currency_format }}</td>
|
||||
<span class="currency">{{CURRENCY}}</span>{{ po_model.po_amount | currency_format }}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
@ -42,7 +42,6 @@
|
||||
<td class="align-middle white-space-nowrap ps-1">
|
||||
<div>
|
||||
<a class="fs-8 fw-bold" href="{% url 'user_detail' request.dealer.slug user.slug%}">{{ user.arabic_name }}</a>
|
||||
{{user.dealer}}
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap align-items-center">{{ user.email }}</td>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user