diff --git a/car_inventory/__pycache__/settings.cpython-311.pyc b/car_inventory/__pycache__/settings.cpython-311.pyc index 51bac618..17f5e185 100644 Binary files a/car_inventory/__pycache__/settings.cpython-311.pyc and b/car_inventory/__pycache__/settings.cpython-311.pyc differ diff --git a/haikalna.py b/haikalna.py index 5c4c7de7..c8bebb9a 100644 --- a/haikalna.py +++ b/haikalna.py @@ -1533,7 +1533,7 @@ def decode_vin(vin): # VR3USHNLWRJ521303 # KNARH81E8P5194005 # Example usage -vin_number = 'LGWCBE196SB652802' +vin_number = 'LGWEE4A53SK607775' decoded_vin = decode_vin(vin_number) print(decoded_vin) diff --git a/inventory/__pycache__/admin.cpython-311.pyc b/inventory/__pycache__/admin.cpython-311.pyc index 39c54534..63a65d3b 100644 Binary files a/inventory/__pycache__/admin.cpython-311.pyc and b/inventory/__pycache__/admin.cpython-311.pyc differ diff --git a/inventory/__pycache__/forms.cpython-311.pyc b/inventory/__pycache__/forms.cpython-311.pyc index dc803557..76217d41 100644 Binary files a/inventory/__pycache__/forms.cpython-311.pyc and b/inventory/__pycache__/forms.cpython-311.pyc differ diff --git a/inventory/__pycache__/models.cpython-311.pyc b/inventory/__pycache__/models.cpython-311.pyc index ae1bee03..08cd8d07 100644 Binary files a/inventory/__pycache__/models.cpython-311.pyc and b/inventory/__pycache__/models.cpython-311.pyc differ diff --git a/inventory/__pycache__/services.cpython-311.pyc b/inventory/__pycache__/services.cpython-311.pyc index 0268e2cd..2fd2882e 100644 Binary files a/inventory/__pycache__/services.cpython-311.pyc and b/inventory/__pycache__/services.cpython-311.pyc differ diff --git a/inventory/__pycache__/urls.cpython-311.pyc b/inventory/__pycache__/urls.cpython-311.pyc index a98ee4c5..973612a0 100644 Binary files a/inventory/__pycache__/urls.cpython-311.pyc and b/inventory/__pycache__/urls.cpython-311.pyc differ diff --git a/inventory/__pycache__/views.cpython-311.pyc b/inventory/__pycache__/views.cpython-311.pyc index 50f2aed0..4bfb1bf5 100644 Binary files a/inventory/__pycache__/views.cpython-311.pyc and b/inventory/__pycache__/views.cpython-311.pyc differ diff --git a/inventory/admin.py b/inventory/admin.py index 82d72cee..4738f2dd 100644 --- a/inventory/admin.py +++ b/inventory/admin.py @@ -46,7 +46,7 @@ class CarModelAdmin(admin.ModelAdmin): @admin.register(models.CarSerie) class CarSeriesAdmin(admin.ModelAdmin): - list_display = ('name', 'arabic_name', 'id_car_model') + list_display = ('name', 'arabic_name', 'id_car_model', ) search_fields = ('name', 'id_car_model__name') list_filter = ('id_car_model__id_car_make__is_sa_import', 'id_car_model__id_car_make__name',) @@ -55,18 +55,18 @@ class CarSeriesAdmin(admin.ModelAdmin): verbose_name = "Car Series" -# @admin.register(models.CarTrim) -# class CarTrimAdmin(admin.ModelAdmin): -# list_display = ('name', -# 'id_car_serie__name', -# 'id_car_serie__id_car_model__name', -# 'id_car_serie__id_car_model__id_car_make__name') -# search_fields = ('name', 'arabic_name', 'id_car_serie__id_car_model__name') -# list_filter = ('id_car_serie__id_car_model__id_car_make__is_sa_import', -# 'id_car_serie__id_car_model__id_car_make__name') +@admin.register(models.CarTrim) +class CarTrimAdmin(admin.ModelAdmin): + list_display = ('name', + 'id_car_serie__name', + 'id_car_serie__id_car_model__name', + 'id_car_serie__id_car_model__id_car_make__name') + search_fields = ('name', 'arabic_name', 'id_car_serie__id_car_model__name') + list_filter = ('id_car_serie__id_car_model__id_car_make__is_sa_import', + 'id_car_serie__id_car_model__id_car_make__name') -# class Meta: -# verbose_name = "Car Trim" + class Meta: + verbose_name = "Car Trim" @admin.register(models.CarSpecification) diff --git a/inventory/forms.py b/inventory/forms.py index 0c36251e..c47d6b68 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -168,6 +168,7 @@ class QuotationForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.fields['cars'].queryset = Car.objects.filter( finances__isnull=False ).distinct() diff --git a/inventory/management/.DS_Store b/inventory/management/.DS_Store index 07bd3626..faeb005f 100644 Binary files a/inventory/management/.DS_Store and b/inventory/management/.DS_Store differ diff --git a/inventory/management/commands/__pycache__/arabic_names.cpython-311.pyc b/inventory/management/commands/__pycache__/arabic_names.cpython-311.pyc deleted file mode 100644 index b589ecb2..00000000 Binary files a/inventory/management/commands/__pycache__/arabic_names.cpython-311.pyc and /dev/null differ diff --git a/inventory/management/commands/__pycache__/translate.cpython-311.pyc b/inventory/management/commands/__pycache__/translate.cpython-311.pyc index 7e4aa3f2..d0144a1d 100644 Binary files a/inventory/management/commands/__pycache__/translate.cpython-311.pyc and b/inventory/management/commands/__pycache__/translate.cpython-311.pyc differ diff --git a/inventory/management/commands/translate.py b/inventory/management/commands/translate.py new file mode 100644 index 00000000..40a2f7a5 --- /dev/null +++ b/inventory/management/commands/translate.py @@ -0,0 +1,36 @@ +from openai import OpenAI +from django.core.management.base import BaseCommand +from inventory.models import CarSerie +from django.conf import settings + + +class Command(BaseCommand): + help = 'Translates to Arabic and saves them in arabic_name field.' + + def handle(self, *args, **kwargs): + client = OpenAI(api_key=settings.OPENAI_API_KEY) + car_serie = CarSerie.objects.filter(id_car_model__car__id_car_make__is_sa_import=True)[:500] + total = car_serie.count() + print(f'Translating {total} names...') + for index, car_serie in enumerate(car_serie, start=1): + try: + completion = client.chat.completions.create( + model="gpt-4", + messages=[ + { + "role": "system", + "content": "You are a translation assistant that translates English to Arabic." + }, + { + "role": "user", + "content": car_serie.name + } + ], + temperature=0.3, + ) + translation = completion.choices[0].message.content.strip() + car_serie.arabic_name = translation + car_serie.save() + print(f"[{index}/{total}] Translated '{car_serie.name}' to '{translation}'") + except Exception as e: + print(f"Error translating '{car_serie.name}': {e}") diff --git a/inventory/migrations/0005_alter_carfinance_options_alter_carfinance_total.py b/inventory/migrations/0005_alter_carfinance_options_alter_carfinance_total.py new file mode 100644 index 00000000..96e72d4e --- /dev/null +++ b/inventory/migrations/0005_alter_carfinance_options_alter_carfinance_total.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.4 on 2024-12-10 11:45 + +from decimal import Decimal +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0004_remove_carfinance_administration_vat_amount_and_more'), + ] + + operations = [ + migrations.AlterModelOptions( + name='carfinance', + options={'verbose_name': 'Car Financial Details'}, + ), + migrations.AlterField( + model_name='carfinance', + name='total', + field=models.DecimalField(blank=True, decimal_places=2, default=Decimal('0.00'), max_digits=14, null=True), + ), + ] diff --git a/inventory/migrations/0006_alter_car_status.py b/inventory/migrations/0006_alter_car_status.py new file mode 100644 index 00000000..1bf8d892 --- /dev/null +++ b/inventory/migrations/0006_alter_car_status.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.4 on 2024-12-10 14:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0005_alter_carfinance_options_alter_carfinance_total'), + ] + + operations = [ + migrations.AlterField( + model_name='car', + name='status', + field=models.CharField(choices=[('available', 'Available'), ('sold', 'Sold'), ('hold', 'Hold'), ('damaged', 'Damaged'), ('reserved', 'Reserved')], default='available', max_length=10, verbose_name='Status'), + ), + ] diff --git a/inventory/migrations/0007_salequotation_amount_salequotation_dealer_and_more.py b/inventory/migrations/0007_salequotation_amount_salequotation_dealer_and_more.py new file mode 100644 index 00000000..f6bf5769 --- /dev/null +++ b/inventory/migrations/0007_salequotation_amount_salequotation_dealer_and_more.py @@ -0,0 +1,43 @@ +# Generated by Django 5.1.4 on 2024-12-10 22:52 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0006_alter_car_status'), + ] + + operations = [ + migrations.AddField( + model_name='salequotation', + name='amount', + field=models.DecimalField(decimal_places=2, default=100000, max_digits=10, verbose_name='Amount'), + preserve_default=False, + ), + migrations.AddField( + model_name='salequotation', + name='dealer', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='sales', to='inventory.dealer'), + preserve_default=False, + ), + migrations.AddField( + model_name='salequotationcar', + name='dealer', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='sale_cars', to='inventory.dealer', verbose_name='Dealer'), + preserve_default=False, + ), + migrations.AddField( + model_name='salequotationcar', + name='price', + field=models.DecimalField(decimal_places=2, default=12000.0, editable=False, max_digits=10, verbose_name='Price'), + preserve_default=False, + ), + migrations.AddField( + model_name='salequotationcar', + name='quantity', + field=models.PositiveIntegerField(default=1, verbose_name='Quantity'), + ), + ] diff --git a/inventory/migrations/0008_carfinance_vat_amount.py b/inventory/migrations/0008_carfinance_vat_amount.py new file mode 100644 index 00000000..fef601a8 --- /dev/null +++ b/inventory/migrations/0008_carfinance_vat_amount.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.4 on 2024-12-10 23:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0007_salequotation_amount_salequotation_dealer_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='carfinance', + name='vat_amount', + field=models.DecimalField(decimal_places=2, default=2300, editable=False, max_digits=14, verbose_name='Vat Amount'), + preserve_default=False, + ), + ] diff --git a/inventory/migrations/0009_alter_salequotation_amount.py b/inventory/migrations/0009_alter_salequotation_amount.py new file mode 100644 index 00000000..4d40abd7 --- /dev/null +++ b/inventory/migrations/0009_alter_salequotation_amount.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.4 on 2024-12-11 12:09 + +from decimal import Decimal +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0008_carfinance_vat_amount'), + ] + + operations = [ + migrations.AlterField( + model_name='salequotation', + name='amount', + field=models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10, verbose_name='Amount'), + ), + ] diff --git a/inventory/migrations/0010_alter_salequotation_dealer.py b/inventory/migrations/0010_alter_salequotation_dealer.py new file mode 100644 index 00000000..bf35e6cf --- /dev/null +++ b/inventory/migrations/0010_alter_salequotation_dealer.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.4 on 2024-12-11 12:16 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0009_alter_salequotation_amount'), + ] + + operations = [ + migrations.AlterField( + model_name='salequotation', + name='dealer', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sales', to='inventory.dealer'), + ), + ] diff --git a/inventory/migrations/0011_remove_salequotationcar_dealer.py b/inventory/migrations/0011_remove_salequotationcar_dealer.py new file mode 100644 index 00000000..f93be917 --- /dev/null +++ b/inventory/migrations/0011_remove_salequotationcar_dealer.py @@ -0,0 +1,17 @@ +# Generated by Django 5.1.4 on 2024-12-11 12:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0010_alter_salequotation_dealer'), + ] + + operations = [ + migrations.RemoveField( + model_name='salequotationcar', + name='dealer', + ), + ] diff --git a/inventory/migrations/0012_remove_salequotationcar_price.py b/inventory/migrations/0012_remove_salequotationcar_price.py new file mode 100644 index 00000000..9a60dafd --- /dev/null +++ b/inventory/migrations/0012_remove_salequotationcar_price.py @@ -0,0 +1,17 @@ +# Generated by Django 5.1.4 on 2024-12-11 12:18 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0011_remove_salequotationcar_dealer'), + ] + + operations = [ + migrations.RemoveField( + model_name='salequotationcar', + name='price', + ), + ] diff --git a/inventory/migrations/__pycache__/0001_initial.cpython-311.pyc b/inventory/migrations/__pycache__/0001_initial.cpython-311.pyc index d1195297..8b45c8e3 100644 Binary files a/inventory/migrations/__pycache__/0001_initial.cpython-311.pyc and b/inventory/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/inventory/models.py b/inventory/models.py index 6188a371..e3de20bf 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -112,6 +112,7 @@ class CarStatusChoices(models.TextChoices): SOLD = 'sold', _('Sold') HOLD = 'hold', _('Hold') DAMAGED = 'damaged', _('Damaged') + RESERVED = 'reserved', _('Reserved') class CarStockTypeChoices(models.TextChoices): @@ -171,13 +172,13 @@ class Car(models.Model): ) status = models.CharField( max_length=10, - choices=CarStatusChoices.choices, + choices=CarStatusChoices, default=CarStatusChoices.AVAILABLE, verbose_name=_("Status") ) stock_type = models.CharField( max_length=10, - choices=CarStockTypeChoices.choices, + choices=CarStockTypeChoices, default=CarStockTypeChoices.NEW, verbose_name=_("Stock Type") ) @@ -204,6 +205,11 @@ class Car(models.Model): finance = self.finances.first() return finance.selling_price if finance else Decimal('0.00') + @property + def discount_amount(self): + finance = self.finances.first() + return finance.discount_amount if finance else Decimal('0.00') + @property def vat_amount(self): finance = self.finances.first() @@ -238,6 +244,10 @@ class CarFinance(models.Model): decimal_places=2, verbose_name=_("Profit Margin"), editable=False) + vat_amount = models.DecimalField(max_digits=14, + decimal_places=2, + verbose_name=_("Vat Amount"), + editable=False) discount_amount = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Discount Amount"), default=Decimal('0.00')) registration_fee = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Registration Fee"), @@ -249,17 +259,19 @@ class CarFinance(models.Model): custom_card_fee = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Custom Card Fee"), default=Decimal('0.00')) vat_rate = models.DecimalField(max_digits=14, decimal_places=2, default=Decimal('0.15'), verbose_name=_("VAT Rate"),) + total = models.DecimalField(max_digits=14, decimal_places=2, default=Decimal('0.00'), null=True, blank=True) - class Meta: - verbose_name = _("Car Financial Details") + def __str__(self): + return f"{self.selling_price}" def save(self, *args, **kwargs): self.full_clean() try: - self.profit_margin = self.selling_price - self.cost_price - self.discount_amount services = self.administration_fee + self.transportation_fee + self.custom_card_fee price_after_discount = self.selling_price - self.discount_amount total_vat_amount = (price_after_discount + services) * self.vat_rate + self.vat_amount = self.selling_price * self.vat_rate + self.profit_margin = self.selling_price - self.cost_price - self.discount_amount - self.registration_fee self.total = price_after_discount + services + total_vat_amount + self.registration_fee except InvalidOperation as e: @@ -267,10 +279,14 @@ class CarFinance(models.Model): super().save(*args, **kwargs) + class Meta: + verbose_name = _("Car Financial Details") @property def total_vat_amount(self): - return self.total if self.total else Decimal('0.00') + services = self.administration_fee + self.transportation_fee + self.custom_card_fee + price_after_discount = self.selling_price - self.discount_amount + return (price_after_discount + services) * self.vat_rate class ExteriorColors(models.Model, LocalizedNameMixin): @@ -413,6 +429,10 @@ class Customer(models.Model): middle = f" {self.middle_name}" if self.middle_name else '' return f"{self.first_name}{middle} {self.last_name}" + @property + def get_full_name(self): + return f"{self.first_name} {self.middle_name} {self.last_name}" + class SaleQuotation(models.Model): STATUS_CHOICES = [ @@ -420,8 +440,9 @@ class SaleQuotation(models.Model): ("CONFIRMED", _("Confirmed")), ("CANCELED", _("Canceled")), ] - + dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='sales', null=True) customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="quotations", verbose_name=_("Customer")) + amount = models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10, verbose_name=_("Amount")) remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks")) status = models.CharField(max_length=10, choices=STATUS_CHOICES, default="DRAFT", verbose_name=_("Status")) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At")) @@ -457,6 +478,11 @@ class SaleQuotationCar(models.Model): on_delete=models.CASCADE, verbose_name=_("Car") ) + # dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="sale_cars", verbose_name=_("Dealer")) + quantity = models.PositiveIntegerField(default=1, verbose_name=_("Quantity")) + # price = models.DecimalField(decimal_places=2, max_digits=10, verbose_name=_("Price"), editable=False) + + def get_financial_details(self): """Retrieve financial details dynamically from CarFinance.""" @@ -488,128 +514,3 @@ class SalesOrder(models.Model): return f"Sales Order #{self.id} from Quotation #{self.quotation.id}" -# Create Entity -@receiver(post_save, sender=Dealer) -def create_ledger_entity(sender, instance, created, **kwargs): - if created: - entity = EntityModel.objects.create( - name=instance.name, - admin=instance.user, - address_1=instance.address, - fy_start_month=1, - accrual_method=True, - depth=0, - ) - - default_coa = entity.create_chart_of_accounts(assign_as_default=True, - commit=True, - coa_name=_("Chart of Accounts")) - if default_coa: - entity.populate_default_coa(activate_accounts=True, coa_model=default_coa) - print(f"Ledger entity created for Dealer: {instance.name}") - - -# # Create Vendor -@receiver(post_save, sender=Vendor) -def create_ledger_vendor(sender, instance, created, **kwargs): - - if created: - entity = EntityModel.objects.filter(name=instance.dealer.name).first() - - vendor = VendorModel.objects.update_or_create( - entity_model=entity, - vendor_name=instance.name, - vendor_number=instance.crn, - address_1=instance.address, - phone=instance.phone_number, - tax_id_number=instance.vrn, - active=True, - hidden=False, - additional_info={ - "arabic_name": instance.arabic_name, - "contact_person": instance.contact_person, - }, - ) - - print(f"VendorModel created for Vendor: {instance.name}") - - -@receiver(post_save, sender=Customer) -def create_customer(sender, instance, created, **kwargs): - - if created: - entity = EntityModel.objects.filter(name=instance.dealer.name).first() - name = f"{instance.first_name} {instance.middle_name} {instance.last_name}" - - customer = CustomerModel.objects.create( - entity_model=entity, - customer_name=name, - customer_number=instance.national_id, - address_1=instance.address, - phone=instance.phone_number, - email=instance.email, - sales_tax_rate=0.15, - ) - - print(f"Customer created: {name}") - - -# # Create Item -# @receiver(post_save, sender=Car) -# def create_item_model(sender, instance, created, **kwargs): -# item_name = f"{instance.year} - {instance.id_car_make} - {instance.id_car_model} - {instance.id_car_trim}" -# uom_name = _("Car") -# unit_abbr = _("C") -# -# uom, uom_created = UnitOfMeasureModel.objects.get_or_create( -# name=uom_name, -# unit_abbr=unit_abbr -# ) -# -# if uom_created: -# print(f"UOM created: {uom_name}") -# else: -# print(f"Using existing UOM: {uom_name}") -# -# entity = EntityModel.objects.filter(name=instance.dealer.name).first() -# -# inventory_account = AccountModel.objects.first() -# cogs_account = AccountModel.objects.first() -# earnings_account = AccountModel.objects.first() -# -# item = ItemModel.objects.create( -# entity=entity, -# uom=uom, -# name=item_name, -# item_role=ItemModelAbstract.ITEM_ROLE_INVENTORY, -# item_type=ItemModelAbstract.ITEM_TYPE_MATERIAL, -# item_id=instance.vin, -# sold_as_unit=True, -# inventory_received=1.00, -# inventory_received_value=0.00, -# inventory_account=inventory_account, -# for_inventory=True, -# is_product_or_service=True, -# cogs_account=cogs_account, -# earnings_account=earnings_account, -# is_active=True, -# additional_info={ -# "remarks": instance.remarks, -# "status": instance.status, -# "stock_type": instance.stock_type, -# "mileage": instance.mileage, -# }, -# ) -# -# print(f"ItemModel {'created' if created else 'updated'} for Car: {item.name}") -# -# -# # update price - CarFinance -# @receiver(post_save, sender=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, -# ) -# print(f"Inventory item updated with CarFinance data for Car: {instance.car}") diff --git a/inventory/services.py b/inventory/services.py index 07e0eb30..616ef4aa 100644 --- a/inventory/services.py +++ b/inventory/services.py @@ -68,3 +68,11 @@ def calculate_stock_value(): total_value = sum(car.selling_price for car in cars) return total_value + +# from django_ledger.models import EntityModel +# +# def get_purchase_orders(): +# entity = EntityModel.objects.get(name='Marwan2') +# items = entity. +# print(items.values()) +# return items diff --git a/inventory/signals.py b/inventory/signals.py index 23d5e379..a2dd8833 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -1,20 +1,175 @@ -# from django.db.models.signals import post_save, post_delete -# from django.dispatch import receiver -# from . import models -# -# -# @receiver(post_save, sender=models.CarReservation) -# def update_car_status_on_reservation(sender, instance, created, **kwargs): -# if created: -# car = instance.car -# car.status = models.CarStatusChoices.RESERVED -# car.save() -# -# -# @receiver(post_delete, sender=models.CarReservation) -# def update_car_status_on_reservation_delete(sender, instance, **kwargs): -# car = instance.car -# if not car.get_current_reservation(): -# car.status = models.CarStatusChoices.AVAILABLE -# car.save() +from random import randint +from django.db.models.signals import post_save, post_delete +from django.dispatch import receiver +from django_ledger.models import EntityModel, VendorModel, CustomerModel, UnitOfMeasureModel +from django.utils.translation import gettext_lazy as _ +from . import models + + +@receiver(post_save, sender=models.CarReservation) +def update_car_status_on_reservation(sender, instance, created, **kwargs): + if created: + car = instance.car + car.status = models.CarStatusChoices.RESERVED + car.save() + + +@receiver(post_delete, sender=models.CarReservation) +def update_car_status_on_reservation_delete(sender, instance, **kwargs): + car = instance.car + if not car.get_current_reservation(): + car.status = models.CarStatusChoices.AVAILABLE + car.save() + + +# Create Entity +@receiver(post_save, sender=models.Dealer) +def create_ledger_entity(sender, instance, created, **kwargs): + if created: + entity = EntityModel.objects.create( + name=instance.name, + admin=instance.user, + address_1=instance.address, + fy_start_month=1, + accrual_method=True, + depth=0, + ) + + default_coa = entity.create_chart_of_accounts(assign_as_default=True, + commit=True, + coa_name=_("Chart of Accounts")) + # entity.create_account( + # coa_model=coa, + # code=1010, + # role='asset_ca_cash', + # name=_('Cash'), + # balance_type="debit", + # ) + # entity.create_account( + # coa_model=coa, + # code=1200, + # role='asset_ca_inv', + # name=_('Inventory'), + # balance_type="debit", + # active=True) + + + if default_coa: + entity.populate_default_coa(activate_accounts=True, coa_model=default_coa) + + uom_name = _("Unit") + unit_abbr = _("U") + + entity.create_uom(uom_name, unit_abbr) + + print(f"Ledger entity created for Dealer: {instance.name}") + + +# # Create Vendor +@receiver(post_save, sender=models.Vendor) +def create_ledger_vendor(sender, instance, created, **kwargs): + + if created: + entity = EntityModel.objects.filter(name=instance.dealer.name).first() + + entity.create_vendor( + vendor_name=instance.name, + vendor_number=instance.crn, + address_1=instance.address, + phone=instance.phone_number, + tax_id_number=instance.vrn, + active=True, + hidden=False, + additional_info={ + "arabic_name": instance.arabic_name, + "contact_person": instance.contact_person, + }) + + + print(f"VendorModel created for Vendor: {instance.name}") + + +@receiver(post_save, sender=models.Customer) +def create_customer(sender, instance, created, **kwargs): + + if created: + entity = EntityModel.objects.filter(name=instance.dealer.name).first() + name = f"{instance.first_name} {instance.middle_name} {instance.last_name}" + + entity.create_customer( + customer_name=name, + customer_number=instance.national_id, + address_1=instance.address, + phone=instance.phone_number, + email=instance.email, + sales_tax_rate=0.15, + active=True, + hidden=False, + additional_info={} + ) + + print(f"Customer created: {name}") + + +# Create Item +@receiver(post_save, sender=models.Car) +def create_item_model(sender, instance, created, **kwargs): + item_name = f"{instance.year} - {instance.id_car_make} - {instance.id_car_model} - {instance.id_car_trim}" + uom_name = _("Car") + unit_abbr = _("C") + + uom, uom_created = UnitOfMeasureModel.objects.get_or_create( + name=uom_name, + unit_abbr=unit_abbr + ) + + if uom_created: + print(f"UOM created: {uom_name}") + else: + print(f"Using existing UOM: {uom_name}") + + entity = EntityModel.objects.filter(name=instance.dealer.name).first() + + inventory_account = AccountModel.objects.first() + cogs_account = AccountModel.objects.first() + earnings_account = AccountModel.objects.first() + + entity.create_i + + item = ItemModel.objects.create( + entity=entity, + uom=uom, + name=item_name, + item_role=ItemModelAbstract.ITEM_ROLE_INVENTORY, + item_type=ItemModelAbstract.ITEM_TYPE_MATERIAL, + item_id=instance.vin, + sold_as_unit=True, + inventory_received=1.00, + inventory_received_value=0.00, + inventory_account=inventory_account, + for_inventory=True, + is_product_or_service=True, + cogs_account=cogs_account, + earnings_account=earnings_account, + is_active=True, + additional_info={ + "remarks": instance.remarks, + "status": instance.status, + "stock_type": instance.stock_type, + "mileage": instance.mileage, + }, + ) + + print(f"ItemModel {'created' if created else 'updated'} for Car: {item.name}") +# +# +# # update price - CarFinance +# @receiver(post_save, sender=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, +# ) +# print(f"Inventory item updated with CarFinance data for Car: {instance.car}") diff --git a/inventory/views.py b/inventory/views.py index f84de195..72bd1103 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -153,7 +153,7 @@ class AjaxHandlerView(LoginRequiredMixin, View): manufacturer_name = vin_info.Make.strip() model_name = vin_info.Model.strip() year_model = vin_info.ModelYear - if not manufacturer_name or not year_model: + if not manufacturer_name or not model_name or not year_model: raise ValueError('PYVIN returned incomplete data.') elif method_name == 'VIN': manufacturer_name = vin_info.make.strip() @@ -164,15 +164,11 @@ class AjaxHandlerView(LoginRequiredMixin, View): elif method_name == 'ELM': elm_data = vin_info.get('data', {}) manufacturer_name = elm_data.get('maker', '').strip() - print(manufacturer_name) model_name = elm_data.get('model', '').strip() - print(model_name) year_model = elm_data.get('modelYear', '').strip() - print(year_model) if not manufacturer_name or not model_name or not year_model: raise ValueError('ELM returned incomplete data.') - # model_name = normalize_name(model_name_before) decoding_method = method_name print(f"decoded by {method_name}") break @@ -188,19 +184,27 @@ class AjaxHandlerView(LoginRequiredMixin, View): f"VIN decoded using {decoding_method}: Make={manufacturer_name}, Model={model_name}, Year={year_model}" ) regex_make= manufacturer_name.replace(" ", "[- ]?") - car_make = models.CarMake.objects.filter(name__iregex=regex_make).first() + car_make = (models.CarMake.objects + .filter(name__iregex=regex_make, is_sa_import=True) + .first()) if not car_make: return JsonResponse({'success': False, 'error': 'Manufacturer not found in the database.'}, status=404) + vin_data['make_id'] = car_make.id_car_make vin_data['name'] = car_make.name vin_data['arabic_name'] = car_make.arabic_name - # car_model = models.CarModel.objects.filter(id_car_make=car_make.id_car_make, name__contains=model_name).first() regex_pattern = model_name.replace(" ", "[- ]?") - car_model = models.CarModel.objects.filter(id_car_make=car_make.id_car_make, name__iregex=regex_pattern).first() + car_model = (models.CarModel.objects + .filter(id_car_make=car_make.id_car_make, + name__iregex=regex_pattern) + .first() + ) if not car_model: - return JsonResponse({'success': False, 'error': 'Model not found for the given manufacturer.'}, status=404) + return JsonResponse( + {'success': False, + 'error': 'Model not found for the given manufacturer.'}, status=404) vin_data['model_id'] = car_model.id_car_model vin_data['year'] = year_model @@ -208,7 +212,11 @@ class AjaxHandlerView(LoginRequiredMixin, View): def get_models(self, request): make_id = request.GET.get('make_id') - car_models = models.CarModel.objects.filter(id_car_make=make_id).values('id_car_model', 'name', 'arabic_name') + car_models = (models.CarModel.objects + .filter(id_car_make=make_id, + id_car_make__is_sa_import=True) + .values('id_car_model', 'name', 'arabic_name') + ) return JsonResponse(list(car_models), safe=False) def get_series(self, request): @@ -685,6 +693,7 @@ class QuotationCreateView(LoginRequiredMixin, CreateView): template_name = 'sales/quotation_form.html' def form_valid(self, form): + form.instance.dealer = self.request.user.dealer quotation = form.save() selected_cars = form.cleaned_data.get("cars") for car in selected_cars: diff --git a/ledger_testing.py b/ledger_testing.py new file mode 100644 index 00000000..b28b04f6 --- /dev/null +++ b/ledger_testing.py @@ -0,0 +1,3 @@ + + + diff --git a/static/images/logos/suppliers/muhammad-yousef-naghi.png b/static/images/logos/suppliers/muhammad-yousef-naghi.png new file mode 100644 index 00000000..523df613 Binary files /dev/null and b/static/images/logos/suppliers/muhammad-yousef-naghi.png differ diff --git a/static/images/logos/suppliers/unnamed.png b/static/images/logos/suppliers/unnamed.png new file mode 100644 index 00000000..ec3d7053 Binary files /dev/null and b/static/images/logos/suppliers/unnamed.png differ diff --git a/static/images/logos/vendors/Alamjdouie-Hyundai-01_OX3eq7o.png b/static/images/logos/vendors/Alamjdouie-Hyundai-01_OX3eq7o.png new file mode 100644 index 00000000..67e8deb9 Binary files /dev/null and b/static/images/logos/vendors/Alamjdouie-Hyundai-01_OX3eq7o.png differ diff --git a/static/images/logos/vendors/Aljomaih-Automotive-Company-3_3uzBd9i.png b/static/images/logos/vendors/Aljomaih-Automotive-Company-3_3uzBd9i.png new file mode 100644 index 00000000..26e8ac35 Binary files /dev/null and b/static/images/logos/vendors/Aljomaih-Automotive-Company-3_3uzBd9i.png differ diff --git a/static/images/logos/vendors/muhammad-yousef-naghi.png b/static/images/logos/vendors/muhammad-yousef-naghi.png new file mode 100644 index 00000000..523df613 Binary files /dev/null and b/static/images/logos/vendors/muhammad-yousef-naghi.png differ diff --git a/templates/base.html b/templates/base.html index 35106123..ac50be5f 100644 --- a/templates/base.html +++ b/templates/base.html @@ -55,9 +55,9 @@ small, .small {