from django.utils import timezone from django.db.models.signals import post_save, post_delete from django.dispatch import receiver from core.models import Sale, SalePayment, Purchase, PurchasePayment, Expense, SaleReturn, PurchaseReturn from .models import Account, JournalEntry, JournalItem from django.contrib.contenttypes.models import ContentType from decimal import Decimal def get_account(code): try: return Account.objects.get(code=code) except Account.DoesNotExist: return None def create_journal_entry(obj, description, items): """ items: list of dicts {'account': Account, 'type': 'debit'/'credit', 'amount': Decimal} """ content_type = ContentType.objects.get_for_model(obj) # Delete existing entry if any (to handle updates) JournalEntry.objects.filter(content_type=content_type, object_id=obj.id).delete() if not items: return None # Filter out items with 0 amount valid_items = [] for item in items: try: # Ensure amount is Decimal amount = Decimal(str(item['amount'])) if amount > 0: item['amount'] = amount valid_items.append(item) except: continue if not valid_items: return None # Determine Entry Date entry_date = timezone.now().date() if hasattr(obj, 'date'): entry_date = obj.date elif hasattr(obj, 'payment_date'): entry_date = obj.payment_date elif hasattr(obj, 'created_at'): entry_date = obj.created_at.date() entry = JournalEntry.objects.create( date=entry_date, description=description, content_type=content_type, object_id=obj.id, reference=f"{obj.__class__.__name__} #{obj.id}" ) for item in valid_items: JournalItem.objects.create( entry=entry, account=item['account'], type=item['type'], amount=item['amount'] ) return entry @receiver(post_save, sender=Sale) def sale_accounting_handler(sender, instance, created, **kwargs): # Sale (Invoice) Entry # Debit: Accounts Receivable (1200) # Credit: Sales Revenue (4000) # Credit: VAT Payable (2100) (if applicable) ar_acc = get_account('1200') sales_acc = get_account('4000') if not ar_acc or not sales_acc: return items = [ {'account': ar_acc, 'type': 'debit', 'amount': instance.total_amount}, {'account': sales_acc, 'type': 'credit', 'amount': instance.total_amount}, ] create_journal_entry(instance, f"Sale Invoice #{instance.id}", items) @receiver(post_save, sender=SalePayment) def sale_payment_accounting_handler(sender, instance, created, **kwargs): # Debit: Cash (1000) # Credit: Accounts Receivable (1200) cash_acc = get_account('1000') ar_acc = get_account('1200') if not cash_acc or not ar_acc: return items = [ {'account': cash_acc, 'type': 'debit', 'amount': instance.amount}, {'account': ar_acc, 'type': 'credit', 'amount': instance.amount}, ] create_journal_entry(instance, f"Payment for Sale #{instance.sale.id}", items) @receiver(post_save, sender=Purchase) def purchase_accounting_handler(sender, instance, created, **kwargs): # Debit: Inventory (1300) # Credit: Accounts Payable (2000) inv_acc = get_account('1300') ap_acc = get_account('2000') if not inv_acc or not ap_acc: return items = [ {'account': inv_acc, 'type': 'debit', 'amount': instance.total_amount}, {'account': ap_acc, 'type': 'credit', 'amount': instance.total_amount}, ] create_journal_entry(instance, f"Purchase Invoice #{instance.id}", items) @receiver(post_save, sender=PurchasePayment) def purchase_payment_accounting_handler(sender, instance, created, **kwargs): # Debit: Accounts Payable (2000) # Credit: Cash (1000) ap_acc = get_account('2000') cash_acc = get_account('1000') if not ap_acc or not cash_acc: return items = [ {'account': ap_acc, 'type': 'debit', 'amount': instance.amount}, {'account': cash_acc, 'type': 'credit', 'amount': instance.amount}, ] create_journal_entry(instance, f"Payment for Purchase #{instance.purchase.id}", items) @receiver(post_save, sender=Expense) def expense_accounting_handler(sender, instance, created, **kwargs): # Debit: Expense Account # Credit: Cash (1000) expense_acc = instance.category.accounting_account or get_account('5400') # Default to General Expense cash_acc = get_account('1000') if not expense_acc or not cash_acc: return items = [ {'account': expense_acc, 'type': 'debit', 'amount': instance.amount}, {'account': cash_acc, 'type': 'credit', 'amount': instance.amount}, ] create_journal_entry(instance, f"Expense: {instance.category.name_en}", items) @receiver(post_delete, sender=Sale) @receiver(post_delete, sender=SalePayment) @receiver(post_delete, sender=Purchase) @receiver(post_delete, sender=PurchasePayment) @receiver(post_delete, sender=Expense) def delete_accounting_entry(sender, instance, **kwargs): content_type = ContentType.objects.get_for_model(instance) JournalEntry.objects.filter(content_type=content_type, object_id=instance.id).delete()