haikal/inventory/tasks.py
2025-09-24 11:07:31 +03:00

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}