update
This commit is contained in:
parent
d78643de95
commit
d772e1993c
Binary file not shown.
Binary file not shown.
Binary file not shown.
124
inventory/migrations/0002_alter_subscription_options_and_more.py
Normal file
124
inventory/migrations/0002_alter_subscription_options_and_more.py
Normal 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),
|
||||
),
|
||||
]
|
||||
@ -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}")
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
BIN
static/images/.DS_Store
vendored
BIN
static/images/.DS_Store
vendored
Binary file not shown.
BIN
static/images/car_make/maserati.png
Normal file
BIN
static/images/car_make/maserati.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
static/images/logos/.DS_Store
vendored
BIN
static/images/logos/.DS_Store
vendored
Binary file not shown.
BIN
static/images/logos/car_make/maserati.png
Normal file
BIN
static/images/logos/car_make/maserati.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
@ -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 %}
|
||||
|
||||
@ -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.' %}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user