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:
model = Staff
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 django.conf import settings
from django.contrib.auth.models import Permission
from inventory.validators import SaudiPhoneNumberValidator
from decimal import Decimal
from django.urls import reverse
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"))
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(
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"))
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(
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"))
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(
max_length=200, blank=True, null=True, verbose_name=_("Address")
)
@ -1959,7 +1960,7 @@ class Representative(models.Model, LocalizedNameMixin):
id_number = models.CharField(
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"))
address = models.CharField(
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"))
last_name = models.CharField(max_length=50, verbose_name=_("Last Name"))
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(
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"))
name = models.CharField(max_length=255, verbose_name=_("English Name"))
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"))
address = models.CharField(max_length=200, verbose_name=_("Address"))
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])
# 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_accounts_for_make(instance.pk)

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
import json
import secrets
import logging
import datetime
import requests
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.journal_entry import JournalEntryModel
from django.db import transaction
import logging
from django_q.models import Schedule as DjangoQSchedule
from django_ledger.io import roles
logger = logging.getLogger(__name__)
@ -223,13 +224,21 @@ def reserve_car(car, request):
:return: Redirection to the car's detail page.
"""
try:
reserved_until = timezone.now() + timezone.timedelta(hours=24)
models.CarReservation.objects.create(
# reserved_until = timezone.now() + timezone.timedelta(hours=24)
reserved_until = timezone.now() + timezone.timedelta(minutes=1)
reservation = models.CarReservation.objects.create(
car=car, reserved_by=request.user, reserved_until=reserved_until
)
car.status = models.CarStatusChoices.RESERVED
car.save()
# --- 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(
f"Car {car.pk} ('{car.id_car_make} {car.id_car_model}') reserved successfully "
f"by user {request.user}. "
@ -1288,7 +1297,7 @@ def get_finance_data(estimate,dealer):
)
discount = extra_info.data.get("discount", 0)
discount = Decimal(discount)
additional_services = car.get_additional_services()
discounted_price=(Decimal(car.marked_price) - discount)
vat_amount = discounted_price * vat.rate
@ -1915,3 +1924,416 @@ def handle_payment(request, order):
# def get_user_quota(user):
# 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}")