diff --git a/inventory/forms.py b/inventory/forms.py index 95d0388a..f3a02770 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -442,6 +442,13 @@ class CarSelectionTable(tables.Table): class WizardForm1(forms.Form): + hx_attrs = { + "hx-post":"", + "hx-target": "#wizardValidationForm1", + "hx-select": "#wizardValidationForm1", + "hx-trigger": "blur delay:500ms", + "hx-swap": "innerHTML", + } email = forms.EmailField( label=_("Email Address"), widget=forms.EmailInput( @@ -450,6 +457,7 @@ class WizardForm1(forms.Form): "placeholder": _("Email address"), "name": _("email"), "required": "required", + **hx_attrs } ), error_messages={ @@ -464,11 +472,14 @@ class WizardForm1(forms.Form): "class": "form-control form-control-sm", "placeholder": _("Password"), "required": "required", - } + **hx_attrs + }, + render_value=True ), error_messages={ "required": _("This field is required."), }, + min_length=8, ) confirm_password = forms.CharField( @@ -478,11 +489,14 @@ class WizardForm1(forms.Form): "class": "form-control form-control-sm", "placeholder": _("Confirm Password"), "required": "required", - } + **hx_attrs + }, + render_value=True ), error_messages={ "required": _("This field is required."), }, + min_length=8, ) terms = forms.BooleanField( @@ -491,12 +505,30 @@ class WizardForm1(forms.Form): attrs={ "class": "form-check-input", "required": "required", + **hx_attrs } ), error_messages={ "required": _("You must accept the terms and privacy policy."), - }, + }, ) + + + def clean_email(self): + email = self.cleaned_data.get("email") + if email: + if User.objects.filter(email=email).exists(): + raise forms.ValidationError( + _("An account with this email already exists.") + ) + return email + + def clean_confirm_password(self): + password = self.cleaned_data.get("password") + confirm_password = self.cleaned_data.get("confirm_password") + if password and confirm_password and password != confirm_password: + raise forms.ValidationError(_("Passwords do not match.")) + return confirm_password class WizardForm2(forms.Form): @@ -531,10 +563,8 @@ class WizardForm2(forms.Form): phone_number = PhoneNumberField( label=_("Phone Number"), widget=forms.TextInput( - attrs={ - "class": "form-control form-control-sm", + attrs={ "placeholder": _("Phone"), - "required": "required", } ), region="SA", @@ -542,6 +572,7 @@ class WizardForm2(forms.Form): "required": _("This field is required."), "invalid": _("Phone number must be in the format 05xxxxxxxx"), }, + required=True, ) @@ -596,15 +627,15 @@ class WizardForm3(forms.Form): }, ) - def clean(self): - cleaned_data = super().clean() - password = cleaned_data.get("password") - confirm_password = cleaned_data.get("confirm_password") + # def clean(self): + # cleaned_data = super().clean() + # password = cleaned_data.get("password") + # confirm_password = cleaned_data.get("confirm_password") - if password != confirm_password: - raise forms.ValidationError("Passwords do not match.") - else: - return cleaned_data + # if password != confirm_password: + # raise forms.ValidationError("Passwords do not match.") + # else: + # return cleaned_data class ItemForm(forms.Form): @@ -752,9 +783,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 d1e81d43..66c3d73d 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -19,7 +19,6 @@ from django_ledger.models import EntityModel, ItemModel,EstimateModel,InvoiceMod from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType - class DealerUserManager(UserManager): def create_user_with_dealer( self, @@ -408,10 +407,18 @@ class Car(models.Model): trim = self.id_car_trim.name if self.id_car_trim else "Unknown Trim" return f"{self.year} - {make} - {model} - {trim}" + def get_reservation(self): + return self.reservations.filter(reserved_until__gt=now()).first() def is_reserved(self): active_reservations = self.reservations.filter(reserved_until__gt=now()) return active_reservations.exists() + @property + def ready(self): + try: + return all([self.colors.exists() ,self.finances,]) + except Exception as e: + return False def get_transfer(self): return self.transfer_logs.filter(active=True).first() @property diff --git a/inventory/utils.py b/inventory/utils.py index 10234d5a..f2dad1c2 100644 --- a/inventory/utils.py +++ b/inventory/utils.py @@ -791,15 +791,15 @@ class CarFinanceCalculator: total_vat_amount = total_price_discounted * self.vat_rate return { - "total_price": total_price_discounted, - "total_vat_amount": total_vat_amount, - "total_discount": total_discount, - "total_additionals": total_additionals, + "total_price": round(total_price_discounted, 2), # total_price_discounted, + "total_vat_amount": round(total_vat_amount, 2), # total_vat_amount, + "total_discount": round(total_discount,2), + "total_additionals": round(total_additionals, 2), # total_additionals, "grand_total": round(total_price_discounted + total_vat_amount + total_additionals, 2) } def get_finance_data(self): - totals = self.calculate_totals() + totals = self.calculate_totals() return { "cars": [self._get_car_data(item) for item in self.item_transactions], "quantity": sum(self._get_quantity(item) for item in self.item_transactions), diff --git a/inventory/views.py b/inventory/views.py index 638b1514..06fdc929 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -187,12 +187,19 @@ def switch_language(request): def dealer_signup(request, *args, **kwargs): + form1 = forms.WizardForm1() + form2 = forms.WizardForm2() + form3 = forms.WizardForm3() + 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}) + data = json.loads(request.body) wf1 = data.get("wizardValidationForm1") wf2 = data.get("wizardValidationForm2") - wf3 = data.get("wizardValidationForm3") - # username = wf1.get("username") + wf3 = data.get("wizardValidationForm3") email = wf1.get("email") password = wf1.get("password") password_confirm = wf1.get("confirm_password") @@ -208,7 +215,6 @@ def dealer_signup(request, *args, **kwargs): try: with transaction.atomic(): - # user = User.objects.create(username=username, email=email) user = User.objects.create(username=email, email=email) user.set_password(password) user.save() @@ -222,26 +228,12 @@ def dealer_signup(request, *args, **kwargs): phone_number=phone, address=address, ) - # user = authenticate(request, email=email, password=password) - # if user is not None: - # return JsonResponse( - # {"message": "User created successfully."}, status=200 - # ) - # else: - # return JsonResponse({"error": "User creation failed."}, status=400) - # return redirect("account_login") - + return JsonResponse( + {"message": "User created successfully."}, status=200 + ) except Exception as e: return JsonResponse({"error": str(e)}, status=400) - - form1 = forms.WizardForm1() - form2 = forms.WizardForm2() - form3 = forms.WizardForm3() - 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): @@ -663,8 +655,8 @@ class CarListView(LoginRequiredMixin, ListView): paginate_by = 10 def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - cars = models.Car.objects.all() - + dealer = get_user_type(self.request) + cars = models.Car.objects.filter(dealer=dealer) context["stats"] = { 'all': cars.count(), 'available':cars.filter(status='available').count(), @@ -685,12 +677,12 @@ class CarListView(LoginRequiredMixin, ListView): 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): - qs = super().get_queryset() + 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) @@ -712,9 +704,7 @@ class CarListView(LoginRequiredMixin, ListView): if year: query &= Q(year=year) if car_status: - query &= Q(status=car_status) - # else: - # query &= Q(status="available") + query &= Q(status=car_status) qs = qs.filter(query) return qs @@ -964,7 +954,10 @@ class CarTransferDetailView(LoginRequiredMixin, SuccessMessageMixin, DetailView) template_name = "inventory/transfer_details.html" context_object_name = "transfer" - + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["action"] = self.request.GET.get("action") + return context def car_transfer_approve(request, car_pk, transfer_pk): car = get_object_or_404(models.Car, pk=car_pk) transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk) @@ -2328,6 +2321,7 @@ class AccountCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): form.instance.entity_model = dealer.entity form.instance.coa_model = dealer.entity.get_default_coa() form.instance.depth = 0 + form.instance.path = form.instance.code return super().form_valid(form) def get_form_kwargs(self): @@ -2565,7 +2559,7 @@ def create_estimate(request,pk=None): if isinstance(items, list): for item in estimate_itemtxs.keys(): - item_instance = ItemModel.objects.get(item_number=item) + item_instance = ItemModel.objects.filter(item_number=item).first() instance = models.Car.objects.get(vin=item_instance.name) reserve_car(instance, request) # for item in items: @@ -2574,12 +2568,12 @@ def create_estimate(request,pk=None): # reserve_car(instance, request) else: - item_instance = ItemModel.objects.get(additioinal_info__car_info__hash=items) + item_instance = ItemModel.objects.filter(additioinal_info__car_info__hash=items).first() instance = models.Car.objects.get(hash=item) response = reserve_car(instance, request) opportunity_id = data.get("opportunity_id") - if opportunity_id: + if opportunity_id != "None": opportunity = models.Opportunity.objects.get(pk=int(opportunity_id)) opportunity.estimate = estimate opportunity.save() @@ -2603,7 +2597,7 @@ def create_estimate(request,pk=None): customer = opportunity.customer form.initial['customer'] = customer - car_list = models.Car.objects.filter(dealer=dealer).exclude(status="reserved").annotate(color=F('colors__exterior__rgb'),color_name=F('colors__exterior__name')).values_list( + 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__name')).values_list( 'id_car_make__name', 'id_car_model__name','id_car_serie__name','id_car_trim__name','color','color_name','hash').distinct() context = { "form": form, @@ -2752,6 +2746,12 @@ def estimate_mark_as(request, pk): if not estimate.can_complete(): messages.error(request, _("Estimate is not ready for completion")) return redirect("estimate_detail", pk=estimate.pk) + elif mark == "canceled": + if not estimate.can_cancel(): + messages.error(request, _("Estimate is not ready for cancelation")) + return redirect("estimate_detail", pk=estimate.pk) + estimate.mark_as_canceled() + messages.success(request, _("Estimate canceled successfully.")) estimate.save() messages.success(request, "Estimate marked as " + mark.upper()) diff --git a/scripts/run.py b/scripts/run.py index 207a2e85..10581ba2 100644 --- a/scripts/run.py +++ b/scripts/run.py @@ -1,7 +1,7 @@ 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 +from django_ledger.models import EstimateModel,EntityModel,ItemModel,ItemTransactionModel,AccountModel from rich import print from datetime import date from inventory.models import Car, Dealer, VatRate,Lead,CarMake,CarModel,Schedule @@ -94,25 +94,27 @@ def run(): # 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') - data["data"]["finance"] = tx.item_model.additional_info.get('car_finance') - if tx.has_estimate(): - data["data"]["estimate"] = tx.ce_model - data["data"]["has_estimate"] = True - data["data"]["customer"] = tx.ce_model.customer - if tx.has_invoice(): - data["data"]["invoice"] = tx.invoice_model - data["data"]["has_invoice"] = True - data["data"]["customer"] = tx.invoice_model.customer - return data + # def get_item(tx:ItemTransactionModel): + # data = {"data": {}} + # data["data"]["info"] = tx.item_model.additional_info.get('car_info') + # data["data"]["finance"] = tx.item_model.additional_info.get('car_finance') + # if tx.has_estimate(): + # data["data"]["estimate"] = tx.ce_model + # data["data"]["has_estimate"] = True + # data["data"]["customer"] = tx.ce_model.customer + # if tx.has_invoice(): + # data["data"]["invoice"] = tx.invoice_model + # data["data"]["has_invoice"] = True + # data["data"]["customer"] = tx.invoice_model.customer + # return data - transactions = ItemTransactionModel.objects.all() - output = [] - for transaction in transactions: - output.append(get_item(transaction)) - print(output) + # transactions = ItemTransactionModel.objects.all() + # output = [] + # 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"]}) \ No newline at end of file + # 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) \ No newline at end of file diff --git a/templates/account/signup-wizard.html b/templates/account/signup-wizard.html index c31887d3..8b6f322c 100644 --- a/templates/account/signup-wizard.html +++ b/templates/account/signup-wizard.html @@ -4,7 +4,7 @@ {% block content %} -
{{ _("This car information is not complete , please add colors and finances before making it ready for sale .") }}
+ +{{ _("This car is in transfer process to another dealer, please wait for the acceptance .") }}
+ +{{ _("This car is reserved until ") }}{{ car.get_reservation.reserved_until }}
+ +{% trans "No finance details available." %}
{% trans "Add" %}{% if account.created %} - {{ _("Edit Account") }} + {{ _("Edit Account") }} {% else %} - {{ _("Add Account") }} + {{ _("Add Account") }} {% endif %}
@@ -27,9 +27,9 @@ {% endfor %} diff --git a/templates/ledger/coa_accounts/account_list.html b/templates/ledger/coa_accounts/account_list.html index f16cf7ff..61760f6a 100644 --- a/templates/ledger/coa_accounts/account_list.html +++ b/templates/ledger/coa_accounts/account_list.html @@ -11,7 +11,7 @@