168 lines
5.3 KiB
Python
168 lines
5.3 KiB
Python
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()
|