diff --git a/inventory/apps.py b/inventory/apps.py index 6e8c8729..f316aa12 100644 --- a/inventory/apps.py +++ b/inventory/apps.py @@ -6,7 +6,7 @@ class InventoryConfig(AppConfig): def ready(self): import inventory.signals - from decimal import Decimal - from inventory.models import VatRate - VatRate.objects.get_or_create(rate=Decimal('0.15'), is_active=True) + # from decimal import Decimal + # from inventory.models import VatRate + # VatRate.objects.get_or_create(rate=Decimal('0.15'), is_active=True) diff --git a/inventory/forms.py b/inventory/forms.py index 6cd912b5..f0b01caf 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -136,7 +136,21 @@ class CustomerForm(forms.Form): last_name = forms.CharField() arabic_name = forms.CharField() email = forms.EmailField() - phone_number = PhoneNumberField(region="SA") + phone_number = PhoneNumberField( + label=_("Phone Number"), + widget=forms.TextInput( + attrs={ + "placeholder": _("Phone"), + } + ), + region="SA", + error_messages={ + "required": _("This field is required."), + "invalid": _("Phone number must be in the format 05xxxxxxxx"), + }, + required=True, + ) + national_id = forms.CharField(max_length=10,required=False) crn = forms.CharField(required=False) vrn = forms.CharField(required=False) @@ -790,9 +804,9 @@ class InvoiceModelCreateForm(InvoiceModelCreateFormBase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields["cash_account"].widget = forms.HiddenInput() - self.fields["prepaid_account"].widget = forms.HiddenInput() - self.fields["unearned_account"].widget = forms.HiddenInput() + # self.fields["cash_account"].widget = forms.HiddenInput() + # self.fields["prepaid_account"].widget = forms.HiddenInput() + # self.fields["unearned_account"].widget = forms.HiddenInput() self.fields["date_draft"] = forms.DateField( widget=DateInput(attrs={"type": "date"}) ) diff --git a/inventory/models.py b/inventory/models.py index 860bb2f9..c248bc97 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -15,6 +15,7 @@ from django_ledger.io.io_core import get_localdate from django.core.exceptions import ValidationError from phonenumber_field.modelfields import PhoneNumberField from django.utils.timezone import now +from django.contrib.auth.models import Group from inventory.utils import get_user_type from .mixins import LocalizedNameMixin @@ -1007,6 +1008,11 @@ class Staff(models.Model, LocalizedNameMixin): EntityManagementModel.objects.get_or_create( user=self.user,entity=self.dealer.entity ) + else: + self.user.groups.clear() + group = Group.objects.filter(customgroup__name__iexact=self.staff_type).first() + if group: + self.add_group(group) class Meta: verbose_name = _("Staff") verbose_name_plural = _("Staff") @@ -1318,6 +1324,18 @@ class Lead(models.Model): return self.schedules.order_by('-scheduled_at').first() def get_latest_schedules(self): return self.schedules.filter(scheduled_at__gt=now()).exclude(status='Canceled').order_by('-scheduled_at')[:5] + def get_all_schedules(self): + return self.schedules.all().order_by('-scheduled_at') + def get_calls(self): + return self.get_all_schedules().filter(scheduled_type='Call') + def get_meetings(self): + return self.get_all_schedules().filter(scheduled_type='Meeting') + def get_emails(self): + return Email.objects.filter(content_type__model="lead", object_id=self.id) + def get_notes(self): + return Notes.objects.filter(content_type__model="lead", object_id=self.id) + def get_activities(self): + return Activity.objects.filter(content_type__model="lead", object_id=self.id) class Schedule(models.Model): PURPOSE_CHOICES = [ diff --git a/inventory/signals.py b/inventory/signals.py index 075e4181..1ceb051b 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -1,8 +1,8 @@ from django.contrib.auth.models import Group from decimal import Decimal from django.db.models.signals import post_save, post_delete, pre_delete, pre_save +from inventory.models import VatRate from plans.quota import get_user_quota - from .utils import to_dict from django.dispatch import receiver from django.utils.translation import gettext_lazy as _ @@ -645,7 +645,7 @@ def create_ledger_vendor(sender, instance, created, **kwargs): if created: entity = EntityModel.objects.filter(name=instance.dealer.name).first() additionals = to_dict(instance) - entity.create_vendor( + vendor = entity.create_vendor( vendor_model_kwargs={ "vendor_name": instance.name, "vendor_number": instance.crn, @@ -659,6 +659,7 @@ def create_ledger_vendor(sender, instance, created, **kwargs): } ) + coa = entity.get_default_coa() last_account = entity.get_all_accounts().filter(role=roles.LIABILITY_CL_ACC_PAYABLE).order_by('-created').first() # code = f"{int(last_account.code)}{1:03d}" @@ -676,17 +677,6 @@ def create_ledger_vendor(sender, instance, created, **kwargs): active=True ) print(f"VendorModel created for Vendor: {instance.name}") - else: - additionals = to_dict(instance) - entity.get_vendors().filter(email=instance.email).first().update( - vendor_name= instance.name, - vendor_number= instance.crn, - address_1= instance.address, - phone= instance.phone_number, - email= instance.email, - tax_id_number= instance.vrn, - additional_info= additionals, - ) @receiver(post_save, sender=models.CustomerModel) def create_customer_user(sender, instance, created, **kwargs): @@ -938,6 +928,10 @@ def create_dealer_settings(sender, instance, created, **kwargs): # raise ValidationError(_("You have reached the maximum number of staff users allowed for your plan.")) +@receiver(post_save, sender=models.Dealer) +def create_vat(sender, instance, created, **kwargs): + VatRate.objects.get_or_create(rate=Decimal('0.15'), is_active=True) + @receiver(post_save, sender=models.Dealer) def create_make_ledger_accounts(sender, instance, created, **kwargs): if created: @@ -960,6 +954,7 @@ def create_make_ledger_accounts(sender, instance, created, **kwargs): active=True ) + # @receiver(post_save, sender=VendorModel) # def create_vendor_accounts(sender, instance, created, **kwargs): # if created: @@ -982,7 +977,7 @@ def create_make_ledger_accounts(sender, instance, created, **kwargs): def save_journal(car_finance,ledger,vendor): entity = ledger.entity - + coa = entity.get_default_coa() journal = JournalEntryModel.objects.create( posted=False, description=f"Finances of Car:{car_finance.car.vin} for Vendor:{car_finance.car.vendor.vendor_name}", @@ -994,7 +989,23 @@ def save_journal(car_finance,ledger,vendor): ledger.save() inventory_account = entity.get_default_coa_accounts().filter(role=roles.ASSET_CA_INVENTORY).first() - vendor_account = entity.get_default_coa_accounts().get(name=vendor.vendor_name) + vendor_account = entity.get_default_coa_accounts().filter(name=vendor.vendor_name).first() + + if not vendor_account: + last_account = entity.get_all_accounts().filter(role=roles.LIABILITY_CL_ACC_PAYABLE).order_by('-created').first() + if len(last_account.code) == 4: + code = f"{int(last_account.code)}{1:03d}" + elif len(last_account.code) > 4: + code = f"{int(last_account.code)+1}" + + vendor_account = entity.create_account( + name=vendor.vendor_name, + code=code, + role=roles.LIABILITY_CL_ACC_PAYABLE, + coa_model=coa, + balance_type="credit", + active=True + ) additional_services_account = entity.get_default_coa_accounts().filter(name="Additional Services",role=roles.COGS).first() # Debit Inventory Account diff --git a/inventory/urls.py b/inventory/urls.py index b29a9dca..5e626732 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -416,6 +416,9 @@ path( path( "ledgers/", views.LedgerModelListView.as_view(), name="ledger_list" ), + path( + "ledgers/create/", views.LedgerModelCreateView.as_view(), name="ledger_create" + ), path( "ledgers//detail//", views.LedgerModelDetailView.as_view(), name="ledger_detail" ), @@ -451,6 +454,35 @@ path( path('journalentries///detail//txs/', views.JournalEntryModelTXSDetailView.as_view(), name='journalentry_txs'), + # ledger actions + + path('ledgers//action//post/', + views.LedgerModelModelActionView.as_view(action_name='post'), + name='ledger-action-post'), + path('ledgers//action//post-journal-entries/', + views.LedgerModelModelActionView.as_view(action_name='post_journal_entries'), + name='ledger-action-post-journal-entries'), + path('ledgers//action//unpost/', + views.LedgerModelModelActionView.as_view(action_name='unpost'), + name='ledger-action-unpost'), + path('ledgers//action//lock/', + views.LedgerModelModelActionView.as_view(action_name='lock'), + name='ledger-action-lock'), + path('ledgers//action//lock-journal-entries/', + views.LedgerModelModelActionView.as_view(action_name='lock_journal_entries'), + name='ledger-action-lock-journal-entries'), + path('ledgers//action//unlock/', + views.LedgerModelModelActionView.as_view(action_name='unlock'), + name='ledger-action-unlock'), + path('ledgers//action//hide/', + views.LedgerModelModelActionView.as_view(action_name='hide'), + name='ledger-action-hide'), + path('ledgers//action//unhide/', + views.LedgerModelModelActionView.as_view(action_name='unhide'), + name='ledger-action-unhide'), + path('ledgers//delete//', + views.LedgerModelDeleteView.as_view(), + name='ledger-delete'), # Bank Account path( "bank_accounts/", views.BankAccountListView.as_view(), name="bank_account_list" diff --git a/inventory/utils.py b/inventory/utils.py index 1e42766f..79c0bab1 100644 --- a/inventory/utils.py +++ b/inventory/utils.py @@ -908,33 +908,33 @@ def handle_account_process(invoice,amount,finance_data): TransactionModel.objects.create( journal_entry=journal, account=make_account, # Debit car make Account - amount=Decimal(car.finances.total), + amount=Decimal(finance_data.get("grand_total")), tx_type="debit", description="Payment Received", ) - TransactionModel.objects.create( - journal_entry=journal, - account=additional_services_account, # Debit Additional Services - amount=Decimal(car.finances.total_additionals), - tx_type="debit", - description="Additional Services", - ) + # TransactionModel.objects.create( + # journal_entry=journal, + # account=additional_services_account, # Debit Additional Services + # amount=Decimal(car.finances.total_additionals), + # tx_type="debit", + # description="Additional Services", + # ) TransactionModel.objects.create( journal_entry=journal, account=inventory_account, # Credit Inventory account - amount=Decimal(car.finances.total), + amount=Decimal(finance_data.get("grand_total")), tx_type="credit", description="Account Adjustment", ) - TransactionModel.objects.create( - journal_entry=journal, - account=vat_payable_account, # Credit VAT Payable - amount=finance_data.get("total_vat_amount"), - tx_type="credit", - description="VAT Payable on Invoice", - ) + # TransactionModel.objects.create( + # journal_entry=journal, + # account=vat_payable_account, # Credit VAT Payable + # amount=finance_data.get("total_vat_amount"), + # tx_type="credit", + # description="VAT Payable on Invoice", + # ) def create_make_accounts(dealer): entity = dealer.entity diff --git a/inventory/views.py b/inventory/views.py index 94a4c6b7..260475b2 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -2,6 +2,7 @@ import cv2 import json import logging +import datetime import numpy as np from rich import print from random import randint @@ -9,8 +10,10 @@ from decimal import Decimal from calendar import month_name from pyzbar.pyzbar import decode from urllib.parse import urlparse, urlunparse + ##################################################################### from django.db.models.deletion import RestrictedError + # Django from django.db.models import Q from django.conf import settings @@ -55,10 +58,22 @@ from django.views.generic import ( # Django Ledger from django_ledger.io import roles from django_ledger.utils import accruable_net_summary -from django_ledger.views.invoice import InvoiceModelDetailView as InvoiceModelDetailViewBase -from django_ledger.views import LedgerModelListView as LedgerModelListViewBase,JournalEntryModelTXSDetailView as JournalEntryModelTXSDetailViewBase +from django_ledger.views.invoice import ( + InvoiceModelDetailView as InvoiceModelDetailViewBase, +) +from django_ledger.views import ( + LedgerModelListView as LedgerModelListViewBase, + JournalEntryModelTXSDetailView as JournalEntryModelTXSDetailViewBase, + LedgerModelModelActionView as LedgerModelModelActionViewBase, + LedgerModelDeleteView as LedgerModelDeleteViewBase, + LedgerModelCreateView as LedgerModelCreateViewBase +) from django_ledger.forms.account import AccountModelCreateForm, AccountModelUpdateForm -from django_ledger.views.entity import EntityModelDetailBaseView,EntityModelDetailHandlerView +from django_ledger.views.entity import ( + EntityModelDetailBaseView, + EntityModelDetailHandlerView, +) +from django_ledger.forms.ledger import LedgerModelCreateForm from django_ledger.forms.item import ( ExpenseItemCreateForm, ExpenseItemUpdateForm, @@ -118,7 +133,7 @@ from . import models, forms, tables from plans.quota import get_user_quota from django_tables2 import SingleTableView from django_tables2.export.views import ExportMixin -from appointment.models import Appointment,AppointmentRequest,Service,StaffMember +from appointment.models import Appointment, AppointmentRequest, Service, StaffMember from .services import ( decodevin, @@ -143,8 +158,10 @@ from .utils import ( logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) + class Hash(Func): - function = 'get_hash' + function = "get_hash" + def switch_language(request): language = request.GET.get("language", "en") @@ -190,7 +207,11 @@ def dealer_signup(request, *args, **kwargs): if request.method == "POST": if "Hx-Request" in request.headers: form1 = forms.WizardForm1(request.POST) - return render(request,"account/signup-wizard.html",{"form1": form1, "form2": form2, "form3": form3}) + return render( + request, + "account/signup-wizard.html", + {"form1": form1, "form2": form2, "form3": form3}, + ) data = json.loads(request.body) wf1 = data.get("wizardValidationForm1") @@ -214,9 +235,11 @@ def dealer_signup(request, *args, **kwargs): user = User.objects.create(username=email, email=email) user.set_password(password) user.save() - group = Group.objects.create(name=f'{user.pk}-Admin') + group = Group.objects.create(name=f"{user.pk}-Admin") user.groups.add(group) - for perm in Permission.objects.filter(content_type__app_label__in=["inventory","django_ledger"]): + for perm in Permission.objects.filter( + content_type__app_label__in=["inventory", "django_ledger"] + ): group.permissions.add(perm) StaffMember.objects.create(user=user) @@ -234,7 +257,11 @@ def dealer_signup(request, *args, **kwargs): ) except Exception as e: return JsonResponse({"error": str(e)}, status=400) - return render(request,"account/signup-wizard.html",{"form1": form1, "form2": form2, "form3": form3}) + return render( + request, + "account/signup-wizard.html", + {"form1": form1, "form2": form2, "form3": form3}, + ) # class OTPView(View, LoginRequiredMixin): @@ -255,14 +282,14 @@ def dealer_signup(request, *args, **kwargs): # messages.error(request, _("Invalid OTP. Please try again.")) # return render(request, self.template_name) - # def verify_otp(self, otp_code, user): - # device = default_device(user) - # if device and device.verify_token(otp_code): - # return True - # return False +# def verify_otp(self, otp_code, user): +# device = default_device(user) +# if device and device.verify_token(otp_code): +# return True +# return False -class HomeView(LoginRequiredMixin,TemplateView): +class HomeView(LoginRequiredMixin, TemplateView): template_name = "index.html" def dispatch(self, request, *args, **kwargs): @@ -323,9 +350,11 @@ class HomeView(LoginRequiredMixin,TemplateView): # }) # return context + class TestView(TemplateView): template_name = "inventory/cars_list_api.html" + class ManagerDashboard(LoginRequiredMixin, TemplateView): template_name = "dashboards/manager.html" @@ -350,18 +379,40 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView): total_selling_price = stats["total_selling_price"] or 0 total_profit = total_selling_price - total_cost_price - new_leads = models.Lead.objects.filter(dealer=dealer, status=models.Status.NEW).count() - pending_leads = models.Lead.objects.filter(dealer=dealer, status=models.Status.PENDING).count() - canceled_leads = models.Lead.objects.filter(dealer=dealer, status=models.Status.CANCELED).count() - available_cars = models.Car.objects.filter(dealer=dealer, status=models.CarStatusChoices.AVAILABLE).count() - sold_cars = models.Car.objects.filter(dealer=dealer, status=models.CarStatusChoices.SOLD).count() - reserved_cars = models.Car.objects.filter(dealer=dealer, status=models.CarStatusChoices.RESERVED).count() - hold_cars = models.Car.objects.filter(dealer=dealer, status=models.CarStatusChoices.HOLD).count() - damaged_cars = models.Car.objects.filter(dealer=dealer, status=models.CarStatusChoices.DAMAGED).count() - transfer_cars = models.Car.objects.filter(dealer=dealer, status=models.CarStatusChoices.TRANSFER).count() + new_leads = models.Lead.objects.filter( + dealer=dealer, status=models.Status.NEW + ).count() + pending_leads = models.Lead.objects.filter( + dealer=dealer, status=models.Status.PENDING + ).count() + canceled_leads = models.Lead.objects.filter( + dealer=dealer, status=models.Status.CANCELED + ).count() + available_cars = models.Car.objects.filter( + dealer=dealer, status=models.CarStatusChoices.AVAILABLE + ).count() + sold_cars = models.Car.objects.filter( + dealer=dealer, status=models.CarStatusChoices.SOLD + ).count() + reserved_cars = models.Car.objects.filter( + dealer=dealer, status=models.CarStatusChoices.RESERVED + ).count() + hold_cars = models.Car.objects.filter( + dealer=dealer, status=models.CarStatusChoices.HOLD + ).count() + damaged_cars = models.Car.objects.filter( + dealer=dealer, status=models.CarStatusChoices.DAMAGED + ).count() + transfer_cars = models.Car.objects.filter( + dealer=dealer, status=models.CarStatusChoices.TRANSFER + ).count() reserved_percentage = reserved_cars / total_cars * 100 sold_percentage = sold_cars / total_cars * 100 - qs = models.Car.objects.values('id_car_make__name').annotate(count=Count('id')).order_by('id_car_make__name') + qs = ( + models.Car.objects.values("id_car_make__name") + .annotate(count=Count("id")) + .order_by("id_car_make__name") + ) car_by_make = list(qs) total_activity = models.UserActivityLog.objects.filter(user=dealer.user).count() staff = models.Staff.objects.filter(dealer=dealer).count() @@ -377,25 +428,24 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView): context["total_cost_price"] = total_cost_price context["total_selling_price"] = total_selling_price context["total_profit"] = total_profit - context['new_leads'] = new_leads - context['pending_leads'] = pending_leads - context['canceled_leads'] = canceled_leads - context['reserved_percentage'] = reserved_percentage - context['sold_percentage'] = sold_percentage - context['available_cars'] = available_cars - context['sold_cars'] = sold_cars - context['reserved_cars'] = reserved_cars - context['hold_cars'] = hold_cars - context['damaged_cars'] = damaged_cars - context['transfer_cars'] = transfer_cars - context['car'] = json.dumps(car_by_make) - context['customers'] = customers - context['staff'] = staff - context['total_leads'] = total_leads - context['invoices'] = invoices - context['estimates'] = estimates - context['purchase_orders'] = purchase_orders - + context["new_leads"] = new_leads + context["pending_leads"] = pending_leads + context["canceled_leads"] = canceled_leads + context["reserved_percentage"] = reserved_percentage + context["sold_percentage"] = sold_percentage + context["available_cars"] = available_cars + context["sold_cars"] = sold_cars + context["reserved_cars"] = reserved_cars + context["hold_cars"] = hold_cars + context["damaged_cars"] = damaged_cars + context["transfer_cars"] = transfer_cars + context["car"] = json.dumps(car_by_make) + context["customers"] = customers + context["staff"] = staff + context["total_leads"] = total_leads + context["invoices"] = invoices + context["estimates"] = estimates + context["purchase_orders"] = purchase_orders return context @@ -409,23 +459,39 @@ class SalesDashboard(LoginRequiredMixin, TemplateView): staff = getattr(self.request.user, "staff", None) total_cars = models.Car.objects.filter(dealer=dealer).count() total_reservations = models.CarReservation.objects.filter( - reserved_by=self.request.user, - reserved_until__gte=timezone.now() + reserved_by=self.request.user, reserved_until__gte=timezone.now() ).count() - # new_leads = models.Lead.objects.filter(dealer=dealer, status=models.Status.NEW).count() - pending_leads = models.Lead.objects.filter(dealer=dealer, dealer__staff__assigned=staff, status=models.Status.PENDING).count() + pending_leads = models.Lead.objects.filter( + dealer=dealer, dealer__staff__assigned=staff, status=models.Status.PENDING + ).count() # canceled_leads = models.Lead.objects.filter(dealer=dealer, status=models.Status.CANCELED).count() - available_cars = models.Car.objects.filter(dealer=dealer, status=models.CarStatusChoices.AVAILABLE).count() - sold_cars = models.Car.objects.filter(dealer=dealer, status=models.CarStatusChoices.SOLD).count() - reserved_cars = models.Car.objects.filter(dealer=dealer, status=models.CarStatusChoices.RESERVED).count() - hold_cars = models.Car.objects.filter(dealer=dealer, status=models.CarStatusChoices.HOLD).count() - damaged_cars = models.Car.objects.filter(dealer=dealer, status=models.CarStatusChoices.DAMAGED).count() - transfer_cars = models.Car.objects.filter(dealer=dealer, status=models.CarStatusChoices.TRANSFER).count() + available_cars = models.Car.objects.filter( + dealer=dealer, status=models.CarStatusChoices.AVAILABLE + ).count() + sold_cars = models.Car.objects.filter( + dealer=dealer, status=models.CarStatusChoices.SOLD + ).count() + reserved_cars = models.Car.objects.filter( + dealer=dealer, status=models.CarStatusChoices.RESERVED + ).count() + hold_cars = models.Car.objects.filter( + dealer=dealer, status=models.CarStatusChoices.HOLD + ).count() + damaged_cars = models.Car.objects.filter( + dealer=dealer, status=models.CarStatusChoices.DAMAGED + ).count() + transfer_cars = models.Car.objects.filter( + dealer=dealer, status=models.CarStatusChoices.TRANSFER + ).count() reserved_percentage = reserved_cars / total_cars * 100 sold_percentage = sold_cars / total_cars * 100 - qs = models.Car.objects.values('id_car_make__name').annotate(count=Count('id')).order_by('id_car_make__name') + qs = ( + models.Car.objects.values("id_car_make__name") + .annotate(count=Count("id")) + .order_by("id_car_make__name") + ) car_by_make = list(qs) context["dealer"] = dealer @@ -438,21 +504,20 @@ class SalesDashboard(LoginRequiredMixin, TemplateView): # context['new_leads'] = new_leads # context['pending_leads'] = pending_leads # context['canceled_leads'] = canceled_leads - context['reserved_percentage'] = reserved_percentage - context['sold_percentage'] = sold_percentage - context['available_cars'] = available_cars - context['sold_cars'] = sold_cars - context['reserved_cars'] = reserved_cars - context['hold_cars'] = hold_cars - context['damaged_cars'] = damaged_cars - context['transfer_cars'] = transfer_cars - context['car'] = json.dumps(car_by_make) + context["reserved_percentage"] = reserved_percentage + context["sold_percentage"] = sold_percentage + context["available_cars"] = available_cars + context["sold_cars"] = sold_cars + context["reserved_cars"] = reserved_cars + context["hold_cars"] = hold_cars + context["damaged_cars"] = damaged_cars + context["transfer_cars"] = transfer_cars + context["car"] = json.dumps(car_by_make) # context['customers'] = customers # context['staff'] = staff # context['total_leads'] = total_leads # context['invoices'] = invoices - return context @@ -468,11 +533,11 @@ class WelcomeView(TemplateView): return context -class CarCreateView(LoginRequiredMixin,PermissionRequiredMixin, CreateView): +class CarCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): model = models.Car form_class = forms.CarForm template_name = "inventory/car_form.html" - permission_required = ['inventory.add_car'] + permission_required = ["inventory.add_car"] # success_url = reverse_lazy('inventory_stats') @@ -495,10 +560,16 @@ class CarCreateView(LoginRequiredMixin,PermissionRequiredMixin, CreateView): messages.success(self.request, _("Car saved successfully")) return super().form_valid(form) -def car_history(request,pk): + +def car_history(request, pk): car = get_object_or_404(models.Car, pk=pk) - activities = models.Activity.objects.filter(content_type__model="car", object_id=car.id) - return render(request,'inventory/car_history.html',{"car":car,"activities":activities}) + activities = models.Activity.objects.filter( + content_type__model="car", object_id=car.id + ) + return render( + request, "inventory/car_history.html", {"car": car, "activities": activities} + ) + class AjaxHandlerView(LoginRequiredMixin, View): def get(self, request, *args, **kwargs): @@ -581,16 +652,13 @@ class AjaxHandlerView(LoginRequiredMixin, View): year = int(year) query = Q(id_car_model=model_id) & ( - Q(year_begin__lte=year, year_end__gte=year) | - Q(year_end__isnull=True) | - Q(year_begin__isnull=True) + Q(year_begin__lte=year, year_end__gte=year) + | Q(year_end__isnull=True) + | Q(year_begin__isnull=True) ) try: series = models.CarSerie.objects.filter(query).values( - "id_car_serie", - "name", - "arabic_name", - "generation_name" + "id_car_serie", "name", "arabic_name", "generation_name" ) except Exception as e: return JsonResponse({"error": "Server error occurred"}, status=500) @@ -681,7 +749,7 @@ class AjaxHandlerView(LoginRequiredMixin, View): @method_decorator(csrf_exempt, name="dispatch") -class SearchCodeView(LoginRequiredMixin,View): +class SearchCodeView(LoginRequiredMixin, View): template_name = "inventory/scan_vin.html" def get(self, request, *args, **kwargs): @@ -714,14 +782,14 @@ class SearchCodeView(LoginRequiredMixin,View): return JsonResponse({"success": False, "error": "No image provided"}) -class CarInventory(LoginRequiredMixin,PermissionRequiredMixin, ListView): +class CarInventory(LoginRequiredMixin, PermissionRequiredMixin, ListView): model = models.Car home_label = _("inventory") template_name = "inventory/car_inventory.html" context_object_name = "cars" paginate_by = 10 ordering = ["receiving_date"] - permission_required = ['inventory.view_car'] + permission_required = ["inventory.view_car"] def get_queryset(self, *args, **kwargs): query = self.request.GET.get("q") @@ -748,11 +816,11 @@ class CarInventory(LoginRequiredMixin,PermissionRequiredMixin, ListView): return context -class CarColorCreate(LoginRequiredMixin,PermissionRequiredMixin, CreateView): +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.view_car"] def form_valid(self, form): car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"]) @@ -767,54 +835,67 @@ class CarColorCreate(LoginRequiredMixin,PermissionRequiredMixin, CreateView): context["car"] = get_object_or_404(models.Car, pk=self.kwargs["car_pk"]) return context -class CarListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): + +class CarListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): model = models.Car template_name = "inventory/car_list_view.html" context_object_name = "cars" paginate_by = 20 - permission_required = 'inventory.view_car' + permission_required = "inventory.view_car" + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) dealer = get_user_type(self.request) cars = models.Car.objects.filter(dealer=dealer).order_by("receiving_date") context["stats"] = { - 'all': cars.count(), - 'available':cars.filter(status='available').count(), - 'reserved':cars.filter(status='reserved').count(), - 'sold':cars.filter(status='sold').count(), - 'transfer':cars.filter(status='transfer').count() - } - context['make'] = models.CarMake.objects.filter(car__in=cars).distinct() - context['model'] = models.CarModel.objects.none() - context['year'] = models.Car.objects.none() - make = self.request.GET.get('make') - model = self.request.GET.get('model') + "all": cars.count(), + "available": cars.filter(status="available").count(), + "reserved": cars.filter(status="reserved").count(), + "sold": cars.filter(status="sold").count(), + "transfer": cars.filter(status="transfer").count(), + } + context["make"] = models.CarMake.objects.filter(car__in=cars).distinct() + context["model"] = models.CarModel.objects.none() + context["year"] = models.Car.objects.none() + make = self.request.GET.get("make") + model = self.request.GET.get("model") if make: make_ = models.CarMake.objects.get(id_car_make=int(make)) - context['model'] = make_.carmodel_set.filter(car__in=cars).distinct() + context["model"] = make_.carmodel_set.filter(car__in=cars).distinct() if make and model: make_ = models.CarMake.objects.get(id_car_make=int(make)) model_ = models.CarModel.objects.get(id_car_model=int(model)) - context['year'] = models.Car.objects.filter(id_car_make=make_,id_car_model=model_).values_list('year').distinct() + context["year"] = ( + models.Car.objects.filter(id_car_make=make_, id_car_model=model_) + .values_list("year") + .distinct() + ) return context + def get_queryset(self): dealer = get_user_type(self.request) qs = super().get_queryset() qs = qs.filter(dealer=dealer) - status = self.request.GET.get('status') - search = self.request.GET.get('search') - make = self.request.GET.get('make',None) - model = self.request.GET.get('model',None) - year = self.request.GET.get('year',None) - car_status = self.request.GET.get('car_status',None) + status = self.request.GET.get("status") + search = self.request.GET.get("search") + make = self.request.GET.get("make", None) + model = self.request.GET.get("model", None) + year = self.request.GET.get("year", None) + car_status = self.request.GET.get("car_status", None) if status: - qs=qs.filter(status=status) + qs = qs.filter(status=status) if search: - query = Q(vin__icontains=search)|Q(id_car_make__name__icontains=search)|Q(id_car_model__name__icontains=search)|Q(id_car_trim__name__icontains=search)|Q(vin=search) - qs=qs.filter(query) + query = ( + Q(vin__icontains=search) + | Q(id_car_make__name__icontains=search) + | Q(id_car_model__name__icontains=search) + | Q(id_car_trim__name__icontains=search) + | Q(vin=search) + ) + qs = qs.filter(query) if any([make, model, year, car_status]): query = Q() if make: @@ -828,6 +909,7 @@ class CarListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): qs = qs.filter(query) return qs + @login_required def inventory_stats_view(request): dealer = get_user_type(request) @@ -873,11 +955,11 @@ def inventory_stats_view(request): trim = car.id_car_trim if ( - trim - and trim.id_car_trim - not in inventory[make.id_car_make]["models"][model.id_car_model][ - "trims" - ] + trim + and trim.id_car_trim + not in inventory[make.id_car_make]["models"][model.id_car_model][ + "trims" + ] ): inventory[make.id_car_make]["models"][model.id_car_model]["trims"][ trim.id_car_trim @@ -916,17 +998,18 @@ def inventory_stats_view(request): return render(request, "inventory/inventory_stats.html", {"inventory": result}) -class CarDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): +class CarDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): model = models.Car template_name = "inventory/car_detail.html" context_object_name = "car" - permission_required = ['inventory.view_car'] + permission_required = ["inventory.view_car"] -class CarFinanceCreateView(LoginRequiredMixin,PermissionRequiredMixin, CreateView): + +class CarFinanceCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): model = models.CarFinance form_class = forms.CarFinanceForm template_name = "inventory/car_finance_form.html" - permission_required = ['inventory.add_carfinance'] + permission_required = ["inventory.add_carfinance"] def dispatch(self, request, *args, **kwargs): self.car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"]) @@ -954,12 +1037,14 @@ class CarFinanceCreateView(LoginRequiredMixin,PermissionRequiredMixin, CreateVie return form -class CarFinanceUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, UpdateView): +class CarFinanceUpdateView( + LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView +): model = models.CarFinance form_class = forms.CarFinanceForm template_name = "inventory/car_finance_form.html" - success_message = _("Car finance details updated successfully") - permission_required = ['inventory.change_carfinance'] + success_message = _("Car finance details updated successfully.") + permission_required = ["inventory.change_carfinance"] def get_success_url(self): return reverse("car_detail", kwargs={"pk": self.object.car.pk}) @@ -980,37 +1065,43 @@ class CarFinanceUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMe def get_form(self, form_class=None): form = super().get_form(form_class) dealer = get_user_type(self.request) - form.fields["additional_finances"].queryset = models.AdditionalServices.objects.filter(dealer=dealer) + form.fields[ + "additional_finances" + ].queryset = models.AdditionalServices.objects.filter(dealer=dealer) return form -class CarUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, UpdateView): +class CarUpdateView( + LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView +): model = models.Car form_class = forms.CarUpdateForm template_name = "inventory/car_edit.html" - success_message = _("Car updated successfully") - permission_required = ['inventory.change_car'] + success_message = _("Car updated successfully.") + permission_required = ["inventory.change_car"] def get_success_url(self): return reverse("car_detail", kwargs={"pk": self.object.pk}) -class CarDeleteView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, DeleteView): +class CarDeleteView( + LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, DeleteView +): model = models.Car template_name = "inventory/car_confirm_delete.html" success_url = reverse_lazy("inventory_stats") - permission_required = ['inventory.delete_car'] + permission_required = ["inventory.delete_car"] def delete(self, request, *args, **kwargs): messages.success(request, _("Car deleted successfully")) return super().delete(request, *args, **kwargs) -class CarLocationCreateView(LoginRequiredMixin,PermissionRequiredMixin,CreateView): +class CarLocationCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): model = models.CarLocation form_class = forms.CarLocationForm template_name = "inventory/car_location_form.html" - permission_required = ['inventory.add_carlocation'] + permission_required = ["inventory.add_carlocation"] def get_success_url(self): return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk}) @@ -1024,11 +1115,11 @@ class CarLocationCreateView(LoginRequiredMixin,PermissionRequiredMixin,CreateVie return super().form_valid(form) -class CarLocationUpdateView(LoginRequiredMixin,PermissionRequiredMixin,UpdateView): +class CarLocationUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): model = models.CarLocation form_class = forms.CarLocationForm template_name = "inventory/car_location_form.html" - permission_required = ['inventory.update_carlocation'] + permission_required = ["inventory.update_carlocation"] # def get_initial(self): # initial = super().get_initial() @@ -1047,7 +1138,7 @@ class CarLocationUpdateView(LoginRequiredMixin,PermissionRequiredMixin,UpdateVie return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk}) -class CarTransferCreateView(LoginRequiredMixin,CreateView): +class CarTransferCreateView(LoginRequiredMixin, CreateView): model = models.CarTransfer form_class = forms.CarTransferForm template_name = "inventory/car_transfer_form.html" @@ -1074,6 +1165,7 @@ class CarTransferCreateView(LoginRequiredMixin,CreateView): def get_success_url(self): return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk}) + class CarTransferDetailView(LoginRequiredMixin, SuccessMessageMixin, DetailView): model = models.CarTransfer template_name = "inventory/transfer_details.html" @@ -1084,6 +1176,7 @@ class CarTransferDetailView(LoginRequiredMixin, SuccessMessageMixin, DetailView) context["action"] = self.request.GET.get("action") return context + @login_required def car_transfer_approve(request, car_pk, transfer_pk): car = get_object_or_404(models.Car, pk=car_pk) @@ -1136,8 +1229,12 @@ def car_transfer_accept_reject(request, car_pk, transfer_pk): transfer_process = CarTransfer(car, transfer) success = transfer_process.transfer_car() if success: - messages.success(request, _("Car Transfer Completed successfully")) - models.Activity.objects.create(content_object=car,notes=f"Transfered from {transfer.from_dealer} to {transfer.to_dealer}",created_by=request.user) + messages.success(request, _("Car Transfer Completed successfully.")) + models.Activity.objects.create( + content_object=car, + notes=f"Transfered from {transfer.from_dealer} to {transfer.to_dealer}", + created_by=request.user, + ) models.Notification.objects.create( user=transfer.from_dealer.user, message=f"Car transfer request from {transfer.to_dealer} is completed.", @@ -1176,7 +1273,7 @@ class CustomCardCreateView(LoginRequiredMixin, CreateView): class CarRegistrationCreateView(LoginRequiredMixin, CreateView): model = models.CarRegistration form_class = forms.CarRegistrationForm - template_name = 'inventory/car_registration_form.html' + template_name = "inventory/car_registration_form.html" def form_valid(self, form): car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"]) @@ -1246,7 +1343,9 @@ class DealerDetailView(LoginRequiredMixin, DetailView): def get_queryset(self): return models.Dealer.objects.annotate( - staff_count=Coalesce(Count("staff"), Value(0)) # Get the number of staff members + staff_count=Coalesce( + Count("staff"), Value(0) + ) # Get the number of staff members ) def get_context_data(self, **kwargs): @@ -1266,7 +1365,9 @@ class DealerDetailView(LoginRequiredMixin, DetailView): context["cars_count"] = cars_count context["allowed_users"] = allowed_users context["allowed_cars"] = allowed_cars - context["quota_display"] = f"{staff_count}/{allowed_users}" if allowed_users is not None else "N/A" + context["quota_display"] = ( + f"{staff_count}/{allowed_users}" if allowed_users is not None else "N/A" + ) return context @@ -1281,19 +1382,22 @@ class DealerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): def get_success_url(self): return reverse("dealer_detail", kwargs={"pk": self.object.pk}) -class CustomerListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): + +class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): model = CustomerModel home_label = _("customers") context_object_name = "customers" paginate_by = 10 template_name = "customers/customer_list.html" ordering = ["-created"] - permission_required = ['django_ledger.view_customermodel'] + permission_required = ["django_ledger.view_customermodel"] def get_queryset(self): query = self.request.GET.get("q") dealer = get_user_type(self.request) - customers = dealer.entity.get_customers().filter(additional_info__type="customer") + customers = dealer.entity.get_customers().filter( + additional_info__type="customer" + ) return apply_search_filters(customers, query) def get_context_data(self, **kwargs): @@ -1302,11 +1406,11 @@ class CustomerListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): return context -class CustomerDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): +class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): model = CustomerModel template_name = "customers/view_customer.html" context_object_name = "customer" - permission_required = ['django_ledger.view_customermodel'] + permission_required = ["django_ledger.view_customermodel"] def get_context_data(self, **kwargs): dealer = get_user_type(self.request) @@ -1322,6 +1426,7 @@ class CustomerDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView) context["total"] = total return context + @login_required def add_note_to_customer(request, customer_id): customer = get_object_or_404(CustomerModel, uuid=customer_id) @@ -1336,7 +1441,9 @@ def add_note_to_customer(request, customer_id): return redirect("customer_detail", pk=customer.pk) else: form = forms.NoteForm() - return render(request, "customers/note_form.html", {"form": form, "customer": customer}) + return render( + request, "customers/note_form.html", {"form": form, "customer": customer} + ) @login_required @@ -1358,7 +1465,7 @@ def add_activity_to_customer(request, pk): @login_required -@permission_required('django_ledger.add_customermodel',raise_exception=True) +@permission_required("django_ledger.add_customermodel", raise_exception=True) def CustomerCreateView(request): form = forms.CustomerForm() if request.method == "POST": @@ -1366,15 +1473,23 @@ def CustomerCreateView(request): dealer = get_user_type(request) if form.is_valid(): - if dealer.entity.get_customers().filter(email=form.cleaned_data["email"]).exists(): - messages.error(request, _("Customer with this email already exists")) + if ( + dealer.entity.get_customers() + .filter(email=form.cleaned_data["email"]) + .exists() + ): + messages.error(request, _("Customer with this email already exists.")) else: # Create customer name customer_name = ( f"{form.cleaned_data['first_name']} " f"{form.cleaned_data['last_name']}" ) - customer_dict = { x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"} + customer_dict = { + x: request.POST[x] + for x in request.POST + if x != "csrfmiddlewaretoken" + } # Create customer instance try: customer = dealer.entity.create_customer( @@ -1384,7 +1499,7 @@ def CustomerCreateView(request): "address_1": form.cleaned_data["address"], "phone": form.cleaned_data["phone_number"], "email": form.cleaned_data["email"], - } + }, ) # customer.additional_info = {} customer.additional_info.update({"customer_info": customer_dict}) @@ -1404,7 +1519,7 @@ def CustomerCreateView(request): @login_required -@permission_required('django_ledger.change_customermodel',raise_exception=True) +@permission_required("django_ledger.change_customermodel", raise_exception=True) def CustomerUpdateView(request, pk): customer = get_object_or_404(CustomerModel, pk=pk) if request.method == "POST": @@ -1413,11 +1528,7 @@ def CustomerUpdateView(request, pk): x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken" } dealer = get_user_type(request) - customer_name = ( - customer_dict["first_name"] - + " " - + customer_dict["last_name"] - ) + customer_name = customer_dict["first_name"] + " " + customer_dict["last_name"] instance = dealer.entity.get_customers().get(pk=pk) instance.customer_name = customer_name @@ -1428,7 +1539,9 @@ def CustomerUpdateView(request, pk): customer_dict["pk"] = str(instance.pk) instance.additional_info.update({"customer_info": customer_dict}) try: - user = User.objects.filter(pk=int(instance.additional_info['user_info']['id'])).first() + user = User.objects.filter( + pk=int(instance.additional_info["user_info"]["id"]) + ).first() if user: user.username = customer_dict["email"] user.email = customer_dict["email"] @@ -1448,7 +1561,6 @@ def CustomerUpdateView(request, pk): return render(request, "customers/customer_form.html", {"form": form}) - @login_required def delete_customer(request, pk): customer = get_object_or_404(models.CustomerModel, pk=pk) @@ -1475,10 +1587,13 @@ class VendorListView(LoginRequiredMixin, ListView): vendors = dealer.entity.get_vendors().filter(active=True) return apply_search_filters(vendors, query) + @login_required def vendorDetailView(request, pk): vendor = get_object_or_404(models.Vendor, pk=pk) - return render(request, template_name="vendors/view_vendor.html", context={"vendor": vendor}) + return render( + request, template_name="vendors/view_vendor.html", context={"vendor": vendor} + ) class VendorCreateView( @@ -1510,6 +1625,26 @@ class VendorUpdateView( success_url = reverse_lazy("vendor_list") success_message = _("Vendor updated successfully") + def get_initial(self): + initial = super().get_initial() + initial = self.object.additional_info + return initial + + def form_valid(self, form): + instance = form.save(commit=False) + + instance.vendor_name = self.request.POST["name"] + instance.vendor_number = self.request.POST["crn"] + instance.address_1 = self.request.POST["address"] + instance.phone = self.request.POST["phone_number"] + instance.email = self.request.POST["email"] + instance.tax_id_number = self.request.POST["vrn"] + additionals = form.cleaned_data + additionals["phone_number"] = str(additionals["phone_number"]) + instance.additional_info = additionals + instance.save() + return super().form_valid(form) + @login_required def delete_vendor(request, pk): @@ -1519,7 +1654,8 @@ def delete_vendor(request, pk): messages.success(request, _("Vendor deleted successfully")) return redirect("vendor_list") -#group + +# group class GroupListView(LoginRequiredMixin, ListView): model = models.CustomGroup context_object_name = "groups" @@ -1530,6 +1666,7 @@ class GroupListView(LoginRequiredMixin, ListView): dealer = get_user_type(self.request) return dealer.groups.all() + class GroupDetailView(LoginRequiredMixin, DetailView): model = models.CustomGroup template_name = "groups/group_detail.html" @@ -1576,6 +1713,7 @@ class GroupUpdateView( instance.save() return super().form_valid(form) + @login_required def GroupDeleteview(request, pk): group = get_object_or_404(models.CustomGroup, pk=pk) @@ -1583,6 +1721,7 @@ def GroupDeleteview(request, pk): messages.success(request, _("Group deleted successfully")) return redirect("group_list") + @login_required def GroupPermissionView(request, pk): group = get_object_or_404(models.CustomGroup, pk=pk) @@ -1595,7 +1734,10 @@ def GroupPermissionView(request, pk): messages.success(request, _("Permission added successfully")) return redirect("group_detail", pk=group.pk) form = forms.PermissionForm(initial={"name": group.permissions}) - return render(request,"groups/group_permission_form.html",{"group": group, "form": form}) + return render( + request, "groups/group_permission_form.html", {"group": group, "form": form} + ) + # Users @login_required @@ -1614,8 +1756,11 @@ def UserGroupView(request, pk): return redirect("user_detail", pk=staff.pk) form = forms.UserGroupForm(initial={"name": staff.groups}) - form.fields['name'].queryset = models.CustomGroup.objects.filter(dealer=staff.dealer) - return render(request,"users/user_group_form.html",{"staff": staff, "form": form}) + form.fields["name"].queryset = models.CustomGroup.objects.filter( + dealer=staff.dealer + ) + return render(request, "users/user_group_form.html", {"staff": staff, "form": form}) + class UserListView(LoginRequiredMixin, ListView): model = models.Staff @@ -1664,7 +1809,9 @@ class UserCreateView( email = form.cleaned_data["email"] password = "Tenhal@123" - user = User.objects.create_user(username=form.cleaned_data["name"], email=email, password=password) + user = User.objects.create_user( + username=form.cleaned_data["name"], email=email, password=password + ) user.is_staff = True user.save() staff_member = StaffMember.objects.create(user=user) @@ -1697,15 +1844,17 @@ class UserUpdateView( kwargs["instance"] = self.get_object() # Pass the Staff instance to the form return kwargs - def get_form(self, form_class = None): + def get_form(self, form_class=None): form = super().get_form(form_class) - form.fields['email'].disabled = True + form.fields["email"].disabled = True return form + def get_initial(self): initial = super().get_initial() - initial['email'] = self.object.staff_member.user.email - initial['service_offered'] = self.object.staff_member.services_offered.all() + initial["email"] = self.object.staff_member.user.email + initial["service_offered"] = self.object.staff_member.services_offered.all() return initial + def form_valid(self, form): services = form.cleaned_data["service_offered"] if not services: @@ -1719,10 +1868,12 @@ class UserUpdateView( staff.arabic_name = form.cleaned_data["arabic_name"] staff.phone_number = form.cleaned_data["phone_number"] staff.staff_type = form.cleaned_data["staff_type"] + staff.add_as_manager() staff.save() return super().form_valid(form) + @login_required def UserDeleteview(request, pk): staff = get_object_or_404(models.Staff, pk=pk) @@ -1741,12 +1892,14 @@ class OrganizationListView(LoginRequiredMixin, ListView): def get_queryset(self): query = self.request.GET.get("q") dealer = get_user_type(self.request) - organization = dealer.entity.get_customers().filter(additional_info__type="organization", active=True) + organization = dealer.entity.get_customers().filter( + additional_info__type="organization", active=True + ) return apply_search_filters(organization, query) -class OrganizationDetailView(LoginRequiredMixin,DetailView): +class OrganizationDetailView(LoginRequiredMixin, DetailView): model = CustomerModel template_name = "organizations/organization_detail.html" context_object_name = "organization" @@ -1757,7 +1910,9 @@ def OrganizationCreateView(request): if request.method == "POST": form = forms.OrganizationForm(request.POST) if CustomerModel.objects.filter(email=request.POST["email"]).exists(): - messages.error(request, _("An organization with this email already exists")) + messages.error( + request, _("An organization with this email already exists.") + ) return redirect("organization_create") organization_dict = { @@ -1772,7 +1927,7 @@ def OrganizationCreateView(request): "address_1": organization_dict["address"], "phone": organization_dict["phone_number"], "email": organization_dict["email"], - } + }, ) image = request.FILES.get("logo") if image: @@ -1792,7 +1947,7 @@ def OrganizationCreateView(request): @login_required -def OrganizationUpdateView(request,pk): +def OrganizationUpdateView(request, pk): organization = get_object_or_404(CustomerModel, pk=pk) if request.method == "POST": form = forms.OrganizationForm(request.POST) @@ -1817,7 +1972,9 @@ def OrganizationUpdateView(request,pk): file_url = default_storage.url(file_name) organization_dict["logo"] = file_url else: - organization_dict["logo"] = organization.additional_info["customer_info"]["logo"] + organization_dict["logo"] = organization.additional_info["customer_info"][ + "logo" + ] organization_dict["pk"] = str(instance.pk) instance.additional_info["customer_info"] = organization_dict @@ -1847,9 +2004,10 @@ def OrganizationDeleteView(request, pk): messages.success(request, _("Organization deleted successfully")) except Exception as e: print("unable to delete user", e) - messages.error(request,_("Unable to delete organization")) + messages.error(request, _("Unable to delete organization")) return redirect("organization_list") + class RepresentativeListView(LoginRequiredMixin, ListView): model = models.Representative template_name = "representatives/representative_list.html" @@ -1863,7 +2021,7 @@ class RepresentativeListView(LoginRequiredMixin, ListView): return apply_search_filters(representative, query) -class RepresentativeDetailView(LoginRequiredMixin,DetailView): +class RepresentativeDetailView(LoginRequiredMixin, DetailView): model = models.Representative template_name = "representatives/representative_detail.html" context_object_name = "representative" @@ -1901,12 +2059,12 @@ class RepresentativeDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteVi # BANK ACCOUNT -class BankAccountListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): +class BankAccountListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): model = BankAccountModel template_name = "ledger/bank_accounts/bank_account_list.html" context_object_name = "bank_accounts" paginate_by = 10 - permission_required = ['inventory.view_carfinance'] + permission_required = ["inventory.view_carfinance"] def get_queryset(self): query = self.request.GET.get("q") @@ -1915,7 +2073,9 @@ class BankAccountListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): return apply_search_filters(bank_accounts, query) -class BankAccountCreateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, CreateView): +class BankAccountCreateView( + LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView +): model = BankAccountModel form_class = BankAccountCreateForm template_name = "ledger/bank_accounts/bank_account_form.html" @@ -1937,14 +2097,16 @@ class BankAccountCreateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessM return kwargs -class BankAccountDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): +class BankAccountDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): model = BankAccountModel template_name = "ledger/bank_accounts/bank_account_detail.html" context_object_name = "bank_account" - permission_required = ['inventory.view_carfinance'] + permission_required = ["inventory.view_carfinance"] -class BankAccountUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, UpdateView): +class BankAccountUpdateView( + LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView +): model = BankAccountModel form_class = BankAccountUpdateForm template_name = "ledger/bank_accounts/bank_account_form.html" @@ -1974,14 +2136,16 @@ def bank_account_delete(request, pk): {"bank_account": bank_account}, ) + # Accounts -class AccountListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): + +class AccountListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): model = AccountModel template_name = "ledger/coa_accounts/account_list.html" context_object_name = "accounts" paginate_by = 10 - permission_required = ['inventory.view_carfinance'] + permission_required = ["inventory.view_carfinance"] def get_queryset(self): query = self.request.GET.get("q") @@ -1990,7 +2154,9 @@ class AccountListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): return apply_search_filters(accounts, query) -class AccountCreateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, CreateView): +class AccountCreateView( + LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView +): model = AccountModel form_class = AccountModelCreateForm template_name = "ledger/coa_accounts/account_form.html" @@ -2015,17 +2181,17 @@ class AccountCreateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessa def get_form(self, form_class=None): form = super().get_form(form_class) entity = get_user_type(self.request).entity - form.initial['coa_model'] = entity.get_default_coa() + form.initial["coa_model"] = entity.get_default_coa() return form -class AccountDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): +class AccountDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): model = AccountModel template_name = "ledger/coa_accounts/account_detail.html" context_object_name = "account" slug_field = "uuid" DEFAULT_TXS_DAYS = 30 - permission_required = ['inventory.view_carfinance'] + permission_required = ["inventory.view_carfinance"] extra_context = { "DEFAULT_TXS_DAYS": DEFAULT_TXS_DAYS, "header_subtitle_icon": "ic:round-account-tree", @@ -2057,7 +2223,10 @@ class AccountDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): return context -class AccountUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, UpdateView): + +class AccountUpdateView( + LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView +): model = AccountModel form_class = AccountModelUpdateForm template_name = "ledger/coa_accounts/account_form.html" @@ -2073,7 +2242,7 @@ class AccountUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessa @login_required -@permission_required('inventory.view_carfinance') +@permission_required("inventory.view_carfinance") def account_delete(request, pk): account = get_object_or_404(AccountModel, pk=pk) @@ -2084,14 +2253,16 @@ def account_delete(request, pk): # Sales list @login_required -@permission_required('inventory.view_lead',raise_exception=True) +@permission_required("inventory.view_lead", raise_exception=True) def sales_list_view(request): dealer = get_user_type(request) entity = dealer.entity - transactions = ItemTransactionModel.objects.for_entity(entity_slug=entity.slug, user_model=dealer.user) + transactions = ItemTransactionModel.objects.for_entity( + entity_slug=entity.slug, user_model=dealer.user + ) paginator = Paginator(transactions, 10) - page_number = request.GET.get('page') + page_number = request.GET.get("page") page_obj = paginator.get_page(page_number) txs = get_item_transactions(page_obj) context = {"txs": txs, "page_obj": page_obj} @@ -2099,26 +2270,27 @@ def sales_list_view(request): # Estimates -class EstimateListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): +class EstimateListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): model = EstimateModel template_name = "sales/estimates/estimate_list.html" context_object_name = "estimates" paginate_by = 20 - permission_required = ['django_ledger.view_estimatemodel'] + permission_required = ["django_ledger.view_estimatemodel"] def get_queryset(self): dealer = get_user_type(self.request) entity = dealer.entity - status = self.request.GET.get('status') + status = self.request.GET.get("status") queryset = entity.get_estimates() if status: queryset = queryset.filter(status=status) return queryset + # @csrf_exempt @login_required -@permission_required('django_ledger.add_estimatemodel',raise_exception=True) -def create_estimate(request,pk=None): +@permission_required("django_ledger.add_estimatemodel", raise_exception=True) +def create_estimate(request, pk=None): dealer = get_user_type(request) entity = dealer.entity @@ -2151,12 +2323,18 @@ def create_estimate(request,pk=None): ) if isinstance(items, list): for item, quantity in zip(items, quantities): - if int(quantity) > models.Car.objects.filter(hash=item,status='available').count(): + if ( + int(quantity) + > models.Car.objects.filter(hash=item, status="available").count() + ): return JsonResponse( {"status": "error", "message": _("Quantity must be less than or equal to the number of cars in stock")}, ) else: - if int(quantities) > models.Car.objects.filter(hash=items,status='available').count(): + if ( + int(quantities) + > models.Car.objects.filter(hash=items, status="available").count() + ): return JsonResponse( {"status": "error", "message": _("Quantity must be less than or equal to the number of cars in stock")}, ) @@ -2179,16 +2357,24 @@ def create_estimate(request,pk=None): ] items_txs = [] for item in items_list: - car_instance = ItemModel.objects.filter(additional_info__car_info__hash=item.get("item_id")).all() + car_instance = ItemModel.objects.filter( + additional_info__car_info__hash=item.get("item_id") + ).all() - for i in car_instance[:int(quantities[0])]: + for i in car_instance[: int(quantities[0])]: items_txs.append( { "item_number": i.item_number, "quantity": 1, - "unit_cost": i.additional_info.get('car_finance').get("selling_price"), - "unit_revenue": i.additional_info.get('car_finance').get("selling_price"), - "total_amount": (i.additional_info.get('car_finance').get("total_vat")) + "unit_cost": i.additional_info.get("car_finance").get( + "selling_price" + ), + "unit_revenue": i.additional_info.get("car_finance").get( + "selling_price" + ), + "total_amount": ( + i.additional_info.get("car_finance").get("total_vat") + ), } ) @@ -2226,7 +2412,9 @@ def create_estimate(request,pk=None): reserve_car(instance, request) else: - item_instance = ItemModel.objects.filter(additioinal_info__car_info__hash=items).first() + item_instance = ItemModel.objects.filter( + additioinal_info__car_info__hash=items + ).first() instance = models.Car.objects.get(hash=item) response = reserve_car(instance, request) @@ -2248,12 +2436,14 @@ def create_estimate(request,pk=None): form = forms.EstimateModelCreateForm( entity_slug=entity.slug, user_model=entity.admin ) - form.fields["customer"].queryset = entity.get_customers().filter(active=True,additional_info__type="customer") + form.fields["customer"].queryset = entity.get_customers().filter( + active=True, additional_info__type="customer" + ) if pk: opportunity = models.Opportunity.objects.get(pk=pk) customer = opportunity.customer - form.initial['customer'] = customer + form.initial["customer"] = customer car_list = models.Car.objects.filter(dealer=dealer,colors__isnull=False,finances__isnull=False,status="available").annotate(color=F('colors__exterior__rgb'),color_name=F('colors__exterior__arabic_name')).values_list( 'id_car_make__arabic_name', 'id_car_model__arabic_name','id_car_serie__arabic_name','id_car_trim__arabic_name','color','color_name','hash').annotate(hash_count=Count('hash')).distinct() @@ -2261,29 +2451,29 @@ def create_estimate(request,pk=None): "form": form, "items": [ { - 'make':x[0], - 'model':x[1], - 'serie':x[2], - 'trim':x[3], - 'color':x[4], - 'color_name':x[5], - 'hash': x[6], - 'hash_count': x[7] + "make": x[0], + "model": x[1], + "serie": x[2], + "trim": x[3], + "color": x[4], + "color_name": x[5], + "hash": x[6], + "hash_count": x[7], } for x in car_list ], "opportunity_id": pk if pk else None, - "customer_count": entity.get_customers().count() + "customer_count": entity.get_customers().count(), } return render(request, "sales/estimates/estimate_form.html", context) -class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin,DetailView): +class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): model = EstimateModel template_name = "sales/estimates/estimate_detail.html" context_object_name = "estimate" - permission_required = ['django_ledger.view_estimatemodel'] + permission_required = ["django_ledger.view_estimatemodel"] def get_context_data(self, **kwargs): estimate = kwargs.get("object") @@ -2299,7 +2489,7 @@ class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin,DetailView) @login_required -@permission_required('inventory.add_saleorder', raise_exception=True) +@permission_required("inventory.add_saleorder", raise_exception=True) def create_sale_order(request, pk): estimate = get_object_or_404(EstimateModel, pk=pk) items = estimate.get_itemtxs_data()[0].all() @@ -2313,7 +2503,7 @@ def create_sale_order(request, pk): estimate.save() for item in estimate.get_itemtxs_data()[0].all(): try: - item.item_model.additional_info['car_info']['status'] = 'sold' + item.item_model.additional_info["car_info"]["status"] = "sold" item.item_model.save() except KeyError: pass @@ -2345,11 +2535,11 @@ def preview_sale_order(request, pk): ) -class PaymentRequest(LoginRequiredMixin,PermissionRequiredMixin,DetailView): +class PaymentRequest(LoginRequiredMixin, PermissionRequiredMixin, DetailView): model = EstimateModel template_name = "sales/estimates/payment_request_detail.html" context_object_name = "estimate" - permission_required = ['django_ledger.view_invoicemodel'] + permission_required = ["django_ledger.view_invoicemodel"] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -2361,11 +2551,11 @@ class PaymentRequest(LoginRequiredMixin,PermissionRequiredMixin,DetailView): return context -class EstimatePreviewView(LoginRequiredMixin,PermissionRequiredMixin,DetailView): +class EstimatePreviewView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): model = EstimateModel context_object_name = "estimate" template_name = "sales/estimates/estimate_preview.html" - permission_required = ['django_ledger.view_estimatemodel'] + permission_required = ["django_ledger.view_estimatemodel"] def get_context_data(self, **kwargs): dealer = get_user_type(self.request) @@ -2383,7 +2573,7 @@ class EstimatePreviewView(LoginRequiredMixin,PermissionRequiredMixin,DetailView) @login_required -@permission_required('django_ledger.change_estimatemodel', raise_exception=True) +@permission_required("django_ledger.change_estimatemodel", raise_exception=True) def estimate_mark_as(request, pk): estimate = get_object_or_404(EstimateModel, pk=pk) dealer = get_user_type(request) @@ -2418,7 +2608,9 @@ def estimate_mark_as(request, pk): return redirect("estimate_detail", pk=estimate.pk) estimate.mark_as_canceled() try: - car = models.Car.objects.get(vin=estimate.get_itemtxs_data()[0].first().item_model.name) + car = models.Car.objects.get( + vin=estimate.get_itemtxs_data()[0].first().item_model.name + ) car.status = "available" car.save() except Exception as e: @@ -2431,12 +2623,12 @@ def estimate_mark_as(request, pk): # Invoice -class InvoiceListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): +class InvoiceListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): model = InvoiceModel template_name = "sales/invoices/invoice_list.html" context_object_name = "invoices" paginate_by = 20 - permission_required = ['django_ledger.view_invoicemodel'] + permission_required = ["django_ledger.view_invoicemodel"] def get_queryset(self): query = self.request.GET.get("q") @@ -2444,11 +2636,12 @@ class InvoiceListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): invoices = dealer.entity.get_invoices() return apply_search_filters(invoices, query) -class InvoiceDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): + +class InvoiceDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): model = InvoiceModel template_name = "sales/invoices/invoice_detail.html" context_object_name = "invoice" - permission_required = ['django_ledger.view_invoicemodel'] + permission_required = ["django_ledger.view_invoicemodel"] def get_context_data(self, **kwargs): invoice = kwargs.get("object") @@ -2464,12 +2657,14 @@ class InvoiceDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): return super().get_context_data(**kwargs) -class DraftInvoiceModelUpdateFormView(LoginRequiredMixin,PermissionRequiredMixin, UpdateView): +class DraftInvoiceModelUpdateFormView( + LoginRequiredMixin, PermissionRequiredMixin, UpdateView +): model = InvoiceModel form_class = DraftInvoiceModelUpdateForm template_name = "sales/invoices/draft_invoice_update.html" success_url = reverse_lazy("invoice_list") - permission_required = ['django_ledger.view_invoicemodel'] + permission_required = ["django_ledger.view_invoicemodel"] def get_form_kwargs(self): kwargs = super().get_form_kwargs() @@ -2479,12 +2674,14 @@ class DraftInvoiceModelUpdateFormView(LoginRequiredMixin,PermissionRequiredMixin return kwargs -class ApprovedInvoiceModelUpdateFormView(LoginRequiredMixin,PermissionRequiredMixin, UpdateView): +class ApprovedInvoiceModelUpdateFormView( + LoginRequiredMixin, PermissionRequiredMixin, UpdateView +): model = InvoiceModel form_class = ApprovedInvoiceModelUpdateForm template_name = "sales/invoices/approved_invoice_update.html" success_url = reverse_lazy("invoice_list") - permission_required = ['django_ledger.view_invoicemodel'] + permission_required = ["django_ledger.view_invoicemodel"] def get_form_kwargs(self): kwargs = super().get_form_kwargs() @@ -2497,12 +2694,14 @@ class ApprovedInvoiceModelUpdateFormView(LoginRequiredMixin,PermissionRequiredMi return reverse_lazy("invoice_detail", kwargs={"pk": self.object.pk}) -class PaidInvoiceModelUpdateFormView(LoginRequiredMixin,PermissionRequiredMixin, UpdateView): +class PaidInvoiceModelUpdateFormView( + LoginRequiredMixin, PermissionRequiredMixin, UpdateView +): model = InvoiceModel form_class = PaidInvoiceModelUpdateForm template_name = "sales/invoices/paid_invoice_update.html" success_url = reverse_lazy("invoice_list") - permission_required = ['django_ledger.view_invoicemodel'] + permission_required = ["django_ledger.view_invoicemodel"] def get_form_kwargs(self): kwargs = super().get_form_kwargs() @@ -2527,7 +2726,7 @@ class PaidInvoiceModelUpdateFormView(LoginRequiredMixin,PermissionRequiredMixin, @login_required -@permission_required('django_ledger.change_invoicemodel',raise_exception=True) +@permission_required("django_ledger.change_invoicemodel", raise_exception=True) def invoice_mark_as(request, pk): invoice = get_object_or_404(InvoiceModel, pk=pk) dealer = get_user_type(request) @@ -2544,7 +2743,7 @@ def invoice_mark_as(request, pk): @login_required -@permission_required('django_ledger.add_invoicemodel',raise_exception=True) +@permission_required("django_ledger.add_invoicemodel", raise_exception=True) def invoice_create(request, pk): estimate = get_object_or_404(EstimateModel, pk=pk) dealer = get_user_type(request) @@ -2562,7 +2761,6 @@ def invoice_create(request, pk): ledger.save() invoice.save() - calculator = CarFinanceCalculator(estimate) finance_data = calculator.get_finance_data() @@ -2609,11 +2807,11 @@ def invoice_create(request, pk): return render(request, "sales/invoices/invoice_create.html", context) -class InvoicePreviewView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): +class InvoicePreviewView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): model = InvoiceModel context_object_name = "invoice" template_name = "sales/invoices/invoice_preview.html" - permission_required = ['django_ledger.view_invoicemodel'] + permission_required = ["django_ledger.view_invoicemodel"] def get_context_data(self, **kwargs): dealer = get_user_type(self.request) @@ -2622,13 +2820,15 @@ class InvoicePreviewView(LoginRequiredMixin,PermissionRequiredMixin, DetailView) calculator = CarFinanceCalculator(invoice) finance_data = calculator.get_finance_data() kwargs["data"] = finance_data - kwargs['dealer'] = dealer + kwargs["dealer"] = dealer return super().get_context_data(**kwargs) + # payments + @login_required -@permission_required('django_ledger.add_journalentrymodel',raise_exception=True) +@permission_required("django_ledger.add_journalentrymodel", raise_exception=True) def PaymentCreateView(request, pk): invoice = InvoiceModel.objects.filter(pk=pk).first() bill = BillModel.objects.filter(pk=pk).first() @@ -2681,7 +2881,7 @@ def PaymentCreateView(request, pk): @login_required -@permission_required('django_ledger.view_journalentrymodel',raise_exception=True) +@permission_required("django_ledger.view_journalentrymodel", raise_exception=True) def PaymentListView(request): dealer = get_user_type(request) entity = dealer.entity @@ -2690,7 +2890,7 @@ def PaymentListView(request): @login_required -@permission_required('django_ledger.view_journalentrymodel',raise_exception=True) +@permission_required("django_ledger.view_journalentrymodel", raise_exception=True) def PaymentDetailView(request, pk): journal = JournalEntryModel.objects.filter(pk=pk).first() transactions = ( @@ -2706,7 +2906,7 @@ def PaymentDetailView(request, pk): @login_required -@permission_required('django_ledger.change_journalentrymodel',raise_exception=True) +@permission_required("django_ledger.change_journalentrymodel", raise_exception=True) def payment_mark_as_paid(request, pk): invoice = get_object_or_404(InvoiceModel, pk=pk) if request.method == "POST": @@ -2722,7 +2922,7 @@ def payment_mark_as_paid(request, pk): invoice.ledger.lock_journal_entries() invoice.ledger.post_journal_entries() - invoice.ledger.post() + # invoice.ledger.post() invoice.ledger.save() messages.success(request, _("Payment created successfully")) else: @@ -2736,7 +2936,7 @@ def payment_mark_as_paid(request, pk): # activity log -class UserActivityLogListView(LoginRequiredMixin,ListView): +class UserActivityLogListView(LoginRequiredMixin, ListView): model = models.UserActivityLog template_name = "dealers/activity_log.html" context_object_name = "logs" @@ -2746,16 +2946,16 @@ class UserActivityLogListView(LoginRequiredMixin,ListView): queryset = super().get_queryset() if "user" in self.request.GET: queryset = queryset.filter(user__email=self.request.GET["user"]) - return queryset[:100] # will update later with better pagination + return queryset[:100] # will update later with better pagination # CRM RELATED VIEWS -class LeadListView(LoginRequiredMixin,PermissionRequiredMixin,ListView): +class LeadListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): model = models.Lead template_name = "crm/leads/lead_list.html" context_object_name = "leads" paginate_by = 10 - permission_required = ['inventory.view_lead'] + permission_required = ["inventory.view_lead"] def get_queryset(self): dealer = get_user_type(self.request) @@ -2768,10 +2968,10 @@ class LeadListView(LoginRequiredMixin,PermissionRequiredMixin,ListView): return models.Lead.objects.none() -class LeadDetailView(LoginRequiredMixin,PermissionRequiredMixin,DetailView): +class LeadDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): model = models.Lead template_name = "crm/leads/lead_detail.html" - permission_required = ['inventory.view_lead'] + permission_required = ["inventory.view_lead"] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -2796,21 +2996,23 @@ class LeadDetailView(LoginRequiredMixin,PermissionRequiredMixin,DetailView): @login_required -@permission_required('inventory.add_lead',raise_exception=True) +@permission_required("inventory.add_lead", raise_exception=True) def lead_create(request): form = forms.LeadForm() make = request.GET.get("id_car_make", None) if make: - form.fields['id_car_model'].queryset = models.CarModel.objects.filter(id_car_make=int(make)) + form.fields["id_car_model"].queryset = models.CarModel.objects.filter( + id_car_make=int(make) + ) if request.method == "POST": form = forms.LeadForm(request.POST) # Filter car models based on the selected make (for POST requests) - if 'id_car_make' in request.POST: - form.fields['id_car_model'].queryset = models.CarModel.objects.filter( - id_car_make=int(request.POST['id_car_make']) + if "id_car_make" in request.POST: + form.fields["id_car_model"].queryset = models.CarModel.objects.filter( + id_car_make=int(request.POST["id_car_make"]) ) try: if form.is_valid(): @@ -2820,7 +3022,9 @@ def lead_create(request): instance.staff = form.cleaned_data.get("staff") # creating customer in ledger - customer = dealer.entity.get_customers().filter(email=instance.email).first() + customer = ( + dealer.entity.get_customers().filter(email=instance.email).first() + ) if not customer: customer = dealer.entity.create_customer( commit=False, @@ -2830,7 +3034,7 @@ def lead_create(request): "phone": instance.phone_number, "email": instance.email, "sales_tax_rate": 0.15, - } + }, ) customer_info = { "first_name": instance.first_name, @@ -2841,8 +3045,8 @@ def lead_create(request): "crn": form.cleaned_data["crn"], "vrn": form.cleaned_data["vrn"], } - customer.additional_info.update({"customer_info": customer_info }) - customer.additional_info.update({"type":"lead"}) + customer.additional_info.update({"customer_info": customer_info}) + customer.additional_info.update({"type": "lead"}) customer.save() instance.customer = customer # try: @@ -2856,27 +3060,29 @@ def lead_create(request): messages.success(request, _("Lead created successfully")) return redirect("lead_list") except Exception as e: - messages.error(request, f"Lead was not created ... : {str(e)}") + messages.error(request, f"Lead was not created ... : {str(e)}") return render(request, "crm/leads/lead_form.html", {"form": form}) -class LeadUpdateView(LoginRequiredMixin,PermissionRequiredMixin,UpdateView): +class LeadUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): model = models.Lead form_class = forms.LeadForm template_name = "crm/leads/lead_form.html" success_url = reverse_lazy("lead_list") - permission_required = ['inventory.change_lead'] + permission_required = ["inventory.change_lead"] def get_form(self, form_class=None): form = super().get_form(form_class) - form.fields["id_car_model"].queryset = form.instance.id_car_make.carmodel_set.all() + form.fields[ + "id_car_model" + ].queryset = form.instance.id_car_make.carmodel_set.all() return form @login_required -@permission_required('inventory.delete_lead',raise_exception=True) -def LeadDeleteView(request,pk): +@permission_required("inventory.delete_lead", raise_exception=True) +def LeadDeleteView(request, pk): lead = get_object_or_404(models.Lead, pk=pk) try: User.objects.get(email=lead.customer.email).delete() @@ -2889,7 +3095,6 @@ def LeadDeleteView(request,pk): @login_required -@permission_required('inventory.add_notes',raise_exception=True) def add_note_to_lead(request, pk): lead = get_object_or_404(models.Lead, pk=pk) if request.method == "POST": @@ -2905,8 +3110,8 @@ def add_note_to_lead(request, pk): form = forms.NoteForm() return render(request, "crm/note_form.html", {"form": form, "lead": lead}) + @login_required -@permission_required('inventory.add_notes',raise_exception=True) def add_note_to_opportunity(request, pk): opportunity = get_object_or_404(models.Opportunity, pk=pk) if request.method == "POST": @@ -2920,7 +3125,6 @@ def add_note_to_opportunity(request, pk): @login_required -@permission_required('inventory.change_notes',raise_exception=True) def update_note(request, pk): note = get_object_or_404(models.Notes, pk=pk, created_by=request.user) lead_pk = note.content_object.pk @@ -2941,7 +3145,6 @@ def update_note(request, pk): @login_required -@permission_required('inventory.delete_notes',raise_exception=True) def delete_note(request, pk): note = get_object_or_404(models.Notes, pk=pk, created_by=request.user) lead_pk = note.content_object.pk @@ -2951,7 +3154,7 @@ def delete_note(request, pk): @login_required -@permission_required('inventory.change_lead',raise_exception=True) +@permission_required("inventory.change_lead", raise_exception=True) def lead_convert(request, pk): lead = get_object_or_404(models.Lead, pk=pk) dealer = get_user_type(request) @@ -2963,8 +3166,9 @@ def lead_convert(request, pk): messages.success(request, _("Lead converted to customer successfully")) return redirect("lead_list") + @login_required -@permission_required('inventory.add_appointmentrequest',raise_exception=True) +@permission_required("inventory.add_lead", raise_exception=True) def schedule_lead(request, pk): if not request.is_staff: messages.error(request, _("You do not have permission to schedule lead")) @@ -2980,10 +3184,11 @@ def schedule_lead(request, pk): instance.customer = lead.customer # Create AppointmentRequest - service = Service.objects.filter(name=instance.scheduled_type).first() - if not service: - messages.error(request, _("Service not found")) - return redirect("lead_list") + service,_ = Service.objects.get_or_create(name=instance.scheduled_type,duration=datetime.timedelta(minutes=10),price=0) + # service = Service.objects.filter(name=instance.scheduled_type).first() + # if not service: + # messages.error(request, "Service not found!") + # return redirect("lead_list") try: appointment_request = AppointmentRequest.objects.create( @@ -3018,8 +3223,8 @@ def schedule_lead(request, pk): @login_required -@permission_required('inventory.change_lead',raise_exception=True) -def lead_transfer(request,pk): +@permission_required("inventory.change_lead", raise_exception=True) +def lead_transfer(request, pk): lead = get_object_or_404(models.Lead, pk=pk) if request.method == "POST": form = forms.LeadTransferForm(request.POST) @@ -3031,9 +3236,9 @@ def lead_transfer(request,pk): messages.error(request, f"Invalid form data: {str(form.errors)}") return redirect("lead_list") + @login_required -@permission_required('inventory.add_email',raise_exception=True) -def send_lead_email(request, pk,email_pk=None): +def send_lead_email(request, pk, email_pk=None): lead = get_object_or_404(models.Lead, pk=pk) status = request.GET.get("status") dealer = get_user_type(request) @@ -3042,7 +3247,7 @@ def send_lead_email(request, pk,email_pk=None): models.Activity.objects.create(dealer=dealer,content_object=lead, notes="Email Draft",created_by=request.user,activity_type=models.ActionChoices.EMAIL) messages.success(request, _("Email Draft successfully")) response = HttpResponse(redirect("lead_detail", pk=lead.pk)) - response['HX-Redirect'] = reverse('lead_detail', args=[lead.pk]) + response["HX-Redirect"] = reverse("lead_detail", args=[lead.pk]) return response lead.status = models.Status.CONTACTED @@ -3050,13 +3255,21 @@ def send_lead_email(request, pk,email_pk=None): # lead.convert_to_customer(dealer.entity) if request.method == "POST": - email_pk = request.POST.get('email_pk') - if email_pk not in [None,"None",""]: + email_pk = request.POST.get("email_pk") + if email_pk not in [None, "None", ""]: email = get_object_or_404(models.Email, pk=int(email_pk)) email.status = models.EmailStatus.SENT email.save() else: - models.Email.objects.create(content_object=lead, created_by=request.user,from_email="manager@tenhal.com", to_email=request.POST.get("to"), subject=request.POST.get("subject"), message=request.POST.get("message"),status=models.EmailStatus.SENT) + models.Email.objects.create( + content_object=lead, + created_by=request.user, + from_email="manager@tenhal.com", + to_email=request.POST.get("to"), + subject=request.POST.get("subject"), + message=request.POST.get("message"), + status=models.EmailStatus.SENT, + ) send_email( "manager@tenhal.com", request.POST.get("to"), @@ -3097,7 +3310,7 @@ def send_lead_email(request, pk,email_pk=None): return render( request, "crm/leads/lead_send.html", - {"lead": lead, "message": msg,"subject":subject, "email_pk": email_pk}, + {"lead": lead, "message": msg, "subject": subject, "email_pk": email_pk}, ) @@ -3129,10 +3342,10 @@ class OpportunityCreateView(CreateView, LoginRequiredMixin): def get_initial(self): initial = super().get_initial() - if self.kwargs.get('pk',None): - lead = models.Lead.objects.get(pk=self.kwargs.get('pk')) + if self.kwargs.get("pk", None): + lead = models.Lead.objects.get(pk=self.kwargs.get("pk")) - initial['customer'] = lead.customer + initial["customer"] = lead.customer return initial def form_valid(self, form): @@ -3144,7 +3357,7 @@ class OpportunityCreateView(CreateView, LoginRequiredMixin): return reverse_lazy("opportunity_detail", kwargs={"pk": self.object.pk}) -class OpportunityUpdateView(LoginRequiredMixin,UpdateView): +class OpportunityUpdateView(LoginRequiredMixin, UpdateView): model = models.Opportunity form_class = forms.OpportunityForm template_name = "crm/opportunities/opportunity_form.html" @@ -3153,7 +3366,7 @@ class OpportunityUpdateView(LoginRequiredMixin,UpdateView): return reverse_lazy("opportunity_detail", kwargs={"pk": self.object.pk}) -class OpportunityDetailView(LoginRequiredMixin,DetailView): +class OpportunityDetailView(LoginRequiredMixin, DetailView): model = models.Opportunity template_name = "crm/opportunities/opportunity_detail.html" context_object_name = "opportunity" @@ -3167,9 +3380,15 @@ class OpportunityDetailView(LoginRequiredMixin,DetailView): form.fields["status"].initial = self.object.status form.fields["stage"].initial = self.object.stage context["status_form"] = form - context["notes"] = models.Notes.objects.filter(content_type__model="opportunity", object_id=self.object.id) - context["activities"] = models.Activity.objects.filter(content_type__model="opportunity", object_id=self.object.id) - email_qs = models.Email.objects.filter(content_type__model="opportunity", object_id=self.object.id) + context["notes"] = models.Notes.objects.filter( + content_type__model="opportunity", object_id=self.object.id + ) + context["activities"] = models.Activity.objects.filter( + content_type__model="opportunity", object_id=self.object.id + ) + email_qs = models.Email.objects.filter( + content_type__model="opportunity", object_id=self.object.id + ) context["emails"] = { "sent": email_qs.filter(status="SENT"), "draft": email_qs.filter(status="DRAFT"), @@ -3177,7 +3396,7 @@ class OpportunityDetailView(LoginRequiredMixin,DetailView): return context -class OpportunityListView(LoginRequiredMixin,ListView): +class OpportunityListView(LoginRequiredMixin, ListView): model = models.Opportunity template_name = "crm/opportunities/opportunity_list.html" context_object_name = "opportunities" @@ -3187,6 +3406,7 @@ class OpportunityListView(LoginRequiredMixin,ListView): dealer = get_user_type(self.request) return models.Opportunity.objects.filter(dealer=dealer).all() + @login_required def delete_opportunity(request, pk): opportunity = get_object_or_404(models.Opportunity, pk=pk) @@ -3194,8 +3414,9 @@ def delete_opportunity(request, pk): messages.success(request, _("Opportunity deleted successfully")) return redirect("opportunity_list") + @login_required -def opportunity_update_status(request,pk): +def opportunity_update_status(request, pk): opportunity = get_object_or_404(models.Opportunity, pk=pk) status = request.GET.get("status") stage = request.GET.get("stage") @@ -3239,7 +3460,9 @@ def fetch_notifications(request): return render(request, "notifications.html", {"notifications_": notifications}) -class ItemServiceCreateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, CreateView): +class ItemServiceCreateView( + LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView +): model = models.AdditionalServices form_class = forms.AdditionalServiceForm template_name = "items/service/service_create.html" @@ -3257,7 +3480,9 @@ class ItemServiceCreateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessM return super().form_valid(form) -class ItemServiceUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessMessageMixin, UpdateView): +class ItemServiceUpdateView( + LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView +): model = models.AdditionalServices form_class = forms.AdditionalServiceForm template_name = "items/service/service_create.html" @@ -3275,7 +3500,7 @@ class ItemServiceUpdateView(LoginRequiredMixin,PermissionRequiredMixin, SuccessM return super().form_valid(form) -class ItemServiceListView(LoginRequiredMixin,PermissionRequiredMixin,ListView): +class ItemServiceListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): model = models.AdditionalServices template_name = "items/service/service_list.html" context_object_name = "services" @@ -3287,7 +3512,7 @@ class ItemServiceListView(LoginRequiredMixin,PermissionRequiredMixin,ListView): return models.AdditionalServices.objects.filter(dealer=dealer).all() -class ItemExpenseCreateView(LoginRequiredMixin,PermissionRequiredMixin, CreateView): +class ItemExpenseCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): model = ItemModel form_class = ExpenseItemCreateForm template_name = "items/expenses/expense_create.html" @@ -3307,7 +3532,7 @@ class ItemExpenseCreateView(LoginRequiredMixin,PermissionRequiredMixin, CreateVi return super().form_valid(form) -class ItemExpenseUpdateView(LoginRequiredMixin,PermissionRequiredMixin, UpdateView): +class ItemExpenseUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): model = ItemModel form_class = ExpenseItemUpdateForm template_name = "items/expenses/expense_update.html" @@ -3327,7 +3552,7 @@ class ItemExpenseUpdateView(LoginRequiredMixin,PermissionRequiredMixin, UpdateVi return super().form_valid(form) -class ItemExpenseListView(LoginRequiredMixin,PermissionRequiredMixin,ListView): +class ItemExpenseListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): model = ItemModel template_name = "items/expenses/expenses_list.html" context_object_name = "expenses" @@ -3339,7 +3564,7 @@ class ItemExpenseListView(LoginRequiredMixin,PermissionRequiredMixin,ListView): return dealer.entity.get_items_expenses() -class BillListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): +class BillListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): model = BillModel template_name = "ledger/bills/bill_list.html" context_object_name = "bills" @@ -3351,7 +3576,7 @@ class BillListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): return qs -class BillDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): +class BillDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): model = BillModel template_name = "ledger/bills/bill_detail.html" context_object_name = "bill" @@ -3369,10 +3594,7 @@ class BillDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): } for x in txs ] - grand_total = sum( - 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 @@ -3380,7 +3602,7 @@ class BillDetailView(LoginRequiredMixin,PermissionRequiredMixin, DetailView): return super().get_context_data(**kwargs) -class InReviewBillView(LoginRequiredMixin,PermissionRequiredMixin, UpdateView): +class InReviewBillView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): model = BillModel form_class = InReviewBillModelUpdateForm template_name = "ledger/bills/bill_update_form.html" @@ -3406,7 +3628,7 @@ class InReviewBillView(LoginRequiredMixin,PermissionRequiredMixin, UpdateView): return super().form_valid(form) -class ApprovedBillModelView(LoginRequiredMixin,PermissionRequiredMixin, UpdateView): +class ApprovedBillModelView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): model = BillModel form_class = ApprovedBillModelUpdateForm template_name = "ledger/bills/bill_update_form.html" @@ -3469,6 +3691,7 @@ def bill_mark_as_paid(request, pk): messages.error(request, _("Amount paid is not equal to amount due")) return redirect("bill_detail", pk=bill.pk) + @login_required @permission_required("django_ledger.add_billmodel", raise_exception=True) def bill_create(request): @@ -3568,7 +3791,7 @@ def bill_create(request): { "cash_account": dealer.settings.bill_cash_account, "prepaid_account": dealer.settings.bill_prepaid_account, - "unearned_account": dealer.settings.bill_unearned_account + "unearned_account": dealer.settings.bill_unearned_account, } ) car_list = models.Car.objects.filter(dealer=dealer) @@ -3593,8 +3816,9 @@ def BillDeleteView(request, pk): bill.delete() return redirect("bill_list") + # orders -class OrderListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): +class OrderListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): model = models.SaleOrder template_name = "sales/orders/order_list.html" context_object_name = "orders" @@ -3605,6 +3829,7 @@ class OrderListView(LoginRequiredMixin,PermissionRequiredMixin, ListView): qs = super().get_queryset() return qs.filter(estimate__entity=dealer.entity) + # email @login_required @permission_required("django_ledger.view_estimate", raise_exception=True) @@ -3616,7 +3841,9 @@ def send_email_view(request, pk): messages.error(request, _("Quotation has no items")) return redirect("estimate_detail", pk=estimate.pk) - link = request.build_absolute_uri(reverse_lazy("estimate_preview", kwargs={"pk": estimate.pk})) + link = request.build_absolute_uri( + reverse_lazy("estimate_preview", kwargs={"pk": estimate.pk}) + ) msg = f""" السلام عليكم @@ -3681,9 +3908,12 @@ class BaseBalanceSheetRedirectView(RedirectView): ) def get_login_url(self): - return reverse('account_login') + return reverse("account_login") -class FiscalYearBalanceSheetViewBase(FiscalYearBalanceSheetView,DjangoLedgerSecurityMixIn): + +class FiscalYearBalanceSheetViewBase( + FiscalYearBalanceSheetView, DjangoLedgerSecurityMixIn +): template_name = "ledger/reports/balance_sheet.html" # AUTHORIZE_SUPERUSER = False # permission_required = [] @@ -3693,21 +3923,28 @@ class FiscalYearBalanceSheetViewBase(FiscalYearBalanceSheetView,DjangoLedgerSecu # return EntityModel.objects.filter(admin=dealer) def get_login_url(self): - return reverse('account_login') + return reverse("account_login") -class QuarterlyBalanceSheetView(FiscalYearBalanceSheetViewBase, QuarterlyReportMixIn, DjangoLedgerSecurityMixIn): + +class QuarterlyBalanceSheetView( + FiscalYearBalanceSheetViewBase, QuarterlyReportMixIn, DjangoLedgerSecurityMixIn +): """ Quarter Balance Sheet View. """ -class MonthlyBalanceSheetView(FiscalYearBalanceSheetViewBase, MonthlyReportMixIn, DjangoLedgerSecurityMixIn): +class MonthlyBalanceSheetView( + FiscalYearBalanceSheetViewBase, MonthlyReportMixIn, DjangoLedgerSecurityMixIn +): """ Monthly Balance Sheet View. """ -class DateBalanceSheetView(FiscalYearBalanceSheetViewBase, DateReportMixIn, DjangoLedgerSecurityMixIn): +class DateBalanceSheetView( + FiscalYearBalanceSheetViewBase, DateReportMixIn, DjangoLedgerSecurityMixIn +): """ Date Balance Sheet View. """ @@ -3716,20 +3953,29 @@ class DateBalanceSheetView(FiscalYearBalanceSheetViewBase, DateReportMixIn, Djan # Income Statement ----------- -class BaseIncomeStatementRedirectViewBase(BaseIncomeStatementRedirectView, DjangoLedgerSecurityMixIn): +class BaseIncomeStatementRedirectViewBase( + BaseIncomeStatementRedirectView, DjangoLedgerSecurityMixIn +): def get_redirect_url(self, *args, **kwargs): year = get_localdate().year dealer = get_user_type(self.request) return reverse( "entity-ic-year", kwargs={"entity_slug": dealer.entity.slug, "year": year} ) + def get_login_url(self): - return reverse('account_login') -class FiscalYearIncomeStatementViewBase(FiscalYearIncomeStatementView, DjangoLedgerSecurityMixIn): + return reverse("account_login") + + +class FiscalYearIncomeStatementViewBase( + FiscalYearIncomeStatementView, DjangoLedgerSecurityMixIn +): template_name = "ledger/reports/income_statement.html" - permission_required = ['inventory.view_carfinance'] + permission_required = ["inventory.view_carfinance"] + def get_login_url(self): - return reverse('account_login') + return reverse("account_login") + class QuarterlyIncomeStatementView( FiscalYearIncomeStatementViewBase, QuarterlyReportMixIn, DjangoLedgerSecurityMixIn @@ -3739,13 +3985,17 @@ class QuarterlyIncomeStatementView( """ -class MonthlyIncomeStatementView(FiscalYearIncomeStatementViewBase, MonthlyReportMixIn, DjangoLedgerSecurityMixIn): +class MonthlyIncomeStatementView( + FiscalYearIncomeStatementViewBase, MonthlyReportMixIn, DjangoLedgerSecurityMixIn +): """ Monthly Income Statement View. """ -class DateModelIncomeStatementView(FiscalYearIncomeStatementViewBase, DateReportMixIn, DjangoLedgerSecurityMixIn): +class DateModelIncomeStatementView( + FiscalYearIncomeStatementViewBase, DateReportMixIn, DjangoLedgerSecurityMixIn +): """ Date Income Statement View. """ @@ -3754,21 +4004,29 @@ class DateModelIncomeStatementView(FiscalYearIncomeStatementViewBase, DateReport # Cash Flow ----------- -class BaseCashFlowStatementRedirectViewBase(BaseCashFlowStatementRedirectView, DjangoLedgerSecurityMixIn): +class BaseCashFlowStatementRedirectViewBase( + BaseCashFlowStatementRedirectView, DjangoLedgerSecurityMixIn +): def get_redirect_url(self, *args, **kwargs): year = get_localdate().year dealer = get_user_type(self.request) return reverse( "entity-cf-year", kwargs={"entity_slug": dealer.entity.slug, "year": year} ) - def get_login_url(self): - return reverse('account_login') -class FiscalYearCashFlowStatementViewBase(FiscalYearCashFlowStatementView, DjangoLedgerSecurityMixIn): - template_name = "ledger/reports/cash_flow_statement.html" - permission_required = ['inventory.view_carfinance'] def get_login_url(self): - return reverse('account_login') + return reverse("account_login") + + +class FiscalYearCashFlowStatementViewBase( + FiscalYearCashFlowStatementView, DjangoLedgerSecurityMixIn +): + template_name = "ledger/reports/cash_flow_statement.html" + permission_required = ["inventory.view_carfinance"] + + def get_login_url(self): + return reverse("account_login") + class QuarterlyCashFlowStatementView( FiscalYearCashFlowStatementViewBase, QuarterlyReportMixIn, DjangoLedgerSecurityMixIn @@ -3786,7 +4044,9 @@ class MonthlyCashFlowStatementView( """ -class DateCashFlowStatementView(FiscalYearCashFlowStatementViewBase, DateReportMixIn, DjangoLedgerSecurityMixIn): +class DateCashFlowStatementView( + FiscalYearCashFlowStatementViewBase, DateReportMixIn, DjangoLedgerSecurityMixIn +): """ Date Cash Flow Statement View. """ @@ -3794,86 +4054,114 @@ class DateCashFlowStatementView(FiscalYearCashFlowStatementViewBase, DateReportM # Dashboard -class EntityModelDetailHandlerViewBase(EntityModelDetailHandlerView, DjangoLedgerSecurityMixIn): +class EntityModelDetailHandlerViewBase( + EntityModelDetailHandlerView, DjangoLedgerSecurityMixIn +): def get_redirect_url(self, *args, **kwargs): loc_date = get_localdate() dealer = get_user_type(self.request) unit_slug = self.get_unit_slug() if unit_slug: - return reverse('unit-dashboard-month', - kwargs={ - 'entity_slug': dealer.entity.slug, - 'unit_slug': unit_slug, - 'year': loc_date.year, - 'month': loc_date.month, - }) - return reverse('entity-dashboard-month', - kwargs={ - 'entity_slug': dealer.entity.slug, - 'year': loc_date.year, - 'month': loc_date.month, - }) + return reverse( + "unit-dashboard-month", + kwargs={ + "entity_slug": dealer.entity.slug, + "unit_slug": unit_slug, + "year": loc_date.year, + "month": loc_date.month, + }, + ) + return reverse( + "entity-dashboard-month", + kwargs={ + "entity_slug": dealer.entity.slug, + "year": loc_date.year, + "month": loc_date.month, + }, + ) -class EntityModelDetailBaseViewBase(EntityModelDetailBaseView, DjangoLedgerSecurityMixIn): +class EntityModelDetailBaseViewBase( + EntityModelDetailBaseView, DjangoLedgerSecurityMixIn +): template_name = "ledger/reports/dashboard.html" def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - dealer = get_user_type(self.request) - entity_model: EntityModel = dealer.entity - context['page_title'] = entity_model.name - context['header_title'] = entity_model.name - context['header_subtitle'] = _('Dashboard') - context['header_subtitle_icon'] = 'mdi:monitor-dashboard' + context = super().get_context_data(**kwargs) + dealer = get_user_type(self.request) + entity_model: EntityModel = dealer.entity + context["page_title"] = entity_model.name + context["header_title"] = entity_model.name + context["header_subtitle"] = _("Dashboard") + context["header_subtitle_icon"] = "mdi:monitor-dashboard" - unit_slug = context.get('unit_slug', self.get_unit_slug()) - KWARGS = dict(entity_slug=self.kwargs['entity_slug']) + unit_slug = context.get("unit_slug", self.get_unit_slug()) + KWARGS = dict(entity_slug=self.kwargs["entity_slug"]) - if unit_slug: - KWARGS['unit_slug'] = unit_slug + if unit_slug: + KWARGS["unit_slug"] = unit_slug - url_pointer = 'entity' if not unit_slug else 'unit' - context['pnl_chart_id'] = f'djl-entity-pnl-chart-{randint(10000, 99999)}' - context['pnl_chart_endpoint'] = reverse(f'django_ledger:{url_pointer}-json-pnl', kwargs=KWARGS) - context['payables_chart_id'] = f'djl-entity-payables-chart-{randint(10000, 99999)}' - context['payables_chart_endpoint'] = reverse(f'django_ledger:{url_pointer}-json-net-payables', kwargs=KWARGS) - context['receivables_chart_id'] = f'djl-entity-receivables-chart-{randint(10000, 99999)}' - context['receivables_chart_endpoint'] = reverse(f'django_ledger:{url_pointer}-json-net-receivables', - kwargs=KWARGS) + url_pointer = "entity" if not unit_slug else "unit" + context["pnl_chart_id"] = f"djl-entity-pnl-chart-{randint(10000, 99999)}" + context["pnl_chart_endpoint"] = reverse( + f"django_ledger:{url_pointer}-json-pnl", kwargs=KWARGS + ) + context["payables_chart_id"] = ( + f"djl-entity-payables-chart-{randint(10000, 99999)}" + ) + context["payables_chart_endpoint"] = reverse( + f"django_ledger:{url_pointer}-json-net-payables", kwargs=KWARGS + ) + context["receivables_chart_id"] = ( + f"djl-entity-receivables-chart-{randint(10000, 99999)}" + ) + context["receivables_chart_endpoint"] = reverse( + f"django_ledger:{url_pointer}-json-net-receivables", kwargs=KWARGS + ) - return context + return context -class FiscalYearEntityModelDashboardView(EntityModelDetailBaseViewBase, DjangoLedgerSecurityMixIn): +class FiscalYearEntityModelDashboardView( + EntityModelDetailBaseViewBase, DjangoLedgerSecurityMixIn +): """ Entity Fiscal Year Dashboard View. """ - permission_required = ['inventory.view_carfinance'] - def get_login_url(self): - return reverse('account_login') -class QuarterlyEntityDashboardView(FiscalYearEntityModelDashboardView, QuarterlyReportMixIn, DjangoLedgerSecurityMixIn): + permission_required = ["inventory.view_carfinance"] + + def get_login_url(self): + return reverse("account_login") + + +class QuarterlyEntityDashboardView( + FiscalYearEntityModelDashboardView, QuarterlyReportMixIn, DjangoLedgerSecurityMixIn +): """ Entity Quarterly Dashboard View. """ -class MonthlyEntityDashboardView(FiscalYearEntityModelDashboardView, MonthlyReportMixIn, DjangoLedgerSecurityMixIn): +class MonthlyEntityDashboardView( + FiscalYearEntityModelDashboardView, MonthlyReportMixIn, DjangoLedgerSecurityMixIn +): """ Monthly Entity Dashboard View. """ -class DateEntityDashboardView(FiscalYearEntityModelDashboardView, DateReportMixIn, DjangoLedgerSecurityMixIn): +class DateEntityDashboardView( + FiscalYearEntityModelDashboardView, DateReportMixIn, DjangoLedgerSecurityMixIn +): """ Date-specific Entity Dashboard View. """ class PayableNetAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View): - http_method_names = ['get'] + http_method_names = ["get"] def get(self, request, *args, **kwargs): if request.user.is_authenticated: @@ -3884,13 +4172,9 @@ class PayableNetAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View): ).unpaid() net_summary = accruable_net_summary(bill_qs) - net_payables = { - 'net_payable_data': net_summary - } + net_payables = {"net_payable_data": net_summary} - return JsonResponse({ - 'results': net_payables - }) + return JsonResponse({"results": net_payables}) return JsonResponse({ 'message': _('Unauthorized') @@ -3898,7 +4182,7 @@ class PayableNetAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View): class ReceivableNetAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View): - http_method_names = ['get'] + http_method_names = ["get"] def get(self, request, *args, **kwargs): if request.user.is_authenticated: @@ -3910,13 +4194,9 @@ class ReceivableNetAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View): net_summary = accruable_net_summary(invoice_qs) - net_receivable = { - 'net_receivable_data': net_summary - } + net_receivable = {"net_receivable_data": net_summary} - return JsonResponse({ - 'results': net_receivable - }) + return JsonResponse({"results": net_receivable}) return JsonResponse({ 'message': _('Unauthorized') @@ -3924,14 +4204,14 @@ class ReceivableNetAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View): class PnLAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View): - http_method_names = ['get'] + http_method_names = ["get"] def get(self, request, *args, **kwargs): if request.user.is_authenticated: dealer = get_user_type(request) - entity = EntityModel.objects.for_user( - user_model=dealer.entity.admin).get( - slug__exact=dealer.entity.slug) + entity = EntityModel.objects.for_user(user_model=dealer.entity.admin).get( + slug__exact=dealer.entity.slug + ) unit_slug = self.get_unit_slug() @@ -3942,30 +4222,30 @@ class PnLAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View): signs=False, by_period=True, process_groups=True, - from_date=self.request.GET.get('fromDate'), - to_date=self.request.GET.get('toDate'), - + from_date=self.request.GET.get("fromDate"), + to_date=self.request.GET.get("toDate"), # todo: For PnL to display proper period values must not use closing entries. - use_closing_entries=False + use_closing_entries=False, ) io_data = io_digest.get_io_data() - group_balance_by_period = io_data['group_balance_by_period'] - group_balance_by_period = dict(sorted((k, v) for k, v in group_balance_by_period.items())) + group_balance_by_period = io_data["group_balance_by_period"] + group_balance_by_period = dict( + sorted((k, v) for k, v in group_balance_by_period.items()) + ) entity_data = { - f'{month_name[k[1]]} {k[0]}': {d: float(f) for d, f in v.items()} for k, v in - group_balance_by_period.items()} - - entity_pnl = { - 'entity_slug': entity.slug, - 'entity_name': entity.name, - 'pnl_data': entity_data + f"{month_name[k[1]]} {k[0]}": {d: float(f) for d, f in v.items()} + for k, v in group_balance_by_period.items() } - return JsonResponse({ - 'results': entity_pnl - }) + entity_pnl = { + "entity_slug": entity.slug, + "entity_name": entity.name, + "pnl_data": entity_data, + } + + return JsonResponse({"results": entity_pnl}) return JsonResponse({ 'message': _('Unauthorized') @@ -3973,17 +4253,22 @@ class PnLAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View): class EmployeeCalendarView(LoginRequiredMixin, ListView): - template_name = 'crm/employee_calendar.html' + template_name = "crm/employee_calendar.html" model = Appointment - context_object_name = 'appointments' + context_object_name = "appointments" def get_queryset(self): - query = self.request.GET.get('q') + query = self.request.GET.get("q") dealer = get_user_type(self.request) - staff = getattr(self.request, 'staff', None) + staff = getattr(self.request, "staff", None) if staff: - appointments = Appointment.objects.filter(appointment_request__staff_member=staff, ppointment_request__date__gt=timezone.now()) - appointments = Appointment.objects.filter(appointment_request__date__gt=timezone.now()) + appointments = Appointment.objects.filter( + appointment_request__staff_member=staff, + ppointment_request__date__gt=timezone.now(), + ) + appointments = Appointment.objects.filter( + appointment_request__date__gt=timezone.now() + ) return apply_search_filters(appointments, query) @@ -3995,7 +4280,11 @@ def apply_search_filters(queryset, query): model = queryset.model for field in model._meta.get_fields(): - if hasattr(field, 'attname') and field.get_internal_type() in ["CharField", "TextField", "EmailField"]: + if hasattr(field, "attname") and field.get_internal_type() in [ + "CharField", + "TextField", + "EmailField", + ]: search_filters |= Q(**{f"{field.name}__icontains": query}) return queryset.filter(search_filters).distinct() @@ -4011,11 +4300,12 @@ class CarListViewTable(LoginRequiredMixin, ExportMixin, SingleTableView): "finances", "colors__exterior", "colors__interior" ).filter(dealer=dealer) + @login_required -def DealerSettingsView(request,pk): +def DealerSettingsView(request, pk): dealer_setting = get_object_or_404(models.DealerSettings, dealer__pk=pk) dealer = get_user_type(request) - if request.method == 'POST': + if request.method == "POST": form = forms.DealerSettingsForm(request.POST, instance=dealer_setting) if form.is_valid(): instance = form.save(commit=False) @@ -4025,18 +4315,37 @@ def DealerSettingsView(request,pk): return redirect('dealer_detail', pk=dealer.pk) else: print(form.errors) - form = forms.DealerSettingsForm(instance=dealer_setting,initial={'dealer':dealer}) - form.fields['invoice_cash_account'].queryset = dealer.entity.get_all_accounts().filter(role=roles.ASSET_CA_CASH) - form.fields['invoice_prepaid_account'].queryset = dealer.entity.get_all_accounts().filter(role=roles.ASSET_CA_RECEIVABLES) - form.fields['invoice_unearned_account'].queryset = dealer.entity.get_all_accounts().filter(role=roles.LIABILITY_CL_DEFERRED_REVENUE) + form = forms.DealerSettingsForm(instance=dealer_setting, initial={"dealer": dealer}) + form.fields[ + "invoice_cash_account" + ].queryset = dealer.entity.get_all_accounts().filter(role=roles.ASSET_CA_CASH) + form.fields[ + "invoice_prepaid_account" + ].queryset = dealer.entity.get_all_accounts().filter( + role=roles.ASSET_CA_RECEIVABLES + ) + form.fields[ + "invoice_unearned_account" + ].queryset = dealer.entity.get_all_accounts().filter( + role=roles.LIABILITY_CL_DEFERRED_REVENUE + ) + + form.fields["bill_cash_account"].queryset = dealer.entity.get_all_accounts().filter( + role=roles.ASSET_CA_CASH + ) + form.fields[ + "bill_prepaid_account" + ].queryset = dealer.entity.get_all_accounts().filter(role=roles.ASSET_CA_PREPAID) + form.fields[ + "bill_unearned_account" + ].queryset = dealer.entity.get_all_accounts().filter( + role=roles.LIABILITY_CL_ACC_PAYABLE + ) + return render(request, "account/user_settings.html", {"form": form}) - form.fields['bill_cash_account'].queryset = dealer.entity.get_all_accounts().filter(role=roles.ASSET_CA_CASH) - form.fields['bill_prepaid_account'].queryset = dealer.entity.get_all_accounts().filter(role=roles.ASSET_CA_PREPAID) - form.fields['bill_unearned_account'].queryset = dealer.entity.get_all_accounts().filter(role=roles.LIABILITY_CL_ACC_PAYABLE) - return render(request, 'account/user_settings.html', {'form': form}) @login_required -def schedule_cancel(request,pk): +def schedule_cancel(request, pk): schedule = get_object_or_404(models.Schedule, pk=pk) schedule.status = "Canceled" schedule.save() @@ -4055,29 +4364,33 @@ def assign_car_makes(request): return redirect("dealer_detail", pk=dealer.pk) else: # Pre-fill the form with existing selections - existing_car_makes = models.DealersMake.objects.filter(dealer=dealer).values_list("car_make", flat=True) - form = forms.DealersMakeForm(initial={"car_makes": existing_car_makes}, dealer=dealer) + existing_car_makes = models.DealersMake.objects.filter( + dealer=dealer + ).values_list("car_make", flat=True) + form = forms.DealersMakeForm( + initial={"car_makes": existing_car_makes}, dealer=dealer + ) return render(request, "dealers/assign_car_makes.html", {"form": form}) -class LedgerModelListView(LoginRequiredMixin, ListView,ArchiveIndexView): +class LedgerModelListView(LoginRequiredMixin, ListView, ArchiveIndexView): model = LedgerModel context_object_name = "ledgers" template_name = "ledger/ledger/ledger_list.html" - date_field = 'created' - ordering = '-created' + date_field = "created" + ordering = "-created" show_all = False show_current = False show_visible = False - + allow_empty = True def get_queryset(self): qs = super().get_queryset() dealer = get_user_type(self.request) qs = qs.filter(entity=dealer.entity) - qs = qs.select_related('billmodel', 'invoicemodel') - qs = qs.order_by('-created') + qs = qs.select_related("billmodel", "invoicemodel") + qs = qs.order_by("-created") if self.show_all: return qs if self.show_current: @@ -4086,12 +4399,51 @@ class LedgerModelListView(LoginRequiredMixin, ListView,ArchiveIndexView): qs = qs.visible() return qs + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + dealer = get_user_type(self.request) + context["entity_slug"] = dealer.entity.slug + return context + + # class LedgerModelDetailView(InvoiceModelDetailViewBase): class LedgerModelDetailView(LoginRequiredMixin, DetailView): model = LedgerModel context_object_name = "ledger" template_name = "ledger/ledger/ledger_detail.html" +class LedgerModelCreateView(LedgerModelCreateViewBase): + template_name = "ledger/ledger/ledger_form.html" + + def get_form(self, form_class=None): + dealer = get_user_type(self.request) + return LedgerModelCreateForm( + entity_slug=dealer.entity.slug, + user_model=dealer.entity.admin, + **self.get_form_kwargs() + ) + def form_valid(self, form): + dealer = get_user_type(self.request) + instance = form.save(commit=False) + instance.entity = dealer.entity + return super().form_valid(form) + + def get_success_url(self): + return reverse('ledger_list') + + +class LedgerModelModelActionView(LedgerModelModelActionViewBase): + def get_redirect_url(self, *args, **kwargs): + return reverse("ledger_list") + + +class LedgerModelDeleteView(LedgerModelDeleteViewBase, SuccessMessageMixin): + template_name = "ledger/ledger/ledger_delete.html" + success_message = "Ledger deleted" + + def get_success_url(self): + return reverse("ledger_list") + # class LedgerModelCreateView(LoginRequiredMixin,SuccessMessageMixin, CreateView): # model = LedgerModel @@ -4113,6 +4465,7 @@ class LedgerModelDetailView(LoginRequiredMixin, DetailView): # instance.save() # return super().form_valid(form) + class JournalEntryListView(LoginRequiredMixin, ListView): model = JournalEntryModel context_object_name = "journal_entries" @@ -4120,16 +4473,17 @@ class JournalEntryListView(LoginRequiredMixin, ListView): def get_queryset(self): qs = super().get_queryset() - ledger = LedgerModel.objects.filter(pk=self.kwargs['pk']).first() + ledger = LedgerModel.objects.filter(pk=self.kwargs["pk"]).first() qs = qs.filter(ledger=ledger) return qs def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['ledger'] = LedgerModel.objects.filter(pk=self.kwargs['pk']).first() + context["ledger"] = LedgerModel.objects.filter(pk=self.kwargs["pk"]).first() return context -class JournalEntryCreateView(LoginRequiredMixin,SuccessMessageMixin, CreateView): + +class JournalEntryCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): model = JournalEntryModel template_name = "ledger/journal_entry/journal_entry_form.html" form_class = forms.JournalEntryModelCreateForm @@ -4138,33 +4492,39 @@ class JournalEntryCreateView(LoginRequiredMixin,SuccessMessageMixin, CreateView) def get_form(self, form_class=None): dealer = get_user_type(self.request) - ledger = LedgerModel.objects.filter(pk=self.kwargs['pk']).first() + ledger = LedgerModel.objects.filter(pk=self.kwargs["pk"]).first() form = forms.JournalEntryModelCreateForm( - entity_model=dealer.entity, - ledger_model=ledger, - **self.get_form_kwargs() + entity_model=dealer.entity, ledger_model=ledger, **self.get_form_kwargs() ) - form.fields.pop('entity_unit') + form.fields.pop("entity_unit") return form + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context["ledger"] = LedgerModel.objects.filter(pk=self.kwargs['pk']).first() + context["ledger"] = LedgerModel.objects.filter(pk=self.kwargs["pk"]).first() return context def get_success_url(self): - ledger = LedgerModel.objects.filter(pk=self.kwargs['pk']).first() + ledger = LedgerModel.objects.filter(pk=self.kwargs["pk"]).first() return reverse("journalentry_list", kwargs={"pk": ledger.pk}) -def JournalEntryDeleteView(request,pk): +def JournalEntryDeleteView(request, pk): journal_entry = get_object_or_404(JournalEntryModel, pk=pk) - ledger = journal_entry.ledger - if not journal_entry.can_delete(): - messages.error(request, _('Journal Entry cannot be deleted')) + if request.method == "POST": + ledger = journal_entry.ledger + if not journal_entry.can_delete(): + messages.error(request, "Journal Entry cannot be deleted") + return redirect("journalentry_list", pk=ledger.pk) + journal_entry.delete() + messages.success(request, "Journal Entry deleted") return redirect("journalentry_list", pk=ledger.pk) - journal_entry.delete() - messages.success(request, 'Journal Entry deleted') - return redirect("journalentry_list", pk=ledger.pk) + return render( + request, + "ledger/journal_entry/journal_entry_delete.html", + {"journal_entry": journal_entry}, + ) + def JournalEntryTransactionsView(request, pk): journal = JournalEntryModel.objects.filter(pk=pk).first() @@ -4181,10 +4541,10 @@ def JournalEntryTransactionsView(request, pk): class JournalEntryModelTXSDetailView(JournalEntryModelTXSDetailViewBase): - template_name = 'ledger/journal_entry/journal_entry_txs.html' + template_name = "ledger/journal_entry/journal_entry_txs.html" -def ledger_lock_all_journals(request,entity_slug,pk): +def ledger_lock_all_journals(request, entity_slug, pk): ledger = LedgerModel.objects.filter(pk=pk).first() if ledger.is_locked(): messages.error(request, _("Ledger is already locked")) @@ -4194,7 +4554,8 @@ def ledger_lock_all_journals(request,entity_slug,pk): ledger.save() return redirect("journalentry_list", pk=ledger.pk) -def ledger_unlock_all_journals(request,entity_slug,pk): + +def ledger_unlock_all_journals(request, entity_slug, pk): ledger = LedgerModel.objects.filter(pk=pk).first() if not ledger.is_locked(): messages.error(request, _("Ledger is already Unlocked")) @@ -4208,7 +4569,8 @@ def ledger_unlock_all_journals(request,entity_slug,pk): je.save() return redirect("journalentry_list", pk=ledger.pk) -def ledger_post_all_journals(request,entity_slug,pk): + +def ledger_post_all_journals(request, entity_slug, pk): ledger = LedgerModel.objects.filter(pk=pk).first() if ledger.is_posted(): messages.error(request, _("Ledger is already posted")) @@ -4218,16 +4580,17 @@ def ledger_post_all_journals(request,entity_slug,pk): ledger.save() return redirect("journalentry_list", pk=ledger.pk) -def ledger_unpost_all_journals(request,entity_slug,pk): + +def ledger_unpost_all_journals(request, entity_slug, pk): ledger = LedgerModel.objects.filter(pk=pk).first() if not ledger.is_posted(): messages.error(request, _("Ledger is already Unposted")) return redirect("journalentry_list", pk=ledger.pk) qs = ledger.journal_entries.posted() - for je in qs: + for je in qs: je.mark_as_unposted() je.save() - + ledger.unpost() ledger.save() return redirect("journalentry_list", pk=ledger.pk) diff --git a/scripts/run.py b/scripts/run.py index da6299e6..43362480 100644 --- a/scripts/run.py +++ b/scripts/run.py @@ -1,4 +1,4 @@ -from django_ledger.forms.account import AccountModelUpdateForm,AccountModelCreateForm +from django_ledger.forms.account import AccountModelUpdateForm, AccountModelCreateForm import requests import os from dotenv import load_dotenv @@ -7,12 +7,29 @@ from django.contrib.auth.models import Group from django_ledger.models.invoice import InvoiceModel from django_ledger.utils import accruable_net_summary from decimal import Decimal -from django_ledger.models import EstimateModel,EntityModel,ItemModel,ItemTransactionModel,AccountModel,CustomerModel,EntityManagementModel +from django_ledger.models import ( + EstimateModel, + EntityModel, + ItemModel, + ItemTransactionModel, + AccountModel, + CustomerModel, + EntityManagementModel, +) from rich import print from datetime import date -from inventory.models import Car, Dealer, VatRate,Lead,CarMake,CarModel,Schedule,CustomGroup +from inventory.models import ( + Car, + Dealer, + VatRate, + Lead, + CarMake, + CarModel, + Schedule, + CustomGroup, +) from inventory.utils import CarFinanceCalculator -from appointment.models import Appointment,AppointmentRequest,Service,StaffMember +from appointment.models import Appointment, AppointmentRequest, Service, StaffMember from django.contrib.auth import get_user_model from django_ledger.io.io_core import get_localdate from datetime import datetime, timedelta @@ -23,6 +40,8 @@ from django_ledger.io import roles User = get_user_model() load_dotenv(".env") + + def run(): # print(Service.objects.first().pk) # print(Appointment.objects.first().client) @@ -101,7 +120,6 @@ def run(): # hash_object.update(f"{i.id_car_make.name}{i.id_car_model.name}".encode('utf-8')) # print(hash_object.hexdigest() , i.id_car_make.name, i.id_car_model.name) - # def get_item(tx:ItemTransactionModel): # data = {"data": {}} # data["data"]["info"] = tx.item_model.additional_info.get('car_info') @@ -121,9 +139,9 @@ def run(): # for transaction in transactions: # output.append(get_item(transaction)) # print(output) - # info = item.additional_info["car_info"] - # finance = item.additional_info["car_finance"] - # print({"vin":info["make"],"mode":info["model"],"year":info["year"],"trim":info["trim"],"mileage":info["mileage"],"cost_price":finance["cost_price"],"selling_price":finance["selling_price"]}) + # info = item.additional_info["car_info"] + # finance = item.additional_info["car_finance"] + # print({"vin":info["make"],"mode":info["model"],"year":info["year"],"trim":info["trim"],"mileage":info["mileage"],"cost_price":finance["cost_price"],"selling_price":finance["selling_price"]}) # for account in AccountModel.objects.all(): # print(account.path) @@ -156,8 +174,7 @@ def run(): # EntityManagementModel.objects.create(entity=,user=) # print(Permission.objects.filter(codename__icontains='customermodel').first().codename) # print(os.getenv("DJANGO_ALLOWED_HOSTS")) - - + car_makes = CarMake.objects.all()[:10] # Fetch the entity and COGS account @@ -167,8 +184,8 @@ def run(): # Loop through car makes and create accounts for make in range(len(car_makes)): # Start from 0 to include all items - # Generate a unique code - + # Generate a unique code + # Create the account # account = AccountModel.objects.create( # name=car_makes[make].name, @@ -188,11 +205,16 @@ def run(): # balance_type="debit", # active=True # ) - last_account = entity.get_all_accounts().filter(role=roles.COGS).order_by('-created').first() + last_account = ( + entity.get_all_accounts() + .filter(role=roles.COGS) + .order_by("-created") + .first() + ) if len(last_account.code) == 4: code = f"{int(last_account.code)}{1:03d}" elif len(last_account.code) > 4: - code = f"{int(last_account.code)+1}" + code = f"{int(last_account.code) + 1}" # account = entity.create_account( # name=car_makes[make].name, @@ -209,24 +231,29 @@ def run(): coa_model=coa, balance_type="debit", active=True, - depth=3, - ) - #00060004001S - last_account = entity.get_all_accounts().filter(role=roles.COGS).order_by('-created').first() - path = '' + depth=3, + ) + # 00060004001S + last_account = ( + entity.get_all_accounts() + .filter(role=roles.COGS) + .order_by("-created") + .first() + ) + path = "" if len(last_account.code) == 12: path = f"{int(last_account.path)}{1:03d}" elif len(last_account.code) > 12: - path = f"{int(last_account.path)+1}" + path = f"{int(last_account.path) + 1}" # account.path = path - try: + try: account = cogs.add_child(instance=account) account.move(cogs, pos="sorted-sibling") account.refresh_from_db() account.save() except Exception as e: print(e) - + # form_data = { # 'name': car_makes[make].name, # 'code': code, @@ -235,27 +262,27 @@ def run(): # 'active': True, # 'coa_model': coa # Ensure the COA model is included # } - + # Create the form instance with the data - # create_form = AccountModelCreateForm(data=form_data, coa_model=coa) - # # Validate and save the form - # if create_form.is_valid(): - # account = create_form.save(commit=False) - # account.coa_model = coa # Set the entity for the account - - # Add the account as a child of the COGS account - # cogs.add_child(instance=account) - - # print(f"Account '{account.name}' created successfully.") - # else: - # print(f"Failed to create account. Errors: {create_form.errors}") - # form = AccountModelUpdateForm(instance=account) - - # if form.is_valid(): - # instance = form.save(commit=False) - # instance._position = "sorted-sibling" - # instance._ref_node_id = cogs.pk - # instance.save() - # print(f"Account {account.name} created successfully.") - # else: - # print(f"Failed to create account {account.name}. Errors: {form.errors}") \ No newline at end of file + # create_form = AccountModelCreateForm(data=form_data, coa_model=coa) + # # Validate and save the form + # if create_form.is_valid(): + # account = create_form.save(commit=False) + # account.coa_model = coa # Set the entity for the account + + # Add the account as a child of the COGS account + # cogs.add_child(instance=account) + + # print(f"Account '{account.name}' created successfully.") + # else: + # print(f"Failed to create account. Errors: {create_form.errors}") + # form = AccountModelUpdateForm(instance=account) + + # if form.is_valid(): + # instance = form.save(commit=False) + # instance._position = "sorted-sibling" + # instance._ref_node_id = cogs.pk + # instance.save() + # print(f"Account {account.name} created successfully.") + # else: + # print(f"Failed to create account {account.name}. Errors: {form.errors}") diff --git a/scripts/run2.py b/scripts/run2.py new file mode 100644 index 00000000..bf1a5f85 --- /dev/null +++ b/scripts/run2.py @@ -0,0 +1,38 @@ +from django_ledger.forms.account import AccountModelUpdateForm,AccountModelCreateForm +import requests +import os +from dotenv import load_dotenv +from django.contrib.auth.models import Permission +from django.contrib.auth.models import Group +from django_ledger.models.invoice import InvoiceModel +from django_ledger.utils import accruable_net_summary +from decimal import Decimal +from django_ledger.models import EstimateModel,EntityModel,ItemModel,ItemTransactionModel,AccountModel,CustomerModel,EntityManagementModel +from rich import print +from datetime import date +from inventory.models import Car, Dealer, VatRate,Lead,CarMake,CarModel,Schedule,CustomGroup +from inventory.utils import CarFinanceCalculator +from appointment.models import Appointment,AppointmentRequest,Service,StaffMember +from django.contrib.auth import get_user_model +from django_ledger.io.io_core import get_localdate +from datetime import datetime, timedelta +from django.utils import timezone +import hashlib +from django_ledger.io import roles + +User = get_user_model() + +load_dotenv(".env") +def run(): + invoice = InvoiceModel.objects.filter(invoice_number='I-2025-0000000001').first() + calculator = CarFinanceCalculator(invoice) + finance_data = calculator.get_finance_data() + + for i in invoice.get_itemtxs_data()[0]: + car = Car.objects.get(vin=invoice.get_itemtxs_data()[0].first().item_model.name) + print(car.finances.total + car.finances.total_additionals) + print(car.finances.total_additionals) + print(finance_data.get("total_vat_amount")) + print(finance_data.get("total")) + + diff --git a/templates/base.html b/templates/base.html index 850dbb3e..08c34baf 100644 --- a/templates/base.html +++ b/templates/base.html @@ -24,9 +24,9 @@ - + + - @@ -36,7 +36,7 @@ - + @@ -105,7 +105,7 @@ - {% comment %} {% endcomment %} + @@ -115,9 +115,9 @@ - + - + \ No newline at end of file diff --git a/templates/crm/opportunities/opportunity_detail.html b/templates/crm/opportunities/opportunity_detail.html index 36aa4634..e3690273 100644 --- a/templates/crm/opportunities/opportunity_detail.html +++ b/templates/crm/opportunities/opportunity_detail.html @@ -7,8 +7,7 @@

{{ _("Opportunity details")}}

- - + @@ -32,11 +29,11 @@
-
+
{% if opportunity.car %}

{{ opportunity.car.id_car_make.get_local_name }} - {{ opportunity.car.id_car_model.get_local_name }} - {{ opportunity.car.year }}

- {% endif %} -

{{ opportunity.customer.customer_name }}

+ {% endif %} +

{{ opportunity.customer.customer_name }}

{% if opportunity.car.finances %}
{{ opportunity.car.finances.total }} {{ CURRENCY }}
@@ -91,7 +88,7 @@
{{ _("Status") }}
{{ _("Update Status")}}
- {{status_form.status}} + {{status_form.status}}
@@ -99,7 +96,7 @@ {{ _("Update Stage")}}
{{status_form.stage}} -
+
@@ -280,10 +277,10 @@ - + {% comment %} {% endcomment %} - + {% comment %} {% endcomment %}
@@ -302,7 +299,7 @@
- {% for activity in activities %} + {% for activity in opportunity.lead.get_activities %}
@@ -314,7 +311,7 @@ {% elif activity.activity_type == "whatsapp" %} - {% endif %} + {% endif %}
@@ -328,7 +325,7 @@
- {% endfor %} + {% endfor %}

Notes

@@ -346,7 +343,7 @@
{{note.created}}

by{{note.created_by}}

-
+ {% endfor %} @@ -368,329 +365,35 @@
- + Add Meeting
-
-
-
-
-
-

Onboarding Meeting

-
5:30 pm to 7:00pm - 1h 30min
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
+1
+ {% for metting in opportunity.lead.get_meetings %} +
+
+
+
+
+

{{metting.purpose}}

+
{{metting.scheduled_at}} - {{meeting.duration}}
-
today -
Urgent
- -
-
-
-
-
-
-
-

Agile Mindset Meetup

-
4:30 pm to 6:00pm - 1h 30min
-
-
-
- - -
- -
-
+1
-
-
-
-
tomorrow -
Medium
- -
-
-
-
-
-
-
-
-
-

Meeting Fundamentals

-
6:00 pm to 7:20pm - 1h 20min
-
-
-
-
R
-
-
-
+2
-
-
-
-
tomorrow -
High
- -
-
-
-
-
-
-
-
-
-

Design System Meeting

-
7:30 pm to 8:45pm - 1h 45min
-
-
-
- -
-
-
-
tomorrow -
Low
- -
-
-
-
+ {% endfor %}
-
-

Tasks

-
-
- -
-
-

23 tasks

- -
-
-
-
-
-
- - -
-
-
-
-
-

19 Nov, 2022

-
-
- - -
-
-
-

11:56 PM

-
-
-
-
-
-
-
-
- - -
-
-
-
-
-

05 Nov, 2022

-
-
- - -
-
-
-

09:30 PM

-
-
-
-
-
-
-
-
- - -
-
-
-
-
-

02 Nov, 2022

-
-
- - -
-
-
-

05:25 AM

-
-
-
-
-
-
-
-
- - -
-
-
-
-
-

29 Oct, 2022

-
-
- - -
-
-
-

08:21 PM

-
-
-
-
-
-
-
-
- - -
-
-
-
-
-

21 Oct, 2022

-
-
- - -
-
-
-

03:45 PM

-
-
-
-
-
-
-
-
- - -
-
-
-
-
-

14 Oct, 2022

-
-
- - -
-
-
-

10:00 PM

-
-
-
-
-
-
-
-
- - -
-
-
-
-
-

12 Oct, 2022

-
-
- - -
-
-
-

02:00 AM

-
-
-
-
Add new task -

Call

+
-
-
- - -
-
- - -
-
- - -
-
-
-
- + Add Call
@@ -698,47 +401,27 @@ - - - - - - - + + + - - - - + + + + - - - - - - + + + {% endfor %}
-
- -
-
Namedescriptioncreate datecreate byLast ActivityPurposeScheduled ByCreated at
-
- -
-
-
+ {% for call in opportunity.lead.get_calls %} +
{{call.purpose}}{{call.scheduled_by}}{{call.created_at}} +
+ +
-
-
Purchasing-Related VendorsDec 29, 2021Ansolo Lazinarov -
Active
-
-
- - -
-
@@ -759,9 +442,7 @@
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
-
SubjectSent byDateActionStatus
-
- -
-
Quary about purchased soccer socks -
jackson@mail.com
-
Jackson PollockDec 29, 2021 10:23 amCallsent
-
- -
-
How to take the headache out of Order -
ansolo45@mail.com
-
Ansolo LazinatovDec 27, 2021 3:27 pmCalldelivered
-
- -
-
The Arnold Schwarzenegger of Order -
ansolo45@mail.com
-
Ansolo LazinatovDec 24, 2021 10:44 amCallBounce
-
- -
-
My order is not being taken -
jackson@mail.com
-
Jackson PollockDec 19, 2021 4:55 pmCallSpam
-
- -
-
Shipment is missing -
jackson@mail.com
-
Jackson PollockDec 19, 2021 2:43 pmCallsent
-
-
- -
- -
    - -
    -
    -
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {% endfor %}
    -
    - -
    -
    SubjectSent byDateActionStatus
    -
    - -
    -
    Quary about purchased soccer socks -
    jackson@mail.com
    -
    Jackson PollockDec 29, 2021 10:23 amCallsent
    -
    - -
    -
    How to take the headache out of Order -
    ansolo45@mail.com
    -
    Ansolo LazinatovDec 27, 2021 3:27 pmCalldelivered
    -
    - -
    -
    The Arnold Schwarzenegger of Order -
    ansolo45@mail.com
    -
    Ansolo LazinatovDec 24, 2021 10:44 amCallBounce
    -
    - -
    -
    My order is not being taken -
    jackson@mail.com
    -
    Jackson PollockDec 19, 2021 4:55 pmCallSpam
    diff --git a/templates/header.html b/templates/header.html index d5a19b46..4dfe8614 100644 --- a/templates/header.html +++ b/templates/header.html @@ -286,7 +286,7 @@ diff --git a/templates/items/expenses/expenses_list.html b/templates/items/expenses/expenses_list.html index 3d971d81..286c2bda 100644 --- a/templates/items/expenses/expenses_list.html +++ b/templates/items/expenses/expenses_list.html @@ -10,30 +10,30 @@

    {% trans "Expenses" %}

    {% trans "Add Expense" %}
    -
    +
    - - + + {% for expense in expenses %} - + - + + {% empty %} @@ -43,7 +43,7 @@
    {% trans "Item Number" %}{% trans "Name" %}{% trans "Unit of Measure" %}{% trans "Name" %}{% trans "Unit of Measure" %} {% trans "Action" %}
    {{ expense.item_number }} {{ expense.name }}{{ expense.uom }}{{ expense.uom }} - {% trans "Update" %} -
    -
    +
    diff --git a/templates/ledger/bills/bill_form.html b/templates/ledger/bills/bill_form.html index c903e5a9..fd96d4d0 100644 --- a/templates/ledger/bills/bill_form.html +++ b/templates/ledger/bills/bill_form.html @@ -10,15 +10,15 @@
    {% csrf_token %}
    - {{ form|crispy }} + {{ form|crispy }}

    Unit Items

    -
    +
    @@ -74,7 +74,7 @@
    -
    +
    @@ -123,7 +123,7 @@ document.querySelectorAll('[name="quantity[]"]').forEach(input => { formData.quantity.push(input.value); }); - + try { // Send data to the server using fetch const response = await fetch("{% url 'bill_create' %}", { @@ -150,8 +150,8 @@ notify("error","Unexpected response from the server"); } } catch (error) { - - notify("error", error); + + notify("error", error); } }); diff --git a/templates/ledger/bills/bill_list.html b/templates/ledger/bills/bill_list.html index 8fde0dff..f9b85e9c 100644 --- a/templates/ledger/bills/bill_list.html +++ b/templates/ledger/bills/bill_list.html @@ -11,6 +11,11 @@ {% endblock %} {% block content %}
    +
    @@ -30,7 +35,7 @@
    - +
    @@ -41,37 +46,15 @@ + - + - + {% for bill in bills %} - - @@ -149,7 +131,7 @@ - + diff --git a/templates/ledger/coa_accounts/account_list.html b/templates/ledger/coa_accounts/account_list.html index 61760f6a..e85b3c9e 100644 --- a/templates/ledger/coa_accounts/account_list.html +++ b/templates/ledger/coa_accounts/account_list.html @@ -11,7 +11,13 @@
    -

    {% trans "Accounts" %}

    + +

    + {% trans "Accounts" %}

    @@ -34,8 +40,7 @@
    {% if page_obj.object_list %} - -
    +
    {% trans 'Bill Status' %} - {% trans 'Vendor' %} Action
    {{ bill.bill_number }} @@ -90,12 +73,11 @@ {{bill.vendor.vendor_name}} +
    diff --git a/templates/ledger/journal_entry/journal_entry_delete.html b/templates/ledger/journal_entry/journal_entry_delete.html new file mode 100644 index 00000000..5603ab19 --- /dev/null +++ b/templates/ledger/journal_entry/journal_entry_delete.html @@ -0,0 +1,31 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load static %} +{% load django_ledger %} + +{% block content %} +
    +
    + + {% csrf_token %} +
    +
    +
    Are you sure you want to delete?
    +
    +
    + {% trans 'Go Back' %} + +
    +
    + +
    +
    + + +{% endblock %} + + + + diff --git a/templates/ledger/ledger/ledger_delete.html b/templates/ledger/ledger/ledger_delete.html new file mode 100644 index 00000000..32acf618 --- /dev/null +++ b/templates/ledger/ledger/ledger_delete.html @@ -0,0 +1,31 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load static %} +{% load django_ledger %} + +{% block content %} +
    +
    +
    + {% csrf_token %} +
    +
    +
    {{ ledger_model.get_delete_message }}
    +
    +
    + {% trans 'Go Back' %} + +
    +
    + +
    +
    + + +{% endblock %} + + + + diff --git a/templates/ledger/ledger/ledger_list.html b/templates/ledger/ledger/ledger_list.html index 7936c14d..c4a04c96 100644 --- a/templates/ledger/ledger/ledger_list.html +++ b/templates/ledger/ledger/ledger_list.html @@ -6,16 +6,20 @@ {% block content %}
    -

    {% trans "Ledger" %}

    +

    {% trans "Ledger" %}

    - -
    - + - + @@ -23,16 +27,16 @@ {% for ledger in ledgers %} - + + {{ ledger.name }} + {% endif %} + - + {% empty %} @@ -70,7 +119,7 @@
    {% trans "Ledger Name" %}{% trans "Ledger Name" %} {% trans "Journal Entries" %}{% trans "Earliest Date" %}{% trans "Created Date" %} {% trans "Posted" %} {% trans "Locked" %} {% trans "Action" %}
    {% if ledger.invoicemodel %} {{ ledger.get_wrapped_model_instance }} {% elif ledger.billmodel %} {{ ledger.get_wrapped_model_instance }} {% else %} - {{ ledger.name }} - {% endif %} - @@ -44,7 +48,7 @@ - {% if ledger.earliest_timestamp %}{{ ledger.earliest_timestamp | date }}{% endif %} + {{ ledger.created |date }} {% if ledger.is_posted %} @@ -60,7 +64,52 @@ {% endif %} +
    + + +
    +
    -
    +