diff --git a/inventory/forms.py b/inventory/forms.py index 92ba04a4..d5dfa247 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -10,6 +10,7 @@ from .mixins import AddClassMixin from django.forms.models import inlineformset_factory from django_ledger.forms.invoice import InvoiceModelCreateForm as InvoiceModelCreateFormBase from django_ledger.forms.bill import BillModelCreateForm as BillModelCreateFormBase +from django_ledger.forms.vendor import VendorModelForm from .models import ( Dealer, # Branch, @@ -31,9 +32,10 @@ from .models import ( SaleQuotationCar, AdditionalServices, Staff, - Opportunity, Priority, Sources, Lead, Activity, Notes, CarModel + Opportunity, Priority, Sources, Lead, Activity, Notes, CarModel, + SaleOrder ) -from django_ledger.models import ItemModel, InvoiceModel, BillModel +from django_ledger.models import ItemModel, InvoiceModel, BillModel,VendorModel from django.forms import ModelMultipleChoiceField, ValidationError, DateInput from django.utils.translation import gettext_lazy as _ import django_tables2 as tables @@ -98,27 +100,53 @@ class DealerForm(forms.ModelForm): ] -# Customer Form -class CustomerForm(forms.ModelForm, AddClassMixin): - class Meta: - model = Customer - fields = [ - "title", - "first_name", - "middle_name", - "last_name", - "gender", - "dob", - "email", - "national_id", +class CustomerForm(forms.Form): + first_name = forms.CharField() + middle_name = forms.CharField() + last_name = forms.CharField() + national_id = forms.CharField(max_length=10) + email = forms.EmailField() + phone_number = PhoneNumberField( + min_length=10, + max_length=10, + region="SA", + ) + address = forms.CharField() + +class OrganizationForm(forms.Form): + name = forms.CharField() + arabic_name = forms.CharField() + email = forms.EmailField() + phone_number = PhoneNumberField( + min_length=10, + max_length=10, + region="SA", + ) + crn = forms.CharField() + vrn = forms.CharField() + address = forms.CharField() + contact_person = forms.CharField(required=False) + logo = forms.ImageField(required=False) +# class CustomerForm(forms.ModelForm, AddClassMixin): +# class Meta: +# model = Customer +# fields = [ +# "title", +# "first_name", +# "middle_name", +# "last_name", +# "gender", +# "dob", +# "email", +# "national_id", - "phone_number", - "address", - ] - widgets = { - "phone_number": forms.TextInput(attrs={"class": "phone"}), - "dob": forms.DateInput(attrs={"type": "date"}), - } +# "phone_number", +# "address", +# ] +# widgets = { +# "phone_number": forms.TextInput(attrs={"class": "phone"}), +# "dob": forms.DateInput(attrs={"type": "date"}), +# } class CarForm( @@ -160,10 +188,11 @@ class CarForm( (obj.id_car_model, obj.get_local_name()) for obj in queryset ] if "vendor" in self.fields: - queryset = self.fields["vendor"].queryset - self.fields["vendor"].choices = [ - (obj.pk, obj.get_local_name()) for obj in queryset - ] + self.fields["vendor"].queryset = VendorModel.objects.filter(active=True) + # queryset = self.fields["vendor"].queryset + # self.fields["vendor"].choices = [ + # (obj.pk, obj.get_local_name()) for obj in queryset + # ] class CarUpdateForm(forms.ModelForm, AddClassMixin): @@ -193,12 +222,12 @@ class CarUpdateForm(forms.ModelForm, AddClassMixin): # (branch.id, branch.get_local_name()) for branch in self.fields['branch'].queryset # ] - if "vendor" in self.fields: - queryset = self.fields["vendor"].queryset - if queryset: - self.fields["vendor"].choices = [ - (vendor.id, vendor.get_local_name()) for vendor in queryset - ] + # if "vendor" in self.fields: + # queryset = self.fields["vendor"].queryset + # if queryset: + # self.fields["vendor"].choices = [ + # (vendor.id, vendor.get_local_name()) for vendor in queryset + # ] class CarFinanceForm(AddClassMixin, forms.ModelForm): @@ -269,20 +298,22 @@ class CarRegistrationForm(forms.ModelForm): fields = ["car", "plate_number", "text1", "text2", "text3", "registration_date"] -class VendorForm(forms.ModelForm): - class Meta: - model = Vendor - fields = [ - "name", - "arabic_name", - "crn", - "vrn", - "email", - "phone_number", - "contact_person", - "address", - "logo", - ] +class VendorForm(VendorModelForm): + pass +# class VendorForm(forms.ModelForm): +# class Meta: +# model = Vendor +# fields = [ +# "name", +# "arabic_name", +# "crn", +# "vrn", +# "email", +# "phone_number", +# "contact_person", +# "address", +# "logo", +# ] class CarColorsForm(forms.ModelForm): @@ -343,22 +374,22 @@ class QuotationForm(forms.ModelForm): ).distinct() -class OrganizationForm(forms.ModelForm): - class Meta: - model = Organization - fields = [ - "name", - "arabic_name", - "crn", - "vrn", - "phone_number", - "address", - "logo", - ] +# class OrganizationForm(forms.ModelForm): +# class Meta: +# model = Organization +# fields = [ +# "name", +# "arabic_name", +# "crn", +# "vrn", +# "phone_number", +# "address", +# "logo", +# ] - def __init__(self, *args, **kwargs): - dealer = kwargs.pop("dealer", None) - super().__init__(*args, **kwargs) +# def __init__(self, *args, **kwargs): +# dealer = kwargs.pop("dealer", None) +# super().__init__(*args, **kwargs) class RepresentativeForm(forms.ModelForm): @@ -654,3 +685,12 @@ class BillModelCreateForm(BillModelCreateFormBase): 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'})) + +class SaleOrderForm(forms.ModelForm): + class Meta: + model = SaleOrder + fields = '__all__' + widgets = { + 'address': forms.Textarea(attrs={'rows': 3}), + 'comments': forms.Textarea(attrs={'rows': 3}), + } \ No newline at end of file diff --git a/inventory/migrations/0004_purchaseorder.py b/inventory/migrations/0004_purchaseorder.py new file mode 100644 index 00000000..dfc29557 --- /dev/null +++ b/inventory/migrations/0004_purchaseorder.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.17 on 2025-01-23 08:51 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0003_cartransfer'), + ] + + operations = [ + migrations.CreateModel( + name='PurchaseOrder', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('payment_method', models.CharField(choices=[('cash', 'Cash'), ('finance', 'Finance'), ('lease', 'Lease')], max_length=20)), + ('trade_in', models.CharField(blank=True, max_length=100, null=True)), + ('comments', models.TextField(blank=True, null=True)), + ('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='purchase_orders', to='inventory.car', verbose_name='Car')), + ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='purchase_orders', to='inventory.customer', verbose_name='Customer')), + ], + ), + ] diff --git a/inventory/migrations/0005_saleorder_delete_purchaseorder.py b/inventory/migrations/0005_saleorder_delete_purchaseorder.py new file mode 100644 index 00000000..84dddfa1 --- /dev/null +++ b/inventory/migrations/0005_saleorder_delete_purchaseorder.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.17 on 2025-01-23 11:06 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0004_purchaseorder'), + ] + + operations = [ + migrations.CreateModel( + name='SaleOrder', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('payment_method', models.CharField(choices=[('cash', 'Cash'), ('finance', 'Finance'), ('lease', 'Lease')], max_length=20)), + ('trade_in', models.CharField(blank=True, max_length=100, null=True)), + ('comments', models.TextField(blank=True, null=True)), + ('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='inventory.car', verbose_name='Car')), + ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='inventory.customer', verbose_name='Customer')), + ], + ), + migrations.DeleteModel( + name='PurchaseOrder', + ), + ] diff --git a/inventory/migrations/0006_alter_car_vendor.py b/inventory/migrations/0006_alter_car_vendor.py new file mode 100644 index 00000000..b9df356f --- /dev/null +++ b/inventory/migrations/0006_alter_car_vendor.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.17 on 2025-01-24 15:32 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_ledger', '0017_alter_accountmodel_unique_together_and_more'), + ('inventory', '0005_saleorder_delete_purchaseorder'), + ] + + operations = [ + migrations.AlterField( + model_name='car', + name='vendor', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='django_ledger.vendormodel', verbose_name='Vendor'), + ), + ] diff --git a/inventory/migrations/0007_alter_cartransfer_status.py b/inventory/migrations/0007_alter_cartransfer_status.py new file mode 100644 index 00000000..7fa30fab --- /dev/null +++ b/inventory/migrations/0007_alter_cartransfer_status.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.17 on 2025-01-26 07:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0006_alter_car_vendor'), + ] + + operations = [ + migrations.AlterField( + model_name='cartransfer', + name='status', + field=models.CharField(default='draft', max_length=10, verbose_name=[('draft', 'Draft'), ('approved', 'Approved'), ('pending', 'Pending'), ('accepted', 'Accepted'), ('success', 'Success'), ('reject', 'Reject'), ('cancelled', 'Cancelled')]), + ), + ] diff --git a/inventory/models.py b/inventory/models.py index ae0cc00b..a901e9b9 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -278,6 +278,7 @@ class CarTransferStatusChoices(models.TextChoices): accepted = "accepted", _("Accepted") success = "success", _("Success") reject = "reject", _("Reject") + cancelled = "cancelled", _("Cancelled") class CarStatusChoices(models.TextChoices): @@ -332,7 +333,7 @@ class Car(models.Model): ) vendor = models.ForeignKey( - "Vendor", + VendorModel, models.DO_NOTHING, null=True, blank=True, @@ -408,6 +409,21 @@ class Car(models.Model): def get_car_group(self): return f"{self.id_car_make.get_local_name} {self.id_car_model.get_local_name}" + def to_dict(self): + return { + "vin": self.vin, + "make": self.id_car_make.name if self.id_car_make else "Unknown Make", + "model": self.id_car_model.name if self.id_car_model else "Unknown Model", + "trim": self.id_car_trim.name if self.id_car_trim else "Unknown Trim", + "year": self.year, + "display_name": self.get_car_group, + "status": self.status, + "stock_type": self.stock_type, + "remarks": self.remarks, + "mileage": self.mileage, + "receiving_date": self.receiving_date.strftime('%Y-%m-%d %H:%M:%S'), + "id": self.id, + } class CarTransfer(models.Model): car = models.ForeignKey( @@ -445,7 +461,7 @@ class CarTransfer(models.Model): @property def total_price(self): - return self.quantity * self.car.finances.cost_price + return self.quantity * self.car.finances.total_vat class Meta: verbose_name = _("Car Transfer Log") verbose_name_plural = _("Car Transfer Logs") @@ -529,6 +545,16 @@ class CarFinance(models.Model): def revenue(self): return self.selling_price - self.cost_price + def to_dict(self): + return { + "cost_price": str(self.cost_price), + "selling_price": str(self.selling_price), + "discount_amount": str(self.discount_amount), + "total": str(self.total), + "total_discount": str(self.total_discount), + "total_vat": str(self.total_vat), + "vat_amount": str(self.vat_amount), + } def __str__(self): return f"Car: {self.car}, Selling Price: {self.selling_price}" @@ -1513,3 +1539,35 @@ class UserActivityLog(models.Model): def __str__(self): return f"{self.user.email} - {self.action} - {self.timestamp}" + +class SaleOrder(models.Model): + customer = models.ForeignKey( + Customer, + on_delete=models.CASCADE, + related_name="sale_orders", + verbose_name=_("Customer"), + ) + car = models.ForeignKey( + Car, + on_delete=models.CASCADE, + related_name="sale_orders", + verbose_name=_("Car"), + ) + payment_method = models.CharField(max_length=20, choices=[ + ('cash', 'Cash'), + ('finance', 'Finance'), + ('lease', 'Lease'), + ]) + trade_in = models.CharField(max_length=100, blank=True, null=True) + comments = models.TextField(blank=True, null=True) + + def __str__(self): + return f"Sale Order for {self.full_name} - {self.make} {self.model}" + + @property + def full_name(self): + return f"{self.customer.first_name} {self.customer.last_name}" + + @property + def price(self): + return self.car.finances.selling_price \ No newline at end of file diff --git a/inventory/signals.py b/inventory/signals.py index 3f8bcc7f..4422d3ae 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -1,4 +1,6 @@ +from decimal import Decimal from django.db.models.signals import post_save, post_delete, pre_delete, pre_save +from .utils import to_dict from django.dispatch import receiver from django.utils.translation import gettext_lazy as _ from django.contrib.auth import get_user_model @@ -10,6 +12,7 @@ from django_ledger.models import ( ItemModelAbstract, UnitOfMeasureModel, VendorModel, + EstimateModel ) from . import models from django.utils.timezone import now @@ -674,35 +677,36 @@ def create_customer(sender, instance, created, **kwargs): # Create Item @receiver(post_save, sender=models.Car) -def create_item_model(sender, instance, created, **kwargs): - name = instance.dealer.name - entity = EntityModel.objects.filter(name=name).first() +def create_item_model(sender, instance, created, **kwargs): + entity = instance.dealer.entity if created: coa = entity.get_default_coa() uom = entity.get_uom_all().get(name="Unit") - if not entity.get_items_all().filter(name=instance.vin).first(): - entity.create_item_product( - name=f"{instance.vin}", + if not entity.get_items_all().filter(name=instance.vin).exists(): + product = entity.create_item_product( + name=instance.vin, item_type=ItemModel.ITEM_TYPE_MATERIAL, uom_model=uom, coa_model=coa, + additional_info={} ) - entity.create_item_inventory( - name=f"{instance.vin}", - item_type=ItemModel.ITEM_TYPE_MATERIAL, - uom_model=uom, - coa_model=coa, - ) - + + product = entity.get_items_all().filter(name=instance.vin).first() + product.additional_info.update({'car_info': instance.to_dict()}) + product.save() # # update price - CarFinance @receiver(post_save, sender=models.CarFinance) def update_item_model_cost(sender, instance, created, **kwargs): - ItemModel.objects.filter(item_id=instance.car.vin).update( - inventory_received_value=instance.cost_price, - default_amount=instance.cost_price, - ) + entity = instance.car.dealer.entity + + product = entity.get_items_all().filter(name=instance.car.vin).first() + product.default_amount = instance.selling_price + product.additional_info = {} + product.additional_info.update({"car_finance":instance.to_dict()}) + product.additional_info.update({"additional_services": [to_dict(service) for service in instance.additional_services.all()]}) + product.save() print(f"Inventory item updated with CarFinance data for Car: {instance.car}") @@ -852,4 +856,10 @@ def update_car_status_on_reservation_update(sender, instance, **kwargs): else: if not car.reservations.filter(reserved_until__gt=now()).exists(): car.status = models.CarStatusChoices.AVAILABLE - car.save() \ No newline at end of file + car.save() + +# @receiver(post_save, sender=EstimateModel) +# def update_estimate_status(sender, instance,created, **kwargs): + +# items = instance.get_itemtxs_data()[0].all() +# total = sum([Decimal(item.item_model.additional_info['car_finance']["selling_price"]) * Decimal(item.ce_quantity) for item in items]) diff --git a/inventory/urls.py b/inventory/urls.py index 0f35f4d7..80ec7da9 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -64,26 +64,26 @@ urlpatterns = [ # CRM URLs path("customers/", views.CustomerListView.as_view(), name="customer_list"), path( - "customers//", + "customers//", views.CustomerDetailView.as_view(), name="customer_detail", ), path( - "customers/create/", views.CustomerCreateView.as_view(), name="customer_create" + "customers/create/", views.CustomerCreateView, name="customer_create" ), path( - "customers//update/", - views.CustomerUpdateView.as_view(), + "customers//update/", + views.CustomerUpdateView, name="customer_update", ), - path("customers//delete/", views.delete_customer, name="customer_delete"), + path("customers//delete/", views.delete_customer, name="customer_delete"), path( - "customers//opportunities/create/", + "customers//opportunities/create/", views.OpportunityCreateView.as_view(), name="create_opportunity", ), path( - "customers//add-note/", + "customers//add-note/", views.add_note_to_customer, name="add_note_to_customer", ), @@ -147,16 +147,16 @@ urlpatterns = [ ), # Vendor URLs path("vendors", views.VendorListView.as_view(), name="vendor_list"), - path("vendors//", views.VendorDetailView.as_view(), name="vendor_detail"), + path("vendors//", views.VendorDetailView.as_view(), name="vendor_detail"), path("vendors/create/", views.VendorCreateView.as_view(), name="vendor_create"), path( - "vendors//update/", + "vendors//update/", views.VendorUpdateView.as_view(), name="vendor_update", ), path( - "vendors//delete/", - views.VendorDetailView.as_view(), + "vendors//delete/", + views.delete_vendor, name="vendor_delete", ), # Car URLs @@ -263,9 +263,8 @@ urlpatterns = [ name="mark_quotation", ), path( - "sales/quotations//post_quotation/", - views.post_quotation, - name="post_quotation", + "sales/quotations//post_quotation/",views.post_quotation, + name="post_quotation" ), path( "sales/quotations//invoice_detail/", @@ -291,22 +290,22 @@ urlpatterns = [ "organizations/", views.OrganizationListView.as_view(), name="organization_list" ), path( - "organizations//", + "organizations//", views.OrganizationDetailView.as_view(), name="organization_detail", ), path( "organizations/create/", - views.OrganizationCreateView.as_view(), + views.OrganizationCreateView, name="organization_create", ), path( - "organizations//update/", - views.OrganizationUpdateView.as_view(), + "organizations//update/", + views.OrganizationUpdateView, name="organization_update", ), path( - "organizations//delete/", + "organizations//delete/", views.OrganizationDeleteView.as_view(), name="organization_delete", ), @@ -403,6 +402,8 @@ urlpatterns = [ path( "sales/estimates//send_email", views.send_email_view, name="send_email" ), + path('sales/estimates//sale_order/', views.create_sale_order, name='create_sale_order'), + # Invoice path("sales/invoices/", views.InvoiceListView.as_view(), name="invoice_list"), path( diff --git a/inventory/utils.py b/inventory/utils.py index a95a19ac..2abf4681 100644 --- a/inventory/utils.py +++ b/inventory/utils.py @@ -15,7 +15,7 @@ from django.core.mail import send_mail from django.utils.translation import gettext_lazy as _ from inventory.utilities.financials import get_financial_value from django_ledger.models.items import ItemModel -from django_ledger.models import InvoiceModel, EstimateModel,BillModel +from django_ledger.models import InvoiceModel, EstimateModel,BillModel,VendorModel,CustomerModel from decimal import Decimal @@ -136,51 +136,61 @@ def get_financial_values(model): "car_and_item_info": [], "additional_services": [], } + data = model.get_itemtxs_data()[0].all() - data = model.get_itemtxs_data()[0].all() - + for item in data: + if not item.item_model.additional_info.get("car_finance"): + return { + "vat_amount": 0, + "total": 0, + "grand_total": 0, + "discount_amount": 0, + "vat": 0, + "car_and_item_info": [], + "additional_services": [], + } + if isinstance(model, InvoiceModel): - data = model.ce_model.get_itemtxs_data()[0].all() - - car_and_item_info = [ - { - "car": models.Car.objects.get(vin=x.item_model.name), - "total": models.Car.objects.get( - vin=x.item_model.name - ).finances.selling_price - * Decimal(x.ce_quantity), - "itemmodel": x, - } - for x in data - ] - total = sum( - Decimal(models.Car.objects.get(vin=x.item_model.name).finances.total) - * Decimal(x.ce_quantity) - for x in data - ) + if model.ce_model: + data = model.ce_model.get_itemtxs_data()[0].all() + else: + data = model.get_itemtxs_data()[0].all() + total = sum([Decimal(item.item_model.additional_info["car_finance"]["selling_price"]) * Decimal(item.ce_quantity or item.quantity) for item in data]) + discount_amount = sum( - models.CarFinance.objects.get(car__vin=i.item_model.name).discount_amount + Decimal(i.item_model.additional_info['car_finance']["discount_amount"]) for i in data ) additional_services = [] - for i in data: - cf = models.CarFinance.objects.get(car__vin=i.item_model.name) - if cf.additional_services.exists(): + for i in data: + if i.item_model.additional_info['additional_services']: additional_services.extend( [ {"name": x.name, "price": x.price} - for x in cf.additional_services.all() + for x in i.item_model.additional_info['additional_services'] ] ) grand_total = Decimal(total) - Decimal(discount_amount) vat_amount = round(Decimal(grand_total) * Decimal(vat.rate), 2) + + car_and_item_info = [ + { + "info": x.item_model.additional_info['car_info'], + "finances": x.item_model.additional_info['car_finance'], + "quantity": x.ce_quantity or x.quantity, + "total": Decimal(x.item_model.additional_info['car_finance']['selling_price']) + * Decimal(x.ce_quantity or x.quantity), + } + for x in data + ] + return { - "car_and_item_info": car_and_item_info, "total": total, "discount_amount": discount_amount, + "car_and_item_info": car_and_item_info, "additional_services": additional_services, "grand_total": grand_total + vat_amount, "vat_amount": vat_amount, @@ -194,12 +204,13 @@ def set_invoice_payment(dealer, entity, invoice, amount, payment_method): if invoice.terms == "on_receipt": for x in invoice.get_itemtxs_data()[0].all(): - vat_amount += models.Car.objects.get( - vin=x.item_model.name - ).finances.vat_amount * Decimal(x.quantity) + # vat_amount += models.Car.objects.get( + # vin=x.item_model.name + # ).finances.vat_amount * Decimal(x.quantity) total_amount += Decimal(x.unit_cost) * Decimal(x.quantity) - grand_total = total_amount - Decimal(vat_amount) + # grand_total = total_amount - Decimal(vat_amount) + total_amount ledger = LedgerModel.objects.filter( name__icontains=str(invoice.pk), entity=entity @@ -234,11 +245,13 @@ def set_invoice_payment(dealer, entity, invoice, amount, payment_method): tx_type="debit", description="Payment Received", ) + + # if total_amount + invoice. TransactionModel.objects.create( journal_entry=journal, account=credit_account, # Credit Accounts Receivable - amount=grand_total, # Payment amount + amount=total_amount, # Payment amount tx_type="credit", description="Payment Received", ) @@ -329,26 +342,19 @@ def transfer_car(car,transfer): from_dealer = transfer.from_dealer to_dealer = transfer.to_dealer # add transfer.to_dealer as customer in transfer.from_dealer entity - instance = models.Customer.objects.filter( - dealer=from_dealer, - email=to_dealer.user.email, - ).first() - if not instance: - instance = models.Customer.objects.create( - dealer=from_dealer, - title=models.Title.MR, - email=to_dealer.user.email, - first_name=to_dealer.user.first_name, - last_name=to_dealer.user.last_name, - phone_number=f"05685412{random.randint(10, 99)}", - address=to_dealer.address, - national_id=f"{random.randint(100, 9999)}", - dob="1990-01-01", - ) - # create invoice from transfer.from_dealer to transfer.to_dealer - name = f"{instance.first_name} {instance.middle_name} {instance.last_name}" - customer = from_dealer.entity.get_customers().filter(customer_name=name).first() + customer = from_dealer.entity.get_customers().filter( + email=to_dealer.user.email).first() + if not customer: + customer = from_dealer.entity.create_customer( + customer_model_kwargs={ + "customer_name": to_dealer.name, + "email": to_dealer.user.email, + "address_1": to_dealer.address + } + ) + customer.additional_info.update({"type":"organization"}) + customer.save() invoice = from_dealer.entity.create_invoice( customer_model=customer, @@ -369,7 +375,7 @@ def transfer_car(car,transfer): invoice_itemtxs = { item.item_number: { - "unit_cost": car.finances.cost_price, + "unit_cost": transfer.total_price, "quantity": transfer.quantity, "total_amount": transfer.total_price, } @@ -381,46 +387,33 @@ def transfer_car(car,transfer): operation=InvoiceModel.ITEMIZE_APPEND, ) + invoice.save() invoice.mark_as_review() invoice.mark_as_approved(from_dealer.entity.slug, from_dealer.entity.admin) - invoice.mark_as_paid(from_dealer.entity.slug, from_dealer.entity.admin) + # invoice.mark_as_paid(from_dealer.entity.slug, from_dealer.entity.admin) invoice.save() - #create car item product in to_dealer entity uom = to_dealer.entity.get_uom_all().filter(name=item.uom.name).first() + #create item product in the reciever ledger product = to_dealer.entity.create_item_product( name=item.name, uom_model=uom, item_type=item.item_type, coa_model=to_dealer.entity.get_default_coa(), ) - - car_dict = vars(car).copy() - del car_dict["_state"] - for key, value in car_dict.items(): - if isinstance(value, datetime.datetime): - car_dict[key] = value.strftime('%Y-%m-%d %H:%M:%S') - product.additional_info = json.dumps({"car_info": car_dict}) + + product.additional_info.update({'car_info': car.to_dict()}) product.save() #add the sender as vendor and create a bill for it - vendor_instance, created = models.Vendor.objects.get_or_create( - dealer=to_dealer, - crn=from_dealer.crn, - vrn=from_dealer.vrn, - name=from_dealer.name, - email=from_dealer.user.email, - arabic_name=from_dealer.arabic_name, - address=from_dealer.address, - phone_number=from_dealer.phone_number, - contact_person='', - ) - + vendor = None + vendor = to_dealer.entity.get_vendors().filter(vendor_name=from_dealer.name).first() + if not vendor: + vendor = VendorModel.objects.create(entity_model=to_dealer.entity, vendor_name=from_dealer.name,additional_info={"info":to_dict(from_dealer)}) + #transfer the car to to_dealer and create items record - - vendor = to_dealer.entity.get_vendors().filter(vendor_name=vendor_instance.name).first() - + bill = to_dealer.entity.create_bill( vendor_model=vendor, terms=BillModel.TERMS_NET_30, @@ -428,10 +421,10 @@ def transfer_car(car,transfer): prepaid_account=to_dealer.entity.get_default_coa_accounts().get(name="Prepaid Expenses", active=True), coa_model=to_dealer.entity.get_default_coa(), ) - + bill.additional_info = {} bill_itemtxs = { - item.item_number: { - "unit_cost": car.finances.cost_price, + product.item_number: { + "unit_cost": transfer.total_price, "quantity": transfer.quantity, "total_amount": transfer.total_price, } @@ -439,16 +432,23 @@ def transfer_car(car,transfer): bill_itemtxs = bill.migrate_itemtxs(itemtxs=bill_itemtxs, commit=True, - operation=BillModel.ITEMIZE_REPLACE) + operation=BillModel.ITEMIZE_APPEND) + bill.additional_info.update({'car_info': car.to_dict()}) + bill.additional_info.update({'car_finance': car.finances.to_dict()}) + + bill.mark_as_review() + bill.mark_as_approved(to_dealer.entity.slug, to_dealer.entity.admin) + bill.save() car.dealer = to_dealer - car.vendor = vendor_instance + car.vendor = vendor car.receiving_date = datetime.datetime.now() car.finances.additional_services.clear() if hasattr(car, "custom_cards"): car.custom_cards.delete() - # car.finances.cost_price = 0 + + car.finances.cost_price = transfer.total_price car.finances.selling_price = 0 car.finances.discount_amount = 0 car.finances.save() @@ -456,8 +456,6 @@ def transfer_car(car,transfer): car.location.showroom = to_dealer car.location.description = "" car.location.save() - - # car.reservations.all().delete() car.status = models.CarStatusChoices.AVAILABLE transfer.status = models.CarTransferStatusChoices.success transfer.active = False @@ -467,6 +465,20 @@ def transfer_car(car,transfer): return True #pay the pill # set_bill_payment(to_dealer,to_dealer.entity,bill,transfer.total_price,"credit") + +def to_dict(obj): + obj_dict = vars(obj).copy() + if '_state' in vars(obj): + del obj_dict["_state"] - - \ No newline at end of file + for key, value in obj_dict.items(): + if isinstance(value, datetime.datetime): + obj_dict[key] = value.strftime('%Y-%m-%d %H:%M:%S') + elif hasattr(value,'pk') or hasattr(value,'id'): + try: + obj_dict[key] = value.name + except AttributeError: + obj_dict[key] = str(value) + else: + obj_dict[key] = str(value) + return obj_dict \ No newline at end of file diff --git a/inventory/views.py b/inventory/views.py index aa6c2e9d..8b9bd00d 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -1,7 +1,10 @@ from decimal import Decimal from django.core.paginator import Paginator from django.forms import DateField, DateInput, HiddenInput, TextInput -from django_ledger.forms.bill import ApprovedBillModelUpdateForm, InReviewBillModelUpdateForm +from django_ledger.forms.bill import ( + ApprovedBillModelUpdateForm, + InReviewBillModelUpdateForm, +) from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt from django_ledger.models import ( @@ -15,12 +18,15 @@ from django_ledger.models import ( CustomerModel, LedgerModel, ItemModel, - BillModel + BillModel, + VendorModel, ) from django_ledger.forms.bank_account import ( BankAccountCreateForm, BankAccountUpdateForm, ) + +from django_ledger.forms.customer import CustomerModelForm from django_ledger.forms.bill import BillModelCreateForm from django_ledger.forms.invoice import ( DraftInvoiceModelUpdateForm, @@ -84,6 +90,7 @@ from .utils import ( get_user_type, set_bill_payment, set_invoice_payment, + to_dict, transfer_car, ) from django.contrib.auth.models import User @@ -93,6 +100,7 @@ from django.contrib.auth import authenticate import cv2 import numpy as np from pyzbar.pyzbar import decode +from django.core.files.storage import default_storage logger = logging.getLogger(__name__) @@ -289,6 +297,12 @@ class CarCreateView(LoginRequiredMixin, CreateView): template_name = "inventory/car_form.html" # success_url = reverse_lazy('inventory_stats') + def get_form(self, form_class=None): + form = super().get_form(form_class) + dealer = get_user_type(self.request) + form.fields["vendor"].queryset = dealer.entity.get_vendors().filter(active=True) + return form + def get_success_url(self): """Determine the redirect URL based on user choice.""" if self.request.POST.get("add_another"): @@ -779,52 +793,76 @@ class CarLocationUpdateView(UpdateView): def get_success_url(self): return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk}) + class CarTransferCreateView(CreateView): model = models.CarTransfer form_class = forms.CarTransferForm template_name = "inventory/car_location_form.html" - - def get_form(self, form_class = None): + + def get_form(self, form_class=None): form = super().get_form(form_class) - form.fields['to_dealer'].queryset = models.Dealer.objects.exclude(pk=get_user_type(self.request).pk).all() - form.fields['car'].queryset = models.Car.objects.filter(pk=self.kwargs["pk"]) + form.fields["to_dealer"].queryset = models.Dealer.objects.exclude( + pk=get_user_type(self.request).pk + ).all() + form.fields["car"].queryset = models.Car.objects.filter(pk=self.kwargs["pk"]) return form + def get_initial(self): initial = super().get_initial() initial["car"] = get_object_or_404(models.Car, pk=self.kwargs["pk"]) return initial - - def form_valid(self, form): + + def form_valid(self, form): form.instance.from_dealer = get_user_type(self.request) form.instance.car.status = "transfer" form.instance.car.save() return super().form_valid(form) - + def get_success_url(self): return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk}) + def CarTransferDetailView(request, pk): transfer = get_object_or_404(models.CarTransfer, pk=pk) - context = {"transfer":transfer} - return render(request,'inventory/transfer_details.html',context) + context = {"transfer": transfer} + return render(request, "inventory/transfer_details.html", context) -def car_transfer_approve(request, car_pk,transfer_pk): + +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) + transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk) + action = request.GET.get("action") + if action == "cancel": + transfer.status = "cancel" + transfer.active = False + transfer.save() + transfer.car.status = "available" + transfer.car.save() + messages.success(request, _("Car transfer canceled successfully.")) + models.Notification.objects.create( + user=transfer.from_dealer.user, + message=f"Car transfer request from {transfer.to_dealer} is canceled.", + ) + return redirect("car_detail", pk=car.pk) transfer.status = "approved" transfer.save() - url = request.build_absolute_uri(reverse('transfer_preview',kwargs={'car_pk':car.pk,"transfer_pk":transfer.pk})) + url = request.build_absolute_uri( + reverse( + "transfer_preview", kwargs={"car_pk": car.pk, "transfer_pk": transfer.pk} + ) + ) models.Notification.objects.create( user=transfer.to_dealer.user, - message=f"Car transfer request from {transfer.from_dealer} is waiting for your acceptance. Accept", + message=f"Car transfer request from {transfer.from_dealer} is waiting for your acceptance. Accept", ) messages.success(request, _("Car transfer approved successfully.")) return redirect("car_detail", pk=car.pk) -def car_transfer_accept_reject(request, car_pk,transfer_pk): + +def car_transfer_accept_reject(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) - status = request.GET.get("status") + transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk) + status = request.GET.get("status") if status == "rejected": transfer.status = "reject" transfer.active = False @@ -837,7 +875,7 @@ def car_transfer_accept_reject(request, car_pk,transfer_pk): elif status == "accepted": transfer.status = "accept" transfer.save() - success = transfer_car(car,transfer) + success = transfer_car(car, transfer) if success: messages.success(request, _("Car Transfer Completed successfully.")) models.Notification.objects.create( @@ -845,12 +883,13 @@ def car_transfer_accept_reject(request, car_pk,transfer_pk): message=f"Car transfer request from {transfer.to_dealer} is completed.", ) return redirect("inventory_stats") - -def CarTransferPreviewView(request, car_pk,transfer_pk): + + +def CarTransferPreviewView(request, car_pk, transfer_pk): transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk) if transfer.to_dealer != get_user_type(request): return redirect("car_detail", pk=car_pk) - return render(request,'inventory/transfer_preview.html',{"transfer":transfer}) + return render(request, "inventory/transfer_preview.html", {"transfer": transfer}) # def get_context_data(self, **kwargs): # estimate = kwargs.get("object") # if estimate.get_itemtxs_data(): @@ -865,11 +904,9 @@ def CarTransferPreviewView(request, car_pk,transfer_pk): # return super().get_context_data(**kwargs) - - # class CarTransferView(View): -# template_name = "inventory/car_location_form.html" - +# template_name = "inventory/car_location_form.html" + # def get(self, request, *args, **kwargs): # form = forms.CarTransferForm() # car = models.Car.objects.filter(pk=self.kwargs["pk"]) @@ -878,7 +915,7 @@ def CarTransferPreviewView(request, car_pk,transfer_pk): # form.initial['car'] = car.first() # context = {"form": form} # return render(request, self.template_name,context) - + # def post(self, request, *args, **kwargs): # form = forms.CarTransferForm(request.POST) # if form.is_valid(): @@ -894,6 +931,7 @@ def CarTransferPreviewView(request, car_pk,transfer_pk): # # messages.success(request, "Car transfered successfully.") # return redirect("car_detail", pk=self.kwargs["pk"]) + class CustomCardCreateView(LoginRequiredMixin, CreateView): model = models.CustomCard form_class = forms.CustomCardForm @@ -997,7 +1035,7 @@ class DealerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): class CustomerListView(LoginRequiredMixin, ListView): - model = models.Customer + model = CustomerModel home_label = _("customers") context_object_name = "customers" paginate_by = 10 @@ -1008,13 +1046,13 @@ class CustomerListView(LoginRequiredMixin, ListView): query = self.request.GET.get("q") dealer = get_user_type(self.request) - customers = models.Customer.objects.filter(dealer=dealer) + customers = dealer.entity.get_customers().filter(active=True,additional_info__type="customer") if query: customers = customers.filter( - Q(national_id__icontains=query) - | Q(first_name__icontains=query) + Q(first_name__icontains=query) | Q(last_name__icontains=query) + | Q(additional_info__info__icontains=query) ) return customers @@ -1025,7 +1063,7 @@ class CustomerListView(LoginRequiredMixin, ListView): class CustomerDetailView(LoginRequiredMixin, DetailView): - model = models.Customer + model = CustomerModel template_name = "customers/view_customer.html" context_object_name = "customer" @@ -1033,21 +1071,22 @@ class CustomerDetailView(LoginRequiredMixin, DetailView): dealer = get_user_type(self.request) entity = dealer.entity context = super().get_context_data(**kwargs) - name = f"{context['customer'].first_name} {context['customer'].middle_name} {context['customer'].last_name}" - context["estimates"] = entity.get_estimates().filter( - customer__customer_name=name - ) - context["notes"] = models.Notes.objects.filter( - content_type__model="customer", object_id=self.object.id - ) - context["activities"] = models.Activity.objects.filter( - content_type__model="customer", object_id=self.object.id - ) + # customer = f"{context['customer'].first_name} {context['customer'].middle_name} {context['customer'].last_name}" + # context["estimates"] = entity.get_estimates().filter( + # customer__customer_name=name + # ) + context["estimates"] = context["customer"].estimatemodel_set.all() + # context["notes"] = models.Notes.objects.filter( + # content_type__model="customer", object_id=self.object.id + # ) + # context["activities"] = models.Activity.objects.filter( + # content_type__model="customer", object_id=self.object.id + # ) return context def add_note_to_customer(request, pk): - customer = get_object_or_404(models.Customer, pk=pk) + customer = get_object_or_404(CustomerModel, pk=pk) if request.method == "POST": form = forms.NoteForm(request.POST) if form.is_valid(): @@ -1063,7 +1102,7 @@ def add_note_to_customer(request, pk): def add_activity_to_customer(request, pk): - customer = get_object_or_404(models.Customer, pk=pk) + customer = get_object_or_404(CustomerModel, pk=pk) if request.method == "POST": form = forms.ActivityForm(request.POST) if form.is_valid(): @@ -1079,32 +1118,69 @@ def add_activity_to_customer(request, pk): ) -class CustomerCreateView( - LoginRequiredMixin, - SuccessMessageMixin, - CreateView, -): - model = models.Customer - form_class = forms.CustomerForm - template_name = "customers/customer_form.html" - success_url = reverse_lazy("customer_list") - success_message = _("Customer created successfully.") +def CustomerCreateView(request): + if request.method == "POST": + customer_dict = { + 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["middle_name"] + + " " + + customer_dict["last_name"] + ) - def form_valid(self, form): - form.instance.dealer = get_user_type(self.request) - return super().form_valid(form) + instance = dealer.entity.create_customer( + customer_model_kwargs={ + "customer_name": customer_name, + "address_1": customer_dict["address"], + "phone": customer_dict["phone_number"], + "email": customer_dict["email"], + } + ) + customer_dict["pk"] = str(instance.pk) + instance.additional_info["customer_info"] = customer_dict + instance.additional_info["type"] = "customer" + instance.save() + messages.success(request, _("Customer created successfully.")) + return redirect("customer_list") + + form = forms.CustomerForm() + return render(request, "customers/customer_form.html", {"form": form}) -class CustomerUpdateView( - LoginRequiredMixin, - SuccessMessageMixin, - UpdateView, -): - model = models.Customer - form_class = forms.CustomerForm - template_name = "customers/customer_form.html" - success_url = reverse_lazy("customer_list") - success_message = _("Customer updated successfully.") +def CustomerUpdateView(request, pk): + customer = get_object_or_404(CustomerModel, pk=pk) + if request.method == "POST": + # form = forms.CustomerForm(request.POST, instance=customer) + customer_dict = { + 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["middle_name"] + + " " + + customer_dict["last_name"] + ) + + instance = dealer.entity.get_customers().get(pk=pk) + instance.customer_name = customer_name + instance.address_1 = customer_dict["address"] + instance.phone = customer_dict["phone_number"] + instance.email = customer_dict["email"] + + customer_dict["pk"] = str(instance.pk) + instance.additional_info["customer_info"] = customer_dict + instance.save() + messages.success(request, _("Customer updated successfully.")) + return redirect("customer_list") + else: + form = forms.CustomerForm(initial=customer.additional_info["customer_info"] if "customer_info" in customer.additional_info else {}) + return render(request, "customers/customer_form.html", {"form": form}) @login_required @@ -1116,7 +1192,7 @@ def delete_customer(request, pk): class VendorListView(LoginRequiredMixin, ListView): - model = models.Vendor + model = VendorModel context_object_name = "vendors" paginate_by = 10 template_name = "vendors/vendors_list.html" @@ -1124,11 +1200,12 @@ class VendorListView(LoginRequiredMixin, ListView): def get_queryset(self): dealer = get_user_type(self.request) - return models.Vendor.objects.filter(dealer=dealer) + + return dealer.entity.get_vendors().filter(active=True) class VendorDetailView(LoginRequiredMixin, DetailView): - model = models.Vendor + model = VendorModel template_name = "vendors/view_vendor.html" @@ -1137,14 +1214,17 @@ class VendorCreateView( SuccessMessageMixin, CreateView, ): - model = models.Vendor + model = VendorModel form_class = forms.VendorForm template_name = "vendors/vendor_form.html" success_url = reverse_lazy("vendor_list") success_message = _("Vendor created successfully.") def form_valid(self, form): - form.instance.dealer = get_user_type(self.request) + dealer = get_user_type(self.request) + instance = form.save(commit=False) + instance.entity_model = dealer.entity + instance.save() return super().form_valid(form) @@ -1153,7 +1233,7 @@ class VendorUpdateView( SuccessMessageMixin, UpdateView, ): - model = models.Vendor + model = VendorModel form_class = forms.VendorForm template_name = "vendors/vendor_form.html" success_url = reverse_lazy("vendor_list") @@ -1162,8 +1242,9 @@ class VendorUpdateView( @login_required def delete_vendor(request, pk): - vendor = get_object_or_404(models.Vendor, pk=pk) - vendor.delete() + vendor = get_object_or_404(VendorModel, pk=pk) + vendor.active = False + vendor.save() messages.success(request, _("Vendor deleted successfully.")) return redirect("vendor_list") @@ -1630,14 +1711,14 @@ def custom_bad_request_view(request, exception=None): class OrganizationListView(LoginRequiredMixin, ListView): - model = models.Organization + model = CustomerModel template_name = "organizations/organization_list.html" context_object_name = "organizations" paginate_by = 10 def get_queryset(self): dealer = get_user_type(self.request) - return models.Organization.objects.filter(dealer=dealer).all() + return dealer.entity.get_customers().filter(additional_info__type="organization",active=True).all() class OrganizationDetailView(DetailView): @@ -1646,29 +1727,69 @@ class OrganizationDetailView(DetailView): context_object_name = "organization" -class OrganizationCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): - model = models.Organization - form_class = forms.OrganizationForm - template_name = "organizations/organization_form.html" - success_url = reverse_lazy("organization_list") - success_message = "Organization created successfully." +def OrganizationCreateView(request): + if request.method == "POST": + form = forms.OrganizationForm(request.POST) + + #upload logo + image = request.FILES.get('logo') + file_name = default_storage.save('images/{}'.format(image.name), image) + file_url = default_storage.url(file_name) + + organization_dict = { + x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken" + } + dealer = get_user_type(request) - def form_valid(self, form): - if form.is_valid(): - form.instance.dealer = self.request.user.dealer - form.save() - return super().form_valid(form) - else: - return form.errors + instance = dealer.entity.create_customer( + customer_model_kwargs={ + "customer_name": organization_dict["name"], + "address_1": organization_dict["address"], + "phone": organization_dict["phone_number"], + "email": organization_dict["email"], + } + ) + organization_dict["logo"] = file_url + organization_dict["pk"] = str(instance.pk) + instance.additional_info["organization_info"] = organization_dict + instance.additional_info["type"] = "organization" + instance.save() + messages.success(request, _("Organization created successfully.")) + return redirect("organization_list") + else: + form = forms.OrganizationForm() + return render(request, "organizations/organization_form.html", {"form": form}) + -class OrganizationUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): - model = models.Organization - form_class = forms.OrganizationForm - template_name = "organizations/organization_form.html" - success_url = reverse_lazy("organization_list") - success_message = "Organization updated successfully." +def OrganizationUpdateView(request,pk): + organization = get_object_or_404(CustomerModel, pk=pk) + if request.method == "POST": + form = forms.OrganizationForm(request.POST) + + organization_dict = { + x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken" + } + dealer = get_user_type(request) + instance = dealer.entity.get_customers().get(pk=organization.additional_info['organization_info']['pk']) + instance.customer_name = organization_dict["name"] + instance.address_1 = organization_dict["address"] + instance.phone = organization_dict["phone_number"] + instance.email = organization_dict["email"] + + organization_dict["logo"] = organization.additional_info['organization_info']['logo'] + organization_dict["pk"] = str(instance.pk) + instance.additional_info["organization_info"] = organization_dict + instance.additional_info["type"] = "organization" + instance.save() + messages.success(request, _("Organization created successfully.")) + return redirect("organization_list") + else: + form = forms.OrganizationForm(initial=organization.additional_info["organization_info"] or {}) + form.fields.pop('logo', None) + return render(request, "organizations/organization_form.html", {"form": form}) + class OrganizationDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): model = models.Organization @@ -2228,6 +2349,7 @@ def create_estimate(request): ) form = EstimateModelCreateForm(entity_slug=entity.slug, user_model=entity.admin) + form.fields["customer"].queryset = entity.get_customers().filter(active=True) car_list = models.Car.objects.filter( dealer=dealer, finances__selling_price__gt=0 ).exclude(status="reserved") @@ -2243,7 +2365,7 @@ def create_estimate(request): for x in car_list ], } - print(context) + return render(request, "sales/estimates/estimate_form.html", context) @@ -2269,6 +2391,24 @@ class EstimateDetailView(LoginRequiredMixin, DetailView): return super().get_context_data(**kwargs) +def create_sale_order(request, pk): + estimate = get_object_or_404(EstimateModel, pk=pk) + items = estimate.get_itemtxs_data()[0].all() + + if request.method == "POST": + form = forms.SaleOrderForm(request.POST) + if form.is_valid(): + form.save() + return redirect("success") + else: + form = forms.SaleOrderForm() + return render( + request, + "sales/estimates/sale_order.html", + {"form": form, "estimate": estimate, "items": items}, + ) + + class PaymentRequest(LoginRequiredMixin, DetailView): model = EstimateModel template_name = "sales/estimates/payment_request_detail.html" @@ -2297,7 +2437,7 @@ class EstimatePreviewView(LoginRequiredMixin, DetailView): kwargs["total"] = data["grand_total"] kwargs["discount_amount"] = data["discount_amount"] kwargs["vat"] = data["vat"] - kwargs["car_and_item_info"] = data["car_and_item_info"] + # kwargs["car_and_item_info"] = data["car_and_item_info"] kwargs["additional_services"] = data["additional_services"] return super().get_context_data(**kwargs) @@ -2558,7 +2698,6 @@ class InvoicePreviewView(LoginRequiredMixin, DetailView): def PaymentCreateView(request, pk): - print(pk) invoice = InvoiceModel.objects.filter(pk=pk).first() bill = BillModel.objects.filter(pk=pk).first() model = invoice if invoice else bill @@ -2572,36 +2711,37 @@ def PaymentCreateView(request, pk): invoice = form.cleaned_data.get("invoice") bill = form.cleaned_data.get("bill") payment_method = form.cleaned_data.get("payment_method") - redirect_url = 'invoice_detail' if invoice else 'bill_detail' + redirect_url = "invoice_detail" if invoice else "bill_detail" model = invoice if invoice else bill if not model.is_approved(): model.mark_as_approved(user_model=entity.admin) try: if invoice: - set_invoice_payment(dealer,entity,invoice,amount,payment_method) + set_invoice_payment(dealer, entity, invoice, amount, payment_method) elif bill: - set_bill_payment(dealer,entity,bill,amount,payment_method) + set_bill_payment(dealer, entity, bill, amount, payment_method) messages.success(request, "Payment created successfully!") - return redirect(redirect_url, pk=model.pk) + return redirect(redirect_url, pk=model.pk) except Exception as e: messages.error(request, f"Error creating payment: {str(e)}") else: - messages.error(request, f"Invalid form data: {str(form.errors)}") + messages.error(request, f"Invalid form data: {str(form.errors)}") # return redirect(redirect_url, pk=model.pk) form = forms.PaymentForm() if model: form.initial["amount"] = model.amount_due - model.amount_paid if isinstance(model, InvoiceModel): form.initial["invoice"] = model - form.fields['bill'].widget = HiddenInput() + form.fields["bill"].widget = HiddenInput() elif isinstance(model, BillModel): form.initial["bill"] = model - form.fields['invoice'].widget = HiddenInput() + form.fields["invoice"].widget = HiddenInput() return render( request, "sales/payments/payment_form.html", {"model": model, "form": form} ) + def PaymentListView(request): dealer = get_user_type(request) @@ -2874,7 +3014,7 @@ def fetch_notifications(request): # for notification in notifications # ] # return JsonResponse({"notifications": notifications_data}) - return render(request,'notifications.html',{'notifications_':notifications}) + return render(request, "notifications.html", {"notifications_": notifications}) class ItemServiceCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): @@ -2973,19 +3113,23 @@ class BillListView(ListView): model = BillModel template_name = "ledger/bills/bill_list.html" context_object_name = "bills" + def get_queryset(self): dealer = get_user_type(self.request) qs = dealer.entity.get_bills() return qs + class BillDetailView(LoginRequiredMixin, DetailView): model = BillModel template_name = "ledger/bills/bill_detail.html" context_object_name = "bill" + def get_context_data(self, **kwargs): bill = kwargs.get("object") if bill.get_itemtxs_data(): txs = bill.get_itemtxs_data()[0] + car_and_item_info = [ { "car": models.Car.objects.get(vin=x.item_model.name), @@ -3004,49 +3148,58 @@ class BillDetailView(LoginRequiredMixin, DetailView): * Decimal(x.quantity) for x in txs ) + vat = models.VatRate.objects.filter(is_active=True).first() + if vat: + + grand_total += round(Decimal(grand_total) * Decimal(vat.rate), 2) kwargs["car_and_item_info"] = car_and_item_info kwargs["grand_total"] = grand_total return super().get_context_data(**kwargs) + class InReviewBillView(LoginRequiredMixin, UpdateView): model = BillModel form_class = InReviewBillModelUpdateForm template_name = "ledger/bills/bill_update_form.html" success_url = reverse_lazy("bill_list") - success_message = _("Bill updated successfully.") + success_message = _("Bill updated successfully.") context_object_name = "bill" - + def get_form_kwargs(self): kwargs = super().get_form_kwargs() dealer = get_user_type(self.request) kwargs["entity_model"] = dealer.entity kwargs["user_model"] = dealer.entity.admin return kwargs - + def get_success_url(self): return reverse_lazy("bill_detail", kwargs={"pk": self.kwargs["pk"]}) + def form_valid(self, form): dealer = get_user_type(self.request) form.instance.entity = dealer.entity - self.object.mark_as_review() + self.object.mark_as_review() return super().form_valid(form) + + class ApprovedBillModelView(LoginRequiredMixin, UpdateView): model = BillModel form_class = ApprovedBillModelUpdateForm template_name = "ledger/bills/bill_update_form.html" success_url = reverse_lazy("bill_list") - success_message = _("Bill updated successfully.") + success_message = _("Bill updated successfully.") context_object_name = "bill" - + def get_form_kwargs(self): kwargs = super().get_form_kwargs() dealer = get_user_type(self.request) kwargs["entity_model"] = dealer.entity kwargs["user_model"] = dealer.entity.admin return kwargs - + def get_success_url(self): return reverse_lazy("bill_detail", kwargs={"pk": self.kwargs["pk"]}) + def form_valid(self, form): dealer = get_user_type(self.request) form.instance.entity = dealer.entity @@ -3054,25 +3207,27 @@ class ApprovedBillModelView(LoginRequiredMixin, UpdateView): self.object.mark_as_approved(user_model=dealer.entity.admin) return super().form_valid(form) -def bill_mark_as_approved(request,pk): - bill = get_object_or_404(BillModel,pk=pk) - if request.method == "POST": - dealer = get_user_type(request) + +def bill_mark_as_approved(request, pk): + bill = get_object_or_404(BillModel, pk=pk) + if request.method == "POST": + dealer = get_user_type(request) if bill.is_approved(): messages.error(request, _("Bill is already approved.")) - return redirect("bill_detail",pk=bill.pk) + return redirect("bill_detail", pk=bill.pk) bill.mark_as_approved(user_model=dealer.entity.admin) - bill.save() + bill.save() messages.success(request, _("Bill marked as approved successfully.")) - return redirect("bill_detail",pk=bill.pk) - -def bill_mark_as_paid(request,pk): - bill = get_object_or_404(BillModel,pk=pk) - if request.method == "POST": - dealer = get_user_type(request) + return redirect("bill_detail", pk=bill.pk) + + +def bill_mark_as_paid(request, pk): + bill = get_object_or_404(BillModel, pk=pk) + if request.method == "POST": + dealer = get_user_type(request) if bill.is_paid(): messages.error(request, _("Bill is already paid.")) - return redirect("bill_detail",pk=bill.pk) + return redirect("bill_detail", pk=bill.pk) if bill.amount_due == bill.amount_paid: bill.mark_as_paid(user_model=dealer.entity.admin) bill.save() @@ -3082,16 +3237,18 @@ def bill_mark_as_paid(request,pk): bill.ledger.save() messages.success(request, _("Bill marked as paid successfully.")) else: - messages.error(request, _("Amount paid is not equal to amount due.")) - return redirect("bill_detail",pk=bill.pk) - + messages.error(request, _("Amount paid is not equal to amount due.")) + return redirect("bill_detail", pk=bill.pk) + # def get_context_data(self, **kwargs): # dealer = get_user_type(self.request) # context = super().get_context_data(**kwargs) # context['entity_model'] = dealer.entity # context['user_model'] = dealer.entity.admin - + # return context + + # class BillCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): # model = BillModel # form_class = BillModelCreateForm @@ -3104,6 +3261,7 @@ def bill_mark_as_paid(request,pk): # kwargs["entity_model"] = dealer.entity # return kwargs + # def form_valid(self, form): # dealer = get_user_type(self.request) # form.instance.entity = dealer.entity @@ -3117,7 +3275,7 @@ def bill_create(request): dealer = get_user_type(request) entity = dealer.entity - if request.method == "POST": + if request.method == "POST": data = json.loads(request.body) vendor_id = data.get("vendor") terms = data.get("terms") @@ -3217,14 +3375,16 @@ def bill_create(request): ), } ) - car_list = models.Car.objects.filter(dealer=dealer) + car_list = models.Car.objects.filter( + dealer=dealer, finances__selling_price__gt=0,status="available" + ) context = { "form": form, "items": [ { "car": x, - "product": entity.get_items_all() - .filter(item_role=ItemModel.ITEM_ROLE_PRODUCT, name=x.vin) + "product": entity.get_items_products() + .filter(name=x.vin) .first(), } for x in car_list @@ -3233,6 +3393,7 @@ def bill_create(request): return render(request, "ledger/bills/bill_form.html", context) + def BillDeleteView(request, pk): bill = get_object_or_404(BillModel, pk=pk) bill.delete() diff --git a/static/images/images/IT-Consulting.jpg b/static/images/images/IT-Consulting.jpg new file mode 100644 index 00000000..ad7036fa Binary files /dev/null and b/static/images/images/IT-Consulting.jpg differ diff --git a/static/images/images/cit.jpg b/static/images/images/cit.jpg new file mode 100644 index 00000000..11b9fef2 Binary files /dev/null and b/static/images/images/cit.jpg differ diff --git a/templates/crm/notifications_history.html b/templates/crm/notifications_history.html index bf86da1e..3d4b6bfa 100644 --- a/templates/crm/notifications_history.html +++ b/templates/crm/notifications_history.html @@ -13,7 +13,7 @@

{{ _("System")}}:

{% if not notification.is_read %} -

{{ notification.message }} {{ notification.created|timesince }}

+

{{ notification.message|safe }} {{ notification.created|timesince }}

{% else %}

{{ notification.message|safe }} {{ notification.created|timesince }}

{% endif %} diff --git a/templates/customers/customer_form.html b/templates/customers/customer_form.html index 84dc23dc..507c95d4 100644 --- a/templates/customers/customer_form.html +++ b/templates/customers/customer_form.html @@ -24,93 +24,9 @@
{% csrf_token %} -
-
- - -
{{ form.first_name.errors|striptags }}
-
-
-
-
- - -
{{ form.middle_name.errors }}
-
-
-
-
- - -
{{ form.last_name.errors }}
-
-
-
-
- - - {{ form.title.errors }} -
-
-
-
- - - {{ form.gender.errors }} -
-
-
-
- - - {{ form.dob.errors }} -
-
-
-
- - - {{ form.email.errors }} -
-
-
-
- - - {{ form.national_id.errors }} -
-
-
-
- - - - {{ form.phone_number.errors }} -
-
-
-
- - - {{ form.address.errors }} -
-
-
-
- - {% trans "cancel"|capfirst %} - -
+ {{ form|crispy }} +
+
diff --git a/templates/customers/customer_list.html b/templates/customers/customer_list.html index 7a9d7d12..b39b42bd 100644 --- a/templates/customers/customer_list.html +++ b/templates/customers/customer_list.html @@ -92,9 +92,9 @@ - + {% comment %} {% trans "Yes" %} - + {% endcomment %}
@@ -106,23 +106,23 @@
- {{ customer.email }} - {{ customer.phone_number }} - {{ customer.national_id }} + {{ customer.phone }} + {{ customer.additional_info.customer_info.national_id }} - {{ customer.address }} + {{ customer.address_1 }} {{ customer.created|date }}
diff --git a/templates/customers/view_customer.html b/templates/customers/view_customer.html index b5b7ac58..86a449d7 100644 --- a/templates/customers/view_customer.html +++ b/templates/customers/view_customer.html @@ -27,7 +27,7 @@ + href="{% url 'customer_delete' customer.pk %}"> {% trans 'Yes' %}
diff --git a/templates/inventory/car_detail.html b/templates/inventory/car_detail.html index d6157359..0152deab 100644 --- a/templates/inventory/car_detail.html +++ b/templates/inventory/car_detail.html @@ -98,7 +98,7 @@ {% if car.finances and not car.get_transfer %} {% if car.location %} {% if car.location.is_owner_showroom %} {% trans 'Our Showroom' %} {% else %} {{ car.location.showroom.get_local_name }} {% endif %} - + {% trans "transfer"|capfirst %} {% else %} {% trans "No location available." %} @@ -312,6 +312,11 @@ Approve {% endif %} + + {% if car.get_transfer.status == "draft" %} + Cancel + {% endif %} + diff --git a/templates/inventory/transfer_details.html b/templates/inventory/transfer_details.html index 9dadec25..a3f08e5b 100644 --- a/templates/inventory/transfer_details.html +++ b/templates/inventory/transfer_details.html @@ -8,6 +8,7 @@ {% endblock %} {% block content %} + + +
@@ -37,8 +56,11 @@
- {% trans 'Cancel' %} + + + {% trans 'Return' %}
diff --git a/templates/inventory/transfer_preview.html b/templates/inventory/transfer_preview.html index 6086ffc0..20b3250d 100644 --- a/templates/inventory/transfer_preview.html +++ b/templates/inventory/transfer_preview.html @@ -278,8 +278,8 @@ {{ transfer.car }} {{ transfer.quantity }} - {{ transfer.car.finances.cost_price }} - {{ transfer.total_price }} + {{ transfer.car.finances.selling_price }} + {{ transfer.total_price }} diff --git a/templates/ledger/bills/bill_detail.html b/templates/ledger/bills/bill_detail.html index 98935178..0cfd5f7f 100644 --- a/templates/ledger/bills/bill_detail.html +++ b/templates/ledger/bills/bill_detail.html @@ -226,10 +226,16 @@ {{item.total}} {% endfor %} + + {% trans "Vat Amount" %} + + {{bill.additional_info.car_finance.vat_amount}} + + {% trans "Grand Total" %} - {{grand_total}} + {{bill.additional_info.car_finance.total_vat}} diff --git a/templates/ledger/bills/bill_list.html b/templates/ledger/bills/bill_list.html index df5ce94a..dade723d 100644 --- a/templates/ledger/bills/bill_list.html +++ b/templates/ledger/bills/bill_list.html @@ -75,12 +75,17 @@ {{ bill.bill_number }} - {% if bill.bill.status == 'draft' %} + {% if bill.is_draft %} + {% elif bill.is_review %} + + {% elif bill.is_approved %} + + {% elif bill.is_paid %} + + {% endif %} {{ bill.bill_status }} - - {% endif %} {{bill.vendor.vendor_name}} diff --git a/templates/notifications.html b/templates/notifications.html index d3ee5cbf..7c5adabd 100644 --- a/templates/notifications.html +++ b/templates/notifications.html @@ -1,5 +1,5 @@