alot of updates

This commit is contained in:
Faheed 2025-09-15 17:28:01 +03:00
parent 5ce24b9bb3
commit 7a2e5af9b7
4 changed files with 58 additions and 133 deletions

View File

@ -6,7 +6,9 @@ from django.contrib.auth.models import Permission
from inventory.validators import SaudiPhoneNumberValidator from inventory.validators import SaudiPhoneNumberValidator
from decimal import Decimal from decimal import Decimal
from django.urls import reverse from django.urls import reverse
from django.utils.text import slugify
# from django.utils.text import slugify
from slugify import slugify
from django.utils import timezone from django.utils import timezone
from django.core.validators import MinValueValidator,MaxValueValidator from django.core.validators import MinValueValidator,MaxValueValidator
import hashlib import hashlib
@ -59,10 +61,11 @@ from encrypted_model_fields.fields import (
# from simple_history.models import HistoricalRecords # from simple_history.models import HistoricalRecords
from plans.models import Invoice from plans.models import Invoice
from django_extensions.db.fields import RandomCharField
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
class Base(models.Model): class Base(models.Model):
id = models.UUIDField( id = models.UUIDField(
unique=True, unique=True,
@ -644,7 +647,7 @@ class Car(Base):
null=True, null=True,
blank=True, blank=True,
) )
vin = models.CharField(max_length=17, unique=True, verbose_name=_("VIN")) vin = models.CharField(max_length=17, verbose_name=_("VIN"))
dealer = models.ForeignKey( dealer = models.ForeignKey(
"Dealer", models.DO_NOTHING, related_name="cars", verbose_name=_("Dealer") "Dealer", models.DO_NOTHING, related_name="cars", verbose_name=_("Dealer")
) )
@ -771,6 +774,11 @@ class Car(Base):
condition=Q(status=CarStatusChoices.AVAILABLE), condition=Q(status=CarStatusChoices.AVAILABLE),
), ),
] ]
constraints = [
models.UniqueConstraint(
fields=["dealer", "vin"], name="unique_vin_per_dealer"
)
]
def __str__(self): def __str__(self):
make = self.id_car_make.name if self.id_car_make else "Unknown Make" make = self.id_car_make.name if self.id_car_make else "Unknown Make"
@ -1519,25 +1527,11 @@ class Staff(models.Model):
active = models.BooleanField(default=True, verbose_name=_("Active")) active = models.BooleanField(default=True, verbose_name=_("Active"))
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
slug = models.SlugField( # slug = models.SlugField(
max_length=255, unique=True, editable=False, null=True, blank=True # max_length=255, unique=True, editable=False, null=True, blank=True,allow_unicode=True
) # )
slug = RandomCharField(length=8, unique=True)
def save(self, *args, **kwargs):
if not self.slug:
base_slug = slugify(f"{self.first_name}-{self.last_name}")
self.slug = base_slug
counter = 1
while (
self.__class__.objects.filter(slug=self.slug)
.exclude(pk=self.pk)
.exists()
):
self.slug = f"{base_slug}-{counter}"
counter += 1
super().save(*args, **kwargs)
objects = StaffUserManager() objects = StaffUserManager()
@property @property
@ -1760,24 +1754,7 @@ class Customer(models.Model):
) )
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
slug = models.SlugField( slug = RandomCharField(length=8, unique=True)
max_length=255, unique=True, editable=False, null=True, blank=True
)
def save(self, *args, **kwargs):
if not self.slug:
base_slug = slugify(f"{self.last_name} {self.first_name}")
self.slug = base_slug
counter = 1
while (
self.__class__.objects.filter(slug=self.slug)
.exclude(pk=self.pk)
.exists()
):
self.slug = f"{base_slug}-{counter}"
counter += 1
super().save(*args, **kwargs)
class Meta: class Meta:
constraints = [ constraints = [
@ -1932,24 +1909,7 @@ class Organization(models.Model, LocalizedNameMixin):
active = models.BooleanField(default=True, verbose_name=_("Active")) active = models.BooleanField(default=True, verbose_name=_("Active"))
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
slug = models.SlugField( slug = RandomCharField(length=8, unique=True)
max_length=255, unique=True, editable=False, null=True, blank=True
)
def save(self, *args, **kwargs):
if not self.slug:
base_slug = slugify(f"{self.name}")
self.slug = base_slug
counter = 1
while (
self.__class__.objects.filter(slug=self.slug)
.exclude(pk=self.pk)
.exists()
):
self.slug = f"{base_slug}-{counter}"
counter += 1
super().save(*args, **kwargs)
class Meta: class Meta:
verbose_name = _("Organization") verbose_name = _("Organization")
@ -2151,7 +2111,7 @@ class Lead(models.Model):
auto_now_add=True, verbose_name=_("Created"), db_index=True auto_now_add=True, verbose_name=_("Created"), db_index=True
) )
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
slug = models.SlugField(unique=True, blank=True, null=True) slug = RandomCharField(length=8, unique=True)
class Meta: class Meta:
verbose_name = _("Lead") verbose_name = _("Lead")
@ -2272,21 +2232,6 @@ class Lead(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse("lead_detail", args=[self.dealer.slug, self.slug]) return reverse("lead_detail", args=[self.dealer.slug, self.slug])
def save(self, *args, **kwargs):
if not self.slug:
base_slug = slugify(f"{self.last_name} {self.first_name}")
self.slug = base_slug
counter = 1
while (
self.__class__.objects.filter(slug=self.slug)
.exclude(pk=self.pk)
.exists()
):
self.slug = f"{base_slug}-{counter}"
counter += 1
super().save(*args, **kwargs)
class Schedule(models.Model): class Schedule(models.Model):
PURPOSE_CHOICES = [ PURPOSE_CHOICES = [
@ -2485,13 +2430,8 @@ class Opportunity(models.Model):
null=True, null=True,
blank=True, blank=True,
) )
slug = models.SlugField( slug = RandomCharField(length=8, unique=True)
null=True,
blank=True,
unique=True,
verbose_name=_("Slug"),
help_text=_("Unique slug for the opportunity."),
)
loss_reason = models.CharField(max_length=255, blank=True, null=True) loss_reason = models.CharField(max_length=255, blank=True, null=True)
def get_notes(self): def get_notes(self):
@ -2534,29 +2474,6 @@ class Opportunity(models.Model):
objects = objects.union(lead_objects).order_by("-created") objects = objects.union(lead_objects).order_by("-created")
return objects return objects
def save(self, *args, **kwargs):
opportinity_for = ""
if self.lead.lead_type == "customer":
self.customer = self.lead.customer
opportinity_for = self.customer.first_name + " " + self.customer.last_name
elif self.lead.lead_type == "organization":
self.organization = self.lead.organization
opportinity_for = self.organization.name
if not self.slug:
base_slug = slugify(f"opportinity {opportinity_for}")
self.slug = base_slug
counter = 1
while (
self.__class__.objects.filter(slug=self.slug)
.exclude(pk=self.pk)
.exists()
):
self.slug = f"{base_slug}-{counter}"
counter += 1
super().save(*args, **kwargs)
class Meta: class Meta:
verbose_name = _("Opportunity") verbose_name = _("Opportunity")
verbose_name_plural = _("Opportunities") verbose_name_plural = _("Opportunities")
@ -2809,30 +2726,13 @@ class Vendor(models.Model, LocalizedNameMixin):
) )
active = models.BooleanField(default=True, verbose_name=_("Active")) active = models.BooleanField(default=True, verbose_name=_("Active"))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At")) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
slug = models.SlugField( slug = RandomCharField(length=8, unique=True)
max_length=255, unique=True, verbose_name=_("Slug"), null=True, blank=True
)
def get_absolute_url(self): def get_absolute_url(self):
return reverse( return reverse(
"vendor_detail", kwargs={"dealer_slug": self.dealer.slug, "slug": self.slug} "vendor_detail", kwargs={"dealer_slug": self.dealer.slug, "slug": self.slug}
) )
def save(self, *args, **kwargs):
if not self.slug:
base_slug = slugify(self.name)
self.slug = base_slug
counter = 1
while (
self.__class__.objects.filter(slug=self.slug)
.exclude(pk=self.pk)
.exists()
):
self.slug = f"{base_slug}-{counter}"
counter += 1
super().save(*args, **kwargs)
class Meta: class Meta:
verbose_name = _("Vendor") verbose_name = _("Vendor")
verbose_name_plural = _("Vendors") verbose_name_plural = _("Vendors")

View File

@ -25,7 +25,9 @@ from django_ledger.models import (
InvoiceModel, InvoiceModel,
BillModel, BillModel,
VendorModel, VendorModel,
AccountModel AccountModel,
EntityModel,
ChartOfAccountModel
) )
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django_ledger.models.items import ItemModel from django_ledger.models.items import ItemModel
@ -1594,10 +1596,13 @@ def _post_sale_and_cogs(invoice, dealer):
1) Cash / A-R / VAT / Revenue journal 1) Cash / A-R / VAT / Revenue journal
2) COGS / Inventory journal 2) COGS / Inventory journal
""" """
entity = invoice.ledger.entity entity:EntityModel = invoice.ledger.entity
# calc = CarFinanceCalculator(invoice) # calc = CarFinanceCalculator(invoice)
data = get_finance_data(invoice, dealer) data = get_finance_data(invoice, dealer)
car = data.get("car") car = data.get("car")
coa:ChartOfAccountModel = entity.get_default_coa()
# cash_acc = ( # cash_acc = (
# entity.get_default_coa_accounts() # entity.get_default_coa_accounts()
# .filter(role_default=True, role=roles.ASSET_CA_CASH) # .filter(role_default=True, role=roles.ASSET_CA_CASH)
@ -1611,6 +1616,25 @@ def _post_sale_and_cogs(invoice, dealer):
add_rev = dealer.settings.invoice_additional_services_account add_rev = dealer.settings.invoice_additional_services_account
if not add_rev:
try:
add_rev = entity.get_default_coa_accounts().filter(name="After-Sales Services", active=True).first()
if not add_rev:
add_rev = coa.create_account(
code="4020",
name="After-Sales Services",
role=roles.INCOME_OPERATIONAL,
balance_type=roles.CREDIT,
active=True,
)
add_rev.role_default = False
add_rev.save(update_fields=['role_default'])
dealer.settings.invoice_additional_services_account = add_rev
dealer.settings.save()
except Exception as e:
logger.error(f"error find or create additional services account {e}")
if car.get_additional_services_amount > 0 and not add_rev:
raise Exception("additional services exist but not account found,please create account for the additional services and set as default in the settings")
cogs_acc = dealer.settings.invoice_cost_of_good_sold_account or entity.get_default_coa_accounts().filter(role_default=True, role=roles.COGS).first() cogs_acc = dealer.settings.invoice_cost_of_good_sold_account or entity.get_default_coa_accounts().filter(role_default=True, role=roles.COGS).first()
inv_acc = dealer.settings.invoice_inventory_account or entity.get_default_coa_accounts().filter(role_default=True, role=roles.ASSET_CA_INVENTORY).first() inv_acc = dealer.settings.invoice_inventory_account or entity.get_default_coa_accounts().filter(role_default=True, role=roles.ASSET_CA_INVENTORY).first()
@ -1672,7 +1696,7 @@ def _post_sale_and_cogs(invoice, dealer):
if car.get_additional_services_amount > 0: if car.get_additional_services_amount > 0:
# Cr Sales Additional Services # Cr Sales Additional Services
if not add_rev: if not add_rev:
logger.warning(f"Additional Services account not set for dealer {dealer}. Skipping additional services revenue entry.") logger.warning(f"Additional Services account not set for dealer {dealer}. Skipping additional services revenue entry.")
else: else:
TransactionModel.objects.create( TransactionModel.objects.create(
journal_entry=je_sale, journal_entry=je_sale,

View File

@ -9875,13 +9875,14 @@ def ledger_unpost_all_journals(request, dealer_slug, entity_slug, pk):
def pricing_page(request, dealer_slug): def pricing_page(request, dealer_slug):
dealer=get_object_or_404(models.Dealer, slug=dealer_slug) dealer=get_object_or_404(models.Dealer, slug=dealer_slug)
vat = models.VatRate.objects.filter(dealer=dealer).first() vat = models.VatRate.objects.filter(dealer=dealer).first()
if not hasattr(dealer.user,'userplan') or dealer.is_plan_expired: now = datetime.now().date() + timedelta(days=15)
if not hasattr(dealer.user,'userplan') or dealer.is_plan_expired or dealer.user.userplan.expire <= now:
plan_list = PlanPricing.objects.annotate( plan_list = PlanPricing.objects.annotate(
price_with_tax=Round(F('price') * vat.rate + F('price'), 2) price_with_tax=Round(F('price') * vat.rate + F('price'), 2)
).all() ).all()
form = forms.PaymentPlanForm() form = forms.PaymentPlanForm()
return render(request, "pricing_page.html", {"plan_list": plan_list, "form": form}) return render(request, "pricing_page.html", {"plan_list": plan_list, "form": form})
else: else:
messages.info(request,_("You already have an plan!!")) messages.info(request,_("You already have an plan!!"))
return redirect('home',dealer_slug=dealer_slug) return redirect('home',dealer_slug=dealer_slug)

View File

@ -6,19 +6,19 @@
<div class="row mb-4"> <div class="row mb-4">
<div class="col-12"> <div class="col-12">
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-header bg-white border-bottom-0 py-3"> <div class="card-header border-bottom-0 py-3">
<h5 class="mb-0 fw-semibold">{% trans "Your Account" %}</h5> <h5 class="mb-0 fw-semibold">{% trans "Your Account" %}</h5>
</div> </div>
<div class="card-body pt-0"> <div class="card-body pt-0">
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6 col-lg-3"> <div class="col-md-6 col-lg-3">
<div class="d-flex flex-column bg-light rounded-3 p-3 h-100"> <div class="d-flex flex-column bg-gray-200 rounded-3 p-3 h-100">
<span class="text-muted small">{% trans "Account" %}</span> <span class="text-muted small">{% trans "Account" %}</span>
<span class="fw-semibold">{{ user.dealer.get_local_name }}</span> <span class="fw-semibold">{{ user.dealer.get_local_name }}</span>
</div> </div>
</div> </div>
<div class="col-md-6 col-lg-3"> <div class="col-md-6 col-lg-3">
<div class="d-flex flex-column bg-light rounded-3 p-3 h-100"> <div class="d-flex flex-column bg-gray-200 rounded-3 p-3 h-100">
<span class="text-muted small">{% trans "Status" %}</span> <span class="text-muted small">{% trans "Status" %}</span>
{% if userplan.active %} {% if userplan.active %}
<span class="badge bg-success bg-opacity-10 text-success">{% trans "Active" %}</span> <span class="badge bg-success bg-opacity-10 text-success">{% trans "Active" %}</span>
@ -28,13 +28,13 @@
</div> </div>
</div> </div>
<div class="col-md-6 col-lg-3"> <div class="col-md-6 col-lg-3">
<div class="d-flex flex-column bg-light rounded-3 p-3 h-100"> <div class="d-flex flex-column bg-gray-200 rounded-3 p-3 h-100">
<span class="text-muted small">{% trans "Active until" %}</span> <span class="text-muted small">{% trans "Active until" %}</span>
<span class="fw-semibold">{{ userplan.expire }}</span> <span class="fw-semibold">{{ userplan.expire }}</span>
</div> </div>
</div> </div>
<div class="col-md-6 col-lg-3"> <div class="col-md-6 col-lg-3">
<div class="d-flex flex-column bg-light rounded-3 p-3 h-100"> <div class="d-flex flex-column bg-gray-200 rounded-3 p-3 h-100">
<span class="text-muted small">{% trans "Plan" %}</span> <span class="text-muted small">{% trans "Plan" %}</span>
<div class="d-flex align-items-center justify-content-between"> <div class="d-flex align-items-center justify-content-between">
<span class="fw-semibold">{{ userplan.plan }}</span> <span class="fw-semibold">{{ userplan.plan }}</span>
@ -52,7 +52,7 @@
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-header bg-white border-bottom-0 py-3"> <div class="card-header border-bottom-0 py-3">
<h5 class="mb-0 fw-semibold">{% trans "Plan Details" %}</h5> <h5 class="mb-0 fw-semibold">{% trans "Plan Details" %}</h5>
</div> </div>
<div class="card-body">{% include "plans/plan_table.html" %}</div> <div class="card-body">{% include "plans/plan_table.html" %}</div>