Compare commits

...

10 Commits

13 changed files with 485 additions and 228 deletions

View File

@ -135,10 +135,15 @@ class StaffForm(forms.ModelForm):
), ),
label=_("Phone Number"), label=_("Phone Number"),
) )
group = forms.ModelMultipleChoiceField(
label=_("Group"),
widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check-input"}),
queryset=CustomGroup.objects.all(),
required=True,
)
class Meta: class Meta:
model = Staff model = Staff
fields = ["name", "arabic_name", "phone_number", "staff_type"] fields = ["name", "arabic_name", "phone_number", "group"]
# Dealer Form # Dealer Form
@ -1523,44 +1528,116 @@ class GroupForm(forms.ModelForm):
fields = ["name"] fields = ["name"]
# class PermissionForm(forms.ModelForm):
# """
# Represents a form for managing permissions using a multiple-choice field.
# This class is a Django ModelForm that is used to handle permission
# assignments. It provides a multiple selection widget pre-populated with
# permissions based on specific app labels. The form offers a way to submit
# and validate permission data for further processing.
# :ivar name: A multiple-choice field that allows users to select permissions
# related to specific app labels (`inventory` and `django_ledger`).
# :type name: ModelMultipleChoiceField
# """
# name = forms.ModelMultipleChoiceField(
# queryset=cache.get(
# "permissions_queryset",
# Permission.objects.filter(
# content_type__app_label__in=["inventory", "django_ledger"]
# ),
# ),
# widget=forms.CheckboxSelectMultiple(),
# required=True,
# )
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
# cache.set(
# "permissions_queryset",
# Permission.objects.filter(
# content_type__app_label__in=["inventory", "django_ledger"]
# ),
# 60 * 60,
# )
# class Meta:
# model = Permission
# fields = ["name"]
class PermissionForm(forms.ModelForm): class PermissionForm(forms.ModelForm):
""" """
Represents a form for managing permissions using a multiple-choice field. Form for managing permissions with grouped checkboxes by app and model.
This class is a Django ModelForm that is used to handle permission
assignments. It provides a multiple selection widget pre-populated with
permissions based on specific app labels. The form offers a way to submit
and validate permission data for further processing.
:ivar name: A multiple-choice field that allows users to select permissions
related to specific app labels (`inventory` and `django_ledger`).
:type name: ModelMultipleChoiceField
""" """
name = forms.ModelMultipleChoiceField(
queryset=cache.get(
"permissions_queryset",
Permission.objects.filter(
content_type__app_label__in=["inventory", "django_ledger"]
),
),
widget=forms.CheckboxSelectMultiple(),
required=True,
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
cache.set( EXCLUDED_MODELS = [
"inventory.car",
"inventory.carfinance",
"inventory.carlocation",
"inventory.customcard",
"inventory.cartransfer",
"inventory.carcolors",
"inventory.carequipment",
"inventory.interiorcolors",
"inventory.exteriorcolors",
"inventory.lead",
"inventory.customgroup",
"inventory.saleorder",
"inventory.payment",
"inventory.staff",
"inventory.schedule",
"inventory.activity",
"inventory.opportunity",
"inventory.carreservation"
"inventory.customer",
"inventory.organization",
# "inventory.salequotation",
# "inventory.salequotationcar"
"django_ledger.bankaccountmodel",
"django_ledger.chartofaccountmodel",
"django_ledger.estimatemodel",
"django_ledger.accountmodel",
"django_ledger.chartofaccountmodel",
"django_ledger.billmodel"
"django_ledger.itemmodel",
"django_ledger.invoicemodel",
"django_ledger.vendormodel",
"django_ledger.journalentrymodel"
]
permissions = cache.get(
"permissions_queryset", "permissions_queryset",
Permission.objects.filter( Permission.objects.filter(
content_type__app_label__in=["inventory", "django_ledger"] content_type__app_label__in=[m.split('.')[0] for m in EXCLUDED_MODELS],
), content_type__model__in=[m.split('.')[1] for m in EXCLUDED_MODELS]
60 * 60, ).select_related('content_type')
)
# Group permissions by app_label and model
self.grouped_permissions = {}
for perm in permissions:
app_label = perm.content_type.app_label
model = perm.content_type.model
if app_label not in self.grouped_permissions:
self.grouped_permissions[app_label] = {}
if model not in self.grouped_permissions[app_label]:
self.grouped_permissions[app_label][model] = []
self.grouped_permissions[app_label][model].append(perm)
# Create a multiple choice field (hidden, will use custom rendering)
self.fields['permissions'] = forms.ModelMultipleChoiceField(
queryset=permissions,
widget=forms.MultipleHiddenInput(),
required=False,
initial=self.instance.permissions.all() if self.instance.pk else []
) )
class Meta: class Meta:
model = Permission model = Permission
fields = ["name"] fields = []
class UserGroupForm(forms.ModelForm): class UserGroupForm(forms.ModelForm):

View File

@ -1188,31 +1188,33 @@ class Staff(models.Model, LocalizedNameMixin):
return [x.customgroup for x in self.user.groups.all()] return [x.customgroup for x in self.user.groups.all()]
def clear_groups(self): def clear_groups(self):
EntityManagementModel.objects.filter( self.remove_superuser_permission()
user=self.user, entity=self.dealer.entity
).delete()
return self.user.groups.clear() return self.user.groups.clear()
def add_group(self, group): def add_group(self, group,clean=False):
if clean:
self.clear_groups()
try: try:
self.user.groups.add(group) self.user.groups.add(group)
if self.staff_type in ["accountant", "manager"]: if "accountant" in group.name.lower() or "manager" in group.name.lower():
self.add_as_manager() self.add_as_superuser()
except Exception as e: except Exception as e:
print(e) print(e)
def add_as_manager(self): def add_as_superuser(self):
if self.staff_type in ["accountant", "manager"]: EntityManagementModel.objects.get_or_create(
EntityManagementModel.objects.get_or_create( user=self.user, entity=self.dealer.entity
user=self.user, entity=self.dealer.entity )
) def remove_superuser_permission(self):
else: EntityManagementModel.objects.filter(
self.user.groups.clear() user=self.user, entity=self.dealer.entity
group = Group.objects.filter( ).delete()
customgroup__name__iexact=self.staff_type # self.user.groups.clear()
).first() # group = Group.objects.filter(
if group: # customgroup__name__iexact=self.staff_type
self.add_group(group) # ).first()
# if group:
# self.add_group(group)
class Meta: class Meta:
verbose_name = _("Staff") verbose_name = _("Staff")
@ -2519,7 +2521,7 @@ class CustomGroup(models.Model):
@property @property
def entity(self): def entity(self):
return self.invoice.entity return self.dealer.entity
@property @property
def users(self): def users(self):

View File

@ -528,25 +528,25 @@ def track_lead_status_change(sender, instance, **kwargs):
pass # Ignore if the lead doesn't exist (e.g., during initial creation) pass # Ignore if the lead doesn't exist (e.g., during initial creation)
@receiver(post_save, sender=models.Lead) # @receiver(post_save, sender=models.Lead)
def notify_assigned_staff(sender, instance, created, **kwargs): # def notify_assigned_staff(sender, instance, created, **kwargs):
""" # """
Signal handler that sends a notification to the staff member when a new lead is assigned. # Signal handler that sends a notification to the staff member when a new lead is assigned.
This function is triggered when a Lead instance is saved. If the lead has been assigned # This function is triggered when a Lead instance is saved. If the lead has been assigned
to a staff member, it creates a Notification object, notifying the staff member of the # to a staff member, it creates a Notification object, notifying the staff member of the
new assignment. # new assignment.
:param sender: The model class that sent the signal. # :param sender: The model class that sent the signal.
:param instance: The instance of the model that was saved. # :param instance: The instance of the model that was saved.
:param created: A boolean indicating whether a new instance was created. # :param created: A boolean indicating whether a new instance was created.
:param kwargs: Additional keyword arguments. # :param kwargs: Additional keyword arguments.
:return: None # :return: None
""" # """
if instance.staff: # Check if the lead is assigned # if instance.staff: # Check if the lead is assigned
models.Notification.objects.create( # models.Notification.objects.create(
user=instance.staff.staff_member.user, # user=instance.staff.staff_member.user,
message=f"You have been assigned a new lead: {instance.full_name}.", # message=f"You have been assigned a new lead: {instance.full_name}.",
) # )
@receiver(post_save, sender=models.CarReservation) @receiver(post_save, sender=models.CarReservation)

View File

@ -645,3 +645,10 @@ def inventory_table(context, queryset):
} }
ctx.update(queryset.aggregate(inventory_total_value=Sum("total_value"))) ctx.update(queryset.aggregate(inventory_total_value=Sum("total_value")))
return ctx return ctx
@register.filter
def count_checked(permissions):
"""Count how many permissions are marked as checked"""
print(permissions)
return sum(1 for perm in permissions if getattr(perm, 'is_checked', False))

View File

@ -913,7 +913,6 @@ urlpatterns = [
name="bill-action-force-migrate", name="bill-action-force-migrate",
), ),
# path("items/bills/create/", views.bill_create, name="bill_create"), # path("items/bills/create/", views.bill_create, name="bill_create"),
# >>>>>>>>>>>>>>>>>>>>>>...
path( path(
"items/bills/<uuid:pk>/bill_detail/", "items/bills/<uuid:pk>/bill_detail/",
views.BillDetailView.as_view(), views.BillDetailView.as_view(),

View File

@ -318,6 +318,7 @@ def dealer_signup(request):
if password != password_confirm: if password != password_confirm:
return JsonResponse({"error": _("Passwords do not match")}, status=400) return JsonResponse({"error": _("Passwords do not match")}, status=400)
try: try:
#TODO make this a django-q task
create_user_dealer( create_user_dealer(
email, password, name, arabic_name, phone, crn, vrn, address email, password, name, arabic_name, phone, crn, vrn, address
) )
@ -2666,20 +2667,26 @@ class GroupCreateView(
success_message = _("Group created successfully") success_message = _("Group created successfully")
def form_valid(self, form): def form_valid(self, form):
dealer = get_user_type(self.request) dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
instance = form.save(commit=False) instance = form.save(commit=False)
group_name = f"{dealer.slug}_{instance.name}" group_name = f"{dealer.slug}_{instance.name}"
group,created = Group.objects.get_or_create(name=group_name) try:
group, created = Group.objects.get_or_create(name=group_name)
instance.dealer = dealer
instance.group = group
instance.save()
except IntegrityError as e:
from django.utils.translation import gettext_lazy as _
print(e)
messages.error(self.request, _("Group name already exists"))
return redirect("group_create", dealer_slug=dealer.slug)
if created: if created:
group_manager, created = models.CustomGroup.objects.get_or_create( group_manager, _ = models.CustomGroup.objects.get_or_create(
name=group_name, dealer=dealer, group=group name=instance.name, dealer=dealer, group=group
) )
group_manager.set_default_permissions() group_manager.set_default_permissions()
dealer.user.groups.add(group) dealer.user.groups.add(group)
else:
instance.dealer = dealer
instance.group = group
instance.save()
return super().form_valid(form) return super().form_valid(form)
def get_success_url(self): def get_success_url(self):
@ -2749,42 +2756,78 @@ def GroupDeleteview(request, dealer_slug,pk):
@login_required @login_required
def GroupPermissionView(request, dealer_slug,pk): def GroupPermissionView(request, dealer_slug, pk):
""" # Verify dealer and group exist
Handles the view for adding or modifying permissions of a specific group. This view get_object_or_404(models.Dealer, slug=dealer_slug)
fetches the group based on the primary key passed as a parameter, and either displays customgroup = get_object_or_404(models.CustomGroup, pk=pk)
a form for editing permissions or processes the submitted permissions.
If the request method is POST, the permissions of the group are cleared and updated
based on the submitted data. A success message is displayed upon completion, and
the user is redirected to the group's detail page.
In case of a GET request, the view renders the form pre-filled with the group's
current permissions.
:param request: The HTTP request object.
:type request: HttpRequest
:param pk: The primary key of the group whose permissions are being modified.
:type pk: int
:return: The HTTP response depending on the request type. For GET requests, renders
the permission form for the specified group. For POST requests, clears and updates
the group's permissions and redirects to the group's detail page.
:rtype: HttpResponse
"""
get_object_or_404(models.Dealer,slug=dealer_slug)
group = get_object_or_404(models.CustomGroup, pk=pk)
if request.method == "POST": if request.method == "POST":
form = forms.PermissionForm(request.POST) form = forms.PermissionForm(request.POST, instance=customgroup)
group.clear_permissions() if form.is_valid():
permissions = request.POST.getlist("name") # Clear existing permissions
for i in permissions: customgroup.clear_permissions()
group.add_permission(Permission.objects.get(id=int(i)))
messages.success(request, _("Permission added successfully")) # Add new permissions from form
return redirect("group_detail", dealer_slug=dealer_slug,pk=group.pk) permissions = form.cleaned_data.get('permissions', [])
form = forms.PermissionForm(initial={"name": group.permissions}) for permission in permissions:
return render( customgroup.add_permission(permission)
request, "groups/group_permission_form.html", {"group": group, "form": form}
) messages.success(request, _("Permissions updated successfully"))
return redirect("group_detail", dealer_slug=dealer_slug, pk=customgroup.pk)
else:
# Initial form with current permissions
form = forms.PermissionForm(instance=customgroup)
group_permission_ids = set(customgroup.permissions.values_list('id', flat=True))
# Mark permissions as checked in the form data
for app_label, model in form.grouped_permissions.items():
for mo, perms in model.items():
for perm in perms:
perm.is_checked = perm.id in group_permission_ids
return render(request,"groups/group_permission_form.html", {
"group": customgroup,
"form": form,
"group_permission_apps": set(customgroup.group.permissions.values_list('content_type__app_label', flat=True)),
"group_permission_models": set(customgroup.group.permissions.values_list('content_type__model', flat=True))
})
# def GroupPermissionView(request, dealer_slug,pk):
# """
# Handles the view for adding or modifying permissions of a specific group. This view
# fetches the group based on the primary key passed as a parameter, and either displays
# a form for editing permissions or processes the submitted permissions.
# If the request method is POST, the permissions of the group are cleared and updated
# based on the submitted data. A success message is displayed upon completion, and
# the user is redirected to the group's detail page.
# In case of a GET request, the view renders the form pre-filled with the group's
# current permissions.
# :param request: The HTTP request object.
# :type request: HttpRequest
# :param pk: The primary key of the group whose permissions are being modified.
# :type pk: int
# :return: The HTTP response depending on the request type. For GET requests, renders
# the permission form for the specified group. For POST requests, clears and updates
# the group's permissions and redirects to the group's detail page.
# :rtype: HttpResponse
# """
# get_object_or_404(models.Dealer,slug=dealer_slug)
# group = get_object_or_404(models.CustomGroup, pk=pk)
# if request.method == "POST":
# form = forms.PermissionForm(request.POST)
# group.clear_permissions()
# permissions = request.POST.getlist("name")
# for i in permissions:
# group.add_permission(Permission.objects.get(id=int(i)))
# messages.success(request, _("Permission added successfully"))
# return redirect("group_detail", dealer_slug=dealer_slug,pk=group.pk)
# form = forms.PermissionForm(initial={"name": group.permissions})
# return render(
# request, "groups/group_permission_form.html", {"group": group, "form": form}
# )
# Users # Users
@ -2915,6 +2958,12 @@ class UserCreateView(
success_url = reverse_lazy("user_list") success_url = reverse_lazy("user_list")
success_message = _("User created successfully") success_message = _("User created successfully")
def get_form(self, form_class=None):
form = super().get_form(form_class)
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
form.fields["group"].queryset = models.CustomGroup.objects.filter(dealer=dealer)
return form
def form_valid(self, form): def form_valid(self, form):
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"]) dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
# quota_dict = get_user_quota(dealer.user) # quota_dict = get_user_quota(dealer.user)
@ -2956,11 +3005,9 @@ class UserCreateView(
staff = form.save(commit=False) staff = form.save(commit=False)
staff.staff_member = staff_member staff.staff_member = staff_member
staff.dealer = dealer staff.dealer = dealer
staff.add_as_manager()
group = models.CustomGroup.objects.filter(dealer=dealer,name__iexact=staff.staff_type).first()
staff.save() staff.save()
if group: for customgroup in form.cleaned_data["group"]:
staff.add_group(group.group) staff.add_group(customgroup.group)
return super().form_valid(form) return super().form_valid(form)
def get_success_url(self): def get_success_url(self):
return reverse_lazy("user_list", args=[self.request.dealer.slug]) return reverse_lazy("user_list", args=[self.request.dealer.slug])
@ -3004,30 +3051,32 @@ class UserUpdateView(
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super().get_form(form_class) form = super().get_form(form_class)
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
form.fields["group"].queryset = models.CustomGroup.objects.filter(dealer=dealer)
form.fields["email"].disabled = True form.fields["email"].disabled = True
return form return form
def get_initial(self): def get_initial(self):
initial = super().get_initial() initial = super().get_initial()
initial["email"] = self.object.staff_member.user.email initial["email"] = self.object.staff_member.user.email
initial["service_offered"] = self.object.staff_member.services_offered.all() initial["group"] = self.object.groups
return initial return initial
def form_valid(self, form): def form_valid(self, form):
services = form.cleaned_data["service_offered"] # services = form.cleaned_data["service_offered"]
if not services: # if not services:
self.object.staff_member.services_offered.clear() # self.object.staff_member.services_offered.clear()
else: # else:
for service in services: # for service in services:
self.object.staff_member.services_offered.add(service) # self.object.staff_member.services_offered.add(service)
staff = form.save(commit=False) staff = form.save(commit=False)
staff.name = form.cleaned_data["name"] staff.name = form.cleaned_data["name"]
staff.arabic_name = form.cleaned_data["arabic_name"] staff.arabic_name = form.cleaned_data["arabic_name"]
staff.phone_number = form.cleaned_data["phone_number"] staff.phone_number = form.cleaned_data["phone_number"]
staff.staff_type = form.cleaned_data["staff_type"] for customgroup in form.cleaned_data["group"]:
staff.add_group(customgroup.group,True)
staff.add_as_manager()
staff.save() staff.save()
return super().form_valid(form) return super().form_valid(form)
def get_success_url(self): def get_success_url(self):
@ -5180,7 +5229,7 @@ def lead_create(request,dealer_slug):
if form.is_valid(): if form.is_valid():
instance = form.save(commit=False) instance = form.save(commit=False)
instance.dealer = dealer instance.dealer = dealer
instance.staff = form.cleaned_data.get("staff") # instance.staff = form.cleaned_data.get("staff")
if instance.lead_type == "customer": if instance.lead_type == "customer":
customer = models.Customer.objects.filter( customer = models.Customer.objects.filter(
@ -5252,12 +5301,12 @@ def lead_create(request,dealer_slug):
is_sa_import=True, pk__in=dealer_make_list is_sa_import=True, pk__in=dealer_make_list
) )
form.fields["staff"].queryset = form.fields["staff"].queryset.filter( form.fields["staff"].queryset = form.fields["staff"].queryset.filter(
dealer=dealer, staff_type="sales" dealer=dealer
) )
if hasattr(request.user.staffmember, "staff"): if hasattr(request.user.staffmember, "staff"):
form.initial["staff"] = request.user.staffmember.staff form.initial["staff"] = request.user.staffmember.staff
form.fields["staff"].widget.attrs["disabled"] = True form.fields["staff"].widget = HiddenInput()
form.fields["id_car_make"].queryset = qs form.fields["id_car_make"].queryset = qs
form.fields["id_car_make"].choices = [ form.fields["id_car_make"].choices = [
(obj.id_car_make, obj.get_local_name()) for obj in qs (obj.id_car_make, obj.get_local_name()) for obj in qs
@ -5817,6 +5866,13 @@ class OpportunityCreateView(CreateView, SuccessMessageMixin, LoginRequiredMixin)
initial["stage"] = models.Stage.QUALIFICATION initial["stage"] = models.Stage.QUALIFICATION
return initial return initial
def get_form(self, form_class=None):
form = super().get_form(form_class)
dealer = get_object_or_404(models.Dealer,slug=self.kwargs.get("dealer_slug"))
form.fields['car'].queryset = models.Car.objects.filter(dealer=dealer)
form.fields['lead'].queryset = models.Lead.objects.filter(dealer=dealer)
return form
def form_valid(self, form): def form_valid(self, form):
dealer = get_object_or_404(models.Dealer,slug=self.kwargs.get("dealer_slug")) dealer = get_object_or_404(models.Dealer,slug=self.kwargs.get("dealer_slug"))
instance = form.save(commit=False) instance = form.save(commit=False)
@ -5859,6 +5915,13 @@ class OpportunityUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView)
template_name = "crm/opportunities/opportunity_form.html" template_name = "crm/opportunities/opportunity_form.html"
success_message = "Opportunity updated successfully." success_message = "Opportunity updated successfully."
def get_form(self, form_class=None):
form = super().get_form(form_class)
dealer = get_object_or_404(models.Dealer,slug=self.kwargs.get("dealer_slug"))
form.fields['car'].queryset = models.Car.objects.filter(dealer=dealer)
form.fields['lead'].queryset = models.Lead.objects.filter(dealer=dealer)
return form
def get_success_url(self): def get_success_url(self):
return reverse_lazy("opportunity_detail", kwargs={"dealer_slug":self.kwargs.get("dealer_slug"),"slug": self.object.slug}) return reverse_lazy("opportunity_detail", kwargs={"dealer_slug":self.kwargs.get("dealer_slug"),"slug": self.object.slug})
@ -8788,7 +8851,6 @@ def payment_callback(request,dealer_slug):
def sse_stream(request): def sse_stream(request):
print("hi")
def event_stream(): def event_stream():
last_id = request.GET.get("last_id", 0) last_id = request.GET.get("last_id", 0)
while True: while True:

View File

@ -11,7 +11,8 @@
<div class="col-auto"> <div class="col-auto">
<div class="d-md-flex justify-content-between"> <div class="d-md-flex justify-content-between">
<div> <div>
<a href="{% url 'group_create' request.dealer.slug %}" class="btn btn-sm btn-phoenix-primary"><span class="fas fa-plus me-2"></span>{% trans "Add Group" %}</a> <a href="{% url 'group_create' request.dealer.slug %}" class="btn btn-sm btn-phoenix-primary me-5"><span class="fas fa-plus me-2"></span>{% trans "Add Group" %}</a>
<a href="{% url 'user_list' request.dealer.slug %}" class="btn btn-sm btn-phoenix-secondary"><span class="fas fas fa-arrow-left me-2"></span>{% trans "Back to Staffs" %}</a>
</div> </div>
</div> </div>

View File

@ -0,0 +1,45 @@
{% extends "base.html" %}
{% load i18n %}
{% load crispy_forms_filters %}
{% block title %}{% trans "Permission" %}{% endblock title %}
{% block content %}
<div class="row">
<div class="row">
<div class="col-sm-9">
<div class="d-sm-flex justify-content-between">
<h3 class="mb-3">
{% if group.created %}
{{ _("Edit Permission") }}
{% else %}
{{ _("Add Permission") }}
{% endif %}
</h3>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-9">
<form class="row g-3 mb-9" method="post" class="form" novalidate>
{% csrf_token %}
{{ redirect_field }}
{{ form|crispy }}
{% for error in form.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
<div class="d-flex mb-3">
<a href="{% url 'group_detail' request.dealer.slug group.pk %}" class="btn btn-phoenix-primary me-2 "><i class="fa-solid fa-ban"></i> {% trans "Cancel"|capfirst %}</a>
<button class="btn btn-phoenix-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i>
{{ _("Save") }}
</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,46 +1,157 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n %} {% load i18n %}
{% load crispy_forms_filters %} {% load custom_filters %}
{% block title %}{% trans "Permission" %}{% endblock title %}
{% block title %}{% trans "Permission Management" %}{% endblock title %}
{% block content %} {% block content %}
<div class="container-fluid">
<div class="row mb-4">
<div class="col">
<div class="d-sm-flex justify-content-between align-items-center">
<h3 class="mb-0">
{% if group %}
{{ _("Edit Permissions for") }}: <strong>{{ group.name }}</strong>
{% else %}
{{ _("Add Permissions") }}
{% endif %}
</h3>
<div class="row">
<div class="row">
<div class="col-sm-9">
<div class="d-sm-flex justify-content-between">
<h3 class="mb-3">
{% if group.created %}
{{ _("Edit Permission") }}
{% else %}
{{ _("Add Permission") }}
{% endif %}
</h3>
</div>
</div> </div>
</div> </div>
<div class="row"> </div>
<div class="col-sm-9">
<form class="row g-3 mb-9" method="post" class="form" novalidate> <form method="post" novalidate>
{% csrf_token %} {% csrf_token %}
{{ redirect_field }}
{{ form|crispy }} <!-- Permissions Grid -->
{% for error in form.errors %} <div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4" id="permissionsGrid">
<div class="text-danger">{{ error }}</div> {% for app_label, models in form.grouped_permissions.items %}
{% endfor %} <div class="col">
<div class="d-flex mb-3"> <div class="card h-100 border-{% if app_label in group_permission_apps %}primary{% else %}light{% endif %}">
<button class="btn btn-phoenix-primary me-2" type="submit"> <div class="card-header bg-{% if app_label in group_permission_apps %}primary text-white{% else %}light{% endif %}">
<i class="fa-solid fa-floppy-disk"></i> <div class="d-flex justify-content-between align-items-center">
{{ _("Save") }} <h5 class="card-title mb-0">
<i class="fas fa-{% if app_label in group_permission_apps %}check-circle{% else %}cube{% endif %} me-2"></i>
{{ app_label|capfirst }}
</h5>
<span class="badge bg-{% if app_label in group_permission_apps %}light text-primary{% else %}secondary{% endif %}">
{{ models|length }} {% trans "models" %}
</span>
</div>
</div>
<div class="card-body">
<div class="accordion">
{% for model, perms in models.items %}
<div class="accordion-item border-0">
<h6 class="accordion-header" id="heading-{{ app_label|slugify }}-{{ model|slugify }}">
<button class="accordion-button collapsed bg-white shadow-none py-2"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapse-{{ app_label|slugify }}-{{ model|slugify }}"
aria-expanded="false">
<i class="fas fa-table me-2"></i>{{ model|capfirst }}
<span class="badge bg-{% if model in group_permission_models %}primary{% else %}secondary{% endif %} rounded-pill ms-2">
{{ perms|length }} / {{ perms|count_checked }}
</span>
</button>
</h6>
<div id="collapse-{{ app_label|slugify }}-{{ model|slugify }}"
class="accordion-collapse collapse"
aria-labelledby="heading-{{ app_label|slugify }}-{{ model|slugify }}">
<div class="accordion-body pt-0">
<div class="list-group list-group-flush">
{% for perm in perms %}
<label class="list-group-item d-flex gap-2 {% if perm.is_checked %}bg-light-primary{% endif %}">
<input class="form-check-input flex-shrink-0 mt-0"
type="checkbox"
name="permissions"
value="{{ perm.id }}"
id="perm_{{ perm.id }}"
{% if perm.is_checked %}checked{% endif %}>
<span>
<span class="d-block fw-bold">{{ perm.name|capfirst }}</span>
<small class="d-block text-muted">{{ perm.codename }}</small>
{% if perm.is_checked %}
<span class="badge bg-success mt-1">
<i class="fas fa-check me-1"></i>{% trans "Assigned" %}
</span>
{% endif %}
</span>
</label>
{% endfor %}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="row mt-4">
<div class="col">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="badge bg-primary rounded-pill me-2">
{{ group.permissions.count }} {% trans "selected" %}
</span>
<span class="text-muted">
{% trans "Permissions will be updated immediately" %}
</span>
</div>
<div>
<a href="{% url 'group_detail' request.dealer.slug group.pk %}"
class="btn btn-outline-secondary me-2">
<i class="fas fa-ban me-2"></i>{% trans "Cancel" %}
</a>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save me-2"></i>{% trans "Save Changes" %}
</button> </button>
<a href="{% url 'group_detail' request.dealer.slug group.pk %}" class="btn btn-phoenix-secondary "><i class="fa-solid fa-ban"></i> {% trans "Cancel"|capfirst %}</a> <a href="{% url 'group_detail' request.dealer.slug group.pk %}" class="btn btn-phoenix-secondary "><i class="fa-solid fa-ban"></i> {% trans "Cancel"|capfirst %}</a>
</div> </div>
</form> </div>
</div> </div>
</div> </div>
</div> </form>
</div>
<style>
.bg-light-primary {
background-color: rgba(13, 110, 253, 0.1);
}
.list-group-item:hover {
background-color: rgba(0, 0, 0, 0.03);
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Search functionality
document.getElementById('permissionSearch').addEventListener('input', function(e) {
const searchTerm = e.target.value.toLowerCase();
document.querySelectorAll('.accordion-body .list-group-item').forEach(item => {
const text = item.textContent.toLowerCase();
item.style.display = text.includes(searchTerm) ? '' : 'none';
});
// Open relevant accordions
document.querySelectorAll('.accordion-collapse').forEach(collapse => {
const visibleItems = collapse.querySelectorAll('.list-group-item[style=""]');
if (visibleItems.length > 0) {
const button = document.querySelector(
`button[data-bs-target="#${collapse.id}"]`
);
if (button && !button.classList.contains('collapsed')) {
new bootstrap.Collapse(collapse, {toggle: true});
}
}
});
});
});
</script>
{% endblock %} {% endblock %}

View File

@ -81,10 +81,6 @@
{{ _("Sale Order")}} #{{ saleorder.formatted_order_id }} {{ _("Sale Order")}} #{{ saleorder.formatted_order_id }}
</h1> </h1>
<div> <div>
<<<<<<< HEAD
=======
>>>>>>> e9e2fd3 (add bulk insert + po item insert)
<button class="btn btn-sm btn-outline-light me-2"> <button class="btn btn-sm btn-outline-light me-2">
<i class="fas fa-print me-1"></i> {{ _("Print") }} <i class="fas fa-print me-1"></i> {{ _("Print") }}
</button> </button>
@ -275,18 +271,10 @@
<!-- Documents Card --> <!-- Documents Card -->
<div class="card mb-4 shadow-sm"> <div class="card mb-4 shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
<<<<<<< HEAD
<h5 class="mb-0">{{ _("Documents") }}</h5> <h5 class="mb-0">{{ _("Documents") }}</h5>
<button class="btn btn-sm btn-primary"> <button class="btn btn-sm btn-primary">
<i class="fas fa-plus me-1"></i> {{ _("Add Document")}} <i class="fas fa-plus me-1"></i> {{ _("Add Document")}}
======= </button>
<h5 class="mb-0">{{ _("Documents") }}</h5>
<button class="btn btn-sm btn-primary">
<i class="fas fa-plus me-1"></i> {{ _("Add Document")}}
>>>>>>> e9e2fd3 (add bulk insert + po item insert)
</button>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="file-upload mb-3"> <div class="file-upload mb-3">
@ -330,13 +318,7 @@
<div class="mb-3"> <div class="mb-3">
<textarea class="form-control" name="comment" rows="3" placeholder="Add a comment or note..." required></textarea> <textarea class="form-control" name="comment" rows="3" placeholder="Add a comment or note..." required></textarea>
<div class="d-flex justify-content-end mt-2"> <div class="d-flex justify-content-end mt-2">
<<<<<<< HEAD
<button type="submit" class="btn btn-primary btn-sm">{{ _("Post Comment")}}</button> <button type="submit" class="btn btn-primary btn-sm">{{ _("Post Comment")}}</button>
=======
<button type="submit" class="btn btn-primary btn-sm">{{ _("Post Comment")}}</button>
>>>>>>> e9e2fd3 (add bulk insert + po item insert)
</div> </div>
</div> </div>
</form> </form>
@ -379,52 +361,27 @@
{% endif %} {% endif %}
{% comment %} <a href="{% url 'edit_sale_order' saleorder.pk %}" class="btn btn-primary"> {% endcomment %} {% comment %} <a href="{% url 'edit_sale_order' saleorder.pk %}" class="btn btn-primary"> {% endcomment %}
<<<<<<< HEAD
=======
>>>>>>> e9e2fd3 (add bulk insert + po item insert)
<a href="" class="btn btn-primary"> <a href="" class="btn btn-primary">
<i class="fas fa-edit me-2"></i> {{ _("Edit Order")}} <i class="fas fa-edit me-2"></i> {{ _("Edit Order")}}
</a> </a>
{% if not saleorder.invoice %} {% if not saleorder.invoice %}
{% comment %} <a href="{% url 'create_invoice_from_order' saleorder.pk %}" class="btn btn-info"> {% endcomment %} {% comment %} <a href="{% url 'create_invoice_from_order' saleorder.pk %}" class="btn btn-info"> {% endcomment %}
<<<<<<< HEAD
<a href="" class="btn btn-info"> <a href="" class="btn btn-info">
<i class="fas fa-file-invoice-dollar me-2"></i> {{ _("Create Invoice")}} <i class="fas fa-file-invoice-dollar me-2"></i> {{ _("Create Invoice")}}
======= </a>
<a href="" class="btn btn-info">
<i class="fas fa-file-invoice-dollar me-2"></i> {{ _("Create Invoice")}}
>>>>>>> e9e2fd3 (add bulk insert + po item insert)
</a>
{% endif %} {% endif %}
{% if saleorder.status == 'approved' and not saleorder.actual_delivery_date %} {% if saleorder.status == 'approved' and not saleorder.actual_delivery_date %}
<<<<<<< HEAD
<button class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#deliveryModal"> <button class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#deliveryModal">
<i class="fas fa-truck me-2"></i> {{ _("Schedule Delivery")}} <i class="fas fa-truck me-2"></i> {{ _("Schedule Delivery")}}
======= </button>
<button class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#deliveryModal">
<i class="fas fa-truck me-2"></i> {{ _("Schedule Delivery")}}
>>>>>>> e9e2fd3 (add bulk insert + po item insert)
</button>
{% endif %} {% endif %}
{% if saleorder.status != 'cancelled' %} {% if saleorder.status != 'cancelled' %}
<<<<<<< HEAD
<button class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#cancelModal"> <button class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#cancelModal">
<i class="fas fa-times-circle me-2"></i> {{ _("Cancel Order")}} <i class="fas fa-times-circle me-2"></i> {{ _("Cancel Order")}}
======= </button>
<button class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#cancelModal">
<i class="fas fa-times-circle me-2"></i> {{ _("Cancel Order")}}
>>>>>>> e9e2fd3 (add bulk insert + po item insert)
</button>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -575,15 +532,8 @@
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<<<<<<< HEAD
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ _("Close") }}</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ _("Close") }}</button>
<button type="submit" class="btn btn-danger">{{ _("Confirm Cancellation")}}</button> <button type="submit" class="btn btn-danger">{{ _("Confirm Cancellation")}}</button>
=======
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ _("Close") }}</button>
<button type="submit" class="btn btn-danger">{{ _("Confirm Cancellation")}}</button>
>>>>>>> e9e2fd3 (add bulk insert + po item insert)
</div> </div>
</form> </form>
</div> </div>
@ -612,15 +562,8 @@
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<<<<<<< HEAD
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ _("Close") }}</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ _("Close") }}</button>
<button type="submit" class="btn btn-primary">{{ _("Schedule Delivery")}}</button> <button type="submit" class="btn btn-primary">{{ _("Schedule Delivery")}}</button>
=======
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ _("Close") }}</button>
<button type="submit" class="btn btn-primary">{{ _("Schedule Delivery")}}</button>
>>>>>>> e9e2fd3 (add bulk insert + po item insert)
</div> </div>
</form> </form>
</div> </div>

View File

@ -18,7 +18,15 @@
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<p><strong>{{ _("Phone Number") }}:</strong> {{ user_.phone_number }}</p> <p><strong>{{ _("Phone Number") }}:</strong> {{ user_.phone_number }}</p>
<p><strong>{{ _("Role") }}:</strong> {{ user_.staff_type }}</p> <div>
<span><strong>{{ _("Roles") }}:</strong></span>
{% for group in user_.groups %}
<span><strong>{{ group.name }}</strong></span>
{% if not forloop.last %}
<span>&amp;</span>
{% endif %}
{% endfor %}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -39,8 +39,7 @@
{{ form.arabic_name|as_crispy_field }} {{ form.arabic_name|as_crispy_field }}
{{ form.email|as_crispy_field }} {{ form.email|as_crispy_field }}
{{ form.phone_number|as_crispy_field }} {{ form.phone_number|as_crispy_field }}
{{ form.staff_type|as_crispy_field }} {{ form.group|as_crispy_field }}
{{ form.service_offered|as_crispy_field }}
{% for error in form.errors %} {% for error in form.errors %}
<div class="text-danger">{{ error }}</div> <div class="text-danger">{{ error }}</div>
{% endfor %} {% endfor %}
@ -56,3 +55,4 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -6,7 +6,6 @@
{% block content %} {% block content %}
<section class=""> <section class="">
<div class="row mt-4"> <div class="row mt-4">
<div class="col-auto"> <div class="col-auto">
<div class="d-md-flex justify-content-between"> <div class="d-md-flex justify-content-between">
@ -47,7 +46,10 @@
<td class="align-middle white-space-nowrap align-items-center">{{ user.email }}</td> <td class="align-middle white-space-nowrap align-items-center">{{ user.email }}</td>
<td class="align-middle white-space-nowrap align-items-center justify-content-center">{{ user.phone_number }}</td> <td class="align-middle white-space-nowrap align-items-center justify-content-center">{{ user.phone_number }}</td>
<td> <td>
<span class="badge badge-sm bg-primary text-center"><i class="fa-solid fa-scroll"></i> {% trans user.staff_type|title %}</span>
{% for group in user.groups %}
<span class="badge badge-sm bg-primary text-center"><i class="fa-solid fa-scroll"></i> {% trans group.name|title %}</span>
{% endfor %}
</td> </td>
<td class="align-middle white-space-nowrap"> <td class="align-middle white-space-nowrap">