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 View. """ ).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}