This commit is contained in:
Marwan Alwali 2024-12-29 20:20:42 +03:00
parent 9d0485e1dd
commit cced58632f
17 changed files with 602 additions and 314 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
db.sqlite

Binary file not shown.

View File

@ -3,6 +3,7 @@ from . import models
admin.site.register(models.Dealer)
admin.site.register(models.Staff)
admin.site.register(models.Vendor)
admin.site.register(models.Customer)
admin.site.register(models.SaleQuotation)

View File

@ -21,7 +21,8 @@ from .models import (
Representative,
Payment,
SaleQuotationCar,
AdditionalServices
AdditionalServices,
Staff
)
from django_ledger.models import ItemModel
@ -41,10 +42,10 @@ class PaymentForm(forms.ModelForm):
model = Payment
fields = ['amount','payment_method', 'reference_number']
class UserForm(forms.ModelForm):
class Meta:
model = Dealer
fields = ['name', 'arabic_name', 'phone_number', 'address','dealer_type']
# class UserForm(forms.ModelForm):
# class Meta:
# model = Staff
# fields = ['name', 'arabic_name', 'phone_number', 'address','staff_type']
# Dealer Form
class DealerForm(forms.ModelForm):
@ -395,8 +396,10 @@ class WizardForm3(forms.Form):
password = cleaned_data.get("password")
confirm_password = cleaned_data.get("confirm_password")
if password and confirm_password and password != confirm_password:
if password != confirm_password:
raise forms.ValidationError("Passwords do not match.")
else:
return cleaned_data
class ItemForm(forms.Form):

View File

@ -0,0 +1,55 @@
# Generated by Django 5.1.4 on 2024-12-29 15:46
import django.db.models.deletion
import inventory.mixins
import phonenumber_field.modelfields
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0007_vendor_created_at'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterModelOptions(
name='dealer',
options={'verbose_name': 'Dealer', 'verbose_name_plural': 'Dealers'},
),
migrations.RemoveField(
model_name='dealer',
name='dealer_type',
),
migrations.RemoveField(
model_name='dealer',
name='parent_dealer',
),
migrations.AddField(
model_name='dealer',
name='updated_at',
field=models.DateTimeField(auto_now=True, verbose_name='Updated At'),
),
migrations.CreateModel(
name='Staff',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')),
('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')),
('staff_type', models.CharField(choices=[('manager', 'Manager'), ('inventory', 'Inventory'), ('accountant', 'Accountant'), ('sales', 'Sales'), ('receptionist', 'Receptionist'), ('technician', 'Technician'), ('driver', 'Driver')], max_length=255, verbose_name='Staff Type')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
('dealer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.dealer')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Staff',
'verbose_name_plural': 'Staff',
'permissions': [],
},
bases=(models.Model, inventory.mixins.LocalizedNameMixin),
),
]

View File

@ -33,7 +33,7 @@ from django_ledger.models import EntityModel
class DealerUserManager(UserManager):
def create_user_with_dealer(self, email, password, dealer_name, arabic_name, crn, vrn, address, **extra_fields):
user = self.create_user(email=email, password=password, **extra_fields)
Dealer.objects.create(user=user, name=dealer_name, )
Dealer.objects.create(user=user, name=dealer_name,arabic_name=arabic_name, crn=crn, vrn=vrn, address=address, **extra_fields)
return user
@ -533,16 +533,17 @@ class Dealer(models.Model, LocalizedNameMixin):
verbose_name=_("Logo"))
entity = models.ForeignKey(EntityModel, on_delete=models.SET_NULL, null=True, blank=True)
joined_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Joined At"))
parent_dealer = models.ForeignKey( "self",
on_delete=models.SET_NULL,
blank=True,
null=True,
verbose_name=_("Parent Dealer"),
related_name="sub_dealers",)
dealer_type = models.CharField(max_length=255,
choices=DEALER_TYPES.choices,
verbose_name=_("Dealer Type"),
default=DEALER_TYPES.OWNER,)
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
# parent_dealer = models.ForeignKey( "self",
# on_delete=models.SET_NULL,
# blank=True,
# null=True,
# verbose_name=_("Parent Dealer"),
# related_name="sub_dealers",)
# dealer_type = models.CharField(max_length=255,
# choices=DEALER_TYPES.choices,
# verbose_name=_("Dealer Type"),
# default=DEALER_TYPES.OWNER,)
objects = DealerUserManager()
@property
@ -567,25 +568,25 @@ class Dealer(models.Model, LocalizedNameMixin):
class Meta:
verbose_name = _("Dealer")
verbose_name_plural = _("Dealers")
permissions = [
('change_dealer_type', 'Can change dealer type'),
]
# permissions = [
# ('change_dealer_type', 'Can change dealer type'),
# ]
def __str__(self):
return self.name
@property
def get_sub_dealers(self):
if self.dealer_type == "OWNER":
return self.sub_dealers.all()
return None
@property
def is_parent(self):
return self.dealer_type == "OWNER"
@property
def get_root_dealer(self):
return self.parent_dealer if self.parent_dealer else self
# @property
# def get_sub_dealers(self):
# if self.dealer_type == "OWNER":
# return self.sub_dealers.all()
# return None
#
# @property
# def is_parent(self):
# return self.dealer_type == "OWNER"
# @property
# def get_root_dealer(self):
# return self.parent_dealer if self.parent_dealer else self
# @receiver(post_save, sender=User)
# def create_dealer(instance, created, *args, **kwargs):
@ -593,30 +594,30 @@ class Dealer(models.Model, LocalizedNameMixin):
# Dealer.objects.create(user=instance)
# class STAFF_TYPES(models.TextChoices):
# # Owner = "Owner", _("Owner")
# MANAGER = "manager", _("Manager")
# INVENTORY = "inventory", _("Inventory")
# ACCOUNTANT = "accountant", _("Accountant")
# SALES = "sales", _("Sales")
# RECEPTIONIST = "receptionist", _("Receptionist")
# TECHNICIAN = "technician", _("Technician")
# DRIVER = "driver", _("Driver")
#
#
# class Staff(models.Model, LocalizedNameMixin):
# user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="staff")
# dealer = models.ForeignKey(Dealer, on_delete=models.SET_NULL, null=True, blank=True)
# name = models.CharField(max_length=255, verbose_name=_("Name"))
# arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
# staff_type = models.CharField(choices=STAFF_TYPES.choices, max_length=255, verbose_name=_("Staff Type"))
# created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
# updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
#
# class Meta:
# verbose_name = _("Staff")
# verbose_name_plural = _("Staff")
# permissions = []
class STAFF_TYPES(models.TextChoices):
MANAGER = "manager", _("Manager")
INVENTORY = "inventory", _("Inventory")
ACCOUNTANT = "accountant", _("Accountant")
SALES = "sales", _("Sales")
RECEPTIONIST = "receptionist", _("Receptionist")
TECHNICIAN = "technician", _("Technician")
DRIVER = "driver", _("Driver")
class Staff(models.Model, LocalizedNameMixin):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="staff")
dealer = models.ForeignKey(Dealer, on_delete=models.SET_NULL, null=True, blank=True)
name = models.CharField(max_length=255, verbose_name=_("Name"))
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number"))
staff_type = models.CharField(choices=STAFF_TYPES.choices, max_length=255, verbose_name=_("Staff Type"))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
class Meta:
verbose_name = _("Staff")
verbose_name_plural = _("Staff")
permissions = []
# Vendor Model

View File

@ -3,7 +3,7 @@ from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
from django.contrib.auth import get_user_model
from django_ledger.io import roles
from django_ledger.models import EntityModel,AccountModel,ItemModel,ItemModelAbstract,UnitOfMeasureModel
from django_ledger.models import EntityModel,AccountModel,ItemModel,ItemModelAbstract,UnitOfMeasureModel, VendorModel
from . import models
User = get_user_model()
@ -49,13 +49,13 @@ def create_car_location(sender, instance, created, **kwargs):
"""
try:
if created:
if instance.dealer is None:
if instance.user.dealer is None:
raise ValueError(f"Cannot create CarLocation for car {instance.vin}: dealer is missing.")
models.CarLocation.objects.create(
car=instance,
owner=instance.dealer,
showroom=instance.dealer,
owner=instance.user.dealer,
showroom=instance.user.dealer,
description=f"Initial location set for car {instance.vin}."
)
print("Car Location created")
@ -83,105 +83,103 @@ def update_car_status_on_reservation_delete(sender, instance, **kwargs):
@receiver(post_save, sender=models.Dealer)
def create_ledger_entity(sender, instance, created, **kwargs):
if created:
root_dealer = instance.get_root_dealer
if not root_dealer.entity:
entity_name = f"{root_dealer.name}-{root_dealer.joined_at.date()}"
entity = EntityModel.create_entity(
name=entity_name,
admin=root_dealer.user,
use_accrual_method=False,
fy_start_month=1,
entity_name = instance.user.dealer.name
entity = EntityModel.create_entity(
name=entity_name,
admin=instance.user,
use_accrual_method=False,
fy_start_month=1,
)
if entity:
instance.entity = entity
instance.save()
coa = entity.create_chart_of_accounts(
assign_as_default=True, commit=True, coa_name=_(f"{entity_name}-COA")
)
if coa:
# entity.populate_default_coa(activate_accounts=True, coa_model=coa)
print(f"Ledger entity created for Dealer: {instance.name}")
# Create Cash Account
entity.create_account(
coa_model=coa,
code="1010",
role=roles.ASSET_CA_CASH,
name=_("Cash"),
balance_type="debit",
active=True,
)
if entity:
instance.entity = entity
instance.save()
coa = entity.create_chart_of_accounts(
assign_as_default=True, commit=True, coa_name=_(f"{entity_name}-COA")
)
if coa:
# entity.populate_default_coa(activate_accounts=True, coa_model=coa)
print(f"Ledger entity created for Dealer: {instance.name}")
# Create Accounts Receivable Account
entity.create_account(
coa_model=coa,
code="1020",
role=roles.ASSET_CA_RECEIVABLES,
name=_("Accounts Receivable"),
balance_type="debit",
active=True,
)
# Create Cash Account
entity.create_account(
coa_model=coa,
code="1010",
role=roles.ASSET_CA_CASH,
name=_("Cash"),
balance_type="debit",
active=True,
)
# Create Inventory Account
entity.create_account(
coa_model=coa,
code="1030",
role=roles.ASSET_CA_INVENTORY,
name=_("Inventory"),
balance_type="debit",
active=True,
)
# Create Accounts Receivable Account
entity.create_account(
coa_model=coa,
code="1020",
role=roles.ASSET_CA_RECEIVABLES,
name=_("Accounts Receivable"),
balance_type="debit",
active=True,
)
# Create Inventory Account
entity.create_account(
coa_model=coa,
code="1030",
role=roles.ASSET_CA_INVENTORY,
name=_("Inventory"),
balance_type="debit",
active=True,
)
# Create Accounts Payable Account
entity.create_account(
coa_model=coa,
code="2010",
role=roles.LIABILITY_CL_ACC_PAYABLE,
name=_("Accounts Payable"),
balance_type="credit",
active=True,
)
# Create Accounts Payable Account
entity.create_account(
coa_model=coa,
code="2010",
role=roles.LIABILITY_CL_ACC_PAYABLE,
name=_("Accounts Payable"),
balance_type="credit",
active=True,
)
# Create Sales Revenue Account
entity.create_account(
coa_model=coa,
code="4010",
role=roles.INCOME_OPERATIONAL,
name=_("Sales Revenue"),
balance_type="credit",
active=True,
)
# Create Sales Revenue Account
entity.create_account(
coa_model=coa,
code="4010",
role=roles.INCOME_OPERATIONAL,
name=_("Sales Revenue"),
balance_type="credit",
active=True,
)
# Create Cost of Goods Sold Account
entity.create_account(
coa_model=coa,
code="5010",
role=roles.COGS,
name=_("Cost of Goods Sold"),
balance_type="debit",
active=True,
)
# Create Cost of Goods Sold Account
entity.create_account(
coa_model=coa,
code="5010",
role=roles.COGS,
name=_("Cost of Goods Sold"),
balance_type="debit",
active=True,
)
# Create Rent Expense Account
entity.create_account(
coa_model=coa,
code="6010",
role=roles.EXPENSE_OPERATIONAL,
name=_("Rent Expense"),
balance_type="debit",
active=True,
)
# Create Rent Expense Account
entity.create_account(
coa_model=coa,
code="6010",
role=roles.EXPENSE_OPERATIONAL,
name=_("Rent Expense"),
balance_type="debit",
active=True,
)
# Create Utilities Expense Account
entity.create_account(
coa_model=coa,
code="6020",
role=roles.EXPENSE_OPERATIONAL,
name=_("Utilities Expense"),
balance_type="debit",
active=True,
)
# Create Utilities Expense Account
entity.create_account(
coa_model=coa,
code="6020",
role=roles.EXPENSE_OPERATIONAL,
name=_("Utilities Expense"),
balance_type="debit",
active=True,
)
# Create Vendor
@ -191,8 +189,9 @@ def create_ledger_vendor(sender, instance, created, **kwargs):
if created:
entity = EntityModel.objects.filter(name=instance.dealer.name).first()
entity.create_vendor(
name=instance.name,
VendorModel.objects.create(
entity_model=entity,
vendor_name=instance.name,
vendor_number=instance.crn,
address_1=instance.address,
phone=instance.phone_number,
@ -202,7 +201,8 @@ def create_ledger_vendor(sender, instance, created, **kwargs):
additional_info={
"arabic_name": instance.arabic_name,
"contact_person": instance.contact_person,
})
}
)
print(f"VendorModel created for Vendor: {instance.name}")
@ -211,7 +211,7 @@ def create_ledger_vendor(sender, instance, created, **kwargs):
@receiver(post_save, sender=models.Customer)
def create_customer(sender, instance, created, **kwargs):
if created:
dealer = instance.dealer.get_root_dealer
dealer = instance.dealer
entity = dealer.entity
name = f"{instance.first_name} {instance.middle_name} {instance.last_name}"
if entity:
@ -241,14 +241,8 @@ def create_item_model(sender, instance, created, **kwargs):
if not entity:
return
uom_name = _("Car")
unit_abbr = _("C")
uom = entity.get_uom_all().filter(name=uom_name, unit_abbr=unit_abbr).first()
if not uom:
uom = entity.create_uom(
name=uom_name,
unit_abbr=unit_abbr
)
uom = entity.get_uom_all()
entity.create_item_product(
name=item_name,

View File

@ -87,11 +87,11 @@ urlpatterns = [
path('sales/quotations/<int:pk>/payment/', views.payment_create, name='payment_create'),
# Users URLs
path('user/create/', views.UserCreateView.as_view(), name='user_create'),
path('user/<int:pk>/update/', views.UserUpdateView.as_view(), name='user_update'),
path('user/<int:pk>/', views.UserDetailView.as_view(), name='user_detail'),
path('user/', views.UserListView.as_view(), name='user_list'),
path('user/<int:pk>/confirm/', views.UserDeleteview, name='user_delete'),
# path('user/create/', views.UserCreateView.as_view(), name='user_create'),
# path('user/<int:pk>/update/', views.UserUpdateView.as_view(), name='user_update'),
# path('user/<int:pk>/', views.UserDetailView.as_view(), name='user_detail'),
# path('user/', views.UserListView.as_view(), name='user_list'),
# path('user/<int:pk>/confirm/', views.UserDeleteview, name='user_delete'),
# Organization URLs
path('organizations/', views.OrganizationListView.as_view(), name='organization_list'),
path('organizations/<int:pk>/', views.OrganizationDetailView.as_view(), name='organization_detail'),

View File

@ -1,3 +1,4 @@
from django.views.decorators.csrf import csrf_exempt
from django_ledger.models import EntityModel, InvoiceModel,BankAccountModel,AccountModel,JournalEntryModel,TransactionModel,EstimateModel,CustomerModel
from django_ledger.forms.bank_account import BankAccountCreateForm,BankAccountUpdateForm
@ -46,6 +47,7 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.auth.models import Group
from .utils import get_calculations
from django.contrib.auth.models import User
from allauth.account import views
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
@ -93,15 +95,16 @@ def dealer_signup(request, *args, **kwargs):
wf1 = data.get("wizardValidationForm1")
wf2 = data.get("wizardValidationForm2")
wf3 = data.get("wizardValidationForm3")
name = wf1.get("name")
arabic_name = wf1.get("arabic_name")
email = wf1.get("email")
password = wf1.get("password")
password_confirm = wf1.get("confirm_password")
name = wf2.get("name")
arabic_name = wf2.get("arabic_name")
phone = wf2.get("phone_number")
crn = wf2.get("crn")
vrn = wf2.get("vrn")
address = wf2.get("address")
password = wf3.get("password")
password_confirm = wf3.get("confirm_password")
crn = wf3.get("crn")
vrn = wf3.get("vrn")
address = wf3.get("address")
if password != password_confirm:
return JsonResponse({"error": "Passwords do not match."}, status=400)
@ -109,16 +112,14 @@ def dealer_signup(request, *args, **kwargs):
try:
with transaction.atomic():
user = User.objects.create(email=email, password=password)
user.set_password(password)
user.save()
models.Dealer.objects.create(user=user,
name=name,
arabic_name=arabic_name,
crn=crn,
vrn=vrn,
phone_number=phone,
address=address,
dealer_type="OWNER",)
address=address,)
return JsonResponse({"message": "User created successfully."}, status=200)
except Exception as e:
return JsonResponse({"error": str(e)}, status=400)
@ -143,8 +144,8 @@ class AccountingDashboard(LoginRequiredMixin, TemplateView):
def dispatch(self, request, *args, **kwargs):
if (
not any(hasattr(request.user, attr) for attr in ["dealer", "subdealer"])
or not request.user.is_authenticated
# not any(hasattr(request.user, attr) for attr in ["dealer", "subdealer"])
not request.user.is_authenticated
):
# messages.error(request, _("You are not associated with any dealer."))
return redirect("welcome")
@ -192,7 +193,7 @@ class CarCreateView(LoginRequiredMixin, CreateView):
return reverse("inventory_stats")
def form_valid(self, form):
form.instance.dealer = self.request.user.dealer.get_root_dealer
form.instance.dealer = self.request.user.dealer
form.save()
messages.success(self.request, "Car saved successfully.")
return super().form_valid(form)
@ -347,7 +348,7 @@ class CarInventory(LoginRequiredMixin, ListView):
trim_id = self.kwargs['trim_id']
cars = models.Car.objects.filter(
dealer=self.request.user.dealer.get_root_dealer,
dealer=self.request.user.dealer,
id_car_make=make_id,
id_car_model=model_id,
id_car_trim=trim_id,
@ -391,7 +392,7 @@ def inventory_stats_view(request):
# Annotate total cars by make, model, and trim
cars = (
models.Car.objects.filter(dealer=dealer.get_root_dealer)
models.Car.objects.filter(dealer=dealer)
.select_related("id_car_make", "id_car_model", "id_car_trim")
.annotate(
make_total=Count("id_car_make"),
@ -636,17 +637,17 @@ class DealerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
def get_success_url(self):
return reverse("dealer_detail", kwargs={"pk": self.object.pk})
def get_form(self, form_class=None):
form = super().get_form(form_class)
if hasattr(form.fields, "dealer_type"):
form.fields.pop("dealer_type")
return form
def get_form_class(self):
if self.request.user.dealer.dealer_type == "OWNER":
return forms.DealerForm
else:
return forms.UserForm
# def get_form(self, form_class=None):
# form = super().get_form(form_class)
# if hasattr(form.fields, "dealer_type"):
# form.fields.pop("dealer_type")
# return form
#
# def get_form_class(self):
# if self.request.user.dealer.dealer_type == "OWNER":
# return forms.DealerForm
# else:
# return forms.UserForm
class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
@ -660,7 +661,7 @@ class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
def get_queryset(self):
query = self.request.GET.get("q")
customers = models.Customer.objects.filter(
dealer=self.request.user.dealer.get_root_dealer
dealer=self.request.user.dealer
)
if query:
@ -697,6 +698,10 @@ class CustomerCreateView(
permission_required = ("inventory.add_customer",)
success_message = _("Customer created successfully.")
def form_valid(self, form):
form.instance.dealer = self.request.user.dealer
return super().form_valid(form)
class CustomerUpdateView(
@ -784,7 +789,7 @@ class QuotationCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateVie
permission_required = ("inventory.add_salequotation",)
def form_valid(self, form):
dealer = self.request.user.dealer.get_root_dealer
dealer = self.request.user.dealer
form.instance.dealer = dealer
quotation = form.save()
selected_cars = form.cleaned_data.get("cars")
@ -809,7 +814,7 @@ class QuotationListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
def get_queryset(self):
status = self.request.GET.get("status")
queryset = self.request.user.dealer.get_root_dealer.sales.all()
queryset = self.request.user.dealer.sales.all()
if status:
queryset = queryset.filter(status=status)
return queryset
@ -834,7 +839,7 @@ class QuotationDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailVie
@login_required
def generate_invoice(request, pk):
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
dealer = request.user.dealer.get_root_dealer
dealer = request.user.dealer
entity = dealer.entity
if not quotation.is_approved:
messages.error(
@ -981,7 +986,7 @@ def generate_invoice(request, pk):
@login_required
def post_quotation(request, pk):
qoutation = get_object_or_404(models.SaleQuotation, pk=pk)
dealer = request.user.dealer.get_root_dealer
dealer = request.user.dealer
entity = dealer.entity
if qoutation.posted:
messages.error(request, "Quotation is already posted")
@ -1037,7 +1042,7 @@ def post_quotation(request, pk):
def mark_quotation(request, pk):
qoutation = get_object_or_404(models.SaleQuotation, pk=pk)
status = request.GET.get("status")
dealer = request.user.dealer.get_root_dealer
dealer = request.user.dealer
entity = dealer.entity
date = datetime.datetime.now()
customer = entity.get_customers().filter(customer_name=qoutation.customer.get_full_name).first()
@ -1052,7 +1057,7 @@ def mark_quotation(request, pk):
messages.error(request, "Quotation is not ready for approval")
return redirect("quotation_detail", pk=pk)
invoice_model.mark_as_approved(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user)
invoice_model.mark_as_approved(entity_slug=entity.slug, user_model=request.user.dealer)
invoice_model.date_approved = date
qoutation.date_approved = date
invoice_model.save()
@ -1072,7 +1077,7 @@ def mark_quotation(request, pk):
messages.error(request, "Quotation is not ready for payment")
return redirect("quotation_detail", pk=pk)
invoice_model.mark_as_paid(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user)
invoice_model.mark_as_paid(entity_slug=entity.slug, user_model=request.user.dealer)
invoice_model.date_paid = date
qoutation.date_paid = date
invoice_model.save()
@ -1117,115 +1122,115 @@ class SalesOrderDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailVi
# Users
class UserListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
model = models.Dealer
context_object_name = "users"
paginate_by = 10
template_name = "users/user_list.html"
permission_required = ("inventory.view_dealer",)
def get_queryset(self):
query = self.request.GET.get("q")
users = self.request.user.dealer.sub_dealers
if query:
users = users.filter(
Q(name__icontains=query)
| Q(arabic_name__icontains=query)
| Q(phone_number__icontains=query)
| Q(address__icontains=query)
)
return users.all()
# class UserListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
# model = models.Staff
# context_object_name = "users"
# paginate_by = 10
# template_name = "users/user_list.html"
# permission_required = ("inventory.view_dealer",)
#
# def get_queryset(self):
# query = self.request.GET.get("q")
# users = self.request.user.dealer.staff.all()
#
# if query:
# users = users.filter(
# Q(name__icontains=query)
# | Q(arabic_name__icontains=query)
# | Q(phone_number__icontains=query)
# | Q(address__icontains=query)
# )
# return users.all()
#
#
# class UserDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
# model = models.Staff
# template_name = "users/user_detail.html"
# context_object_name = "user_"
# permission_required = ("inventory.view_dealer",)
class UserDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
model = models.Dealer
template_name = "users/user_detail.html"
context_object_name = "user_"
permission_required = ("inventory.view_dealer",)
# class UserCreateView(
# LoginRequiredMixin,
# PermissionRequiredMixin,
# SuccessMessageMixin,
# CreateView,
# ):
# model = models.Staff
# form_class = forms.UserForm
# template_name = "users/user_form.html"
# success_url = reverse_lazy("user_list")
# permission_required = ("inventory.add_dealer",)
# success_message = _("User created successfully.")
#
# def get_form(self, form_class=None):
# form = super().get_form(form_class)
# form.fields["staff_type"].choices = [
# t for t in form.fields["staff_type"].choices if t[0] != "OWNER"
# ]
# return form
#
# def form_valid(self, form):
# dealer = self.request.user.dealer
# if dealer.sub_dealers.count() >= dealer.get_active_plan.max_users:
# messages.error(
# self.request, _("You have reached the maximum number of users.")
# )
# return redirect("user_list")
#
# user = User.objects.create_user(username=form.cleaned_data["name"])
# user.set_password("Tenhal@123")
# user.save()
# form.instance.user = user
# form.instance.parent_dealer = dealer
# for group in user.groups.all():
# group.user_set.remove(user)
# Group.objects.get(name=form.cleaned_data["dealer_type"].lower()).user_set.add(
# user
# )
# form.save()
# return super().form_valid(form)
class UserCreateView(
LoginRequiredMixin,
PermissionRequiredMixin,
SuccessMessageMixin,
CreateView,
):
model = models.Dealer
form_class = forms.UserForm
template_name = "users/user_form.html"
success_url = reverse_lazy("user_list")
permission_required = ("inventory.add_dealer",)
success_message = _("User created successfully.")
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields["dealer_type"].choices = [
t for t in form.fields["dealer_type"].choices if t[0] != "OWNER"
]
return form
def form_valid(self, form):
dealer = self.request.user.dealer.get_root_dealer
if dealer.sub_dealers.count() >= dealer.get_active_plan.max_users:
messages.error(
self.request, _("You have reached the maximum number of users.")
)
return redirect("user_list")
user = User.objects.create_user(username=form.cleaned_data["name"])
user.set_password("Tenhal@123")
user.save()
form.instance.user = user
form.instance.parent_dealer = dealer
for group in user.groups.all():
group.user_set.remove(user)
Group.objects.get(name=form.cleaned_data["dealer_type"].lower()).user_set.add(
user
)
form.save()
return super().form_valid(form)
# class UserUpdateView(
# LoginRequiredMixin,
# PermissionRequiredMixin,
# SuccessMessageMixin,
# UpdateView,
# ):
# model = models.Dealer
# form_class = forms.UserForm
# template_name = "users/user_form.html"
# success_url = reverse_lazy("user_list")
# permission_required = ("inventory.change_dealer",)
# success_message = _("User updated successfully.")
#
# def get_form(self, form_class=None):
# form = super().get_form(form_class)
# if not self.request.user.has_perms(["inventory.change_dealer_type"]):
# field = form.fields["dealer_type"]
# field.widget = field.hidden_widget()
# form.fields["dealer_type"].choices = [
# t for t in form.fields["dealer_type"].choices if t[0] != "Owner"
# ]
# return form
#
# def form_valid(self, form):
# user = form.instance.user
# for group in user.groups.all():
# group.user_set.remove(user)
# Group.objects.get(name=form.cleaned_data["dealer_type"].lower()).user_set.add(
# user
# )
# form.save()
# return super().form_valid(form)
class UserUpdateView(
LoginRequiredMixin,
PermissionRequiredMixin,
SuccessMessageMixin,
UpdateView,
):
model = models.Dealer
form_class = forms.UserForm
template_name = "users/user_form.html"
success_url = reverse_lazy("user_list")
permission_required = ("inventory.change_dealer",)
success_message = _("User updated successfully.")
def get_form(self, form_class=None):
form = super().get_form(form_class)
if not self.request.user.has_perms(["inventory.change_dealer_type"]):
field = form.fields["dealer_type"]
field.widget = field.hidden_widget()
form.fields["dealer_type"].choices = [
t for t in form.fields["dealer_type"].choices if t[0] != "Owner"
]
return form
def form_valid(self, form):
user = form.instance.user
for group in user.groups.all():
group.user_set.remove(user)
Group.objects.get(name=form.cleaned_data["dealer_type"].lower()).user_set.add(
user
)
form.save()
return super().form_valid(form)
def UserDeleteview(request, pk):
user = get_object_or_404(models.Dealer, pk=pk)
user.delete()
messages.success(request, _("User deleted successfully."))
return redirect("user_list")
# def UserDeleteview(request, pk):
# user = get_object_or_404(models.Dealer, pk=pk)
# user.delete()
# messages.success(request, _("User deleted successfully."))
# return redirect("user_list")
# errors
@ -1266,7 +1271,7 @@ class OrganizationCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView
def form_valid(self, form):
if form.is_valid():
form.instance.dealer = self.request.user.dealer.get_root_dealer
form.instance.dealer = self.request.user.dealer
form.save()
return super().form_valid(form)
else:
@ -1309,7 +1314,7 @@ class RepresentativeCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateVi
def form_valid(self, form):
if form.is_valid():
form.instance.dealer = self.request.user.dealer.get_root_dealer
form.instance.dealer = self.request.user.dealer
form.save()
return super().form_valid(form)
else:
@ -1382,7 +1387,7 @@ def download_quotation_pdf(request, quotation_id):
@login_required
def invoice_detail(request,pk):
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
dealer = request.user.dealer.get_root_dealer
dealer = request.user.dealer
entity = dealer.entity
customer = entity.get_customers().filter(customer_name=quotation.customer.get_full_name).first()
invoice_model = entity.get_invoices()
@ -1395,7 +1400,7 @@ def invoice_detail(request,pk):
@login_required
def payment_invoice(request,pk):
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
dealer = request.user.dealer.get_root_dealer
dealer = request.user.dealer
entity = dealer.entity
customer = entity.get_customers().filter(customer_name=quotation.customer.get_full_name).first()
invoice_model = entity.get_invoices()
@ -1425,14 +1430,14 @@ def payment_invoice(request,pk):
def payment_create(request, pk):
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
dealer = request.user.dealer.get_root_dealer
dealer = request.user.dealer
if request.method == "POST":
form = forms.PaymentForm(request.POST)
if form.is_valid():
form.instance.quotation = quotation
insatnce = form.save()
dealer = request.user.dealer.get_root_dealer
dealer = request.user.dealer
entity = dealer.entity
customer = entity.get_customers().filter(customer_name=quotation.customer.get_full_name).first()
coa_qs, coa_map = entity.get_all_coa_accounts()
@ -1461,7 +1466,7 @@ def payment_create(request, pk):
invoice_model = entity.get_invoices().filter(date_approved=quotation.date_approved).first()
invoice_model.mark_as_paid(entity_slug=entity.slug, user_model=request.user.dealer.get_root_dealer.user)
invoice_model.mark_as_paid(entity_slug=entity.slug, user_model=request.user.dealer)
date = timezone.now()
invoice_model.date_paid = date
quotation.date_paid = date

BIN
templates/.DS_Store vendored

Binary file not shown.

View File

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "auth_base.html" %}
{% load crispy_forms_filters %}
{% load i18n static %}

229
templates/auth_base.html Normal file
View File

@ -0,0 +1,229 @@
{% load static %} {% load i18n %}
<!DOCTYPE html>
{% get_current_language as LANGUAGE_CODE %}
<html lang="{{ LANGUAGE_CODE }}"
dir="{% if LANGUAGE_CODE == 'ar' %}rtl{% else %}ltr{% endif %}"
data-bs-theme=""
data-navigation-type="default"
data-navbar-horizontal-shape="default">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Haikal - The Backbone of Car Qar: An innovative car inventory management system designed to streamline dealership operations. Manage inventory, sales, transfers, and accounting seamlessly with advanced analytics and intuitive tools. Inspired by Arabic origins, Haikal empowers businesses with precision and efficiency.">
<title>{% block title %}{% trans 'HAIKAL' %}{% endblock %}</title>
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'images/favicons/apple-touch-icon.png' %}">
<link rel="icon" type="image/png" sizes="32x32" href="{% static 'images/favicons/favicon-32x32.png' %}">
<link rel="icon" type="image/png" sizes="16x16" href="{% static 'images/favicons/favicon-16x16.png' %}">
<link rel="shortcut icon" type="image/x-icon" href="{% static 'images/favicons/favicon.ico' %}">
<link rel="manifest" href="{% static 'images/favicons/manifest.json' %}">
<meta name="msapplication-TileImage" content="{% static 'images/logos/logo-d.png' %}">
<meta name="theme-color" content="#ffffff">
<script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script>
<script src="{% static 'js/config.js' %}"></script>
<script src="{% static 'js/sweetalert2.all.min.js' %}"></script>
<!-- ===============================================-->
<!-- Stylesheets-->
<!-- ===============================================-->
<link href="{% static 'vendors/mapbox-gl/mapbox-gl.css' %}" rel="stylesheet">
<link href="{% static 'vendors/swiper/swiper-bundle.min.css' %}" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;800;900&amp;display=swap" rel="stylesheet">
<link href="{% static 'vendors/simplebar/simplebar.min.css' %}" rel="stylesheet">
<link href="{% static 'css/sweetalert2.min.css' %}" rel="stylesheet">
<link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css">
{% if LANGUAGE_CODE == 'en' %}
<link href="{% static 'css/theme.min.css' %}" type="text/css" rel="stylesheet" id="style-default">
<link href="{% static 'css/user.min.css' %}" type="text/css" rel="stylesheet" id="user-style-default">
{% else %}
<link href="{% static 'css/theme-rtl.min.css' %}" type="text/css" rel="stylesheet" id="style-rtl">
<link href="{% static 'css/user-rtl.min.css' %}" type="text/css" rel="stylesheet" id="user-style-rtl">
{% endif %}
</head>
<body>
{% include 'messages.html' %}
<main class="main" id="top">
<div class="content">
{% block content %}
<!-- Main content goes here -->
{% endblock %}
</div>
</main>
<!-- ===============================================-->
<!-- End of Main Content-->
<!-- ===============================================-->
<script>
</script>
{% block extra_js %}{% endblock extra_js %}
<script>
// Function to calculate Total Cost and Total Revenue
function calculateTotals(container) {
const quantity = parseFloat(container.querySelector('.quantity').value) || 0;
const unitCost = parseFloat(container.querySelector('.unitCost').value) || 0;
const unitSalesPrice = parseFloat(container.querySelector('.unitSalesPrice').value) || 0;
const totalCost = quantity * unitCost;
const totalRevenue = quantity * unitSalesPrice;
container.querySelector('.totalCost').value = totalCost.toFixed(2);
container.querySelector('.totalRevenue').value = totalRevenue.toFixed(2);
}
// Add event listeners to inputs for dynamic calculation
function addInputListeners(container) {
container.querySelectorAll('.quantity, .unitCost, .unitSalesPrice').forEach(input => {
input.addEventListener('input', () => calculateTotals(container));
});
}
// Add new form fields
document.getElementById('addMoreBtn').addEventListener('click', function(e) {
e.preventDefault();
const formContainer = document.getElementById('formContainer');
const newForm = document.createElement('div');
newForm.className = 'form-container row g-3 mb-3 mt-5';
newForm.innerHTML = `
<div class="mb-2 col-sm-2">
<select class="form-control item" name="item[]" required>
{% for item in items %}
<option value="{{ item.pk }}">{{ item.name }}</option>
{% endfor %}
</select>
</div>
<div class="mb-2 col-sm-2">
<input class="form-control quantity" type="number" placeholder="Quantity" name="quantity[]" required>
</div>
<div class="mb-2 col-sm-2">
<input class="form-control unitCost" type="number" placeholder="Unit Cost" name="unitCost[]" step="0.01" required>
</div>
<div class="mb-2 col-sm-2">
<input class="form-control unitSalesPrice" type="number" placeholder="Unit Sales Price" name="unitSalesPrice[]" step="0.01" required>
</div>
<div class="mb-2 col-sm-2">
<input class="form-control totalCost" type="number" placeholder="Total Cost" name="totalCost[]" readonly>
</div>
<div class="mb-2 col-sm-1">
<input class="form-control totalRevenue" type="number" placeholder="Total Revenue" name="totalRevenue[]" readonly>
</div>
<div class="mb-2 col-sm-1">
<button class="btn btn-danger removeBtn">Remove</button>
</div>
`;
formContainer.appendChild(newForm);
addInputListeners(newForm); // Add listeners to the new form
// Add remove button functionality
newForm.querySelector('.removeBtn').addEventListener('click', function() {
newForm.remove();
});
});
// Add listeners to the initial form
document.querySelectorAll('.form-container').forEach(container => {
addInputListeners(container);
// Add remove button functionality to the initial form
container.querySelector('.removeBtn').addEventListener('click', function() {
container.remove();
});
});
document.getElementById('mainForm').addEventListener('submit', function(e) {
e.preventDefault();
// Collect all form data
const formData = new FormData(this);
const csrfToken = getCookie('csrftoken');
const data = {};
formData.forEach((value, key) => {
// Handle multi-value fields (e.g., item[], quantity[])
if (data[key]) {
if (!Array.isArray(data[key])) {
data[key] = [data[key]]; // Convert to array
}
data[key].push(value);
} else {
data[key] = value;
}
});
// Send data to the server using fetch
fetch('http://10.10.1.120:8888/en/sales/estimates/create/', {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': csrfToken,
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
console.log('Success:', data);
if(data.status == "error"){
notify("error",data.message);
}
else{
notify("success","Estimate created successfully");
setTimeout(() => {
window.location.href = data.url;
}, 1000);
}
})
.catch(error => {
console.error('Error:', error);
notify("error",error);
alert('An error occurred while submitting the form.');
});
});
</script>
<!-- ===============================================-->
<!-- JavaScripts-->
<!-- ===============================================-->
<script src="{% static 'vendors/popper/popper.min.js' %}"></script>
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
<script src="{% static 'vendors/anchorjs/anchor.min.js' %}"></script>
<script src="{% static 'vendors/is/is.min.js' %}"></script>
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
<script src="{% static 'vendors/lodash/lodash.min.js' %}"></script>
<script src="{% static 'vendors/list.js/list.min.js' %}"></script>
<script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
<script src="{% static 'vendors/dayjs/dayjs.min.js' %}"></script>
<script src="{% static 'js/phoenix.js' %}"></script>
<script src="{% static 'vendors/echarts/echarts.min.js' %}"></script>
<script src="{% static 'js/travel-agency-dashboard.js' %}"></script>
<script src="{% static 'vendors/mapbox-gl/mapbox-gl.js' %}"></script>
<script src="https://unpkg.com/@turf/turf@6/turf.min.js"></script>
<script src="{% static 'vendors/swiper/swiper-bundle.min.js' %}"></script>
</body>
</html>