38086-vm/accounting/signals.py
2026-02-03 03:17:21 +00:00

149 lines
5.0 KiB
Python

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
entry = JournalEntry.objects.create(
date=getattr(obj, 'created_at', timezone.now()).date() if hasattr(obj, 'created_at') else timezone.now().date(),
description=description,
content_type=content_type,
object_id=obj.id,
reference=f"{obj.__class__.__name__} #{obj.id}"
)
for item in items:
if item['amount'] > 0:
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')
vat_acc = get_account('2100')
if not ar_acc or not sales_acc:
return
# Subtotal and VAT logic (assuming total_amount includes VAT for now as per Sale model simplicity)
# Actually Sale model has total_amount and discount.
# Let's assume total_amount is the final amount.
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()