1121 lines
36 KiB
Python
1121 lines
36 KiB
Python
import time
|
|
import base64
|
|
import logging
|
|
import requests
|
|
from PIL import Image
|
|
from io import BytesIO
|
|
from plans.models import Plan
|
|
from django.urls import reverse
|
|
from django.conf import settings
|
|
from django.utils import timezone
|
|
from django.db import transaction
|
|
from django_ledger.io import roles
|
|
from django_q.tasks import async_task
|
|
from django.core.mail import send_mail
|
|
|
|
# from appointment.models import StaffMember
|
|
from django.utils.translation import activate
|
|
from django.core.files.base import ContentFile
|
|
from django.contrib.auth import get_user_model
|
|
from allauth.account.models import EmailAddress
|
|
from django.core.mail import EmailMultiAlternatives
|
|
|
|
# from .utils import get_accounts_data, create_account
|
|
from django.template.loader import render_to_string
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.contrib.auth.models import User, Group, Permission
|
|
from inventory.models import (
|
|
DealerSettings,
|
|
Dealer,
|
|
Schedule,
|
|
Notification,
|
|
CarReservation,
|
|
CarStatusChoices,
|
|
CarImage,
|
|
Car,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
|
|
def create_settings(pk):
|
|
instance = Dealer.objects.get(pk=pk)
|
|
|
|
DealerSettings.objects.create(
|
|
dealer=instance,
|
|
invoice_cash_account=instance.entity.get_all_accounts()
|
|
.filter(role=roles.ASSET_CA_CASH)
|
|
.first(),
|
|
invoice_prepaid_account=instance.entity.get_all_accounts()
|
|
.filter(role=roles.ASSET_CA_RECEIVABLES)
|
|
.first(),
|
|
invoice_unearned_account=instance.entity.get_all_accounts()
|
|
.filter(role=roles.LIABILITY_CL_DEFERRED_REVENUE)
|
|
.first(),
|
|
bill_cash_account=instance.entity.get_all_accounts()
|
|
.filter(role=roles.ASSET_CA_CASH)
|
|
.first(),
|
|
bill_prepaid_account=instance.entity.get_all_accounts()
|
|
.filter(role=roles.ASSET_CA_PREPAID)
|
|
.first(),
|
|
bill_unearned_account=instance.entity.get_all_accounts()
|
|
.filter(role=roles.LIABILITY_CL_ACC_PAYABLE)
|
|
.first(),
|
|
)
|
|
|
|
|
|
def create_coa_accounts(dealer_id, **kwargs):
|
|
"""
|
|
Idempotent: Creates only missing default accounts.
|
|
Safe to retry. Returns True if all done.
|
|
"""
|
|
from .models import Dealer
|
|
from .utils import get_accounts_data, create_account
|
|
|
|
try:
|
|
dealer = Dealer.objects.get(pk=dealer_id)
|
|
entity = dealer.entity
|
|
coa_slug = kwargs.get("coa_slug", None)
|
|
if not entity:
|
|
logger.error(f"❌ No entity for dealer {dealer_id}")
|
|
return False
|
|
|
|
if coa_slug:
|
|
try:
|
|
coa = entity.get_coa_model_qs().get(slug=coa_slug)
|
|
except Exception as e:
|
|
logger.error(
|
|
f"COA with slug {coa_slug} not found for entity {entity.pk}: {e}"
|
|
)
|
|
return False
|
|
else:
|
|
coa = entity.get_default_coa()
|
|
|
|
if not coa:
|
|
logger.error(f"❌ No default COA for entity {entity.pk}")
|
|
return False
|
|
|
|
# Get missing accounts
|
|
existing_codes = set(
|
|
entity.get_all_accounts()
|
|
.filter(coa_model=coa)
|
|
.values_list("code", flat=True)
|
|
)
|
|
accounts_to_create = [
|
|
acc for acc in get_accounts_data() if acc["code"] not in existing_codes
|
|
]
|
|
|
|
if not accounts_to_create:
|
|
logger.info("✅ All default accounts already exist.")
|
|
return True
|
|
|
|
# Create missing ones
|
|
logger.info(f"🔧 Creating {len(accounts_to_create)} missing accounts...")
|
|
success = True
|
|
|
|
for acc in accounts_to_create:
|
|
if not create_account(entity, coa, acc):
|
|
logger.warning(f"⚠️ Failed to create account: {acc['code']}")
|
|
success = False # don't fail task, just log
|
|
|
|
if success:
|
|
logger.info("✅ All missing accounts created successfully.")
|
|
else:
|
|
logger.warning("⚠️ Some accounts failed to create — check logs.")
|
|
|
|
return success # Django-Q will mark as failed if False
|
|
|
|
except Exception as e:
|
|
logger.error(f"💥 Task failed for dealer {dealer_id}: {e}")
|
|
raise # Let Django-Q handle retry if configured
|
|
|
|
|
|
def retry_entity_creation(dealer_id, retry_count=0):
|
|
"""
|
|
Retry entity creation if initial attempt failed
|
|
"""
|
|
from .models import Dealer
|
|
from django_ledger.models import EntityModel
|
|
|
|
max_retries = 3
|
|
|
|
if retry_count >= max_retries:
|
|
logger.error(f"Max retries reached for dealer {dealer_id}")
|
|
return
|
|
|
|
try:
|
|
instance = Dealer.objects.get(id=dealer_id)
|
|
if not instance.entity:
|
|
# Retry entity creation
|
|
entity_name = instance.user.dealer.name
|
|
entity = EntityModel.create_entity(
|
|
name=entity_name,
|
|
admin=instance.user,
|
|
use_accrual_method=True,
|
|
fy_start_month=1,
|
|
)
|
|
|
|
if entity:
|
|
instance.entity = entity
|
|
instance.save()
|
|
logger.info(f"Successfully created entity on retry {retry_count + 1}")
|
|
|
|
# Now trigger account creation
|
|
async_task(
|
|
"inventory.tasks.create_coa_accounts",
|
|
dealer_id=dealer_id,
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Retry {retry_count + 1} failed: {e}")
|
|
# Schedule another retry
|
|
async_task(
|
|
"inventory.tasks.retry_entity_creation",
|
|
dealer_id=dealer_id,
|
|
retry_count=retry_count + 1,
|
|
)
|
|
|
|
|
|
# def create_coa_accounts(**kwargs):
|
|
# logger.info("creating all accounts are created")
|
|
# instance = kwargs.get("dealer")
|
|
# logger.info(f"Dealer Instance : {instance}")
|
|
# entity = instance.entity
|
|
# coa = entity.get_default_coa()
|
|
|
|
# logger.info("Creating default accounts")
|
|
# for account_data in get_accounts_data():
|
|
# logger.info(f"Creating account: {account_data['code']}")
|
|
# create_account(entity, coa, account_data)
|
|
|
|
|
|
# def create_coa_accounts1(pk):
|
|
# with transaction.atomic():
|
|
# instance = Dealer.objects.select_for_update().get(pk=pk)
|
|
# entity = instance.entity
|
|
# coa = entity.get_default_coa()
|
|
|
|
# # Cash Account
|
|
# asset_ca_cash = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="1101",
|
|
# role=roles.ASSET_CA_CASH,
|
|
# name=_("Cash"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
# asset_ca_cash.role_default = True
|
|
# asset_ca_cash.save()
|
|
|
|
# # Accounts Receivable Account
|
|
# asset_ca_receivables = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="1102",
|
|
# role=roles.ASSET_CA_RECEIVABLES,
|
|
# name=_("Accounts Receivable"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
# asset_ca_receivables.role_default = True
|
|
# asset_ca_receivables.save()
|
|
|
|
# # Inventory Account
|
|
# asset_ca_inventory = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="1103",
|
|
# role=roles.ASSET_CA_INVENTORY,
|
|
# name=_("Inventory"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
# asset_ca_inventory.role_default = True
|
|
# asset_ca_inventory.save()
|
|
|
|
# # Prepaid Expenses Account
|
|
# asset_ca_prepaid = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="1104",
|
|
# role=roles.ASSET_CA_PREPAID,
|
|
# name=_("Prepaid Expenses"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
# asset_ca_prepaid.role_default = True
|
|
# asset_ca_prepaid.save()
|
|
|
|
# # Employee Expenses Account
|
|
# asset_ca_prepaid_employee = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="1105",
|
|
# role=roles.ASSET_CA_PREPAID,
|
|
# name=_("Employee Advance"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # VAT Payable Account
|
|
# liability_ltl_vat_receivable = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="1106",
|
|
# role=roles.ASSET_CA_RECEIVABLES,
|
|
# name=_("VAT Receivable"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Buildings Accumulated Depreciation Account
|
|
# asset_ppe_buildings_accum_depreciation = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="1201",
|
|
# role=roles.ASSET_PPE_BUILDINGS_ACCUM_DEPRECIATION,
|
|
# name=_("Buildings - Accum. Depreciation"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
# asset_ppe_buildings_accum_depreciation.role_default = True
|
|
# asset_ppe_buildings_accum_depreciation.save()
|
|
|
|
# # intangible Account
|
|
# asset_lti_land_intangable = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="1202",
|
|
# role=roles.ASSET_INTANGIBLE_ASSETS,
|
|
# name=_("Intangible Assets"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
# asset_lti_land_intangable.role_default = True
|
|
# asset_lti_land_intangable.save()
|
|
|
|
# # investment property Account
|
|
# asset_lti_land_investment = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="1204",
|
|
# role=roles.ASSET_LTI_SECURITIES,
|
|
# name=_("Investments"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
# asset_lti_land_investment.role_default = True
|
|
# asset_lti_land_investment.save()
|
|
|
|
# # # Notes Receivable Account
|
|
# # asset_lti_notes_receivable = entity.create_account(
|
|
# # coa_model=coa,
|
|
# # code="1201",
|
|
# # role=roles.ASSET_LTI_NOTES_RECEIVABLE,
|
|
# # name=_("Notes Receivable"),
|
|
# # balance_type="debit",
|
|
# # active=True,
|
|
# # )
|
|
# # asset_lti_notes_receivable.role_default = True
|
|
# # asset_lti_notes_receivable.save()
|
|
|
|
# # # Land Account
|
|
# # asset_lti_land = entity.create_account(
|
|
# # coa_model=coa,
|
|
# # code="1202",
|
|
# # role=roles.ASSET_LTI_LAND,
|
|
# # name=_("Land"),
|
|
# # balance_type="debit",
|
|
# # active=True,
|
|
# # )
|
|
# # asset_lti_land.role_default = True
|
|
# # asset_lti_land.save()
|
|
|
|
# # Buildings Account
|
|
# asset_ppe_buildings = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="1301",
|
|
# role=roles.ASSET_PPE_BUILDINGS,
|
|
# name=_("Buildings"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
# asset_ppe_buildings.role_default = True
|
|
# asset_ppe_buildings.save()
|
|
|
|
# # Accounts Payable Account
|
|
# liability_cl_acc_payable = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="2101",
|
|
# role=roles.LIABILITY_CL_ACC_PAYABLE,
|
|
# name=_("Accounts Payable"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
# liability_cl_acc_payable.role_default = True
|
|
# liability_cl_acc_payable.save()
|
|
|
|
# # Deferred Revenue Account
|
|
# liability_cl_def_rev = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="2103",
|
|
# role=roles.LIABILITY_CL_DEFERRED_REVENUE,
|
|
# name=_("Deferred Revenue"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
# liability_cl_def_rev.role_default = True
|
|
# liability_cl_def_rev.save()
|
|
|
|
# # Wages Payable Account
|
|
# liability_cl_wages_payable = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="2102",
|
|
# role=roles.LIABILITY_CL_WAGES_PAYABLE,
|
|
# name=_("Wages Payable"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
# liability_cl_wages_payable.role_default = True
|
|
# liability_cl_wages_payable.save()
|
|
|
|
# # Long-Term Notes Payable Account
|
|
# liability_ltl_notes_payable = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="2201",
|
|
# role=roles.LIABILITY_LTL_NOTES_PAYABLE,
|
|
# name=_("Long-Term Notes Payable"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
# liability_ltl_notes_payable.role_default = True
|
|
# liability_ltl_notes_payable.save()
|
|
|
|
# # VAT Payable Account
|
|
# liability_ltl_vat_payable = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="2106",
|
|
# role=roles.LIABILITY_CL_OTHER,
|
|
# name=_("VAT Payable"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # taxes Payable Account
|
|
# liability_ltl_taxes_payable = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="2107",
|
|
# role=roles.LIABILITY_CL_OTHER,
|
|
# name=_("Taxes Payable"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # social insurance Payable Account
|
|
# liability_ltl_social_insurance_payable = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="2108",
|
|
# role=roles.LIABILITY_LTL_NOTES_PAYABLE,
|
|
# name=_("Social Insurance Payable"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # End of Service Benefits
|
|
# entity.create_account(
|
|
# coa_model=coa,
|
|
# code="2202",
|
|
# role=roles.LIABILITY_LTL_NOTES_PAYABLE,
|
|
# name=_("End of Service Benefits"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Mortgage Payable Account
|
|
# liability_ltl_mortgage_payable = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="2203",
|
|
# role=roles.LIABILITY_LTL_MORTGAGE_PAYABLE,
|
|
# name=_("Mortgage Payable"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
# liability_ltl_mortgage_payable.role_default = True
|
|
# liability_ltl_mortgage_payable.save()
|
|
|
|
# # Capital
|
|
# equity_capital = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="3101",
|
|
# role=roles.EQUITY_CAPITAL,
|
|
# name=_("Registered Capital"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
# equity_capital.role_default = True
|
|
# equity_capital.save()
|
|
# entity.create_account(
|
|
# coa_model=coa,
|
|
# code="3102",
|
|
# role=roles.EQUITY_CAPITAL,
|
|
# name=_("Additional Paid-In Capital"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Other Equity
|
|
# other_equity = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="3201",
|
|
# role=roles.EQUITY_COMMON_STOCK,
|
|
# name=_("Opening Balances"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
# other_equity.role_default = True
|
|
# other_equity.save()
|
|
|
|
# # Reserves
|
|
# reserve = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="3301",
|
|
# role=roles.EQUITY_ADJUSTMENT,
|
|
# name=_("Statutory Reserve"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
# reserve.role_default = True
|
|
# reserve.save()
|
|
# entity.create_account(
|
|
# coa_model=coa,
|
|
# code="3302",
|
|
# role=roles.EQUITY_ADJUSTMENT,
|
|
# name=_("Foreign Currency Translation Reserve"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Retained Earnings Account
|
|
# equity_retained_earnings = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="3401",
|
|
# role=roles.EQUITY_PREFERRED_STOCK,
|
|
# name=_("Operating Profits and Losses"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
# equity_retained_earnings.role_default = True
|
|
# equity_retained_earnings.save()
|
|
|
|
# equity_retained_earnings_losses = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="3402",
|
|
# role=roles.EQUITY_PREFERRED_STOCK,
|
|
# name=_("Retained Earnings (or Losses)"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Sales Revenue Account
|
|
# income_operational = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="4101",
|
|
# role=roles.INCOME_OPERATIONAL,
|
|
# name=_("Sales Revenue"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
# income_operational.role_default = True
|
|
# income_operational.save()
|
|
|
|
# # Interest Income Account
|
|
# income_interest = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="4102",
|
|
# role=roles.INCOME_INTEREST,
|
|
# name=_("Interest Income"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
# income_interest.role_default = True
|
|
# income_interest.save()
|
|
|
|
# # Uneared Income Account
|
|
# income_unearned = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="4103",
|
|
# role=roles.INCOME_OTHER,
|
|
# name=_("Unearned Income"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Operating Revenues
|
|
# entity.create_account(
|
|
# coa_model=coa,
|
|
# code="4104",
|
|
# role=roles.INCOME_OPERATIONAL,
|
|
# name=_("Sales/Service Revenue"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Non-Operating Revenues
|
|
# entity.create_account(
|
|
# coa_model=coa,
|
|
# code="4201",
|
|
# role=roles.INCOME_OTHER,
|
|
# name=_("Non-Operating Revenues"),
|
|
# balance_type="credit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Cost of Goods Sold (COGS) Account
|
|
# expense_cogs = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="5101",
|
|
# role=roles.COGS,
|
|
# name=_("Cost of Goods Sold"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
# expense_cogs.role_default = True
|
|
# expense_cogs.save()
|
|
|
|
# # accrued Expenses Account
|
|
# expense_cogs = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6117",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Accrued Expenses"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # accrued salaries Account
|
|
# expense_cogs = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6118",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Accrued Salaries"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Rent Expense Account
|
|
# expense_rent = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6102",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Rent Expense"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
# # expense_rent.role_default = True
|
|
# # expense_rent.save()
|
|
|
|
# # Salaries and Administrative Fees
|
|
# expense_salaries = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6103",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Salaries and Administrative Fees"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Medical Insurance
|
|
# expense_medical_insurance = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6104",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Medical Insurance"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Marketing and Advertising Expenses
|
|
# expense_marketing = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6105",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Marketing and Advertising Expenses"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Commissions and Incentives
|
|
# expense_commissions = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6106",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Commissions and Incentives"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Travel Tickets
|
|
# expense_travel = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6107",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Travel Tickets"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Social Insurance
|
|
# expense_other = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6108",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Social Insurance"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Government Fees
|
|
# expense_other = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6109",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Government Fees"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Fees and Subscriptions
|
|
# expense_other = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6110",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Fees and Subscriptions"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Office Services Expenses
|
|
# expense_other = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6111",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Office Services Expenses"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Office Supplies and Printing
|
|
# expense_other = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6112",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Office Supplies and Printing"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Hospitality Expenses
|
|
# expense_other = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6113",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Hospitality Expenses"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Bank Commissions
|
|
# expense_other = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6114",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Bank Commissions"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Other Expenses
|
|
# expense_other = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6115",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Other Expenses"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # Transportation Expenses
|
|
# expense_other = entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6116",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Transportation Expenses"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # 5.1 Direct Costs
|
|
# entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6201",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Cost of Goods Sold"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
# entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6202",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Salaries and Wages"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
# entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6203",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Sales Commissions"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
# entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6204",
|
|
# role=roles.EXPENSE_OPERATIONAL,
|
|
# name=_("Shipping and Customs Clearance"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # 5.3 Non-Operating Expenses
|
|
# entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6301",
|
|
# role=roles.EXPENSE_OTHER,
|
|
# name=_("Zakat"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
# entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6302",
|
|
# role=roles.EXPENSE_OTHER,
|
|
# name=_("Taxes"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
# entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6303",
|
|
# role=roles.EXPENSE_OTHER,
|
|
# name=_("Foreign Currency Translation"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
# entity.create_account(
|
|
# coa_model=coa,
|
|
# code="6304",
|
|
# role=roles.EXPENSE_OTHER,
|
|
# name=_("Interest Expenses"),
|
|
# balance_type="debit",
|
|
# active=True,
|
|
# )
|
|
|
|
# # create_settings(instance.pk)
|
|
|
|
|
|
# @background
|
|
# def create_groups(instance):
|
|
# group_names = ["Inventory", "Accountant", "Sales"]
|
|
# for group_name in group_names:
|
|
# group, _ = Group.objects.get_or_create(name=f"{instance.pk}_{group_name}")
|
|
# group_manager,_ = CustomGroup.objects.get_or_create(name=group_name, dealer=instance, group=group)
|
|
# group_manager.set_default_permissions()
|
|
# instance.user.groups.add(group)
|
|
|
|
|
|
# @background
|
|
def create_accounts_for_make(dealer, makes):
|
|
entity = dealer.entity
|
|
coa = entity.get_default_coa()
|
|
|
|
name = ["Inventory", "Revenue", "Cogs"]
|
|
role = [roles.ASSET_CA_INVENTORY, roles.ASSET_CA_RECEIVABLES, roles.COGS]
|
|
balance_type = ["debit", "credit", "debit"]
|
|
|
|
for name, role, balance_type in zip(name, role, balance_type):
|
|
create_make_accounts(entity, coa, makes, name, role, balance_type)
|
|
|
|
|
|
def create_make_accounts(entity, coa, makes, name, role, balance_type):
|
|
for make in makes:
|
|
last_account = (
|
|
entity.get_all_accounts().filter(role=role).order_by("-created").first()
|
|
)
|
|
if len(last_account.code) == 4:
|
|
code = f"{int(last_account.code)}{1:03d}"
|
|
elif len(last_account.code) > 4:
|
|
code = f"{int(last_account.code) + 1}"
|
|
acc = (
|
|
entity.get_all_accounts()
|
|
.filter(
|
|
name=f"{name}:{make.name}",
|
|
role=role,
|
|
coa_model=coa,
|
|
balance_type=balance_type,
|
|
active=True,
|
|
)
|
|
.first()
|
|
)
|
|
if not acc:
|
|
acc = entity.create_account(
|
|
name=f"{name}:{make.name}",
|
|
code=code,
|
|
role=role,
|
|
coa_model=coa,
|
|
balance_type=balance_type,
|
|
active=True,
|
|
)
|
|
return acc
|
|
|
|
|
|
def send_email(from_, to_, subject, message):
|
|
subject = subject
|
|
message = message
|
|
from_email = from_
|
|
recipient_list = [to_]
|
|
async_task(send_mail, subject, message, from_email, recipient_list)
|
|
|
|
|
|
def create_user_dealer(email, password, name, arabic_name, phone, crn, vrn, address):
|
|
with transaction.atomic():
|
|
user = User.objects.create(username=email, email=email)
|
|
user.set_password(password)
|
|
user.save()
|
|
# EmailAddress.objects.create(user=user, email=email, verified=True, primary=True)
|
|
group = Group.objects.create(name=f"{user.pk}-Admin")
|
|
user.groups.add(group)
|
|
for perm in Permission.objects.filter(
|
|
content_type__app_label__in=["inventory", "django_ledger"]
|
|
):
|
|
group.permissions.add(perm)
|
|
|
|
# StaffMember.objects.create(user=user)
|
|
dealer = Dealer.objects.create(
|
|
user=user,
|
|
name=name,
|
|
arabic_name=arabic_name,
|
|
crn=crn,
|
|
vrn=vrn,
|
|
phone_number=phone,
|
|
address=address,
|
|
)
|
|
return dealer
|
|
|
|
|
|
# def create_groups(dealer_slug):
|
|
# from inventory.models import CustomGroup
|
|
# instance = Dealer.objects.get(slug=dealer_slug)
|
|
# def run():
|
|
# for group_name in ["Inventory", "Accountant", "Sales", "Manager"]:
|
|
# group, created = Group.objects.get_or_create(
|
|
# name=f"{instance.slug}_{group_name}"
|
|
# )
|
|
# group_manager, created = CustomGroup.objects.get_or_create(
|
|
# name=group_name, dealer=instance, group=group
|
|
# )
|
|
# if created:
|
|
# group_manager.set_default_permissions()
|
|
# instance.user.groups.add(group)
|
|
|
|
# transaction.on_commit(run)
|
|
|
|
|
|
def send_bilingual_reminder(user_id, plan_id, expiration_date, days_until_expire):
|
|
"""Send bilingual email reminder using Django-Q"""
|
|
try:
|
|
user = User.objects.get(id=user_id)
|
|
plan = Plan.objects.get(id=plan_id)
|
|
|
|
# Determine user language preference
|
|
user_language = getattr(user, "language", settings.LANGUAGE_CODE)
|
|
activate(user_language)
|
|
|
|
# Context data
|
|
context = {
|
|
"user": user,
|
|
"plan": plan,
|
|
"expiration_date": expiration_date,
|
|
"days_until_expire": days_until_expire,
|
|
"SITE_NAME": settings.SITE_NAME,
|
|
"RENEWAL_URL": "url", # settings.RENEWAL_URL,
|
|
"direction": "rtl" if user_language.startswith("ar") else "ltr",
|
|
}
|
|
|
|
# Subject with translation
|
|
subject_en = (
|
|
f"Your {plan.name} subscription expires in {days_until_expire} days"
|
|
)
|
|
subject_ar = f"اشتراكك في {plan.name} ينتهي خلال {days_until_expire} أيام"
|
|
|
|
# Render templates
|
|
text_content = render_to_string(
|
|
[
|
|
f"emails/expiration_reminder_{user_language}.txt",
|
|
"emails/expiration_reminder.txt",
|
|
],
|
|
context,
|
|
)
|
|
|
|
html_content = render_to_string(
|
|
[
|
|
f"emails/expiration_reminder_{user_language}.html",
|
|
"emails/expiration_reminder.html",
|
|
],
|
|
context,
|
|
)
|
|
|
|
# Create email
|
|
email = EmailMultiAlternatives(
|
|
subject=subject_ar if user_language.startswith("ar") else subject_en,
|
|
body=text_content,
|
|
from_email=settings.DEFAULT_FROM_EMAIL,
|
|
to=[user.email],
|
|
)
|
|
email.attach_alternative(html_content, "text/html")
|
|
email.send()
|
|
|
|
return f"Sent to {user.email} in {user_language}"
|
|
except Exception as e:
|
|
logger.error(f"Email failed: {str(e)}")
|
|
raise
|
|
|
|
|
|
def handle_email_result(task):
|
|
"""Callback for email results"""
|
|
if task.success:
|
|
logger.info(f"Email task succeeded: {task.result}")
|
|
else:
|
|
logger.error(f"Email task failed: {task.result}")
|
|
|
|
|
|
def send_schedule_reminder_email(schedule_id):
|
|
"""
|
|
Sends an email reminder for a specific schedule.
|
|
This function is designed to be called by django-q.
|
|
"""
|
|
try:
|
|
schedule = Schedule.objects.get(pk=schedule_id)
|
|
if schedule.completed:
|
|
logger.info("Schedule is already completed, existing.")
|
|
return
|
|
# Ensure the user has an email and the schedule is not completed/canceled
|
|
if not schedule.scheduled_by.email or schedule.status in [
|
|
"completed",
|
|
"canceled",
|
|
]:
|
|
logger.error(
|
|
f"Skipping email for Schedule ID {schedule_id}: No email or schedule status is {schedule.status}."
|
|
)
|
|
return
|
|
|
|
user_email = schedule.scheduled_by.email
|
|
Notification.objects.create(
|
|
user=schedule.scheduled_by,
|
|
message=_(
|
|
"""
|
|
Reminder: You have an appointment scheduled for {scheduled_type} After 15 minutes <a href="{url}" target="_blank">View</a>.
|
|
"""
|
|
).format(
|
|
scheduled_type=schedule.scheduled_type,
|
|
url=reverse(
|
|
"schedule_calendar", kwargs={"dealer_slug": schedule.dealer.slug}
|
|
),
|
|
),
|
|
)
|
|
# Prepare context for email templates
|
|
context = {
|
|
"schedule_purpose": schedule.purpose,
|
|
"scheduled_at": schedule.scheduled_at.astimezone(
|
|
timezone.get_current_timezone()
|
|
).strftime("%Y-%m-%d %H:%M %Z"), # Format with timezone
|
|
"schedule_type": schedule.scheduled_type,
|
|
"customer_name": schedule.customer.customer_name
|
|
if schedule.customer
|
|
else "N/A",
|
|
"notes": schedule.notes,
|
|
"user_name": schedule.scheduled_by.get_full_name()
|
|
or schedule.scheduled_by.email,
|
|
}
|
|
|
|
# Render email content from templates
|
|
html_message = render_to_string("emails/schedule_reminder.html", context)
|
|
plain_message = render_to_string("emails/schedule_reminder.txt", context)
|
|
|
|
send_mail(
|
|
f"Reminder: Your Upcoming Schedule - {schedule.purpose}",
|
|
plain_message,
|
|
settings.DEFAULT_FROM_EMAIL,
|
|
[user_email],
|
|
html_message=html_message,
|
|
)
|
|
logger.info(
|
|
f"Successfully sent reminder email for Schedule ID: {schedule_id} to {user_email}"
|
|
)
|
|
|
|
except Schedule.DoesNotExist:
|
|
logger.info(
|
|
f"Schedule with ID {schedule_id} does not exist. Cannot send reminder."
|
|
)
|
|
except Exception as e:
|
|
logger.info(f"Error sending reminder email for Schedule ID {schedule_id}: {e}")
|
|
|
|
|
|
# Optional: A hook function to log the status of the email task (add to your_app/tasks.py)
|
|
def log_email_status(task):
|
|
"""
|
|
This function will be called by django-q after the send_schedule_reminder_email task completes.
|
|
It logs whether the task was successful or not.
|
|
"""
|
|
if task.success:
|
|
logger.info(
|
|
f"Email task for Schedule ID {task.args[0]} completed successfully. Result: {task.result}"
|
|
)
|
|
else:
|
|
logger.error(
|
|
f"Email task for Schedule ID {task.args[0]} failed. Error: {task.result}"
|
|
)
|
|
|
|
|
|
def remove_reservation_by_id(reservation_id):
|
|
try:
|
|
reservation = CarReservation.objects.get(pk=reservation_id)
|
|
reservation.car.status = CarStatusChoices.AVAILABLE
|
|
reservation.car.save()
|
|
reservation.delete()
|
|
except Exception as e:
|
|
logger.error(f"Error removing reservation with ID {reservation_id}: {e}")
|
|
|
|
|
|
def test_task(**kwargs):
|
|
print("TASK : ", kwargs.get("dealer"))
|
|
|
|
|
|
def generate_car_image_task(car_image_id):
|
|
"""
|
|
Simple async task to generate car image
|
|
"""
|
|
from inventory.utils import generate_car_image_simple
|
|
|
|
try:
|
|
# car_image = CarImage.objects.get(id=car_image_id)
|
|
car = Car.objects.get(pk=car_image_id)
|
|
result = generate_car_image_simple(car)
|
|
|
|
return {
|
|
"success": result.get("success", False),
|
|
"car_image_id": car_image_id,
|
|
"error": result.get("error"),
|
|
"message": "Image generated"
|
|
if result.get("success")
|
|
else "Generation failed",
|
|
}
|
|
except Exception as e:
|
|
error_msg = f"Unexpected error: {e}"
|
|
logger.error(error_msg)
|
|
return {"success": False, "error": error_msg}
|