import base64 import logging 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 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"))