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(**kwargs): logger.info("creating all accounts are created") instance = kwargs.get("dealer") entity = instance.entity coa = entity.get_default_coa() for account_data in get_accounts_data(): 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) # 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}