This commit is contained in:
Marwan Alwali 2025-01-05 16:08:50 +03:00
parent d78643de95
commit d772e1993c
18 changed files with 2893 additions and 3023 deletions

BIN
db.sqlite

Binary file not shown.

View File

@ -0,0 +1,124 @@
# Generated by Django 5.1.4 on 2025-01-05 09:01
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterModelOptions(
name='subscription',
options={'verbose_name': 'Subscription', 'verbose_name_plural': 'Subscriptions'},
),
migrations.AlterModelOptions(
name='subscriptionplan',
options={'verbose_name': 'Subscription Plan', 'verbose_name_plural': 'Subscription Plans'},
),
migrations.AlterModelOptions(
name='subscriptionuser',
options={'verbose_name': 'Subscription User', 'verbose_name_plural': 'Subscription Users'},
),
migrations.RemoveField(
model_name='opportunity',
name='source',
),
migrations.RemoveField(
model_name='subscription',
name='max_users',
),
migrations.AddField(
model_name='subscription',
name='billing_cycle',
field=models.CharField(choices=[('monthly', 'Monthly'), ('annual', 'Annual')], default='monthly', help_text='Billing cycle for the subscription', max_length=10),
),
migrations.AddField(
model_name='subscription',
name='last_payment_date',
field=models.DateField(blank=True, help_text='Date of the last payment made', null=True),
),
migrations.AddField(
model_name='subscription',
name='next_payment_date',
field=models.DateField(blank=True, help_text='Date of the next payment due', null=True),
),
migrations.AddField(
model_name='subscriptionplan',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='subscriptionplan',
name='custom_features',
field=models.JSONField(blank=True, help_text='Additional features specific to this plan', null=True),
),
migrations.AddField(
model_name='subscriptionplan',
name='max_inventory_size',
field=models.PositiveIntegerField(default=50, help_text='Maximum number of cars in inventory'),
),
migrations.AddField(
model_name='subscriptionplan',
name='support_level',
field=models.CharField(choices=[('basic', 'Basic Support'), ('priority', 'Priority Support'), ('dedicated', 'Dedicated Support')], default='basic', help_text='Level of support provided', max_length=50),
),
migrations.AddField(
model_name='subscriptionplan',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
migrations.AlterField(
model_name='notes',
name='created_by',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.DO_NOTHING, related_name='notes_created', to=settings.AUTH_USER_MODEL),
preserve_default=False,
),
migrations.AlterField(
model_name='opportunity',
name='deal_status',
field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('canceled', 'Canceled'), ('completed', 'Completed')], default='new', max_length=20, verbose_name='Deal Status'),
),
migrations.AlterField(
model_name='opportunity',
name='priority',
field=models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='low', max_length=10, verbose_name='Priority'),
),
migrations.AlterField(
model_name='staff',
name='staff_type',
field=models.CharField(choices=[('manager', 'Manager'), ('inventory', 'Inventory'), ('accountant', 'Accountant'), ('sales', 'Sales'), ('coordinator', 'Coordinator'), ('receptionist', 'Receptionist'), ('agent', 'Agent')], max_length=255, verbose_name='Staff Type'),
),
migrations.AlterField(
model_name='subscription',
name='end_date',
field=models.DateField(help_text='Date when the subscription ends'),
),
migrations.AlterField(
model_name='subscription',
name='plan',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subscriptions', to='inventory.subscriptionplan'),
),
migrations.AlterField(
model_name='subscription',
name='start_date',
field=models.DateField(help_text='Date when the subscription starts'),
),
migrations.AlterField(
model_name='subscriptionplan',
name='max_users',
field=models.PositiveIntegerField(default=1, help_text='Maximum number of users allowed'),
),
migrations.AlterField(
model_name='subscriptionplan',
name='name',
field=models.CharField(help_text='Name of the subscription plan', max_length=100, unique=True),
),
]

View File

@ -6,6 +6,7 @@ from django_ledger.io import roles
from django_ledger.models import EntityModel,AccountModel,ItemModel,ItemModelAbstract,UnitOfMeasureModel, VendorModel
from . import models
from .models import OpportunityLog
from .utils import get_dealer_from_instance
User = get_user_model()
@ -48,20 +49,16 @@ def create_car_location(sender, instance, created, **kwargs):
"""
Signal to create or update the car's location when a car instance is saved.
"""
try:
if created:
if instance.user.dealer is None:
raise ValueError(f"Cannot create CarLocation for car {instance.vin}: dealer is missing.")
if created:
print(instance)
models.CarLocation.objects.create(
car=instance,
owner=instance.dealer,
showroom=instance.dealer,
description=f"Initial location set for car {instance.vin}."
)
print("Car Location created")
models.CarLocation.objects.create(
car=instance,
owner=instance.user.dealer,
showroom=instance.user.dealer,
description=f"Initial location set for car {instance.vin}."
)
print("Car Location created")
except Exception as e:
print(f"Failed to create CarLocation for car {instance.vin}: {e}")
@receiver(post_save, sender=models.CarReservation)
@ -244,73 +241,32 @@ def create_customer(sender, instance, created, **kwargs):
# 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")
if created:
product_name = f"{instance.year}-{instance.id_car_make}-{instance.id_car_model}_{instance.vin}"
entity = EntityModel.objects.get(name=instance.dealer.name)
uom = entity.get_uom_all().first()
# uom, uom_created = UnitOfMeasureModel.objects.get_or_create(
# name=uom_name,
# unit_abbr=unit_abbr
# )
entity.create_item_product(
name=product_name,
item_type='product', # Type of item (e.g., 'inventory', 'service', etc.)
for_inventory=True, # Mark this product as inventory
for_purchase=False, # Not for purchase (adjust as needed)
for_sale=True, # Mark this product as available for sale
active=True, # Set the product as active
# Add any additional fields required by your ItemModel
)
print(f"Item created: {product_name}")
# if uom_created:
# print(f"UOM created: {uom_name}")
# else:
# print(f"Using existing UOM: {uom_name}")
entity = EntityModel.objects.first()
uom = entity.get_uom_all().first()
coa = entity.get_default_coa()
entity.create_item_product(
name=f"{instance.vin}",
item_type=ItemModel.ITEM_TYPE_OTHER,
uom_model=uom,
coa_model=coa
)
entity.create_item_inventory(
name=f"{instance.vin}",
item_type=ItemModel.ITEM_TYPE_OTHER,
uom_model=uom,
coa_model=coa
)
# 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}")
@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,
)
print(f"Inventory item updated with CarFinance data for Car: {instance.car}")

View File

@ -61,8 +61,15 @@ def send_email(from_, to_, subject, message):
def get_user_type(request):
dealer = ""
if hasattr(request.user, 'dealer'):
dealer = request.user.dealer
elif hasattr(request.user, 'staff'):
dealer = request.user.staff.dealer
return dealer
return dealer
def get_dealer_from_instance(instance):
if instance.dealer.staff:
return instance.dealer
else:
return instance.dealer

View File

@ -1,3 +1,4 @@
from django.core.exceptions import ObjectDoesNotExist
from django.core.paginator import Paginator
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
@ -224,7 +225,8 @@ class CarCreateView(LoginRequiredMixin, CreateView):
return reverse("inventory_stats")
def form_valid(self, form):
form.instance.dealer = self.request.user.dealer
dealer = get_user_type(self.request)
form.instance.dealer = dealer
form.save()
messages.success(self.request, "Car saved successfully.")
return super().form_valid(form)
@ -248,10 +250,13 @@ class AjaxHandlerView(LoginRequiredMixin, View):
def decode_vin(self, request):
vin_no = request.GET.get("vin_no")
car_existed = models.Car.objects.filter(vin=vin_no).exists()
if car_existed:
return JsonResponse({"error": _("VIN number exists")}, status=400)
if not vin_no or len(vin_no.strip()) != 17:
return JsonResponse(
{"success": False, "error": "Invalid VIN number provided."}, status=400
)
return JsonResponse({"success": False, "error": "Invalid VIN number provided."}, status=400)
vin_no = vin_no.strip()
vin_data = {}

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -235,7 +235,7 @@
<tbody>
{% for reservation in car.reservations.all %}
<tr>
<td>{{ reservation.reserved_by.dealer.staff }}</td>
<td>{{ reservation.reserved_by.dealer }}</td>
<td>{{ reservation.reserved_until }}</td>
<td>
{% if reservation.is_active %}

View File

@ -362,7 +362,7 @@
async function decodeVin() {
const vinNumber = vinInput.value.trim();
if (vinNumber.length !== 17) {
notify("error", "{% trans 'Please enter a valid VIN.' %}");
Swal.fire("error", "{% trans 'Please enter a valid VIN.' %}");
/*alert("{% trans 'Please enter a valid VIN.' %}");*/
return;
}
@ -380,12 +380,12 @@
await updateFields(data.data);
} else {
hideLoading();
notify("error", data.error);
Swal.fire("{% trans 'error' %}", data.error);
}
} catch (error) {
console.error("Error decoding VIN:", error);
hideLoading();
notify("error", "{% trans 'An error occurred while decoding the VIN.' %}");
Swal.fire("error", "{% trans 'An error occurred while decoding the VIN.' %}");
}
}

View File

@ -29,7 +29,11 @@
<div class="row align-items-center g-3 g-sm-5 text-start text-sm-start">
<div class="col-12 col-sm-auto ">
<div class="avatar avatar-3xl avatar-bordered mb-3">
{% if cars.first.id_car_make.logo %}
<img class="rounded-circle" src="{{ cars.first.id_car_make.logo.url }}" alt="">
{% else %}
<img class="rounded-circle" src="{% static 'images/logos/car_make/sedan.svg' %}" alt="">
{% endif %}
</div>
</div>
<div class="col-12 col-sm-auto flex-1">

View File

@ -1,11 +1,11 @@
{% extends "base.html" %}
{% load i18n static %}
{% block title %}{{ _("Tranactions") }}{% endblock title %}
{% block title %}{{ _("Transactions") }}{% endblock title %}
{% block content %}
<div class="container mt-4">
<h3 class="text-center">{% trans "Tranactions" %}</h3>
<h3 class="text-center">{% trans "Transactions" %}</h3>
<div class="mx-n4 px-4 mx-lg-n6 px-lg-6 bg-body-emphasis pt-7 border-y">
<div class="table-responsive mx-n1 px-1 scrollbar">
@ -34,7 +34,7 @@
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center">{% trans "No Tranactions Found" %}</td>
<td colspan="6" class="text-center">{% trans "No Transactions Found" %}</td>
</tr>
{% endfor %}
</tbody>