update the lead and opportunity detail page + more fixes

This commit is contained in:
ismail 2025-06-02 14:16:45 +03:00
parent c206a018f7
commit 591bdf9234
8 changed files with 916 additions and 24 deletions

View File

@ -0,0 +1,433 @@
from django_ledger.io import roles
from django.core.management.base import BaseCommand
from django.utils.translation import gettext_lazy as _
from django_ledger.models import ChartOfAccountModel, AccountModel,EntityModel
class Command(BaseCommand):
help = 'Creates Chart of Accounts for Deepseek entity'
def handle(self, *args, **options):
"""
Create chart of accounts for automotive dealership business
"""
# Create Chart of Accounts
entity_model = EntityModel.objects.get(
name="Claude"
)
# entity_model.get_all_accounts().delete()
coa_model, created = ChartOfAccountModel.objects.get_or_create(
entity=entity_model,
name='Automotive Dealership Chart of Accounts',
)
accounts_data = [
# Current Assets
{
'code': '1010',
'name': 'Cash on Hand',
'name_arabic': 'الصندوق',
'role': roles.ASSET_CA_CASH,
'balance_type': roles.DEBIT,
'locked': True
},
{
'code': '1020',
'name': 'Bank',
'name_arabic': 'البنك',
'role': roles.ASSET_CA_CASH,
'balance_type': roles.DEBIT,
'locked': True
},
{
'code': '1030',
'name': 'Accounts Receivable',
'name_arabic': 'العملاء',
'role': roles.ASSET_CA_RECEIVABLES,
'balance_type': roles.DEBIT,
'locked': True
},
{
'code': '1040',
'name': 'Inventory (Cars)',
'name_arabic': 'مخزون السيارات',
'role': roles.ASSET_CA_INVENTORY,
'balance_type': roles.DEBIT,
'locked': True
},
{
'code': '1045',
'name': 'Spare Parts Inventory',
'name_arabic': 'مخزون قطع الغيار',
'role': roles.ASSET_CA_INVENTORY,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '1050',
'name': 'Employee Advances',
'name_arabic': 'سُلف وأمانات الموظفين',
'role': roles.ASSET_CA_RECEIVABLES,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '1060',
'name': 'Prepaid Expenses',
'name_arabic': 'مصروفات مدفوعة مقدماً',
'role': roles.ASSET_CA_PREPAID,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '1070',
'name': 'Notes Receivable',
'name_arabic': 'أوراق القبض',
'role': roles.ASSET_LTI_NOTES_RECEIVABLE,
'balance_type': roles.DEBIT,
'locked': False
},
# Fixed Assets (2000-2999)
{
'code': '2010',
'name': 'Lands',
'name_arabic': 'أراضي',
'role': roles.ASSET_LTI_LAND,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '2011',
'name': 'Buildings',
'name_arabic': 'مباني',
'role': roles.ASSET_PPE_BUILDINGS,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '2012',
'name': 'Company Vehicles',
'name_arabic': 'سيارات الشركة',
'role': roles.ASSET_PPE_EQUIPMENT,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '2013',
'name': 'Equipment & Tools',
'name_arabic': 'أجهزة ومعدات',
'role': roles.ASSET_PPE_EQUIPMENT,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '2014',
'name': 'Furniture & Fixtures',
'name_arabic': 'أثاث وديكور',
'role': roles.ASSET_PPE_EQUIPMENT,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '2015',
'name': 'Other Fixed Assets',
'name_arabic': 'أصول ثابتة أخرى',
'role': roles.ASSET_PPE_EQUIPMENT,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '2020',
'name': 'Long-term Investments',
'name_arabic': 'استثمارات طويلة الأجل',
'role': roles.ASSET_LTI_SECURITIES,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '2030',
'name': 'Intangible Assets',
'name_arabic': 'أصول غير ملموسة',
'role': roles.ASSET_INTANGIBLE_ASSETS,
'balance_type': roles.DEBIT,
'locked': False
},
# Current Liabilities (3000-3999)
{
'code': '3010',
'name': 'Accounts Payable',
'name_arabic': 'الموردين',
'role': roles.LIABILITY_CL_ACC_PAYABLE,
'balance_type': roles.CREDIT,
'locked': True
},
{
'code': '3020',
'name': 'Notes Payable',
'name_arabic': 'أوراق الدفع',
'role': roles.LIABILITY_CL_ST_NOTES_PAYABLE,
'balance_type': roles.CREDIT,
'locked': False
},
{
'code': '3030',
'name': 'Short-term Loans',
'name_arabic': 'قروض قصيرة الأجل',
'role': roles.LIABILITY_CL_ST_NOTES_PAYABLE,
'balance_type': roles.CREDIT,
'locked': False
},
{
'code': '3040',
'name': 'Employee Payables',
'name_arabic': 'السلف المستحقة',
'role': roles.LIABILITY_CL_WAGES_PAYABLE,
'balance_type': roles.CREDIT,
'locked': False
},
{
'code': '3050',
'name': 'Accrued Expenses',
'name_arabic': 'مصروفات مستحقة',
'role': roles.LIABILITY_CL_OTHER,
'balance_type': roles.CREDIT,
'locked': False
},
{
'code': '3060',
'name': 'Accrued Taxes',
'name_arabic': 'ضرائب مستحقة',
'role': roles.LIABILITY_CL_TAXES_PAYABLE,
'balance_type': roles.CREDIT,
'locked': False
},
{
'code': '3070',
'name': 'Provisions',
'name_arabic': 'مخصصات',
'role': roles.LIABILITY_CL_OTHER,
'balance_type': roles.CREDIT,
'locked': False
},
# Long-term Liabilities (4000-4999)
{
'code': '4010',
'name': 'Long-term Bank Loans',
'name_arabic': 'قروض طويلة الأجل',
'role': roles.LIABILITY_LTL_NOTES_PAYABLE,
'balance_type': roles.CREDIT,
'locked': False
},
{
'code': '4020',
'name': 'Lease Liabilities',
'name_arabic': 'التزامات تمويلية',
'role': roles.LIABILITY_LTL_NOTES_PAYABLE,
'balance_type': roles.CREDIT,
'locked': False
},
{
'code': '4030',
'name': 'Other Long-term Liabilities',
'name_arabic': 'التزامات أخرى طويلة الأجل',
'role': roles.LIABILITY_LTL_NOTES_PAYABLE,
'balance_type': roles.CREDIT,
'locked': False
},
# Equity (5000-5999)
{
'code': '5010',
'name': 'Capital',
'name_arabic': 'رأس المال',
'role': roles.EQUITY_CAPITAL,
'balance_type': roles.CREDIT,
'locked': True
},
{
'code': '5020',
'name': 'Statutory Reserve',
'name_arabic': 'الاحتياطي القانوني',
'role': roles.EQUITY_ADJUSTMENT,
'balance_type': roles.CREDIT,
'locked': False
},
{
'code': '5030',
'name': 'Retained Earnings',
'name_arabic': 'احتياطي الأرباح',
'role': roles.EQUITY_ADJUSTMENT,
'balance_type': roles.CREDIT,
'locked': False
},
{
'code': '5040',
'name': 'Profit & Loss for the Period',
'name_arabic': 'أرباح وخسائر الفترة',
'role': roles.EQUITY_ADJUSTMENT,
'balance_type': roles.CREDIT,
'locked': False
},
# Revenue (6000-6999)
{
'code': '6010',
'name': 'Car Sales',
'name_arabic': 'مبيعات السيارات',
'role': roles.INCOME_OPERATIONAL,
'balance_type': roles.CREDIT,
'locked': True
},
{
'code': '6020',
'name': 'After-Sales Services',
'name_arabic': 'إيرادات خدمات ما بعد البيع',
'role': roles.INCOME_OPERATIONAL,
'balance_type': roles.CREDIT,
'locked': False
},
{
'code': '6030',
'name': 'Car Rental Income',
'name_arabic': 'إيرادات تأجير سيارات',
'role': roles.INCOME_PASSIVE,
'balance_type': roles.CREDIT,
'locked': False
},
{
'code': '6040',
'name': 'Other Income',
'name_arabic': 'إيرادات أخرى',
'role': roles.INCOME_OTHER,
'balance_type': roles.CREDIT,
'locked': False
},
# Expenses (7000-7999)
{
'code': '7010',
'name': 'Cost of Goods Sold',
'name_arabic': 'تكلفة البضاعة المباعة',
'role': roles.COGS,
'balance_type': roles.DEBIT,
'locked': True
},
{
'code': '7015',
'name': 'Spare Parts Cost Consumed',
'name_arabic': 'تكلفة قطع الغيار المستهلكة',
'role': roles.COGS,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '7020',
'name': 'Salaries & Wages',
'name_arabic': 'رواتب وأجور',
'role': roles.EXPENSE_OPERATIONAL,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '7030',
'name': 'Rent',
'name_arabic': 'إيجار',
'role': roles.EXPENSE_OPERATIONAL,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '7040',
'name': 'Utilities',
'name_arabic': 'كهرباء ومياه',
'role': roles.EXPENSE_OPERATIONAL,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '7050',
'name': 'Advertising & Marketing',
'name_arabic': 'دعاية وإعلان',
'role': roles.EXPENSE_OPERATIONAL,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '7060',
'name': 'Maintenance',
'name_arabic': 'صيانة',
'role': roles.EXPENSE_OPERATIONAL,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '7070',
'name': 'Operating Expenses',
'name_arabic': 'مصاريف تشغيلية',
'role': roles.EXPENSE_OPERATIONAL,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '7080',
'name': 'Depreciation',
'name_arabic': 'استهلاك أصول ثابتة',
'role': roles.EXPENSE_DEPRECIATION,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '7090',
'name': 'Fees & Taxes',
'name_arabic': 'رسوم وضرائب',
'role': roles.EXPENSE_OPERATIONAL,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '7100',
'name': 'Bank Charges',
'name_arabic': 'مصاريف بنكية',
'role': roles.EXPENSE_OPERATIONAL,
'balance_type': roles.DEBIT,
'locked': False
},
{
'code': '7110',
'name': 'Other Expenses',
'name_arabic': 'مصاريف أخرى',
'role': roles.EXPENSE_OTHER,
'balance_type': roles.DEBIT,
'locked': False
}
]
# Create accounts
created_accounts = []
for account_data in accounts_data:
try:
account = entity_model.create_account(
coa_model=coa_model,
code=account_data['code'],
name=_(account_data['name']),
role=_(account_data['role']),
balance_type=_(account_data['balance_type']),
active=True
)
created_accounts.append(account)
self.stdout.write(self.style.SUCCESS(
f"Created account: {account.code} - {account.name}"
))
except Exception as e:
self.stdout.write(self.style.ERROR(
f"Error creating account {account_data['code']}: {str(e)}"
))
self.stdout.write(self.style.SUCCESS(
f"\nSuccessfully created {len(created_accounts)} accounts in Chart of Accounts"
))
return coa_model, created_accounts

View File

@ -0,0 +1,124 @@
from django.core.management.base import BaseCommand
from django_ledger.models import EntityModel, ChartOfAccountModel, AccountModel
from django.contrib.auth import get_user_model
User = get_user_model()
class Command(BaseCommand):
help = 'Creates Chart of Accounts for Deepseek entity'
def handle(self, *args, **options):
# Get or create admin user
admin_user = User.objects.filter(is_superuser=True).first()
if not admin_user:
self.stdout.write(self.style.ERROR('No admin user found!'))
return
# Get or create Deepseek entity with minimal valid fields
entity, created = EntityModel.objects.get_or_create(
name='Deepseek',
defaults={
'admin': admin_user,
'fy_start_month': 1, # January fiscal year start
'accrual_method': True # Using accrual accounting
}
)
if created:
self.stdout.write(self.style.SUCCESS('Created Deepseek entity'))
else:
self.stdout.write(self.style.SUCCESS('Deepseek entity already exists'))
# Get or create Chart of Accounts
coa, created = ChartOfAccountModel.objects.get_or_create(
slug='deepseek-coa',
defaults={
'name': 'Deepseek Chart of Accounts',
'entity': entity,
'description': 'Standard COA for Deepseek automotive business'
}
)
if created:
self.stdout.write(self.style.SUCCESS('Created Chart of Accounts'))
else:
self.stdout.write(self.style.SUCCESS('Chart of Accounts already exists'))
# Account definitions with string role literals
ACCOUNTS = [
# Assets
('1010', 'Cash on Hand', 'الصندوق', 'asset_ca_cash'),
('1020', 'Bank', 'البنك', 'asset_ca_cash'),
('1030', 'Accounts Receivable', 'العملاء', 'asset_ca_receivable'),
('1040', 'Inventory (Cars)', 'مخزون السيارات', 'asset_ca_inventory'),
('1045', 'Spare Parts Inventory', 'مخزون قطع الغيار', 'asset_ca_inventory'),
('1050', 'Employee Advances', 'سُلف وأمانات الموظفين', 'asset_ca_other'),
('1060', 'Prepaid Expenses', 'مصروفات مدفوعة مقدماً', 'asset_ca_other'),
('1070', 'Notes Receivable', 'أوراق القبض', 'asset_ca_receivable'),
('2010', 'Lands', 'أراضي', 'asset_ppe_land'),
('2011', 'Buildings', 'مباني', 'asset_ppe_building'),
('2012', 'Company Vehicles', 'سيارات الشركة', 'asset_ppe_equipment'),
('2013', 'Equipment & Tools', 'أجهزة ومعدات', 'asset_ppe_equipment'),
('2014', 'Furniture & Fixtures', 'أثاث وديكور', 'asset_ppe_equipment'),
('2015', 'Other Fixed Assets', 'أصول ثابتة أخرى', 'asset_ppe_other'),
('2020', 'Long-term Investments', 'استثمارات طويلة الأجل', 'asset_lt_investments'),
('2030', 'Intangible Assets', 'أصول غير ملموسة', 'asset_lt_intangible'),
# Liabilities
('3010', 'Accounts Payable', 'الموردين', 'liability_cl_acc_payable'),
('3020', 'Notes Payable', 'أوراق الدفع', 'liability_cl_acc_payable'),
('3030', 'Short-term Loans', 'قروض قصيرة الأجل', 'liability_cl_debt'),
('3040', 'Employee Payables', 'السلف المستحقة', 'liability_cl_acc_payable'),
('3050', 'Accrued Expenses', 'مصروفات مستحقة', 'liability_cl_acc_expense'),
('3060', 'Accrued Taxes', 'ضرائب مستحقة', 'liability_cl_acc_expense'),
('3070', 'Provisions', 'مخصصات', 'liability_cl_acc_expense'),
('4010', 'Long-term Bank Loans', 'قروض طويلة الأجل', 'liability_lt_loans'),
('4020', 'Lease Liabilities', 'التزامات تمويلية', 'liability_lt_loans'),
('4030', 'Other Long-term Liabilities', 'التزامات أخرى طويلة الأجل', 'liability_lt_other'),
# Equity
('5010', 'Capital', 'رأس المال', 'equity_capital'),
('5020', 'Statutory Reserve', 'الاحتياطي القانوني', 'equity_retained'),
('5030', 'Retained Earnings', 'احتياطي الأرباح', 'equity_retained'),
('5040', 'Profit & Loss for the Period', 'أرباح وخسائر الفترة', 'equity_earnings'),
# Income
('6010', 'Car Sales', 'مبيعات السيارات', 'income_operating'),
('6020', 'After-Sales Services', 'إيرادات خدمات ما بعد البيع', 'income_operating'),
('6030', 'Car Rental Income', 'إيرادات تأجير سيارات', 'income_operating'),
('6040', 'Other Income', 'إيرادات أخرى', 'income_other'),
# Expenses
('7010', 'Cost of Goods Sold', 'تكلفة البضاعة المباعة', 'expense_cogs'),
('7015', 'Spare Parts Cost Consumed', 'تكلفة قطع الغيار المستهلكة', 'expense_cogs'),
('7020', 'Salaries & Wages', 'رواتب وأجور', 'expense_operating'),
('7030', 'Rent', 'إيجار', 'expense_operating'),
('7040', 'Utilities', 'كهرباء ومياه', 'expense_operating'),
('7050', 'Advertising & Marketing', 'دعاية وإعلان', 'expense_operating'),
('7060', 'Maintenance', 'صيانة', 'expense_operating'),
('7070', 'Operating Expenses', 'مصاريف تشغيلية', 'expense_operating'),
('7080', 'Depreciation', 'استهلاك أصول ثابتة', 'expense_depreciation'),
('7090', 'Fees & Taxes', 'رسوم وضرائب', 'expense_operating'),
('7100', 'Bank Charges', 'مصاريف بنكية', 'expense_operating'),
('7110', 'Other Expenses', 'مصاريف أخرى', 'expense_other'),
]
# Create accounts
created_count = 0
for code, name_en, name_ar, role in ACCOUNTS:
acc, created = AccountModel.objects.get_or_create(
coa=coa,
code=code,
defaults={
'name': name_en,
'name_ar': name_ar,
'role_default': role,
'active': True,
'balance_type': 'debit' if role.startswith(('asset', 'expense')) else 'credit'
}
)
if created:
created_count += 1
self.stdout.write(self.style.SUCCESS(f'Successfully created {created_count} accounts'))
self.stdout.write(self.style.SUCCESS(f'Total accounts in COA: {len(ACCOUNTS)}'))

View File

@ -0,0 +1,127 @@
from django.core.management.base import BaseCommand, CommandError
from django_ledger.models import AccountModel, EntityModel, AccountRole, AccountType # تم التعديل هنا
class Command(BaseCommand):
help = 'ينشئ أو يحدث حسابات Django Ledger المحددة لكيان معين.'
def add_arguments(self, parser):
# إضافة وسيط لتحديد اسم الكيان
parser.add_argument(
'--entity_name',
type=str,
default='qemini', # اسم الكيان الافتراضي
help='اسم الكيان الذي سيتم ربط الحسابات به (افتراضي: qemini).'
)
def handle(self, *args, **options):
entity_name = options['entity_name']
# البحث عن الكيان بناءً على الاسم المقدم
try:
entity = EntityModel.objects.get(name__iexact=entity_name)
self.stdout.write(self.style.SUCCESS(f"تم العثور على الكيان: {entity.name}"))
except EntityModel.DoesNotExist:
raise CommandError(f"لم يتم العثور على كيان باسم '{entity_name}'. يرجى التأكد من وجوده.")
except Exception as e:
raise CommandError(f"حدث خطأ أثناء جلب الكيان '{entity_name}': {e}")
# قائمة الحسابات المراد إنشاؤها
accounts_data = [
# الأصول (مدين)
{'code': '1010', 'name': 'Cash on Hand', 'role': AccountRole.CASH, 'balance_type': AccountType.DEBIT},
{'code': '1020', 'name': 'Bank', 'role': AccountRole.CASH, 'balance_type': AccountType.DEBIT},
{'code': '1030', 'name': 'Accounts Receivable', 'role': AccountRole.RECEIVABLE, 'balance_type': AccountType.DEBIT},
{'code': '1040', 'name': 'Inventory (Cars)', 'role': AccountRole.INVENTORY, 'balance_type': AccountType.DEBIT},
{'code': '1045', 'name': 'Spare Parts Inventory', 'role': AccountRole.INVENTORY, 'balance_type': AccountType.DEBIT},
{'code': '1050', 'name': 'Employee Advances', 'role': AccountRole.OTHER_ASSET, 'balance_type': AccountType.DEBIT},
{'code': '1060', 'name': 'Prepaid Expenses', 'role': AccountRole.OTHER_ASSET, 'balance_type': AccountType.DEBIT},
{'code': '1070', 'name': 'Notes Receivable', 'role': AccountRole.RECEIVABLE, 'balance_type': AccountType.DEBIT},
{'code': '2010', 'name': 'Lands', 'role': AccountRole.FIXED_ASSET, 'balance_type': AccountType.DEBIT},
{'code': '2011', 'name': 'Buildings', 'role': AccountRole.FIXED_ASSET, 'balance_type': AccountType.DEBIT},
{'code': '2012', 'name': 'Company Vehicles', 'role': AccountRole.FIXED_ASSET, 'balance_type': AccountType.DEBIT},
{'code': '2013', 'name': 'Equipment & Tools', 'role': AccountRole.FIXED_ASSET, 'balance_type': AccountType.DEBIT},
{'code': '2014', 'name': 'Furniture & Fixtures', 'role': AccountRole.FIXED_ASSET, 'balance_type': AccountType.DEBIT},
{'code': '2015', 'name': 'Other Fixed Assets', 'role': AccountRole.FIXED_ASSET, 'balance_type': AccountType.DEBIT},
{'code': '2020', 'name': 'Long-term Investments', 'role': AccountRole.OTHER_ASSET, 'balance_type': AccountType.DEBIT},
{'code': '2030', 'name': 'Intangible Assets', 'role': AccountRole.INTANGIBLE_ASSET, 'balance_type': AccountType.DEBIT},
# الخصوم (دائن)
{'code': '3010', 'name': 'Accounts Payable', 'role': AccountRole.PAYABLE, 'balance_type': AccountType.CREDIT},
{'code': '3020', 'name': 'Notes Payable', 'role': AccountRole.PAYABLE, 'balance_type': AccountType.CREDIT},
{'code': '3030', 'name': 'Short-term Loans', 'role': AccountRole.OTHER_LIABILITY, 'balance_type': AccountType.CREDIT},
{'code': '3040', 'name': 'Employee Payables', 'role': AccountRole.OTHER_LIABILITY, 'balance_type': AccountType.CREDIT},
{'code': '3050', 'name': 'Accrued Expenses', 'role': AccountRole.OTHER_LIABILITY, 'balance_type': AccountType.CREDIT},
{'code': '3060', 'name': 'Accrued Taxes', 'role': AccountRole.OTHER_LIABILITY, 'balance_type': AccountType.CREDIT},
{'code': '3070', 'name': 'Provisions', 'role': AccountRole.PROVISION, 'balance_type': AccountType.CREDIT},
{'code': '4010', 'name': 'Long-term Bank Loans', 'role': AccountRole.LONG_TERM_DEBT, 'balance_type': AccountType.CREDIT},
{'code': '4020', 'name': 'Lease Liabilities', 'role': AccountRole.LONG_TERM_DEBT, 'balance_type': AccountType.CREDIT},
{'code': '4030', 'name': 'Other Long-term Liabilities', 'role': AccountRole.LONG_TERM_DEBT, 'balance_type': AccountType.CREDIT},
# حقوق الملكية (دائن)
{'code': '5010', 'name': 'Capital', 'role': AccountRole.EQUITY, 'balance_type': AccountType.CREDIT},
{'code': '5020', 'name': 'Statutory Reserve', 'role': AccountRole.RETAINED_EARNINGS, 'balance_type': AccountType.CREDIT},
{'code': '5030', 'name': 'Retained Earnings', 'role': AccountRole.RETAINED_EARNINGS, 'balance_type': AccountType.CREDIT},
{'code': '5040', 'name': 'Profit & Loss for the Period', 'role': AccountRole.RETAINED_EARNINGS, 'balance_type': AccountType.CREDIT},
# الإيرادات (دائن)
{'code': '6010', 'name': 'Car Sales', 'role': AccountRole.SALES, 'balance_type': AccountType.CREDIT},
{'code': '6020', 'name': 'After-Sales Services', 'role': AccountRole.SALES, 'balance_type': AccountType.CREDIT},
{'code': '6030', 'name': 'Car Rental Income', 'role': AccountRole.SALES, 'balance_type': AccountType.CREDIT},
{'code': '6040', 'name': 'Other Income', 'role': AccountRole.OTHER_INCOME, 'balance_type': AccountType.CREDIT},
# المصروفات (مدين)
{'code': '7010', 'name': 'Cost of Goods Sold', 'role': AccountRole.COST_OF_GOODS_SOLD, 'balance_type': AccountType.DEBIT},
{'code': '7015', 'name': 'Spare Parts Cost Consumed', 'role': AccountRole.COST_OF_GOODS_SOLD, 'balance_type': AccountType.DEBIT},
{'code': '7020', 'name': 'Salaries & Wages', 'role': AccountRole.OPERATING_EXPENSE, 'balance_type': AccountType.DEBIT},
{'code': '7030', 'name': 'Rent', 'role': AccountRole.OPERATING_EXPENSE, 'balance_type': AccountType.DEBIT},
{'code': '7040', 'name': 'Utilities', 'role': AccountRole.OPERATING_EXPENSE, 'balance_type': AccountType.DEBIT},
{'code': '7050', 'name': 'Advertising & Marketing', 'role': AccountRole.OPERATING_EXPENSE, 'balance_type': AccountType.DEBIT},
{'code': '7060', 'name': 'Maintenance', 'role': AccountRole.OPERATING_EXPENSE, 'balance_type': AccountType.DEBIT},
{'code': '7070', 'name': 'Operating Expenses', 'role': AccountRole.OPERATING_EXPENSE, 'balance_type': AccountType.DEBIT},
{'code': '7080', 'name': 'Depreciation', 'role': AccountRole.DEPRECIATION_EXPENSE, 'balance_type': AccountType.DEBIT},
{'code': '7090', 'name': 'Fees & Taxes', 'role': AccountRole.OTHER_EXPENSE, 'balance_type': AccountType.DEBIT},
{'code': '7100', 'name': 'Bank Charges', 'role': AccountRole.OTHER_EXPENSE, 'balance_type': AccountType.DEBIT},
{'code': '7110', 'name': 'Other Expenses', 'role': AccountRole.OTHER_EXPENSE, 'balance_type': AccountType.DEBIT},
]
created_count = 0
updated_count = 0
for acc_data in accounts_data:
try:
# البحث عن الحساب الموجود أو إنشاء حساب جديد
account, created = AccountModel.objects.get_or_create(
entity=entity,
code=acc_data['code'],
defaults={
'name': acc_data['name'],
'role': acc_data['role'], # تم التعديل هنا
'balance_type': acc_data['balance_type'] # تم التعديل هنا
}
)
if created:
created_count += 1
self.stdout.write(self.style.SUCCESS(f"تم إنشاء الحساب: {account.code} - {account.name}"))
else:
# إذا كان الحساب موجودًا، نقوم بتحديث اسمه ودوره ونوع رصيده إذا كان مختلفًا
if (account.name != acc_data['name'] or
account.role != acc_data['role'] or # تم التعديل هنا
account.balance_type != acc_data['balance_type']): # تم التعديل هنا
account.name = acc_data['name']
account.role = acc_data['role']
account.balance_type = acc_data['balance_type']
account.save()
updated_count += 1
self.stdout.write(self.style.WARNING(f"تم تحديث الحساب: {account.code} - {account.name}"))
else:
self.stdout.write(f"الحساب موجود بالفعل ولم يتطلب تحديث: {account.code} - {account.name}")
except Exception as e:
self.stdout.write(self.style.ERROR(f"خطأ في معالجة الحساب {acc_data['code']} - {acc_data['name']}: {e}"))
self.stdout.write(self.style.SUCCESS(f"\nعملية إنشاء/تحديث الحسابات اكتملت."))
self.stdout.write(self.style.SUCCESS(f"عدد الحسابات التي تم إنشاؤها حديثًا: {created_count}"))
self.stdout.write(self.style.SUCCESS(f"عدد الحسابات التي تم تحديثها: {updated_count}"))

View File

@ -0,0 +1,123 @@
from django.core.management.base import BaseCommand
from django_ledger.models.entity import EntityModel
from django_ledger.models.chart_of_accounts import ChartOfAccountModel
from django_ledger.models.accounts import AccountModel
from django_ledger.models.roles import RoleModel
from django_ledger.models.balance_types import BalanceTypeModel
class Command(BaseCommand):
help = 'Create the chart of accounts for the ChatGPT entity'
def handle(self, *args, **options):
# Step 1: Retrieve or create the entity
entity, created = EntityModel.objects.get_or_create(name="chatgpt")
if created:
self.stdout.write(self.style.SUCCESS("Entity 'chatgpt' created."))
else:
self.stdout.write(self.style.WARNING("Entity 'chatgpt' already exists."))
# Step 2: Retrieve or create the default Chart of Accounts
if hasattr(entity, 'default_coa') and entity.default_coa:
coa = entity.default_coa
self.stdout.write(self.style.WARNING("Default Chart of Accounts already exists."))
else:
coa = ChartOfAccountModel.objects.create(name="Default CoA", entity=entity)
entity.default_coa = coa
entity.save()
self.stdout.write(self.style.SUCCESS("Default Chart of Accounts created."))
# Step 3: Define the accounts to be created
accounts = [
(1010, 'Cash on Hand', 'الصندوق', 'asset'),
(1020, 'Bank', 'البنك', 'asset'),
(1030, 'Accounts Receivable', 'العملاء', 'asset'),
(1040, 'Inventory (Cars)', 'مخزون السيارات', 'asset'),
(1045, 'Spare Parts Inventory', 'مخزون قطع الغيار', 'asset'),
(1050, 'Employee Advances', 'سُلف وأمانات الموظفين', 'asset'),
(1060, 'Prepaid Expenses', 'مصروفات مدفوعة مقدماً', 'asset'),
(1070, 'Notes Receivable', 'أوراق القبض', 'asset'),
(2010, 'Lands', 'أراضي', 'asset'),
(2011, 'Buildings', 'مباني', 'asset'),
(2012, 'Company Vehicles', 'سيارات الشركة', 'asset'),
(2013, 'Equipment & Tools', 'أجهزة ومعدات', 'asset'),
(2014, 'Furniture & Fixtures', 'أثاث وديكور', 'asset'),
(2015, 'Other Fixed Assets', 'أصول ثابتة أخرى', 'asset'),
(2020, 'Long-term Investments', 'استثمارات طويلة الأجل', 'asset'),
(2030, 'Intangible Assets', 'أصول غير ملموسة', 'asset'),
(3010, 'Accounts Payable', 'الموردين', 'liability'),
(3020, 'Notes Payable', 'أوراق الدفع', 'liability'),
(3030, 'Short-term Loans', 'قروض قصيرة الأجل', 'liability'),
(3040, 'Employee Payables', 'السلف المستحقة', 'liability'),
(3050, 'Accrued Expenses', 'مصروفات مستحقة', 'liability'),
(3060, 'Accrued Taxes', 'ضرائب مستحقة', 'liability'),
(3070, 'Provisions', 'مخصصات', 'liability'),
(4010, 'Long-term Bank Loans', 'قروض طويلة الأجل', 'liability'),
(4020, 'Lease Liabilities', 'التزامات تمويلية', 'liability'),
(4030, 'Other Long-term Liabilities', 'التزامات أخرى طويلة الأجل', 'liability'),
(5010, 'Capital', 'رأس المال', 'equity'),
(5020, 'Statutory Reserve', 'الاحتياطي القانوني', 'equity'),
(5030, 'Retained Earnings', 'احتياطي الأرباح', 'equity'),
(5040, 'Profit & Loss for the Period', 'أرباح وخسائر الفترة', 'equity'),
(6010, 'Car Sales', 'مبيعات السيارات', 'income'),
(6020, 'After-Sales Services', 'إيرادات خدمات ما بعد البيع', 'income'),
(6030, 'Car Rental Income', 'إيرادات تأجير سيارات', 'income'),
(6040, 'Other Income', 'إيرادات أخرى', 'income'),
(7010, 'Cost of Goods Sold', 'تكلفة البضاعة المباعة', 'expense'),
(7015, 'Spare Parts Cost Consumed', 'تكلفة قطع الغيار المستهلكة', 'expense'),
(7020, 'Salaries & Wages', 'رواتب وأجور', 'expense'),
(7030, 'Rent', 'إيجار', 'expense'),
(7040, 'Utilities', 'كهرباء ومياه', 'expense'),
(7050, 'Advertising & Marketing', 'دعاية وإعلان', 'expense'),
(7060, 'Maintenance', 'صيانة', 'expense'),
(7070, 'Operating Expenses', 'مصاريف تشغيلية', 'expense'),
(7080, 'Depreciation', 'استهلاك أصول ثابتة', 'expense'),
(7090, 'Fees & Taxes', 'رسوم وضرائب', 'expense'),
(7100, 'Bank Charges', 'مصاريف بنكية', 'expense'),
(7110, 'Other Expenses', 'مصاريف أخرى', 'expense'),
]
# Mapping for role and balance_type
role_mapping = {
'asset': 'ASSET',
'liability': 'LIABILITY',
'equity': 'EQUITY',
'income': 'INCOME',
'expense': 'EXPENSE',
}
balance_type_mapping = {
'ASSET': 'DEBIT',
'LIABILITY': 'CREDIT',
'EQUITY': 'CREDIT',
'INCOME': 'CREDIT',
'EXPENSE': 'DEBIT',
}
# Step 4: Delete existing accounts in the CoA
existing_accounts = AccountModel.objects.filter(coa_model=coa)
count_deleted = existing_accounts.count()
existing_accounts.delete()
self.stdout.write(self.style.SUCCESS(f"Deleted {count_deleted} existing accounts."))
# Step 5: Create new accounts
for code, name_en, name_ar, role_slug in accounts:
role = RoleModel.objects.get(slug=role_slug)
balance_type = BalanceTypeModel.objects.get(role=role)
parent = None # Set to None for root accounts
# Determine parent-child relationships
if code in [1040, 1045, 2012, 2013, 2014, 2015, 2020, 2030]:
parent_code = code - 10 # Example logic for determining parent code
parent = AccountModel.objects.get(coa_model=coa, code=parent_code)
account = AccountModel.objects.create(
coa_model=coa,
code=code,
name=name_en,
arabic_name=name_ar,
role=role,
balance_type=balance_type,
parent=parent,
depth=parent.depth + 1 if parent else 0,
)
self.stdout.write(self.style.SUCCESS(f"Account {name_en} ({code}) created."))

View File

@ -0,0 +1,76 @@
from django.core.management.base import BaseCommand
from django_ledger.models import AccountModel
from django_ledger.constants import ACCOUNT_TYPE_ASSET, ACCOUNT_TYPE_LIABILITY, ACCOUNT_TYPE_EQUITY, ACCOUNT_TYPE_INCOME, ACCOUNT_TYPE_EXPENSE
class Command(BaseCommand):
help = 'Creates default accounts for the entity "qwen" in Django Ledger'
def handle(self, *args, **kwargs):
self.stdout.write('Creating accounts for entity "qwen"...')
accounts_data = [
# Assets (Debit Balance)
{'code': '1010', 'name_ar': 'الصندوق', 'name_en': 'Cash on Hand', 'type': ACCOUNT_TYPE_ASSET},
{'code': '1020', 'name_ar': 'البنك', 'name_en': 'Bank', 'type': ACCOUNT_TYPE_ASSET},
{'code': '1030', 'name_ar': 'العملاء', 'name_en': 'Accounts Receivable', 'type': ACCOUNT_TYPE_ASSET},
{'code': '1040', 'name_ar': 'مخزون السيارات', 'name_en': 'Inventory (Cars)', 'type': ACCOUNT_TYPE_ASSET},
{'code': '1045', 'name_ar': 'مخزون قطع الغيار', 'name_en': 'Spare Parts Inventory', 'type': ACCOUNT_TYPE_ASSET},
{'code': '1050', 'name_ar': 'سُلف وأمانات الموظفين', 'name_en': 'Employee Advances', 'type': ACCOUNT_TYPE_ASSET},
{'code': '1060', 'name_ar': 'مصروفات مدفوعة مقدماً', 'name_en': 'Prepaid Expenses', 'type': ACCOUNT_TYPE_ASSET},
{'code': '1070', 'name_ar': 'أوراق القبض', 'name_en': 'Notes Receivable', 'type': ACCOUNT_TYPE_ASSET},
# Liabilities (Credit Balance)
{'code': '3010', 'name_ar': 'الموردين', 'name_en': 'Accounts Payable', 'type': ACCOUNT_TYPE_LIABILITY},
{'code': '3020', 'name_ar': 'أوراق الدفع', 'name_en': 'Notes Payable', 'type': ACCOUNT_TYPE_LIABILITY},
{'code': '3030', 'name_ar': 'قروض قصيرة الأجل', 'name_en': 'Short-term Loans', 'type': ACCOUNT_TYPE_LIABILITY},
{'code': '3040', 'name_ar': 'السلف المستحقة', 'name_en': 'Employee Payables', 'type': ACCOUNT_TYPE_LIABILITY},
{'code': '3050', 'name_ar': 'مصروفات مستحقة', 'name_en': 'Accrued Expenses', 'type': ACCOUNT_TYPE_LIABILITY},
{'code': '3060', 'name_ar': 'ضرائب مستحقة', 'name_en': 'Accrued Taxes', 'type': ACCOUNT_TYPE_LIABILITY},
{'code': '3070', 'name_ar': 'مخصصات', 'name_en': 'Provisions', 'type': ACCOUNT_TYPE_LIABILITY},
# Equity (Credit Balance)
{'code': '5010', 'name_ar': 'رأس المال', 'name_en': 'Capital', 'type': ACCOUNT_TYPE_EQUITY},
{'code': '5020', 'name_ar': 'الاحتياطي القانوني', 'name_en': 'Statutory Reserve', 'type': ACCOUNT_TYPE_EQUITY},
{'code': '5030', 'name_ar': 'احتياطي الأرباح', 'name_en': 'Retained Earnings', 'type': ACCOUNT_TYPE_EQUITY},
{'code': '5040', 'name_ar': 'أرباح وخسائر الفترة', 'name_en': 'Profit & Loss for the Period', 'type': ACCOUNT_TYPE_EQUITY},
# Income (Revenue) (Credit Balance)
{'code': '6010', 'name_ar': 'مبيعات السيارات', 'name_en': 'Car Sales', 'type': ACCOUNT_TYPE_INCOME},
{'code': '6020', 'name_ar': 'إيرادات خدمات ما بعد البيع', 'name_en': 'After-Sales Services', 'type': ACCOUNT_TYPE_INCOME},
{'code': '6030', 'name_ar': 'إيرادات تأجير سيارات', 'name_en': 'Car Rental Income', 'type': ACCOUNT_TYPE_INCOME},
{'code': '6040', 'name_ar': 'إيرادات أخرى', 'name_en': 'Other Income', 'type': ACCOUNT_TYPE_INCOME},
# Expenses (Debit Balance)
{'code': '7010', 'name_ar': 'تكلفة البضاعة المباعة', 'name_en': 'Cost of Goods Sold', 'type': ACCOUNT_TYPE_EXPENSE},
{'code': '7015', 'name_ar': 'تكلفة قطع الغيار المستهلكة', 'name_en': 'Spare Parts Cost Consumed', 'type': ACCOUNT_TYPE_EXPENSE},
{'code': '7020', 'name_ar': 'رواتب وأجور', 'name_en': 'Salaries & Wages', 'type': ACCOUNT_TYPE_EXPENSE},
{'code': '7030', 'name_ar': 'إيجار', 'name_en': 'Rent', 'type': ACCOUNT_TYPE_EXPENSE},
{'code': '7040', 'name_ar': 'كهرباء ومياه', 'name_en': 'Utilities', 'type': ACCOUNT_TYPE_EXPENSE},
{'code': '7050', 'name_ar': 'دعاية وإعلان', 'name_en': 'Advertising & Marketing', 'type': ACCOUNT_TYPE_EXPENSE},
{'code': '7060', 'name_ar': 'صيانة', 'name_en': 'Maintenance', 'type': ACCOUNT_TYPE_EXPENSE},
{'code': '7070', 'name_ar': 'مصاريف تشغيلية', 'name_en': 'Operating Expenses', 'type': ACCOUNT_TYPE_EXPENSE},
{'code': '7080', 'name_ar': 'استهلاك أصول ثابتة', 'name_en': 'Depreciation', 'type': ACCOUNT_TYPE_EXPENSE},
{'code': '7090', 'name_ar': 'رسوم وضرائب', 'name_en': 'Fees & Taxes', 'type': ACCOUNT_TYPE_EXPENSE},
{'code': '7100', 'name_ar': 'مصاريف بنكية', 'name_en': 'Bank Charges', 'type': ACCOUNT_TYPE_EXPENSE},
{'code': '7110', 'name_ar': 'مصاريف أخرى', 'name_en': 'Other Expenses', 'type': ACCOUNT_TYPE_EXPENSE},
]
created_count = 0
for acc in accounts_data:
account, created = objects.get_or_create(
code=acc['code'],
defaults={
'name': acc['name_ar'],
'name_en': acc['name_en'],
'account_type': acc['type'],
'balance_type': BALANCE_TYPE_CREDIT if acc['type'] in [
ACCOUNT_TYPE_LIABILITY,
ACCOUNT_TYPE_EQUITY,
ACCOUNT_TYPE_INCOME
] else BALANCE_TYPE_DEBIT
}
)
if created:
created_count += 1
self.stdout.write(self.style.SUCCESS(f'Successfully created {created_count} accounts for "qwen".'))

View File

@ -1965,6 +1965,10 @@ class Opportunity(models.Model):
def get_tasks(self):
return self._get_filter(Tasks)
def get_meetings(self):
return self.lead.get_meetings()
def get_calls(self):
return self.lead.get_calls()
def get_schedules(self):
return self.lead.get_all_schedules().filter(
scheduled_at__gt=timezone.now()

View File

@ -102,6 +102,21 @@
</div>
</div>
</div>
<div class="card mb-2">
<div class="card-body">
<div class="row align-items-center g-3 text-center text-xxl-start">
<div class="col-6 col-sm-auto d-flex flex-column align-items-center text-center">
<h5 class="fw-bolder mb-2 text-body-highlight">{{ _("Related Records") }}</h5>
<h6 class="fw-bolder mb-2 text-body-highlight">{{ _("Opportunity") }}</h6>
{% if lead.opportunity %}
<a href="{% url 'opportunity_detail' lead.opportunity.slug %}" class="">{{ lead.opportunity }}</a>
{% else %}
<p>{{ _("No Opportunity") }}</p>
{% endif %}
</div>
</div>
</div>
</div>
<div class="card mb-2">
<div class="card-body">
<div class="mb-3">
@ -469,25 +484,7 @@
</thead>
<tbody class="list" id="all-tasks-table-body">
{% for task in tasks %}
<tr class="task-{{task.pk}} hover-actions-trigger btn-reveal-trigger position-static {% if task.completed %}completed-task{% endif %}">
<td class="fs-9 align-middle px-0 py-3">
<div class="form-check mb-0 fs-8">
<input class="form-check-input" type="checkbox" hx-post="{% url 'update_task' 'lead' task.pk %}" hx-trigger="change" hx-swap="outerHTML" hx-target=".task-{{task.pk}}" hx-select=".task-{{task.pk}}" />
</div>
</td>
<td class="subject order align-middle white-space-nowrap py-2 ps-0"><a class="fw-semibold text-primary" href="">{{task.title}}</a>
<div class="fs-10 d-block">{{task.description}}</div>
</td>
<td class="sent align-middle white-space-nowrap text-start fw-bold text-body-tertiary py-2">{{task.assigned_to}}</td>
<td class="date align-middle white-space-nowrap text-body py-2">{{task.created|naturalday|capfirst}}</td>
<td class="date align-middle white-space-nowrap text-body py-2">
{% if task.completed %}
<span class="badge badge-phoenix fs-10 badge-phoenix-success"><i class="fa-solid fa-check"></i></span>
{% else %}
<span class="badge badge-phoenix fs-10 badge-phoenix-warning"><i class="fa-solid fa-xmark"></i></span>
{% endif %}
</td>
</tr>
{% include "partials/task.html" %}
{% endfor %}
</tbody>
</table>

View File

@ -99,6 +99,16 @@
<h4 class="mb-5 d-flex align-items-center"><span class="d-inline-block lh-sm me-1" data-feather="link" style="height:16px;width:16px;"></span> {{ _("Related Records")}}</h4>
<div class="row g-3">
<div class="col-12">
<div class="mb-4">
<div class="d-flex flex-wrap justify-content-between mb-2">
<h5 class="mb-0 text-body-highlight me-2">{{ _("Lead") }}</h5>
</div>
{% if opportunity.lead %}
<a class="dropdown-item d-flex align-items-center" href="{% url 'lead_detail' opportunity.lead.slug %}" target="_blank">{{ _("View Lead")}}&nbsp <span class="d-inline-block lh-sm me-2" data-feather="external-link" style="height:16px;width:16px;"></span></a>
{% else %}
<p>{{ _("No Lead") }}</p>
{% endif %}
</div>
<div class="mb-4">
<div class="d-flex flex-wrap justify-content-between mb-2">
<h5 class="mb-0 text-body-highlight me-2">{{ _("Estimate") }}</h5>
@ -432,7 +442,7 @@
</div>
</div>
<div class="row g-3">
{% for metting in opportunity.lead.get_meetings %}
{% for metting in opportunity.get_meetings %}
<div class="col-xxl-6">
<div class="card h-100">
<div class="card-body">
@ -454,12 +464,10 @@
<div class="col-auto d-flex flex-1">
<h2 class="mb-0">Call</h2>
</div>
<div class="col-auto">
<a href="{% url 'schedule_lead' opportunity.lead.slug %}" class="btn btn-primary"><span class="fa-solid fa-plus me-2"></span>Add Call</a>
</div>
</div>
<pre>{{opportunity.get_all_notes}}</pre>
<div class="border-top border-bottom border-translucent" id="leadDetailsTable" data-list='{"valueNames":["name","description","create_date","create_by","last_activity"],"page":5,"pagination":true}'>
<div class="table-responsive scrollbar mx-n1 px-1">
<table class="table fs-9 mb-0">
@ -471,7 +479,7 @@
</tr>
</thead>
<tbody class="list" id="lead-details-table-body">
{% for call in opportunity.lead.get_calls %}
{% for call in opportunity.get_calls %}
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="description align-middle white-space-nowrap text-start fw-bold text-body-tertiary py-2 pe-6">{{call.purpose}}</td>
<td class="create_date text-end align-middle white-space-nowrap text-body py-2">{{call.scheduled_by}}</td>
@ -543,7 +551,7 @@
</tr>
</thead>
<tbody class="list" id="all-email-table-body">
{% for email in opportunity.lead.get_emails %}
{% for email in opportunity.get_emails %}
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="fs-9 align-middle px-0 py-3">
<div class="form-check mb-0 fs-8">