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 {