Merge branch 'main' of http://10.10.1.136:3000/ismail/haikal into frontend

This commit is contained in:
Faheedkhan 2025-08-24 11:59:41 +03:00
commit cf378f1a1e
7 changed files with 1064 additions and 999 deletions

View File

@ -146,6 +146,7 @@ class StaffForm(forms.ModelForm):
) )
class Meta: class Meta:
model = Staff model = Staff
fields = ["first_name","last_name", "arabic_name", "phone_number", "address", "logo", "group"] fields = ["first_name","last_name", "arabic_name", "phone_number", "address", "logo", "group"]

22
inventory/hooks.py Normal file
View File

@ -0,0 +1,22 @@
import logging
from inventory.models import Dealer
from .utils import get_accounts_data,create_account
logger = logging.getLogger(__name__)
def check_create_coa_accounts(task):
logger.info("Checking if all accounts are created")
instance_id = task.args[0]
instance = Dealer.objects.get(pk=instance_id)
entity = instance.entity
coa = entity.get_default_coa()
for account_data in get_accounts_data():
if entity.get_all_accounts().filter(code=account_data["code"]).exists():
logger.info(f"Default account already exists: {account_data['code']}")
continue
logger.info(f"Default account does not exist: {account_data['code']}")
create_account(entity, coa, account_data)
def print_results(task):
print(task.kwargs.get("dealer"))

View File

@ -0,0 +1,9 @@
from django.core.management.base import BaseCommand
from django_q.tasks import async_task, result
class Command(BaseCommand):
def handle(self, *args, **kwargs):
from inventory.models import Dealer
instance = Dealer.objects.first()
async_task(name="test_task_test",func="inventory.tasks.test_task",dealer=instance,hook="inventory.hooks.print_results")

View File

@ -2,6 +2,7 @@ import uuid
from datetime import datetime from datetime import datetime
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
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
@ -1305,7 +1306,7 @@ class Dealer(models.Model, LocalizedNameMixin):
) )
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
name = models.CharField(max_length=255, verbose_name=_("English Name")) name = models.CharField(max_length=255, verbose_name=_("English Name"))
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number")) phone_number = models.CharField(max_length=255, verbose_name=_("Phone Number"),validators=[SaudiPhoneNumberValidator])
address = models.CharField( address = models.CharField(
max_length=200, blank=True, null=True, verbose_name=_("Address") max_length=200, blank=True, null=True, verbose_name=_("Address")
) )
@ -1431,7 +1432,7 @@ class Staff(models.Model):
last_name = models.CharField(max_length=255, verbose_name=_("Last Name")) last_name = models.CharField(max_length=255, verbose_name=_("Last Name"))
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number")) phone_number = models.CharField(max_length=255, verbose_name=_("Phone Number"),validators=[SaudiPhoneNumberValidator])
staff_type = models.CharField( staff_type = models.CharField(
choices=StaffTypes.choices, max_length=255, verbose_name=_("Staff Type") choices=StaffTypes.choices, max_length=255, verbose_name=_("Staff Type")
) )
@ -1825,7 +1826,7 @@ class Organization(models.Model, LocalizedNameMixin):
) )
vrn = models.CharField(max_length=15, verbose_name=_("VAT Registration Number")) vrn = models.CharField(max_length=15, verbose_name=_("VAT Registration Number"))
email = models.EmailField(verbose_name=_("Email")) email = models.EmailField(verbose_name=_("Email"))
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number")) phone_number = models.CharField(max_length=255, verbose_name=_("Phone Number"),validators=[SaudiPhoneNumberValidator])
address = models.CharField( address = models.CharField(
max_length=200, blank=True, null=True, verbose_name=_("Address") max_length=200, blank=True, null=True, verbose_name=_("Address")
) )
@ -1959,7 +1960,7 @@ class Representative(models.Model, LocalizedNameMixin):
id_number = models.CharField( id_number = models.CharField(
max_length=10, unique=True, verbose_name=_("ID Number") max_length=10, unique=True, verbose_name=_("ID Number")
) )
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number")) phone_number = models.CharField(max_length=255, verbose_name=_("Phone Number"),validators=[SaudiPhoneNumberValidator])
email = models.EmailField(max_length=255, verbose_name=_("Email Address")) email = models.EmailField(max_length=255, verbose_name=_("Email Address"))
address = models.CharField( address = models.CharField(
max_length=200, blank=True, null=True, verbose_name=_("Address") max_length=200, blank=True, null=True, verbose_name=_("Address")
@ -1979,7 +1980,7 @@ class Lead(models.Model):
first_name = models.CharField(max_length=50, verbose_name=_("First Name")) first_name = models.CharField(max_length=50, verbose_name=_("First Name"))
last_name = models.CharField(max_length=50, verbose_name=_("Last Name")) last_name = models.CharField(max_length=50, verbose_name=_("Last Name"))
email = models.EmailField(verbose_name=_("Email")) email = models.EmailField(verbose_name=_("Email"))
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number")) phone_number = models.CharField(max_length=255, verbose_name=_("Phone Number"),validators=[SaudiPhoneNumberValidator])
address = models.CharField( address = models.CharField(
max_length=200, blank=True, null=True, verbose_name=_("Address") max_length=200, blank=True, null=True, verbose_name=_("Address")
) )
@ -2667,7 +2668,7 @@ class Vendor(models.Model, LocalizedNameMixin):
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
name = models.CharField(max_length=255, verbose_name=_("English Name")) name = models.CharField(max_length=255, verbose_name=_("English Name"))
contact_person = models.CharField(max_length=100, verbose_name=_("Contact Person")) contact_person = models.CharField(max_length=100, verbose_name=_("Contact Person"))
phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number")) phone_number = models.CharField(max_length=255, verbose_name=_("Phone Number"),validators=[SaudiPhoneNumberValidator])
email = models.EmailField(max_length=255, verbose_name=_("Email Address")) email = models.EmailField(max_length=255, verbose_name=_("Email Address"))
address = models.CharField(max_length=200, verbose_name=_("Address")) address = models.CharField(max_length=200, verbose_name=_("Address"))
logo = models.ImageField( logo = models.ImageField(

View File

@ -178,7 +178,8 @@ def create_ledger_entity(sender, instance, created, **kwargs):
entity.create_uom(name=u[1], unit_abbr=u[0]) entity.create_uom(name=u[1], unit_abbr=u[0])
# Create COA accounts, background task # Create COA accounts, background task
async_task(create_coa_accounts, instance) async_task(name="create_coa_accounts",func=create_coa_accounts,dealer_pk=instance.pk,hook="inventory.hooks.check_create_coa_accounts")
# async_task('inventory.tasks.check_create_coa_accounts', instance, schedule_type='O', schedule_time=timedelta(seconds=20))
# create_settings(instance.pk) # create_settings(instance.pk)
# create_accounts_for_make(instance.pk) # create_accounts_for_make(instance.pk)

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
import json import json
import secrets import secrets
import logging
import datetime import datetime
import requests import requests
from decimal import Decimal from decimal import Decimal
@ -27,7 +28,7 @@ from django.contrib.contenttypes.models import ContentType
from django_ledger.models.transactions import TransactionModel from django_ledger.models.transactions import TransactionModel
from django_ledger.models.journal_entry import JournalEntryModel from django_ledger.models.journal_entry import JournalEntryModel
from django.db import transaction from django.db import transaction
import logging from django_q.models import Schedule as DjangoQSchedule
from django_ledger.io import roles from django_ledger.io import roles
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -223,13 +224,21 @@ def reserve_car(car, request):
:return: Redirection to the car's detail page. :return: Redirection to the car's detail page.
""" """
try: try:
reserved_until = timezone.now() + timezone.timedelta(hours=24) # reserved_until = timezone.now() + timezone.timedelta(hours=24)
models.CarReservation.objects.create( reserved_until = timezone.now() + timezone.timedelta(minutes=1)
reservation = models.CarReservation.objects.create(
car=car, reserved_by=request.user, reserved_until=reserved_until car=car, reserved_by=request.user, reserved_until=reserved_until
) )
car.status = models.CarStatusChoices.RESERVED car.status = models.CarStatusChoices.RESERVED
car.save() car.save()
# --- Logging for Success --- # --- Logging for Success ---
DjangoQSchedule.objects.create(
name=f"remove_reservation_for_car_with_vin_{car.vin}",
func='inventory.tasks.remove_reservation_by_id',
args=reservation.pk,
schedule_type=DjangoQSchedule.ONCE,
next_run=reserved_until,
)
logger.info( logger.info(
f"Car {car.pk} ('{car.id_car_make} {car.id_car_model}') reserved successfully " f"Car {car.pk} ('{car.id_car_make} {car.id_car_model}') reserved successfully "
f"by user {request.user}. " f"by user {request.user}. "
@ -1288,7 +1297,7 @@ def get_finance_data(estimate,dealer):
) )
discount = extra_info.data.get("discount", 0) discount = extra_info.data.get("discount", 0)
discount = Decimal(discount) discount = Decimal(discount)
additional_services = car.get_additional_services() additional_services = car.get_additional_services()
discounted_price=(Decimal(car.marked_price) - discount) discounted_price=(Decimal(car.marked_price) - discount)
vat_amount = discounted_price * vat.rate vat_amount = discounted_price * vat.rate
@ -1915,3 +1924,416 @@ def handle_payment(request, order):
# def get_user_quota(user): # def get_user_quota(user):
# return user.dealer.quota # return user.dealer.quota
def get_accounts_data():
return [
# Current Assets (must start with 1)
{
"code": "1010",
"name": "Cash on Hand",
"role": roles.ASSET_CA_CASH,
"balance_type": roles.DEBIT,
"locked": False,
"default": True, # Default for ASSET_CA_CASH
},
{
"code": "1020",
"name": "Bank",
"role": roles.ASSET_CA_CASH,
"balance_type": roles.DEBIT,
"locked": False,
"default": False,
},
{
"code": "1030",
"name": "Accounts Receivable",
"role": roles.ASSET_CA_RECEIVABLES,
"balance_type": roles.DEBIT,
"locked": False,
"default": True, # Default for ASSET_CA_RECEIVABLES
},
{
"code": "1040",
"name": "Inventory (Cars)",
"role": roles.ASSET_CA_INVENTORY,
"balance_type": roles.DEBIT,
"locked": False,
"default": True, # Default for ASSET_CA_INVENTORY
},
{
"code": "1045",
"name": "Spare Parts Inventory",
"role": roles.ASSET_CA_INVENTORY,
"balance_type": roles.DEBIT,
"locked": False,
"default": False,
},
{
"code": "1050",
"name": "Employee Advances",
"role": roles.ASSET_CA_RECEIVABLES,
"balance_type": roles.DEBIT,
"locked": False,
"default": False,
},
{
"code": "1060",
"name": "Prepaid Expenses",
"role": roles.ASSET_CA_PREPAID,
"balance_type": roles.DEBIT,
"locked": False,
"default": True, # Default for ASSET_CA_PREPAID
},
{
"code": "1070",
"name": "Notes Receivable",
"role": roles.ASSET_LTI_NOTES_RECEIVABLE,
"balance_type": roles.DEBIT,
"locked": False,
"default": True, # Default for ASSET_LTI_NOTES_RECEIVABLE
},
# Fixed Assets (must also start with 1)
{
"code": "1110",
"name": "Lands",
"role": roles.ASSET_LTI_LAND,
"balance_type": roles.DEBIT,
"locked": False,
"default": True, # Default for ASSET_LTI_LAND
},
{
"code": "1111",
"name": "Buildings",
"role": roles.ASSET_PPE_BUILDINGS,
"balance_type": roles.DEBIT,
"locked": False,
"default": True, # Default for ASSET_PPE_BUILDINGS
},
{
"code": "1112",
"name": "Company Vehicles",
"role": roles.ASSET_PPE_EQUIPMENT,
"balance_type": roles.DEBIT,
"locked": False,
"default": True, # Default for ASSET_PPE_EQUIPMENT
},
{
"code": "1113",
"name": "Equipment & Tools",
"role": roles.ASSET_PPE_EQUIPMENT,
"balance_type": roles.DEBIT,
"locked": False,
"default": False,
},
{
"code": "1114",
"name": "Furniture & Fixtures",
"role": roles.ASSET_PPE_EQUIPMENT,
"balance_type": roles.DEBIT,
"locked": False,
"default": False,
},
{
"code": "1115",
"name": "Other Fixed Assets",
"role": roles.ASSET_PPE_EQUIPMENT,
"balance_type": roles.DEBIT,
"locked": False,
"default": False,
},
{
"code": "1120",
"name": "Long-term Investments",
"role": roles.ASSET_LTI_SECURITIES,
"balance_type": roles.DEBIT,
"locked": False,
"default": True, # Default for ASSET_LTI_SECURITIES
},
{
"code": "1130",
"name": "Intangible Assets",
"role": roles.ASSET_INTANGIBLE_ASSETS,
"balance_type": roles.DEBIT,
"locked": False,
"default": True, # Default for ASSET_INTANGIBLE_ASSETS
},
# Current Liabilities (must start with 2)
{
"code": "2010",
"name": "Accounts Payable",
"role": roles.LIABILITY_CL_ACC_PAYABLE,
"balance_type": roles.CREDIT,
"locked": True,
"default": True, # Default for LIABILITY_CL_ACC_PAYABLE
},
{
"code": "2020",
"name": "Notes Payable",
"role": roles.LIABILITY_CL_ST_NOTES_PAYABLE,
"balance_type": roles.CREDIT,
"locked": False,
"default": True, # Default for LIABILITY_CL_ST_NOTES_PAYABLE
},
{
"code": "2030",
"name": "Short-term Loans",
"role": roles.LIABILITY_CL_ST_NOTES_PAYABLE,
"balance_type": roles.CREDIT,
"locked": False,
"default": False,
},
{
"code": "2040",
"name": "Employee Payables",
"role": roles.LIABILITY_CL_WAGES_PAYABLE,
"balance_type": roles.CREDIT,
"locked": False,
"default": True, # Default for LIABILITY_CL_WAGES_PAYABLE
},
{
"code": "2050",
"name": "Accrued Expenses",
"role": roles.LIABILITY_CL_OTHER,
"balance_type": roles.CREDIT,
"locked": False,
"default": True, # Default for LIABILITY_CL_OTHER
},
{
"code": "2060",
"name": "Accrued Taxes",
"role": roles.LIABILITY_CL_TAXES_PAYABLE,
"balance_type": roles.CREDIT,
"locked": False,
"default": False, # Default for LIABILITY_CL_TAXES_PAYABLE
},
{
"code": "2070",
"name": "Provisions",
"role": roles.LIABILITY_CL_OTHER,
"balance_type": roles.CREDIT,
"locked": False,
"default": False,
},
# Long-term Liabilities (must also start with 2)
{
"code": "2103",
"name": "Deferred Revenue",
"role": roles.LIABILITY_CL_DEFERRED_REVENUE,
"balance_type": roles.CREDIT,
"locked": False,
"default": True, # Default for LIABILITY_CL_DEFERRED_REVENUE
},
{
"code": "2200",
"name": "Tax Payable",
"role": roles.LIABILITY_CL_TAXES_PAYABLE,
"balance_type": roles.CREDIT,
"locked": False,
"default": True,
},
{
"code": "2210",
"name": "Long-term Bank Loans",
"role": roles.LIABILITY_LTL_NOTES_PAYABLE,
"balance_type": roles.CREDIT,
"locked": False,
"default": True, # Default for LIABILITY_LTL_NOTES_PAYABLE
},
{
"code": "2220",
"name": "Lease Liabilities",
"role": roles.LIABILITY_LTL_NOTES_PAYABLE,
"balance_type": roles.CREDIT,
"locked": False,
"default": False,
},
{
"code": "2230",
"name": "Other Long-term Liabilities",
"role": roles.LIABILITY_LTL_NOTES_PAYABLE,
"balance_type": roles.CREDIT,
"locked": False,
"default": False,
},
# Equity (must start with 3)
{
"code": "3010",
"name": "Capital",
"role": roles.EQUITY_CAPITAL,
"balance_type": roles.CREDIT,
"locked": True,
"default": True, # Default for EQUITY_CAPITAL
},
{
"code": "3020",
"name": "Statutory Reserve",
"role": roles.EQUITY_ADJUSTMENT,
"balance_type": roles.CREDIT,
"locked": False,
"default": True, # Default for EQUITY_ADJUSTMENT
},
{
"code": "3030",
"name": "Retained Earnings",
"role": roles.EQUITY_ADJUSTMENT,
"balance_type": roles.CREDIT,
"locked": False,
"default": False,
},
{
"code": "3040",
"name": "Profit & Loss for the Period",
"role": roles.EQUITY_ADJUSTMENT,
"balance_type": roles.CREDIT,
"locked": False,
"default": False,
},
# Revenue (must start with 4)
{
"code": "4010",
"name": "Car Sales",
"role": roles.INCOME_OPERATIONAL,
"balance_type": roles.CREDIT,
"locked": True,
"default": True, # Default for INCOME_OPERATIONAL
},
{
"code": "4020",
"name": "After-Sales Services",
"role": roles.INCOME_OPERATIONAL,
"balance_type": roles.CREDIT,
"locked": False,
"default": False,
},
{
"code": "4030",
"name": "Car Rental Income",
"role": roles.INCOME_PASSIVE,
"balance_type": roles.CREDIT,
"locked": False,
"default": True, # Default for INCOME_PASSIVE
},
{
"code": "4040",
"name": "Other Income",
"role": roles.INCOME_OTHER,
"balance_type": roles.CREDIT,
"locked": False,
"default": True, # Default for INCOME_OTHER
},
# Expenses (must start with 5 for COGS, 6 for others)
{
"code": "5010",
"name": "Cost of Goods Sold",
"role": roles.COGS,
"balance_type": roles.DEBIT,
"locked": True,
"default": True, # Default for COGS
},
{
"code": "5015",
"name": "Spare Parts Cost Consumed",
"role": roles.COGS,
"balance_type": roles.DEBIT,
"locked": False,
"default": False,
},
{
"code": "6010",
"name": "Salaries & Wages",
"role": roles.EXPENSE_OPERATIONAL,
"balance_type": roles.DEBIT,
"locked": False,
"default": True, # Default for EXPENSE_OPERATIONAL
},
{
"code": "6020",
"name": "Rent",
"role": roles.EXPENSE_OPERATIONAL,
"balance_type": roles.DEBIT,
"locked": False,
"default": False,
},
{
"code": "6030",
"name": "Utilities",
"role": roles.EXPENSE_OPERATIONAL,
"balance_type": roles.DEBIT,
"locked": False,
"default": False,
},
{
"code": "6040",
"name": "Advertising & Marketing",
"role": roles.EXPENSE_OPERATIONAL,
"balance_type": roles.DEBIT,
"locked": False,
"default": False,
},
{
"code": "6050",
"name": "Maintenance",
"role": roles.EXPENSE_OPERATIONAL,
"balance_type": roles.DEBIT,
"locked": False,
"default": False,
},
{
"code": "6060",
"name": "Operating Expenses",
"role": roles.EXPENSE_OPERATIONAL,
"balance_type": roles.DEBIT,
"locked": False,
"default": False,
},
{
"code": "6070",
"name": "Depreciation",
"role": roles.EXPENSE_DEPRECIATION,
"balance_type": roles.DEBIT,
"locked": False,
"default": True, # Default for EXPENSE_DEPRECIATION
},
{
"code": "6080",
"name": "Fees & Taxes",
"role": roles.EXPENSE_OPERATIONAL,
"balance_type": roles.DEBIT,
"locked": False,
"default": False,
},
{
"code": "6090",
"name": "Bank Charges",
"role": roles.EXPENSE_OPERATIONAL,
"balance_type": roles.DEBIT,
"locked": False,
"default": False,
},
{
"code": "6100",
"name": "Other Expenses",
"role": roles.EXPENSE_OTHER,
"balance_type": roles.DEBIT,
"locked": False,
"default": True, # Default for EXPENSE_OTHER
},
]
def create_account(entity, coa, account_data):
try:
account = entity.create_account(
coa_model=coa,
code=account_data["code"],
name=account_data["name"],
role=account_data["role"],
balance_type=_(account_data["balance_type"]),
active=True,
)
account.role_default = account_data["default"]
account.save()
logger.info(f"Created default account: {account}")
except Exception as e:
logger.error(f"Error creating default account: {account_data['code']}, {e}")