From e5a088324f973f5ad324b3e8021d4b0b475aac2a Mon Sep 17 00:00:00 2001 From: ismail Date: Thu, 3 Jul 2025 14:40:28 +0300 Subject: [PATCH 1/2] update permissions --- inventory/models.py | 97 ++- inventory/signals.py | 27 +- inventory/urls.py | 30 +- inventory/views.py | 664 ++++++------------ .../appointment_client_information.html | 2 - templates/appointment/appointments.html | 1 - templates/header.html | 47 +- templates/index.html | 2 +- templates/ledger/reports/dashboard.html | 6 +- templates/pricing_page.html | 4 +- templates/purchase_orders/po_list.html | 10 +- .../sales/estimates/estimate_detail.html | 5 +- templates/sales/estimates/estimate_list.html | 26 +- templates/sales/invoices/invoice_detail.html | 4 +- 14 files changed, 374 insertions(+), 551 deletions(-) diff --git a/inventory/models.py b/inventory/models.py index e22a31d2..a46c3c22 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -18,6 +18,7 @@ from django_ledger.models import ( ItemModel, CustomerModel, JournalEntryModel, + LedgerModel ) from django_ledger.io.io_core import get_localdate from django.core.exceptions import ValidationError @@ -2546,11 +2547,21 @@ class CustomGroup(models.Model): pass def set_default_permissions(self): - est = ContentType.objects.get_for_model(EstimateModel) - bill = ContentType.objects.get_for_model(BillModel) - Permission.objects.get_or_create(name="Can approve estimate",codename="can_approve_estimate",content_type=est) - Permission.objects.get_or_create(name="Can approve bill",codename="can_approve_bill",content_type=bill) + Permission.objects.get_or_create(name="Can approve estimate",codename="can_approve_estimatemodel",content_type=ContentType.objects.get_for_model(EstimateModel)) + Permission.objects.get_or_create(name="Can approve bill",codename="can_approve_billmodel",content_type=ContentType.objects.get_for_model(BillModel)) + + Permission.objects.get_or_create(name="Can view inventory",codename="can_view_inventory",content_type=ContentType.objects.get_for_model(Car)) + Permission.objects.get_or_create(name="Can view sales",codename="can_view_sales",content_type=ContentType.objects.get_for_model(EstimateModel)) + Permission.objects.get_or_create(name="Can view crm",codename="can_view_crm",content_type=ContentType.objects.get_for_model(Lead)) + Permission.objects.get_or_create(name="Can view financials",codename="can_view_financials",content_type=ContentType.objects.get_for_model(AccountModel)) + Permission.objects.get_or_create(name="Can view reports",codename="can_view_reports",content_type=ContentType.objects.get_for_model(LedgerModel)) + self.clear_permissions() + ###################################### + ###################################### + #MANAGER + ###################################### + ###################################### if self.name == "Manager": self.set_permissions( app="inventory", @@ -2565,11 +2576,6 @@ class CustomGroup(models.Model): "interiorcolors", "exteriorcolors", "carreservation", - ], - ) - self.set_permissions( - app="inventory", - allowed_models=[ "lead", "customgroup", "saleorder", @@ -2578,6 +2584,11 @@ class CustomGroup(models.Model): "schedule", "activity", "opportunity", + "vendor", + "customer" + "notes", + "tasks", + "activity", ], ) self.set_permissions( @@ -2589,10 +2600,20 @@ class CustomGroup(models.Model): "chartofaccountmodel", "customermodel", "billmodel", - "can_approve_estimate" - "can_approve_bill", + "bankaccountmodel", + "itemmodel", + "vendormodel", + "journalentrymodel", + "purchaseordermodel", ], + other_perms=["can_approve_estimatemodel","can_approve_billmodel","can_view_inventory","can_view_sales","can_view_crm","can_view_financials","can_view_reports"], + ) + ###################################### + ###################################### + #Inventory + ###################################### + ###################################### elif self.name == "Inventory": self.set_permissions( app="inventory", @@ -2605,14 +2626,29 @@ class CustomGroup(models.Model): "carlocation", "customcard", "carreservation", + "notes", + "tasks", + "activity", ], ) + self.set_permissions( + app="django_ledger", + allowed_models=[], + other_perms=[ + "view_purchaseordermodel", + "can_view_financials", + ] + ) + ###################################### + ###################################### + #Sales + ###################################### + ###################################### elif self.name == "Sales": self.set_permissions( app="django_ledger", allowed_models=["estimatemodel", "invoicemodel", "customermodel"], ) - self.set_permissions( app="inventory", allowed_models=[ @@ -2624,23 +2660,34 @@ class CustomGroup(models.Model): "opportunity", "customer", "organization", + "notes", + "taska", + "activity", ], - ) - self.set_permissions( - app="inventory", - allowed_models=["lead", "salequotation", "salequotationcar"], other_perms=[ "view_car", "view_carlocation", "view_customcard", "view_carcolors", "view_cartransfer", + "can_view_inventory", + "can_view_sales", + "can_view_crm", ], ) + ###################################### + ###################################### + #Accountant + ###################################### + ###################################### elif self.name == "Accountant": self.set_permissions( app="inventory", - allowed_models=["carfinance"], + allowed_models=[ + "carfinance", + "notes", + "tasks", + "activity",], other_perms=[ "view_car", "view_carlocation", @@ -2648,6 +2695,7 @@ class CustomGroup(models.Model): "view_carcolors", "view_cartransfer", "view_saleorder", + ], ) self.set_permissions( @@ -2656,19 +2704,20 @@ class CustomGroup(models.Model): "bankaccountmodel", "accountmodel", "chartofaccountmodel", - "customcard", "billmodel", "itemmodel", "invoicemodel", "vendormodel", "journalentrymodel", "purchaseordermodel", + "estimatemodel", + "customermodel", + "vendormodel", + "TransactionModel" ], - other_perms=["view_customermodel", "view_estimatemodel","can_approve_estimatemodel","can_approve_billmodel"], + other_perms=["view_customermodel", "view_estimatemodel","can_view_inventory","can_view_sales","can_view_crm","can_view_financials","can_view_reports"], ) - elif self.name == "Agent": - # Todo : set permissions for agent - pass + def set_permissions(self, app="inventory", allowed_models=[], other_perms=[]): try: @@ -2875,7 +2924,7 @@ class ExtraInfo(models.Model): on_delete=models.CASCADE, related_name="extra_info_primary" ) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=255, null=True, blank=True) content_object = GenericForeignKey('content_type', 'object_id') # Secondary GenericForeignKey (optional additional link) @@ -2886,7 +2935,7 @@ class ExtraInfo(models.Model): blank=True, related_name="extra_info_secondary" ) - related_object_id = models.PositiveIntegerField(null=True, blank=True) + related_object_id = models.CharField(max_length=255, null=True, blank=True) related_object = GenericForeignKey('related_content_type', 'related_object_id') # JSON Data Storage diff --git a/inventory/signals.py b/inventory/signals.py index ef1eb3cf..c62421c3 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -6,6 +6,7 @@ from django.contrib.auth.models import Group from django.db.models.signals import post_save, post_delete from django.dispatch import receiver from django.utils.translation import gettext_lazy as _ +from django.contrib.contenttypes.models import ContentType from django.contrib.auth import get_user_model from django_ledger.io import roles from django_ledger.models import ( @@ -989,8 +990,8 @@ def lead_created_notification(sender, instance, created, **kwargs): @receiver(post_save, sender=EstimateModel) def estimate_in_review_notification(sender, instance, created, **kwargs): if instance.is_review(): - recipients = models.CustomGroup.objects.filter(dealer=instance.dealer,name="Manager").first().group.user_set.exclude(email=instance.dealer.user.email) dealer = models.Dealer.objects.get(entity=instance.entity) + recipients = models.CustomGroup.objects.filter(dealer=dealer,name="Manager").first().group.user_set.exclude(email=dealer.user.email) for recipient in recipients: models.Notification.objects.create( user=recipient, @@ -1003,16 +1004,22 @@ def estimate_in_review_notification(sender, instance, created, **kwargs): @receiver(post_save, sender=EstimateModel) def estimate_in_approve_notification(sender, instance, created, **kwargs): if instance.is_approved(): - recipients = models.CustomGroup.objects.filter(dealer=instance.dealer,name="Manager").first().group.user_set.exclude(email=instance.dealer.user.email) dealer = models.Dealer.objects.get(entity=instance.entity) - for recipient in recipients: - models.Notification.objects.create( - user=recipient, - message=f""" - Estimate {instance.estimate_number} is in review. - Please review and approve it at your earliest convenience. - View - """) + + recipient = models.ExtraInfo.objects.filter( + content_type=ContentType.objects.get_for_model(EstimateModel), + related_content_type=ContentType.objects.get_for_model(models.Staff), + object_id=instance.pk, + ).first() + + models.Notification.objects.create( + user=recipient.related_object.user, + message=f""" + Estimate {instance.estimate_number} has been approved. + View + """ + ) + # @receiver(post_save, sender=models.Lead) # def lead_created_notification(sender, instance, created, **kwargs): diff --git a/inventory/urls.py b/inventory/urls.py index 2b7a0e7c..e57596fe 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -816,6 +816,8 @@ urlpatterns = [ name="bill-update-items", ), ############################################################ + ############################################################ + #BILL MARK AS path( "/items/bills//actions//mark-as-draft/", views.BillModelActionMarkAsDraftView.as_view(), @@ -861,33 +863,7 @@ urlpatterns = [ views.BillModelActionForceMigrateView.as_view(), name="bill-action-force-migrate", ), - # path("items/bills/create/", views.bill_create, name="bill_create"), - path( - "items/bills//bill_detail/", - views.BillDetailView.as_view(), - name="bill_detail", - ), - path("items/bills//delete/", views.BillDeleteView, name="bill_delete"), - path( - "items/bills//in_review/", - views.InReviewBillView.as_view(), - name="in_review_bill", - ), - path( - "items/bills//in_approve/", - views.ApprovedBillModelView.as_view(), - name="in_approve_bill", - ), - path( - "items/bills//mark_as_approved/", - views.bill_mark_as_approved, - name="bill_mark_as_approved", - ), - path( - "items/bills//mark_as_paid/", - views.bill_mark_as_paid, - name="bill_mark_as_paid", - ), + # orders path("orders/", views.OrderListView.as_view(), name="order_list_view"), # BALANCE SHEET Reports... diff --git a/inventory/views.py b/inventory/views.py index c9c42dce..0c75fb71 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -28,6 +28,8 @@ from django.db.models.deletion import RestrictedError from django.http.response import StreamingHttpResponse from django.core.exceptions import ImproperlyConfigured, ValidationError from django.core.exceptions import PermissionDenied +from django.contrib.contenttypes.models import ContentType +from django.views.decorators.http import require_POST # Django from django.db.models import Q from django.conf import settings @@ -974,7 +976,7 @@ class CarColorCreate(LoginRequiredMixin, PermissionRequiredMixin, CreateView): model = models.CarColors form_class = forms.CarColorsForm template_name = "inventory/add_colors.html" - permission_required = ["inventory.view_car"] + permission_required = ["inventory.add_carcolors"] def form_valid(self, form): car = get_object_or_404(models.Car, slug=self.kwargs["slug"]) @@ -1003,7 +1005,7 @@ class CarColorsUpdateView( form_class = forms.CarColorsForm template_name = "inventory/add_colors.html" success_message = _("Car Colors details updated successfully") - permission_required = ["inventory.change_car"] + permission_required = ["inventory.change_carcolors"] def get_object(self, queryset=None): """ @@ -1076,7 +1078,7 @@ class CarListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): template_name = "inventory/car_list_view.html" context_object_name = "cars" paginate_by = 10 - permission_required = "inventory.view_car" + permission_required = "inventory.view_carcolors" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -1146,6 +1148,7 @@ class CarListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): @login_required +@permission_required("inventory.view_car") def inventory_stats_view(request, dealer_slug): """ Handle the inventory stats view for a dealer, displaying detailed information @@ -1672,7 +1675,7 @@ class CarLocationUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateV return reverse_lazy("car_detail", kwargs={"slug": self.object.car.slug}) -class CarTransferCreateView(LoginRequiredMixin, CreateView): +class CarTransferCreateView(LoginRequiredMixin,PermissionRequiredMixin, CreateView): """ A view for creating car transfers. @@ -1692,6 +1695,7 @@ class CarTransferCreateView(LoginRequiredMixin, CreateView): model = models.CarTransfer form_class = forms.CarTransferForm template_name = "inventory/car_transfer_form.html" + permission_required = ["inventory.add_cartransfer"] def get_form(self, form_class=None): form = super().get_form(form_class) @@ -1718,7 +1722,7 @@ class CarTransferCreateView(LoginRequiredMixin, CreateView): return reverse_lazy("car_detail", kwargs={"slug": self.object.car.slug}) -class CarTransferDetailView(LoginRequiredMixin, SuccessMessageMixin, DetailView): +class CarTransferDetailView(LoginRequiredMixin, PermissionRequiredMixin,SuccessMessageMixin, DetailView): """ Provides a detailed view of a specific car transfer record. @@ -1742,6 +1746,7 @@ class CarTransferDetailView(LoginRequiredMixin, SuccessMessageMixin, DetailView) model = models.CarTransfer template_name = "inventory/transfer_details.html" context_object_name = "transfer" + permission_required = ["inventory.view_cartransfer"] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -1750,6 +1755,7 @@ class CarTransferDetailView(LoginRequiredMixin, SuccessMessageMixin, DetailView) @login_required +@permission_required("inventory.change_cartransfer") def car_transfer_approve(request, slug, transfer_pk): """ Approves or cancels a car transfer request based on the action parameter. This view @@ -1794,6 +1800,7 @@ def car_transfer_approve(request, slug, transfer_pk): @login_required +@permission_required("inventory.change_cartransfer") def car_transfer_accept_reject(request, slug, transfer_pk): """ Handles the acceptance or rejection of a car transfer request. Based on the @@ -1840,6 +1847,7 @@ def car_transfer_accept_reject(request, slug, transfer_pk): @login_required +@permission_required("inventory.view_cartransfer") def CarTransferPreviewView(request, slug, transfer_pk): """ Handles the preview of car transfer details and ensures that a user has appropriate @@ -1865,7 +1873,7 @@ def CarTransferPreviewView(request, slug, transfer_pk): return render(request, "inventory/transfer_preview.html", {"transfer": transfer}) -class CustomCardCreateView(LoginRequiredMixin, CreateView): +class CustomCardCreateView(LoginRequiredMixin,PermissionRequiredMixin, CreateView): """ Represents a view for creating a custom card associated with a specific car. @@ -1884,6 +1892,7 @@ class CustomCardCreateView(LoginRequiredMixin, CreateView): model = models.CustomCard form_class = forms.CustomCardForm template_name = "inventory/add_custom_card.html" + permission_required = "inventory.add_customcard" def form_valid(self, form): car = get_object_or_404(models.Car, slug=self.kwargs["slug"]) @@ -1906,7 +1915,7 @@ class CustomCardCreateView(LoginRequiredMixin, CreateView): ) -class CarRegistrationCreateView(LoginRequiredMixin, CreateView): +class CarRegistrationCreateView(LoginRequiredMixin,PermissionRequiredMixin, CreateView): """ Handles the creation of new car registration records. @@ -1932,6 +1941,7 @@ class CarRegistrationCreateView(LoginRequiredMixin, CreateView): model = models.CarRegistration form_class = forms.CarRegistrationForm template_name = "inventory/car_registration_form.html" + permission_required = "inventory.add_carregistration" def form_valid(self, form): car = get_object_or_404(models.Car, slug=self.kwargs["slug"]) @@ -2036,7 +2046,7 @@ def manage_reservation(request, dealer_slug, reservation_id): ) -class DealerDetailView(LoginRequiredMixin, DetailView): +class DealerDetailView(LoginRequiredMixin, PermissionRequiredMixin,DetailView): """ Represents a detailed view for a Dealer model. @@ -2056,6 +2066,7 @@ class DealerDetailView(LoginRequiredMixin, DetailView): model = models.Dealer template_name = "dealers/dealer_detail.html" context_object_name = "dealer" + permission_required = "inventory.view_dealer" def get_queryset(self): return models.Dealer.objects.annotate( @@ -2083,7 +2094,7 @@ class DealerDetailView(LoginRequiredMixin, DetailView): return context -class DealerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): +class DealerUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, UpdateView): """ Handles the update functionality for the Dealer model. @@ -2109,6 +2120,7 @@ class DealerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): template_name = "dealers/dealer_form.html" success_url = reverse_lazy("dealer_detail") success_message = _("Dealer updated successfully") + permission_required = ["inventory.change_dealer"] def get_success_url(self): return reverse("dealer_detail", kwargs={"slug": self.object.slug}) @@ -2147,7 +2159,7 @@ class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): paginate_by = 30 template_name = "customers/customer_list.html" ordering = ["-created"] - permission_required = ["django_ledger.view_customermodel"] + permission_required = ["inventory.view_customer"] def get_queryset(self): query = self.request.GET.get("q") @@ -2182,7 +2194,7 @@ class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView model = models.Customer template_name = "customers/view_customer.html" context_object_name = "customer" - permission_required = ["django_ledger.view_customermodel"] + permission_required = ["inventory.view_customer"] def get_context_data(self, **kwargs): dealer = get_user_type(self.request) @@ -2203,6 +2215,7 @@ class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView @login_required +@permission_required("inventory.add_note", raise_exception=True) def add_note_to_customer(request,dealer_slug, slug): """ This function allows authenticated users to add a note to a specific customer. The @@ -2239,6 +2252,7 @@ def add_note_to_customer(request,dealer_slug, slug): @login_required +@permission_required("inventory.add_activity", raise_exception=True) def add_activity_to_customer(request, pk): """ Adds an activity to a specific customer. @@ -2291,7 +2305,7 @@ class CustomerCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView model = models.Customer form_class = forms.CustomerForm - permission_required = ["django_ledger.add_customermodel"] + permission_required = ["inventory.add_customer"] template_name = "customers/customer_form.html" success_url = reverse_lazy("customer_list") success_message = "Customer created successfully" @@ -2358,7 +2372,7 @@ class CustomerUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView model = models.Customer form_class = forms.CustomerForm - permission_required = ["django_ledger.change_customermodel"] + permission_required = ["inventory.change_customer"] template_name = "customers/customer_form.html" success_url = reverse_lazy("customer_list") success_message = "Customer updated successfully" @@ -2393,7 +2407,7 @@ def delete_customer(request, dealer_slug ,slug): return redirect("customer_list", dealer_slug=dealer_slug) -class VendorListView(LoginRequiredMixin, ListView): +class VendorListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): """ Represents a view for displaying a paginated list of vendors, accessible only to authenticated users. @@ -2424,6 +2438,7 @@ class VendorListView(LoginRequiredMixin, ListView): context_object_name = "vendors" paginate_by = 30 template_name = "vendors/vendors_list.html" + permission_required = ["inventory.view_vendor"] def get_queryset(self): query = self.request.GET.get("q") @@ -2458,6 +2473,7 @@ def vendorDetailView(request, dealer_slug,slug): class VendorCreateView( LoginRequiredMixin, + PermissionRequiredMixin, SuccessMessageMixin, CreateView, ): @@ -2484,6 +2500,7 @@ class VendorCreateView( form_class = forms.VendorForm template_name = "vendors/vendor_form.html" success_message = _("Vendor created successfully") + permission_required = ["inventory.add_vendor"] def form_valid(self, form): if vendor := models.Vendor.objects.filter(email=form.instance.email).first(): @@ -2507,6 +2524,7 @@ class VendorCreateView( class VendorUpdateView( LoginRequiredMixin, + PermissionRequiredMixin, SuccessMessageMixin, UpdateView, ): @@ -2535,6 +2553,7 @@ class VendorUpdateView( form_class = forms.VendorForm template_name = "vendors/vendor_form.html" success_message = _("Vendor updated successfully") + permission_required = ["inventory.change_vendor"] # def get_initial(self): # initial = super().get_initial() @@ -2559,6 +2578,7 @@ class VendorUpdateView( return reverse_lazy("vendor_list", kwargs={"dealer_slug": self.kwargs["dealer_slug"]}) @login_required +@permission_required("inventory.delete_vendor") def delete_vendor(request, dealer_slug,slug): """ Deletes an existing vendor record from the database. @@ -2583,7 +2603,7 @@ def delete_vendor(request, dealer_slug,slug): # group -class GroupListView(LoginRequiredMixin, ListView): +class GroupListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): """ Represents a view for listing groups for a logged-in user. @@ -2608,13 +2628,14 @@ class GroupListView(LoginRequiredMixin, ListView): context_object_name = "groups" paginate_by = 10 template_name = "groups/group_list.html" + permission_required = ["inventory.view_customgroup"] def get_queryset(self): dealer = get_object_or_404(models.Dealer,slug=self.kwargs["dealer_slug"]) return dealer.groups.all() -class GroupDetailView(LoginRequiredMixin, DetailView): +class GroupDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): """ Represents the detail view for a specific group. @@ -2636,10 +2657,12 @@ class GroupDetailView(LoginRequiredMixin, DetailView): model = models.CustomGroup template_name = "groups/group_detail.html" context_object_name = "group" + permission_required = ["inventory.view_customgroup"] class GroupCreateView( LoginRequiredMixin, + PermissionRequiredMixin, SuccessMessageMixin, CreateView, ): @@ -2666,6 +2689,7 @@ class GroupCreateView( form_class = forms.GroupForm template_name = "groups/group_form.html" success_message = _("Group created successfully") + permission_required = ["inventory.add_customgroup"] def form_valid(self, form): dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"]) @@ -2695,6 +2719,7 @@ class GroupCreateView( class GroupUpdateView( LoginRequiredMixin, + PermissionRequiredMixin, SuccessMessageMixin, UpdateView, ): @@ -2724,6 +2749,7 @@ class GroupUpdateView( form_class = forms.GroupForm template_name = "groups/group_form.html" success_message = _("Group updated successfully") + permission_required = ["inventory.change_customgroup"] def form_valid(self, form): dealer = get_user_type(self.request) @@ -2756,7 +2782,7 @@ def GroupDeleteview(request, dealer_slug,pk): return redirect("group_list",dealer_slug=dealer_slug) -@login_required +# @login_required # def GroupPermissionView(request, dealer_slug, pk): # # Verify dealer and group exist # get_object_or_404(models.Dealer, slug=dealer_slug) @@ -2794,6 +2820,8 @@ def GroupDeleteview(request, dealer_slug,pk): # "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)) # }) +@login_required +@permission_required("inventory.change_customgroup") def GroupPermissionView(request, dealer_slug, pk): from django.contrib.contenttypes.models import ContentType from django.db import transaction @@ -3024,6 +3052,7 @@ def GroupPermissionView(request, dealer_slug, pk): # Users @login_required +@permission_required("inventory.change_staff") def UserGroupView(request, dealer_slug,slug): """ Handles the assignment of user groups to a specific staff member. This view @@ -3061,7 +3090,7 @@ def UserGroupView(request, dealer_slug,slug): return render(request, "users/user_group_form.html", {"staff": staff, "form": form}) -class UserListView(LoginRequiredMixin, ListView): +class UserListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): """ Represents a view for listing users with pagination and filters. @@ -3085,6 +3114,7 @@ class UserListView(LoginRequiredMixin, ListView): context_object_name = "users" paginate_by = 10 template_name = "users/user_list.html" + permission_required = ["inventory.view_staff"] def get_queryset(self): query = self.request.GET.get("q") @@ -3093,7 +3123,7 @@ class UserListView(LoginRequiredMixin, ListView): return apply_search_filters(staff, query) -class UserDetailView(LoginRequiredMixin, DetailView): +class UserDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): """ Represents a detailed view for displaying user-specific information. @@ -3114,10 +3144,11 @@ class UserDetailView(LoginRequiredMixin, DetailView): model = models.Staff template_name = "users/user_detail.html" context_object_name = "user_" - + permission_required = ["inventory.view_staff"] class UserCreateView( LoginRequiredMixin, + PermissionRequiredMixin, SuccessMessageMixin, CreateView, ): @@ -3149,6 +3180,7 @@ class UserCreateView( template_name = "users/user_form.html" success_url = reverse_lazy("user_list") success_message = _("User created successfully") + permission_required = ["inventory.add_staff"] def get_form(self, form_class=None): form = super().get_form(form_class) @@ -3206,6 +3238,7 @@ class UserCreateView( class UserUpdateView( LoginRequiredMixin, + PermissionRequiredMixin, SuccessMessageMixin, UpdateView, ): @@ -3235,6 +3268,7 @@ class UserUpdateView( template_name = "users/user_form.html" success_url = reverse_lazy("user_list") success_message = _("User updated successfully") + permission_required = ["inventory.change_staff"] def get_form_kwargs(self): kwargs = super().get_form_kwargs() @@ -3275,6 +3309,7 @@ class UserUpdateView( return reverse_lazy("user_list", args=[self.request.dealer.slug]) @login_required +@permission_required("inventory.delete_staff", login_url="login") def UserDeleteview(request, dealer_slug,slug): """ Deletes a user and its associated staff member from the database and redirects @@ -3293,7 +3328,7 @@ def UserDeleteview(request, dealer_slug,slug): return redirect("user_list",dealer_slug=dealer_slug) -class OrganizationListView(LoginRequiredMixin, ListView): +class OrganizationListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): """ Represents a view to display a paginated list of organizations for a dealer. @@ -3317,6 +3352,7 @@ class OrganizationListView(LoginRequiredMixin, ListView): template_name = "organizations/organization_list.html" context_object_name = "organizations" paginate_by = 20 + permission_required = ["inventory.view_organization"] def get_queryset(self): query = self.request.GET.get("q") @@ -3326,7 +3362,7 @@ class OrganizationListView(LoginRequiredMixin, ListView): return apply_search_filters(organization, query) -class OrganizationDetailView(LoginRequiredMixin, DetailView): +class OrganizationDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): """ Handles displaying detailed information about an organization. @@ -3348,6 +3384,7 @@ class OrganizationDetailView(LoginRequiredMixin, DetailView): model = models.Organization template_name = "organizations/organization_detail.html" context_object_name = "organization" + permission_required = ["inventory.view_organization"] class OrganizationCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): """ @@ -3367,7 +3404,7 @@ class OrganizationCreateView(LoginRequiredMixin, PermissionRequiredMixin, Create model = models.Organization form_class = forms.OrganizationForm - permission_required = ["django_ledger.add_customermodel"] + permission_required = ["inventory.add_organization"] template_name = "organizations/organization_form.html" success_url = reverse_lazy("organization_list") success_message = "Organization created successfully" @@ -3421,7 +3458,7 @@ class OrganizationUpdateView(LoginRequiredMixin, PermissionRequiredMixin, Update model = models.Organization form_class = forms.OrganizationForm - permission_required = ["django_ledger.change_customermodel"] + permission_required = ["inventory.change_organization"] template_name = "organizations/organization_form.html" success_url = reverse_lazy("organization_list") success_message = "Organization updated successfully" @@ -3435,6 +3472,7 @@ class OrganizationUpdateView(LoginRequiredMixin, PermissionRequiredMixin, Update return reverse_lazy("organization_list", kwargs={"dealer_slug": self.kwargs["dealer_slug"]}) @login_required +@permission_required("inventory.delete_organization") def OrganizationDeleteView(request,dealer_slug, slug): """ Handles the deletion of an organization based on the provided primary key (pk). Looks up @@ -3540,7 +3578,7 @@ class RepresentativeCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateVi def form_valid(self, form): if form.is_valid(): - form.instance.dealer = self.request.user.dealer + form.instance.dealer = self.request.dealer form.save() return super().form_valid(form) else: @@ -3677,7 +3715,7 @@ class BankAccountCreateView( form_class = BankAccountCreateForm template_name = "ledger/bank_accounts/bank_account_form.html" success_message = _("Bank account created successfully") - permission_required = ["inventory.view_carfinance"] + permission_required = ["django_ledger.view_bankaccountmodel"] def form_valid(self, form): dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"]) @@ -3731,7 +3769,7 @@ class BankAccountDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailV model = BankAccountModel template_name = "ledger/bank_accounts/bank_account_detail.html" context_object_name = "bank_account" - permission_required = ["inventory.view_carfinance"] + permission_required = ["django_ledger.view_bankaccountmodel"] class BankAccountUpdateView( @@ -3764,7 +3802,7 @@ class BankAccountUpdateView( form_class = BankAccountUpdateForm template_name = "ledger/bank_accounts/bank_account_form.html" success_message = _("Bank account updated successfully") - permission_required = ["inventory.view_carfinance"] + permission_required = ["django_ledger.view_bankaccountmodel"] def get_form_kwargs(self): dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"]) @@ -3793,6 +3831,7 @@ class BankAccountUpdateView( @login_required +@permission_required("django_ledger.delete_bankaccountmodel", raise_exception=True) def bank_account_delete(request, dealer_slug, pk): """ Delete a bank account entry from the database. @@ -3851,8 +3890,7 @@ class AccountListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): model = AccountModel template_name = "ledger/coa_accounts/account_list.html" context_object_name = "accounts" - - permission_required = ["inventory.view_carfinance"] + permission_required = ["django_ledger.view_accountmodel"] def get_queryset(self): query = self.request.GET.get("q") @@ -3900,7 +3938,7 @@ class AccountCreateView( form_class = AccountModelCreateForm template_name = "ledger/coa_accounts/account_form.html" success_message = _("Account created successfully") - permission_required = ["inventory.view_carfinance"] + permission_required = ["django_ledger.add_accountmodel"] def form_valid(self, form): dealer = get_user_type(self.request) @@ -3958,7 +3996,7 @@ class AccountDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView) context_object_name = "account" slug_field = "uuid" DEFAULT_TXS_DAYS = 30 - permission_required = ["inventory.view_carfinance"] + permission_required = ["django_ledger.view_accountmodel"] extra_context = { "DEFAULT_TXS_DAYS": DEFAULT_TXS_DAYS, "header_subtitle_icon": "ic:round-account-tree", @@ -4018,7 +4056,7 @@ class AccountUpdateView( form_class = AccountModelUpdateForm template_name = "ledger/coa_accounts/account_form.html" success_message = _("Account updated successfully") - permission_required = ["inventory.view_carfinance"] + permission_required = ["django_ledger.view_accountmodel"] def get_form(self, form_class=None): form = super().get_form(form_class) @@ -4033,7 +4071,7 @@ class AccountUpdateView( @login_required -@permission_required("inventory.view_carfinance") +@permission_required("django_ledger.delete_accountmodel") def account_delete(request, dealer_slug, pk): """ Handles the deletion of an account object identified by its primary key (pk). Ensures @@ -4058,7 +4096,7 @@ def account_delete(request, dealer_slug, pk): # Sales list @login_required -@permission_required("inventory.view_lead", raise_exception=True) +@permission_required("inventory.view_saleorder", raise_exception=True) def sales_list_view(request, dealer_slug): """ Handles the retrieval and presentation of a paginated list of item transactions for @@ -4087,10 +4125,11 @@ def sales_list_view(request, dealer_slug): return render(request, "sales/sales_list.html", context) -class SaleOrderDetailView(LoginRequiredMixin, DetailView): +class SaleOrderDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): model = models.SaleOrder template_name = "sales/saleorder_detail.html" context_object_name = "sale_order" + permission_required = ["inventory.view_saleorder"] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -4137,6 +4176,24 @@ class EstimateListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): paginate_by = 20 permission_required = ["django_ledger.view_estimatemodel"] + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + staff = getattr(self.request.user.staffmember, "staff", None) + dealer = getattr(self.request.user, "dealer", None) + + if dealer: + qs = models.ExtraInfo.objects.filter( + content_type=ContentType.objects.get_for_model(EstimateModel), + related_content_type=ContentType.objects.get_for_model(models.Staff), + ) + else: + qs = models.ExtraInfo.objects.filter( + content_type=ContentType.objects.get_for_model(EstimateModel), + related_content_type=ContentType.objects.get_for_model(models.Staff), + related_object_id=staff.pk if staff else self.request.user.pk, + ) + context["staff_estimates"] = qs + return context def get_queryset(self): dealer = get_user_type(self.request) entity = dealer.entity @@ -4314,14 +4371,14 @@ def create_estimate(request, dealer_slug, slug=None): for item in estimate_itemtxs.keys(): item_instance = ItemModel.objects.filter(item_number=item).first() instance = models.Car.objects.get(vin=item_instance.name) - reserve_car(instance, request) + # reserve_car(instance, request) #TODO else: item_instance = ItemModel.objects.filter( additioinal_info__car_info__hash=items ).first() instance = models.Car.objects.get(hash=item) - reserve_car(instance, request) + # reserve_car(instance, request) #TODO opportunity_id = data.get("opportunity_id") if opportunity_id != "None": @@ -4329,16 +4386,19 @@ def create_estimate(request, dealer_slug, slug=None): opportunity.estimate = estimate opportunity.save() - # models.ExtraInfo.objects.create( - # content_object=estimate, - # data={ - # "customer": { - # "name": customer.name, - # "phone": customer.phone, - # "email": customer.email, - # } - # }, - # ) + if staff:=getattr(request.user.staffmember,'staff',None): + models.ExtraInfo.objects.create( + content_object=estimate, + related_object=staff, + created_by=request.user, + ) + else: + models.ExtraInfo.objects.create( + content_object=estimate, + related_object=request.user, + created_by=request.user, + ) + url = reverse( "estimate_detail", kwargs={"dealer_slug": dealer.slug, "pk": estimate.pk} ) @@ -4519,10 +4579,11 @@ def create_sale_order(request, dealer_slug, pk): ) -class SaleOrderDetail(DetailView): +class SaleOrderDetail(LoginRequiredMixin,PermissionRequiredMixin,DetailView): model = models.SaleOrder template_name = "sales/orders/order_details.html" context_object_name = "saleorder" + permission_required = ["inventory.view_saleorder"] def get_object(self, queryset=None): order_pk = self.kwargs.get("order_pk") @@ -4541,6 +4602,7 @@ class SaleOrderDetail(DetailView): @login_required +@permission_required("inventory.view_saleorder", raise_exception=True) def preview_sale_order(request, dealer_slug, pk): """ Handles rendering of the sale order preview page for a specific estimate. @@ -4640,6 +4702,7 @@ class EstimatePreviewView(LoginRequiredMixin, PermissionRequiredMixin, DetailVie @login_required +@permission_required("django_ledger.change_estimatemodel", raise_exception=True) def estimate_mark_as(request, dealer_slug, pk): """ Marks an estimate with a specified status based on the requested action and @@ -4680,6 +4743,7 @@ def estimate_mark_as(request, dealer_slug, pk): "estimate_detail", dealer_slug=dealer.slug, pk=estimate.pk ) estimate.mark_as_approved() + estimate.save() messages.success(request, _("Quotation approved successfully")) return redirect( "estimate_list", dealer_slug=dealer.slug @@ -5386,6 +5450,7 @@ class LeadDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): permission_required = ["inventory.view_lead"] def get_context_data(self, **kwargs): + dealer = get_object_or_404(models.Dealer,slug=self.kwargs["dealer_slug"]) context = super().get_context_data(**kwargs) context["notes"] = models.Notes.objects.filter( content_type__model="lead", object_id=self.object.id @@ -5410,8 +5475,8 @@ class LeadDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): context["transfer_form"].fields[ "transfer_to" ].queryset = models.Staff.objects.filter( - dealer=self.object.dealer, staff_type="sales" - ) + dealer=dealer,staff_member__user__groups__permissions__codename__contains="add_lead").exclude(staff_member__user=self.request.user).distinct() + context["activity_form"] = forms.ActivityForm() context["staff_task_form"] = forms.StaffTaskForm() context["note_form"] = forms.NoteForm() @@ -5537,15 +5602,16 @@ def lead_create(request,dealer_slug): return render(request, "crm/leads/lead_form.html", {"form": form}) - +@login_required +@permission_required("inventory.view_lead", raise_exception=True) def lead_tracking(request,dealer_slug): dealer = get_object_or_404(models.Dealer,slug=dealer_slug) staff = models.Staff.objects.filter(dealer=dealer, staff_member__user=request.user).first() if staff: - qs = models.Lead.objects.filter(dealer=dealer,staff=staff).exclude(status="converted") + qs = models.Lead.objects.filter(dealer=dealer,staff=staff) else: - qs = models.Lead.objects.filter(dealer=dealer).exclude(status="converted") + qs = models.Lead.objects.filter(dealer=dealer) won = qs.filter(status="won") new = qs.filter(status="new") @@ -5564,6 +5630,8 @@ def lead_tracking(request,dealer_slug): # @require_POST +@login_required +@permission_required("inventory.change_lead", raise_exception=True) def update_lead_actions(request,dealer_slug): try: lead_id = request.POST.get("lead_id") @@ -5679,6 +5747,7 @@ def LeadDeleteView(request,dealer_slug, slug): @login_required +@permission_required("inventory.add_note", raise_exception=True) def add_note_to_lead(request,dealer_slug, slug): """ Adds a note to a specific lead. This view is accessible only to authenticated @@ -5711,6 +5780,7 @@ def add_note_to_lead(request,dealer_slug, slug): @login_required +@permission_required("inventory.add_note", raise_exception=True) def add_note_to_opportunity(request,dealer_slug, slug): """ Add a note to a specific opportunity identified by its primary key. @@ -5743,6 +5813,7 @@ def add_note_to_opportunity(request,dealer_slug, slug): @login_required +@permission_required("inventory.delete_note", raise_exception=True) def delete_note(request,dealer_slug, pk): """ Deletes a specific note created by the currently logged-in user and redirects @@ -5803,7 +5874,7 @@ def lead_convert(request,dealer_slug, slug): @login_required -@permission_required("inventory.add_lead", raise_exception=True) +@permission_required("inventory.add_schedule", raise_exception=True) def schedule_lead(request, dealer_slug,slug): """ Handles the scheduling of a lead for an appointment. @@ -6032,6 +6103,7 @@ def send_lead_email(request,dealer_slug, slug, email_pk=None): @login_required +@permission_required("inventory.add_activity", raise_exception=True) def add_activity_to_lead(request, pk): """ Handles the process of adding a new activity to a specific lead. This includes @@ -6063,7 +6135,7 @@ def add_activity_to_lead(request, pk): return render(request, "crm/add_activity.html", {"form": form, "lead": lead}) -class OpportunityCreateView(CreateView, SuccessMessageMixin, LoginRequiredMixin): +class OpportunityCreateView(LoginRequiredMixin,PermissionRequiredMixin,CreateView, SuccessMessageMixin): """ Handles the creation of Opportunity instances through a form while enforcing specific user access control and initial data population. This view ensures @@ -6087,6 +6159,7 @@ class OpportunityCreateView(CreateView, SuccessMessageMixin, LoginRequiredMixin) form_class = forms.OpportunityForm template_name = "crm/opportunities/opportunity_form.html" success_message = "Opportunity created successfully." + permission_required = ["inventory.add_opportunity"] def get_initial(self): initial = super().get_initial() @@ -6118,7 +6191,7 @@ class OpportunityCreateView(CreateView, SuccessMessageMixin, LoginRequiredMixin) return reverse_lazy("opportunity_detail", kwargs={"dealer_slug":self.kwargs.get("dealer_slug"),"slug": self.object.slug}) -class OpportunityUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): +class OpportunityUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, UpdateView): """ Handles the update functionality for Opportunity objects. @@ -6145,6 +6218,7 @@ class OpportunityUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView) form_class = forms.OpportunityForm template_name = "crm/opportunities/opportunity_form.html" success_message = "Opportunity updated successfully." + permission_required = ["inventory.change_opportunity"] def get_form(self, form_class=None): form = super().get_form(form_class) @@ -6157,7 +6231,7 @@ class OpportunityUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView) return reverse_lazy("opportunity_detail", kwargs={"dealer_slug":self.kwargs.get("dealer_slug"),"slug": self.object.slug}) -class OpportunityDetailView(LoginRequiredMixin, DetailView): +class OpportunityDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): """ Handles the detailed view of an Opportunity object. @@ -6177,6 +6251,7 @@ class OpportunityDetailView(LoginRequiredMixin, DetailView): model = models.Opportunity template_name = "crm/opportunities/opportunity_detail.html" context_object_name = "opportunity" + permission_required = ["inventory.view_opportunity"] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -6231,11 +6306,12 @@ class OpportunityDetailView(LoginRequiredMixin, DetailView): return context -class OpportunityListView(LoginRequiredMixin, ListView): +class OpportunityListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): model = models.Opportunity template_name = "crm/opportunities/opportunity_list.html" context_object_name = "opportunities" paginate_by = 30 + permission_required = ["inventory.view_opportunity"] def get_queryset(self): dealer = get_user_type(self.request) @@ -6275,6 +6351,7 @@ class OpportunityListView(LoginRequiredMixin, ListView): @login_required +@permission_required("inventory.delete_opportunity", raise_exception=True) def delete_opportunity(request,dealer_slug, pk): """ Deletes an opportunity object from the database and redirects to the opportunity @@ -6296,6 +6373,7 @@ def delete_opportunity(request,dealer_slug, pk): @login_required +@permission_required("inventory.change_opportunity", raise_exception=True) def opportunity_update_status(request,dealer_slug, slug): """ Update the status and/or stage of a specific Opportunity instance. This is a @@ -6692,353 +6770,7 @@ class BillListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): return context -class BillDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): - """ - Handles the functionality for viewing detailed information about a single bill. - - This class allows logged-in users with the required permissions to view specific bills - along with their attributes and calculated data provided in the context, such as a list - of detailed item transactions and a computed grand total. - - :ivar model: The model associated with the view, which in this case represents bills. - :type model: BillModel - :ivar template_name: The template used for rendering the bill detail view. - :type template_name: str - :ivar context_object_name: The name of the context object passed to the template. - :type context_object_name: str - :ivar permission_required: A list of permissions required to access this view. - :type permission_required: list[str] - """ - - model = BillModel - template_name = "bill/bill_detail.html" - context_object_name = "bill" - permission_required = ["django_ledger.view_billmodel"] - - def get_context_data(self, **kwargs): - dealer = get_user_type(self.request) - bill = kwargs.get("object") - if bill.get_itemtxs_data(): - txs = bill.get_itemtxs_data()[0] - - transactions = [ - { - "item": x, - "total": Decimal(x.unit_cost) * Decimal(x.quantity), - } - for x in txs - ] - grand_total = sum(Decimal(x.unit_cost) * Decimal(x.quantity) for x in txs) - - kwargs["transactions"] = transactions - kwargs["grand_total"] = grand_total - kwargs["entity"] = dealer.entity - - return super().get_context_data(**kwargs) - - -class InReviewBillView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): - """ - Handles the in-review update functionality for the BillModel. - - This view is responsible for managing the update operations of bills that are in the - "in-review" state. It ensures all updates are performed according to the associated - permissions, and checks the user's type for processing additional context and logic. - The view also enforces that only authorized users with the required permission can - interact with it. - - :ivar model: The model being used for this view, representing the BillModel. - :type model: BillModel - :ivar form_class: The form class used to handle the update of bill data. - :type form_class: InReviewBillModelUpdateForm - :ivar template_name: The template used to render the bill update form. - :type template_name: str - :ivar success_url: The URL to redirect to upon successful bill update. - :type success_url: str - :ivar success_message: A message displayed upon successfully updating the bill. - :type success_message: str - :ivar context_object_name: The context variable name for the view's object. - :type context_object_name: str - :ivar permission_required: The list of permissions required to use this view. - :type permission_required: list - """ - - model = BillModel - form_class = InReviewBillModelUpdateForm - template_name = "ledger/bills/bill_update_form.html" - success_url = reverse_lazy("bill_list") - success_message = _("Bill updated successfully") - context_object_name = "bill" - permission_required = ["django_ledger.change_billmodel"] - - def get_form_kwargs(self): - kwargs = super().get_form_kwargs() - dealer = get_user_type(self.request) - kwargs["entity_model"] = dealer.entity - kwargs["user_model"] = dealer.entity.admin - return kwargs - - def get_success_url(self): - return reverse_lazy("bill_detail", kwargs={"pk": self.kwargs["pk"]}) - - def form_valid(self, form): - dealer = get_user_type(self.request) - form.instance.entity = dealer.entity - self.object.mark_as_review() - return super().form_valid(form) - - -class ApprovedBillModelView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): - """ - Represents a view to update and approve a bill. - - This class provides the functionality to handle the update and - approval process of a bill within the system. It ensures only - authorized users can access and manage the approval. The view - uses specific permissions and form classes to manage this - process. Additionally, it provides feedback upon successful - completion of the operation, such as updating or approving the - bill. - - :ivar model: The Django model associated with this view. - :type model: BillModel - :ivar form_class: The form class to use for handling bill updates. - :type form_class: ApprovedBillModelUpdateForm - :ivar template_name: The path to the HTML template for this view. - :type template_name: str - :ivar success_url: The redirect destination upon successful form submission. - :type success_url: str - :ivar success_message: The success message to display upon successful form submission. - :type success_message: str - :ivar context_object_name: The name of the context object. - :type context_object_name: str - :ivar permission_required: A list of permissions required to access this view. - :type permission_required: list - """ - - model = BillModel - form_class = ApprovedBillModelUpdateForm - template_name = "ledger/bills/bill_update_form.html" - success_url = reverse_lazy("bill_list") - success_message = _("Bill updated successfully") - context_object_name = "bill" - permission_required = ["django_ledger.change_billmodel"] - - def get_form_kwargs(self): - kwargs = super().get_form_kwargs() - dealer = get_user_type(self.request) - kwargs["entity_model"] = dealer.entity - kwargs["user_model"] = dealer.entity.admin - return kwargs - - def get_success_url(self): - return reverse_lazy("bill_detail", kwargs={"pk": self.kwargs["pk"]}) - - def form_valid(self, form): - dealer = get_user_type(self.request) - form.instance.entity = dealer.entity - if not self.object.is_approved(): - self.object.mark_as_approved(user_model=dealer.entity.admin) - return super().form_valid(form) - - -@login_required -@permission_required("django_ledger.change_billmodel", raise_exception=True) -def bill_mark_as_approved(request, pk): - """ - Marks a bill as approved for the given bill ID (primary key) if it is not - already approved. This action can only be completed by an authorized user - with the corresponding permissions. Once the bill is approved, it is saved - and a success message is displayed. If the bill is already approved, an - error message is shown instead. - - :param request: HttpRequest object representing the current request. - :param pk: Primary key of the bill to be marked as approved. - :return: HttpResponseRedirect to the bill detail page after the operation is - completed or an error/success message is set. - """ - bill = get_object_or_404(BillModel, pk=pk) - if request.method == "POST": - dealer = get_user_type(request) - if bill.is_approved(): - messages.error(request, _("Bill is already approved")) - return redirect("bill_detail", pk=bill.pk) - bill.mark_as_approved(user_model=dealer.entity.admin) - bill.save() - messages.success(request, _("Bill marked as approved successfully")) - return redirect("bill_detail", pk=bill.pk) - - -@login_required -@permission_required("django_ledger.change_billmodel", raise_exception=True) -def bill_mark_as_paid(request, pk): - """ - Marks a bill as paid if certain conditions are met and updates the ledger accordingly. - - This function is used to mark a specific bill as paid. It verifies whether the bill - is already paid or if the amount paid matches the amount due. If the conditions are - met, it updates the bill's status, locks the journal entries in the associated ledger, - posts them, and saves the changes. Appropriate success or error messages are displayed - to the user, and the user is redirected to the bill details page. - - :param request: The HTTP request object containing details about the request made by the user. - :type request: HttpRequest - :param pk: The primary key of the bill to be marked as paid. - :type pk: int - :return: A redirect response to the bill details page. - :rtype: HttpResponseRedirect - """ - bill = get_object_or_404(BillModel, pk=pk) - if request.method == "POST": - dealer = get_user_type(request) - if bill.is_paid(): - messages.error(request, _("Bill is already paid")) - return redirect("bill_detail", pk=bill.pk) - if bill.amount_due == bill.amount_paid: - bill.mark_as_paid(user_model=dealer.entity.admin) - bill.save() - bill.ledger.lock_journal_entries() - bill.ledger.post_journal_entries() - bill.ledger.post() - bill.ledger.save() - messages.success(request, _("Bill marked as paid successfully")) - else: - messages.error(request, _("Amount paid is not equal to amount due")) - return redirect("bill_detail", pk=bill.pk) - - -# class BillModelCreateViewView(Create): -# template_name = 'bill/bill_create.html' - -# def get_context_data(self, **kwargs): -# context = super().get_context_data(**kwargs) -# context["entity"] = get_user_type(self.request).entity -# return context - - -# def post(self, request, *args, **kwargs): -# """ -# Completely override the post method to ensure our form handling is called -# """ -# print("Custom post method called") # Debugging -# form = self.get_form() -# if form.is_valid(): -# return self.custom_form_valid(form) -# else: -# return self.form_invalid(form) - -# def custom_form_valid(self, form): -# """ -# Our own form_valid implementation that will definitely be called -# """ -# print("Custom form_valid called") # Debugging - -# # Handle PO case -# if self.for_purchase_order: -# print("Handling PO case") # Debugging -# return self.handle_po_case(form) - -# # Handle Estimate case -# if self.for_estimate: -# print("Handling Estimate case") # Debugging -# return self.handle_estimate_case(form) - -# # Default case -# print("Handling default case") # Debugging -# return self.handle_default_case(form) - -# def handle_po_case(self, form): -# """Special handling for purchase orders""" -# po_pk = self.kwargs['po_pk'] -# item_uuids = self.request.GET.get('item_uuids', '').split(',') - -# if not item_uuids or not all(item_uuids): -# return HttpResponseBadRequest() - -# # Create bill -# bill_model = form.save(commit=False) -# ledger_model, bill_model = bill_model.configure( -# entity_slug=self.AUTHORIZED_ENTITY_MODEL, -# commit_ledger=True -# ) - -# # Get PO -# po_qs = PurchaseOrderModel.objects.for_entity( -# entity_slug=self.kwargs['entity_slug'], -# user_model=self.request.user -# ) -# po_model = get_object_or_404(po_qs, uuid__exact=po_pk) - -# # Validate -# try: -# bill_model.can_bind_po(po_model, raise_exception=True) -# except ValidationError as e: -# messages.error(self.request, e.message) -# return self.render_to_response(self.get_context_data(form=form)) - -# # Update models -# po_model_items_qs = po_model.itemtransactionmodel_set.filter(uuid__in=item_uuids) -# if po_model.is_contract_bound(): -# bill_model.ce_model_id = po_model.ce_model_id - -# bill_model.update_amount_due() -# bill_model.get_state(commit=True) -# bill_model.clean() -# bill_model.save() -# po_model_items_qs.update(bill_model=bill_model) - -# # Redirect to our custom URL -# return HttpResponseRedirect( -# reverse('purchase_order_update', -# kwargs={ -# 'entity_slug': self.kwargs['entity_slug'], -# 'po_pk': po_pk -# }) -# ) - -# def handle_estimate_case(self, form): -# """Special handling for estimates""" -# bill_model = form.save(commit=False) -# ledger_model, bill_model = bill_model.configure( -# entity_slug=self.AUTHORIZED_ENTITY_MODEL, -# commit_ledger=True -# ) - -# ce_pk = self.kwargs['ce_pk'] -# estimate_model_qs = EstimateModel.objects.for_entity( -# entity_slug=self.kwargs['entity_slug'], -# user_model=self.request.user -# ) -# estimate_model = get_object_or_404(estimate_model_qs, uuid__exact=ce_pk) -# bill_model.bind_estimate(estimate_model=estimate_model, commit=True) - -# # Redirect to our custom URL -# return HttpResponseRedirect( -# reverse('estimate_detail', -# kwargs={ -# 'pk': ce_pk -# }) -# ) - -# def handle_default_case(self, form): -# """Default form handling""" -# bill_model = form.save(commit=False) -# ledger_model, bill_model = bill_model.configure( -# entity_slug=self.AUTHORIZED_ENTITY_MODEL, -# commit_ledger=True -# ) -# bill_model.save() - - -# # Redirect to our custom URL -# return HttpResponseRedirect( -# reverse('bill-detail', -# kwargs={ -# 'entity_slug': self.kwargs['entity_slug'], -# 'bill_pk': bill_model.uuid -# }) -# ) -class BillModelCreateView(CreateView): +class BillModelCreateView(LoginRequiredMixin,PermissionRequiredMixin,CreateView): template_name = "bill/bill_create.html" PAGE_TITLE = _("Create Bill") extra_context = { @@ -7048,6 +6780,7 @@ class BillModelCreateView(CreateView): } for_purchase_order = False for_estimate = False + permission_required = "django_ledger.add_billmodel" def get(self, request, dealer_slug, **kwargs): dealer = get_object_or_404(models.Dealer, slug=dealer_slug) @@ -7448,25 +7181,25 @@ class BillModelUpdateViewView(BillModelUpdateView): # return render(request, "ledger/bills/bill_form.html", context) -@login_required -@permission_required("django_ledger.delete_billmodel", raise_exception=True) -def BillDeleteView(request, pk): - """ - Deletes a specific BillModel instance based on the primary key (pk). - This view requires the user to be logged in and have the appropriate - permission to delete a bill. After the delete operation is successful, - the user is redirected to the bill list view. +# @login_required +# @permission_required("django_ledger.delete_billmodel", raise_exception=True) +# def BillDeleteView(request, pk): +# """ +# Deletes a specific BillModel instance based on the primary key (pk). +# This view requires the user to be logged in and have the appropriate +# permission to delete a bill. After the delete operation is successful, +# the user is redirected to the bill list view. - :param request: The HTTP request object. - :type request: HttpRequest - :param pk: Primary key of the BillModel instance to be deleted. - :type pk: int - :return: Redirect to the bill list view after successful deletion. - :rtype: HttpResponseRedirect - """ - bill = get_object_or_404(BillModel, pk=pk) - bill.delete() - return redirect("bill_list") +# :param request: The HTTP request object. +# :type request: HttpRequest +# :param pk: Primary key of the BillModel instance to be deleted. +# :type pk: int +# :return: Redirect to the bill list view after successful deletion. +# :rtype: HttpResponseRedirect +# """ +# bill = get_object_or_404(BillModel, pk=pk) +# bill.delete() +# return redirect("bill_list") # orders @@ -7563,7 +7296,7 @@ def send_email_view(request, dealer_slug, pk): msg, ) - estimate.mark_as_review() + # estimate.mark_as_review() messages.success(request, _("Email sent successfully")) return redirect("estimate_detail", dealer_slug=dealer.slug, pk=estimate.pk) @@ -8514,6 +8247,7 @@ def schedule_cancel(request,dealer_slug, pk): @login_required +@permission_required("inventory.change_dealer", raise_exception=True) def assign_car_makes(request,dealer_slug): """ Assigns car makes to a dealer. @@ -8555,7 +8289,7 @@ def assign_car_makes(request,dealer_slug): return render(request, "dealers/assign_car_makes.html", {"form": form}) -class LedgerModelListView(LoginRequiredMixin, ListView, ArchiveIndexView): +class LedgerModelListView(LoginRequiredMixin,PermissionRequiredMixin, ListView, ArchiveIndexView): """ Provides a view for listing ledger entries in the system. @@ -8595,6 +8329,7 @@ class LedgerModelListView(LoginRequiredMixin, ListView, ArchiveIndexView): show_visible = False allow_empty = True paginate_by = 30 + permission_required = "ledger.view_ledgermodel" def get_queryset(self): qs = super().get_queryset() @@ -8618,7 +8353,7 @@ class LedgerModelListView(LoginRequiredMixin, ListView, ArchiveIndexView): # class LedgerModelDetailView(InvoiceModelDetailViewBase): -class LedgerModelDetailView(LoginRequiredMixin, DetailView): +class LedgerModelDetailView(LoginRequiredMixin, PermissionRequiredMixin,DetailView): """ This class provides a detailed view of a specific ledger entry. @@ -8639,6 +8374,7 @@ class LedgerModelDetailView(LoginRequiredMixin, DetailView): model = LedgerModel context_object_name = "ledger" template_name = "ledger/ledger/ledger_detail.html" + permission_required = "ledger.view_ledgermodel" class LedgerModelCreateView(LedgerModelCreateViewBase): @@ -8737,7 +8473,7 @@ class LedgerModelDeleteView(LedgerModelDeleteViewBase, SuccessMessageMixin): # return super().form_valid(form) -class JournalEntryListView(LoginRequiredMixin, ListView): +class JournalEntryListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): """ Represents a view to list all journal entries for a specific ledger. @@ -8760,6 +8496,7 @@ class JournalEntryListView(LoginRequiredMixin, ListView): context_object_name = "journal_entries" template_name = "ledger/journal_entry/journal_entry_list.html" ordering = ["-timestamp"] + permission_required = "ledger.view_ledger" def get_queryset(self): qs = super().get_queryset() @@ -8773,7 +8510,7 @@ class JournalEntryListView(LoginRequiredMixin, ListView): return context -class JournalEntryCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): +class JournalEntryCreateView(LoginRequiredMixin, PermissionRequiredMixin,SuccessMessageMixin, CreateView): """ View for creating a new journal entry. @@ -8799,6 +8536,7 @@ class JournalEntryCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView form_class = forms.JournalEntryModelCreateForm ledger_model = None success_message = _("Journal Entry created") + permission_required = ["django_ledger.add_journalentrymodel"] def get_form(self, form_class=None): dealer = get_user_type(self.request) @@ -8819,6 +8557,8 @@ class JournalEntryCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView return reverse("journalentry_list", kwargs={"dealer_slug":self.kwargs["dealer_slug"],"pk": ledger.pk}) +@login_required +@permission_required("django_ledger.delete_journalentrymodel", raise_exception=True) def JournalEntryDeleteView(request, pk): """ Handles the deletion of a specific journal entry. This view facilitates @@ -8851,6 +8591,8 @@ def JournalEntryDeleteView(request, pk): ) +@login_required +@permission_required("django_ledger.view_transactionmodel", raise_exception=True) def JournalEntryTransactionsView(request, dealer_slug,pk): """ Handles the retrieval and display of journal entry transactions for a specific journal @@ -8898,6 +8640,8 @@ class JournalEntryModelTXSDetailView(JournalEntryModelTXSDetailViewBase): template_name = "ledger/journal_entry/journal_entry_txs.html" +@login_required +@permission_required("django_ledger.change_ledgermodel", raise_exception=True) def ledger_lock_all_journals(request,dealer_slug, entity_slug, pk): """ Locks all journals associated with a specific ledger. If the ledger is already locked, @@ -8926,6 +8670,8 @@ def ledger_lock_all_journals(request,dealer_slug, entity_slug, pk): return redirect("journalentry_list", dealer_slug=dealer_slug,pk=ledger.pk) +@login_required +@permission_required("django_ledger.change_ledgermodel", raise_exception=True) def ledger_unlock_all_journals(request, dealer_slug, entity_slug, pk): """ Unlocks all journal entries associated with a specific ledger. This function first checks if the @@ -8958,6 +8704,8 @@ def ledger_unlock_all_journals(request, dealer_slug, entity_slug, pk): return redirect("journalentry_list", dealer_slug=dealer_slug, pk=ledger.pk) +@login_required +@permission_required("django_ledger.change_ledgermodel", raise_exception=True) def ledger_post_all_journals(request, dealer_slug, entity_slug, pk): """ Posts all journal entries associated with a ledger. This function updates the ledger's @@ -8984,6 +8732,8 @@ def ledger_post_all_journals(request, dealer_slug, entity_slug, pk): return redirect("journalentry_list",dealer_slug=dealer_slug, pk=ledger.pk) +@login_required +@permission_required("django_ledger.change_ledgermodel", raise_exception=True) def ledger_unpost_all_journals(request,dealer_slug, entity_slug, pk): """ Unposts all journal entries for a specified ledger and marks the ledger as unposted. @@ -9019,6 +8769,8 @@ def ledger_unpost_all_journals(request,dealer_slug, entity_slug, pk): return redirect("journalentry_list",dealer_slug=dealer_slug, pk=ledger.pk) +@login_required +@permission_required("inventory.change_dealer", raise_exception=True) def pricing_page(request,dealer_slug): get_object_or_404(models.Dealer,slug=dealer_slug) plan_list = PlanPricing.objects.all() @@ -9026,7 +8778,9 @@ def pricing_page(request,dealer_slug): return render(request, "pricing_page.html", {"plan_list": plan_list, "form": form}) -# @require_POST +@login_required +@permission_required("inventory.change_dealer", raise_exception=True) +@require_POST def submit_plan(request,dealer_slug): dealer = get_object_or_404(models.Dealer,slug=dealer_slug) selected_plan_id = request.POST.get("selected_plan") @@ -9045,6 +8799,8 @@ def submit_plan(request,dealer_slug): return redirect(transaction_url) +@login_required +@permission_required("inventory.change_dealer", raise_exception=True) def payment_callback(request,dealer_slug): message = request.GET.get("message") dealer = get_object_or_404(models.Dealer,slug=dealer_slug) @@ -9087,7 +8843,7 @@ def payment_callback(request,dealer_slug): return render(request, "payment_failed.html", {"message": message}) - +@login_required def sse_stream(request): def event_stream(): last_id = request.GET.get("last_id", 0) @@ -9170,6 +8926,8 @@ def notifications_history(request): # return render(request, 'activity_history.html') +@login_required +@permission_required("inventory.add_activity", raise_exception=True) def add_activity(request,dealer_slug, content_type, slug): try: model = apps.get_model(f"inventory.{content_type}") @@ -9195,6 +8953,8 @@ def add_activity(request,dealer_slug, content_type, slug): return redirect(f"{content_type}_detail",dealer_slug=dealer_slug, slug=slug) +@login_required +@permission_required("inventory.add_task", raise_exception=True) def add_task(request,dealer_slug, content_type, slug): try: model = apps.get_model(f"inventory.{content_type}") @@ -9219,6 +8979,8 @@ def add_task(request,dealer_slug, content_type, slug): return redirect(f"{content_type}_detail",dealer_slug=dealer_slug, slug=slug) +@login_required +@permission_required("inventory.change_task", raise_exception=True) def update_task(request,dealer_slug, pk): task = get_object_or_404(models.Tasks, pk=pk) @@ -9231,6 +8993,8 @@ def update_task(request,dealer_slug, pk): return render(request, "partials/task.html", {"task": task}) +@login_required +@permission_required("inventory.add_note", raise_exception=True) def add_note(request,dealer_slug, content_type, slug): try: model = apps.get_model(f"inventory.{content_type}") @@ -9254,6 +9018,8 @@ def add_note(request,dealer_slug, content_type, slug): return redirect(f"{content_type}_detail",dealer_slug=dealer_slug, slug=slug) +@login_required +@permission_required("inventory.change_note", raise_exception=True) def update_note(request,dealer_slug, pk): note = get_object_or_404(models.Notes, pk=pk) lead = get_object_or_404(models.Lead, pk=note.content_object.id) @@ -9274,11 +9040,15 @@ def update_note(request,dealer_slug, pk): # Admin Management +@login_required +@permission_required("inventory.change_dealer", raise_exception=True) def management_view(request,dealer_slug): get_object_or_404(models.Dealer,slug=dealer_slug) return render(request, "admin_management/management.html") +@login_required +@permission_required("inventory.change_dealer", raise_exception=True) def user_management(request,dealer_slug): get_object_or_404(models.Dealer,slug=dealer_slug) context = { @@ -9290,6 +9060,8 @@ def user_management(request,dealer_slug): return render(request, "admin_management/user_management.html", context) +@login_required +@permission_required("inventory.change_dealer", raise_exception=True) def AuditLogDashboardView(request,dealer_slug): """ Displays audit logs (User Actions, Login Events, Request Events) with pagination. @@ -9424,6 +9196,8 @@ def AuditLogDashboardView(request,dealer_slug): return render(request, template_name, context) +@login_required +@permission_required("inventory.change_dealer", raise_exception=True) def activate_account(request,dealer_slug, content_type, slug): get_object_or_404(models.Dealer,slug=dealer_slug) try: @@ -9440,7 +9214,8 @@ def activate_account(request,dealer_slug, content_type, slug): request, "admin_management/confirm_activate_account.html", {"obj": obj} ) - +@login_required +@permission_required("inventory.change_dealer", raise_exception=True) def permenant_delete_account(request,dealer_slug, content_type, slug): get_object_or_404(models.Dealer,slug=dealer_slug) try: @@ -9468,7 +9243,8 @@ def permenant_delete_account(request,dealer_slug, content_type, slug): ##################################################################### - +@login_required +@permission_required("django_ledger.add_purchaseordermodel", raise_exception=True) def PurchaseOrderCreateView(request, dealer_slug,entity_slug): dealer = get_object_or_404(models.Dealer, slug=dealer_slug) entity = dealer.entity @@ -9490,6 +9266,7 @@ def PurchaseOrderCreateView(request, dealer_slug,entity_slug): return render(request, "purchase_orders/po_form.html", {"form": form}) +@login_required def InventoryItemCreateView(request, dealer_slug): dealer = get_object_or_404(models.Dealer, slug=dealer_slug) for_po = request.GET.get("for_po") @@ -9570,7 +9347,8 @@ def InventoryItemCreateView(request, dealer_slug): }, ) - +@login_required +@permission_required("django_ledger.view_purchaseordermodel", raise_exception=True) def inventory_items_filter(request, dealer_slug): dealer = get_object_or_404(models.Dealer, slug=dealer_slug) make = request.GET.get("make") @@ -9602,7 +9380,7 @@ def inventory_items_filter(request, dealer_slug): class PurchaseOrderDetailView(PurchaseOrderModelDetailViewBase): template_name = "purchase_orders/po_detail.html" context_object_name = "po_model" - permission_required = ["inventory.view_carfinance"] + permission_required = ["django_ledger.change_purchaseordermodel"] def get_queryset(self): dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"]) @@ -9621,7 +9399,7 @@ class PurchaseOrderListView(LoginRequiredMixin, PermissionRequiredMixin, ListVie context_object_name = "purchase_orders" paginate_by = 20 template_name = "purchase_orders/po_list.html" - permission_required = ["inventory.view_carfinance"] + permission_required = ["django_ledger.view_purchaseordermodel"] def get_queryset(self): dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"]) @@ -9705,6 +9483,7 @@ class PurchaseOrderListView(LoginRequiredMixin, PermissionRequiredMixin, ListVie class PurchaseOrderUpdateView(PurchaseOrderModelUpdateViewBase): template_name = "purchase_orders/po_update.html" context_object_name = "po_model" + permission_required = ["django_ledger.change_purchaseordermodel"] def get_context_data(self, itemtxs_formset=None, **kwargs): dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"]) @@ -9878,6 +9657,7 @@ class BasePurchaseOrderActionActionView(BasePurchaseOrderActionActionViewBase): class PurchaseOrderModelDeleteView(PurchaseOrderModelDeleteViewBase): template_name = "purchase_orders/po_delete.html" + permission_required = "django_ledger.delete_purchaseordermodel" def get_success_url(self): messages.add_message( @@ -9970,6 +9750,8 @@ class BillModelActionForceMigrateView(BaseBillActionView): ############################################################### +@login_required +@permission_required("inventory.view_poitemsuploaded", raise_exception=True) def view_items_inventory(request, dealer_slug, entity_slug, po_pk): get_object_or_404(models.Dealer, slug=dealer_slug) po = PurchaseOrderModel.objects.get(pk=po_pk) @@ -9977,7 +9759,8 @@ def view_items_inventory(request, dealer_slug, entity_slug, po_pk): return render(request,"purchase_orders/po_upload_cars.html",{"po": po, "items": items}) - +@login_required +@permission_required("inventory.add_poitemsuploaded", raise_exception=True) def upload_cars(request, dealer_slug, pk=None): item = None po_item = None @@ -10109,7 +9892,9 @@ def upload_cars(request, dealer_slug, pk=None): ############################################################### -@require_http_methods(["POST"]) +@login_required +@permission_required("inventory.add_poitemsuploaded", raise_exception=True) +@require_POST def bulk_update_car_price(request): if request.method == "POST": cars = request.POST.getlist("car") @@ -10137,8 +9922,9 @@ def bulk_update_car_price(request): return response -class InventoryListView(InventoryListViewBase): +class InventoryListView(LoginRequiredMixin,PermissionRequiredMixin,InventoryListViewBase): template_name = "inventory/list.html" + permission_required = ["django_ledger.view_purchaseordermodel"] def get_queryset(self): dealer = get_user_type(self.request) diff --git a/templates/appointment/appointment_client_information.html b/templates/appointment/appointment_client_information.html index 8d7d8b4f..6fc4a89b 100644 --- a/templates/appointment/appointment_client_information.html +++ b/templates/appointment/appointment_client_information.html @@ -16,8 +16,6 @@ {% block body %}
- -
diff --git a/templates/appointment/appointments.html b/templates/appointment/appointments.html index 4e301be6..62d35353 100644 --- a/templates/appointment/appointments.html +++ b/templates/appointment/appointments.html @@ -12,7 +12,6 @@ {{ page_description }} {% endblock %} {% block body %} - + {% endif %} + +
+
+ {% endif %} + {% if perms.django_ledger.can_view_reports %} - + - - + + {% endif %} + + diff --git a/templates/purchase_orders/po_list.html b/templates/purchase_orders/po_list.html index 9a702bbd..f2ecd945 100644 --- a/templates/purchase_orders/po_list.html +++ b/templates/purchase_orders/po_list.html @@ -19,8 +19,10 @@ {{ _("Purchase Orders") |capfirst }}
- {{ _("Create New PO") }} + {% if perms.django_ledger.add_purchaseordermodel %} + {{ _("Create New PO") }} + {% endif %}
{% include "partials/search_box.html" %} @@ -65,7 +67,9 @@
- {% if estimate.status == 'draft' %} {% if perms.django_ledger.change_estimatemodel %} {% endif %} {% elif estimate.status == 'in_review' %} {% if perms.django_ledger.can_approve_estimatemodel %} - + {% endif %} {% elif estimate.status == 'approved' %} {% trans 'Send Quotation' %} {% if estimate.sale_orders.first %} - {% if perms.djagno_ledger.add_invoice %} + {% if perms.django_ledger.add_invoicemodel %} {% trans 'Create Invoice' %} {% endif %} {{ _("Preview Sale Order") }} diff --git a/templates/sales/estimates/estimate_list.html b/templates/sales/estimates/estimate_list.html index 8255a8ea..42f6227e 100644 --- a/templates/sales/estimates/estimate_list.html +++ b/templates/sales/estimates/estimate_list.html @@ -20,31 +20,31 @@ - {% for estimate in estimates %} + {% for extra in staff_estimates %} - {{ estimate.estimate_number }} - {{ estimate.customer.customer_name }} + {{ extra.content_object.estimate_number }} + {{ extra.content_object.customer.customer_name }} - {% if estimate.status == 'draft' %} + {% if extra.content_object.status == 'draft' %} {% trans "Draft" %} - {% elif estimate.status == 'in_review' %} + {% elif extra.content_object.status == 'in_review' %} {% trans "In Review" %} - {% elif estimate.status == 'approved' %} + {% elif extra.content_object.status == 'approved' %} {% trans "Approved" %} - {% elif estimate.status == 'declined' %} + {% elif extra.content_object.status == 'declined' %} {% trans "Declined" %} - {% elif estimate.status == 'canceled' %} + {% elif extra.content_object.status == 'canceled' %} {% trans "Canceled" %} - {% elif estimate.status == 'completed' %} + {% elif extra.content_object.status == 'completed' %} {% trans "Completed" %} - {% elif estimate.status == 'void' %} + {% elif extra.content_object.status == 'void' %} {% trans "Void" %} {% endif %} - {{ estimate.get_status_action_date }} - {{ estimate.created }} + {{ extra.content_object.get_status_action_date }} + {{ extra.content_object.created }} - {% trans "view"|capfirst %} diff --git a/templates/sales/invoices/invoice_detail.html b/templates/sales/invoices/invoice_detail.html index 73dd158a..ed16bd65 100644 --- a/templates/sales/invoices/invoice_detail.html +++ b/templates/sales/invoices/invoice_detail.html @@ -86,12 +86,12 @@ {% endif %} {% if invoice.invoice_status == 'approved' %} - {% trans 'Record Payment' %} + {% trans 'Record Payment' %} {% endif %} {% if not invoice.is_paid %} {% endif %} - {% trans 'Preview' %} + {% trans 'Preview' %}
{{invoice.amount_owned}} From 772db4669f4b2363dc2e02433d313c81ea523196 Mon Sep 17 00:00:00 2001 From: ismail Date: Thu, 3 Jul 2025 14:46:42 +0300 Subject: [PATCH 2/2] update perms --- inventory/views.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/inventory/views.py b/inventory/views.py index 0c75fb71..5cc65f95 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -2852,6 +2852,10 @@ def GroupPermissionView(request, dealer_slug, pk): ("inventory", "carreservation"), ("inventory", "customer"), ("inventory", "organization"), + ("inventory", "additionalservices"), + ("inventory", "notes"), + ("inventory", "tasks"), + ("inventory", "activity"), ("django_ledger", "purchaseordermodel"), ("django_ledger", "bankaccountmodel"), @@ -2863,7 +2867,8 @@ def GroupPermissionView(request, dealer_slug, pk): ("django_ledger", "invoicemodel"), ("django_ledger", "vendormodel"), ("django_ledger", "journalentrymodel"), - ("django_ledger", "purchaseordermodel"), + ("django_ledger", "ledgermodel"), + ("django_ledger", "transactionmodel"), ] CUSTOM_PERMISSIONS = [ ('django_ledger', 'can_approve_estimatemodel'), @@ -9922,7 +9927,7 @@ def bulk_update_car_price(request): return response -class InventoryListView(LoginRequiredMixin,PermissionRequiredMixin,InventoryListViewBase): +class InventoryListView(InventoryListViewBase): template_name = "inventory/list.html" permission_required = ["django_ledger.view_purchaseordermodel"]