from django.db import models

from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
import re

from human_resource.models import PayrollSheet, StaffProfile
from procurement.models import ProductPurchaseInstance, PurchaseOrder, Supplier
from sales_and_marketing.models import CustomerOrder, LandedCostInstance, OrderItemReturn
from system_administration.models import CompanyBranch, CompanyProfile


# ---------------------------------------------------------------------------
# ACCOUNT TYPE SYSTEM  (mirrors the 16-type structure from Ocean ERP)
# ---------------------------------------------------------------------------

ACCOUNT_TYPE_CHOICES = [
    ("accounts_receivable", "Accounts Receivable"),
    ("current_assets", "Current Assets"),
    ("cash_and_cash_equivalents", "Cash and Cash Equivalents"),
    ("fixed_assets", "Fixed Assets"),
    ("non_current_assets", "Non-Current Assets"),
    ("accounts_payable", "Accounts Payable"),
    ("credit_card", "Credit Card"),
    ("current_liabilities", "Current Liabilities"),
    ("non_current_liabilities", "Non-Current Liabilities"),
    ("owners_equity", "Owner's Equity"),
    ("income", "Income"),
    ("other_income", "Other Income"),
    ("cost_of_sales", "Cost of Sales"),
    ("expenses", "Expenses"),
    ("other_expense", "Other Expense"),
    ("bank", "Bank"),
]

NORMAL_BALANCE_CHOICES = [("debit", "Debit"), ("credit", "Credit")]

FINANCIAL_STATEMENT_CHOICES = [
    ("balance_sheet", "Balance Sheet"),
    ("income_statement", "Income Statement"),
]

ACCOUNT_CATEGORY_MAP = {
    "accounts_receivable": ("balance_sheet", "debit"),
    "current_assets": ("balance_sheet", "debit"),
    "cash_and_cash_equivalents": ("balance_sheet", "debit"),
    "fixed_assets": ("balance_sheet", "debit"),
    "non_current_assets": ("balance_sheet", "debit"),
    "accounts_payable": ("balance_sheet", "credit"),
    "credit_card": ("balance_sheet", "credit"),
    "current_liabilities": ("balance_sheet", "credit"),
    "non_current_liabilities": ("balance_sheet", "credit"),
    "owners_equity": ("balance_sheet", "credit"),
    "income": ("income_statement", "credit"),
    "other_income": ("income_statement", "credit"),
    "cost_of_sales": ("income_statement", "debit"),
    "expenses": ("income_statement", "debit"),
    "other_expense": ("income_statement", "debit"),
    "bank": ("balance_sheet", "debit"),
}


class AccountType(models.Model):
    name = models.CharField(max_length=100, unique=True)
    type_key = models.CharField(
        max_length=50, choices=ACCOUNT_TYPE_CHOICES, unique=True)
    financial_statement = models.CharField(
        max_length=20, choices=FINANCIAL_STATEMENT_CHOICES, default="balance_sheet")
    normal_balance = models.CharField(
        max_length=10, choices=NORMAL_BALANCE_CHOICES, default="debit")
    created_on = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.name


class AccountTypeDetail(models.Model):
    account_type = models.ForeignKey(
        AccountType, on_delete=models.CASCADE, related_name="type_details")
    name = models.CharField(max_length=200)
    description = models.CharField(max_length=500, default="", blank=True)
    statement_of_cash_flows = models.CharField(
        max_length=50, default="", blank=True)

    def __str__(self):
        return self.name


# ---------------------------------------------------------------------------
# CHART OF ACCOUNTS  (enhanced with account type, hierarchy, opening balance)
# ---------------------------------------------------------------------------

class ChartOfAccount(models.Model):
    company_branch = models.ForeignKey(
        CompanyBranch, null=True, on_delete=models.SET_NULL, related_name="branch_chart_of_accounts")
    # GL classification
    account_type = models.ForeignKey(
        AccountType, null=True, blank=True, on_delete=models.SET_NULL, related_name="accounts")
    account_detail_type = models.ForeignKey(
        AccountTypeDetail, null=True, blank=True, on_delete=models.SET_NULL, related_name="accounts")
    parent_account = models.ForeignKey(
        "self", null=True, blank=True, on_delete=models.SET_NULL, related_name="sub_accounts")
    account_name = models.CharField(max_length=100, unique=False)
    account_number = models.CharField(max_length=50, unique=True)
    account_descriptions = models.CharField(
        max_length=500, default="", blank=True)
    # Opening balance for imported/migrated accounts
    opening_balance = models.DecimalField(
        max_digits=15, decimal_places=2, default=0)
    opening_balance_date = models.DateField(null=True, blank=True)
    # Legacy running balance (kept for backward compatibility)
    running_balance = models.CharField(max_length=50, default="0.00")
    # Banking details
    banking_institution_name = models.CharField(
        max_length=100, default="", blank=True)
    bank_branch_name = models.CharField(
        max_length=100, default="", blank=True)
    bank_branch_code = models.CharField(
        max_length=100, default="", blank=True)
    bank_swift_code = models.CharField(
        max_length=100, default="", blank=True)
    bank_sort_code = models.CharField(
        max_length=100, default="", blank=True)
    is_mpesa_account = models.BooleanField(default=False)
    is_default_account = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    currency_choices = [("usd", "USD"), ("gbp", "GBP"), ("eur", "EUR"),
                        ("kes", "KES"), ("not_selected", "Not Selected")]
    account_currency = models.CharField(
        choices=currency_choices, default="kes", max_length=30)
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="financial_account_created_by")
    last_updated_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="financial_account_last_updated_by")
    recycle_bin = models.BooleanField(default=False)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"{self.account_number} – {self.account_name}"


class CustomerOrderPayment(models.Model):
    payment_number = models.CharField(max_length=50, unique=True, default="")
    customer_order = models.ForeignKey(
        CustomerOrder, null=True, on_delete=models.SET_NULL, related_name="customer_order_payments")
    account_paid_to = models.ForeignKey(
        ChartOfAccount, null=True, on_delete=models.SET_NULL, related_name="account_order_payments")
    payment_amount = models.CharField(
        max_length=50, default="0.00",)
    payment_type_choices = [("m_pesa", "M-Pesa"),
                            ("cash", "Cash"), ("bank_transfer", "Bank Transfer"), ("bankers_cheque", "Bankers Cheque"), ("not_selected", "Not Selected")]
    payment_method = models.CharField(
        max_length=20, choices=payment_type_choices, default="not_selected", blank=False)
    # will store the mobile number or account number that has made the payment
    payer_account_number = models.CharField(
        max_length=30, default="", blank=True)
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="payment_created_by")  # used for payment of offline orders to capture the sales team that serves the order payment
    last_updated_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="payment_last_updated_by")  # used for payment of offline orders to capture the sales team that serves the order payment
    recycle_bin = models.BooleanField(default=False)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)
    timestamp = models.DateTimeField(default=timezone.now)

    def save(self, *args, **kwargs):
        if not self.pk:
            numberReg = re.sub(
                r'[^0-9]', '', self.timestamp.strftime('%Y-%m-%d %H:%M:%S'))
            self.payment_number = "PN"+numberReg
        super().save(*args, **kwargs)


class Refund(models.Model):  # refund will be created by sales team and not customer
    payment = models.ForeignKey(
        CustomerOrderPayment, null=True, on_delete=models.SET_NULL, related_name="payment_refunds")
    order_item_return = models.OneToOneField(
        OrderItemReturn, null=True, on_delete=models.CASCADE, related_name="order_items_requesting_refunds")
    refund_amount = models.CharField(
        max_length=50, default="0.00",)
    refund_approved = models.BooleanField(default=False)
    refund_fullfilled = models.BooleanField(default=False)
    recycle_bin = models.BooleanField(default=False)
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="refund_created_by")
    last_updated_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="refund_last_updated_by")
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)


class Expense(models.Model):
    company_branch = models.ForeignKey(
        CompanyBranch, null=True, on_delete=models.SET_NULL, related_name="branch_expenses")
    expense_number = models.CharField(max_length=50, unique=True, default="")
    expense_title = models.CharField(max_length=100, default="",)
    expense_description = models.CharField(
        max_length=500, default="", blank=True)
    expense_type_choices = [("capital_expenditure", "Purchase of Fixed Assets"),
                            ("purchase_expense", "Inventory Purchase Expense"),
                            ("salary_expense", "Salary Expense"), ("loan_repayment_expense", "Loan Repayment Expense"), ("utility_expense", "Utility Expense"), ("rent_expense", "Rent Expense"), ("operations_expense", "Operations Expense"), ("refund_expense", "Refund Expense"), ("tax_and_license_expense", "Tax and License Expense"), ("entertainment_and_private_expense", "Entertainment and Private Expense"), ("other", "Other"), ("not_selected", "Not Selected")]
    expense_type = models.CharField(
        max_length=50, choices=expense_type_choices, default="not_selected", blank=False)
    # expense_account = models.ForeignKey(
    #     ChartOfAccount, null=True, on_delete=models.SET_NULL, related_name="account_expenses")
    payroll_sheet = models.OneToOneField(
        PayrollSheet, null=True, on_delete=models.SET_NULL, related_name="payroll_sheet_expense")  # not null for expense of type salary, otherwise null
    product_purchase_instance = models.ManyToManyField(
        ProductPurchaseInstance, blank=True, related_name="product_purchase_expense")  # same as above
    landed_cost_instance = models.ForeignKey(
        LandedCostInstance, null=True, on_delete=models.SET_NULL, related_name="expense_landed_cost")
    refund = models.OneToOneField(
        Refund, null=True, on_delete=models.SET_NULL, related_name="refund_expense")  # same as above
    expense_amount = models.CharField(
        max_length=50, default="0.00",)
    expense_approved = models.BooleanField(default=False)
    expense_approved_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="expense_approved_by")
    expense_settled = models.BooleanField(default=False)
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="expense_created_by")
    last_updated_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="expense_last_updated_by")
    recycle_bin = models.BooleanField(default=False)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)
    timestamp = models.DateTimeField(default=timezone.now)

    def save(self, *args, **kwargs):
        if not self.pk:
            numberReg = re.sub(
                r'[^0-9]', '', self.timestamp.strftime('%Y-%m-%d %H:%M:%S'))
            self.expense_number = "EP"+numberReg
        super().save(*args, **kwargs)


# if deposit is deleted, the balance must be subtracted from the account
class Deposit(models.Model):
    company_branch = models.ForeignKey(
        CompanyBranch, null=True, on_delete=models.SET_NULL, related_name="branch_deposits")
    deposit_number = models.CharField(max_length=50, unique=True, default="")
    # deposit_account = models.ForeignKey(
    #     ChartOfAccount, null=True, on_delete=models.SET_NULL, related_name="account_deposits")
    source_choices = [("sales_revenue", "Sales Revenue"), ("loans", "Loans"),
                      ("other", "Other"), ("vehicle_and_equipment_lease_revenue", "Vehicle and Equipment Lease Revenue"), ("share_capital", "Share Capital"), ("not_selected", "Not Selected")]
    deposit_source = models.CharField(
        max_length=50, choices=source_choices, default="not_selected", blank=False)
    deposit_amount = models.CharField(
        max_length=50, default="0.00",)
    order_payment_reference = models.CharField(
        max_length=50, blank=True, default="")
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="deposit_created_by")
    last_updated_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="deposit_last_updated_by")
    # payment = models.ForeignKey(
    #     CustomerOrderPayment, null=True, on_delete=models.SET_NULL, related_name="deposit_from_order_payments")
    recycle_bin = models.BooleanField(default=False)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)
    timestamp = models.DateTimeField(default=timezone.now)

    def save(self, *args, **kwargs):
        if not self.pk:
            numberReg = re.sub(
                r'[^0-9]', '', self.timestamp.strftime('%Y-%m-%d %H:%M:%S'))
            self.deposit_number = "DN"+numberReg
        super().save(*args, **kwargs)


class LoanPortfolio(models.Model):
    company_profile = models.ForeignKey(
        CompanyProfile, null=True, on_delete=models.CASCADE, related_name="company_loan_portfolio")
    loan_title = models.CharField(
        max_length=100,)
    loan_type_choices = [
        ("term_loan", "Term Loan"), ("line_of_credit", "Line of Credit"), ("vehicle_loan", "Vehicle Loan"), ("business_loan", "Business Loan"), ("other", "Other"), ("not_selected", "Not Selected")]
    loan_type = models.CharField(
        max_length=20, choices=loan_type_choices, default="not_selected", blank=False)
    lender_name = models.CharField(
        max_length=100,)
    lender_phone = models.CharField(
        max_length=20,)
    lender_email = models.EmailField(max_length=50, default="", blank=False)
    loan_amount = models.CharField(
        max_length=15,)
    interest_rate = models.CharField(
        max_length=15,)
    repayment_period = models.CharField(
        max_length=15, default="0")  # months
    currency_choices = [("kes", "KES"), ("not_selected", "Not Selected")]
    loan_currency = models.CharField(
        choices=currency_choices, default="not_selected", max_length=30,)
    total_repayment_amount = models.CharField(
        max_length=15, default="0")
    principal_paid = models.CharField(
        max_length=15, default="0")
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="loan_created_by")
    last_updated_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="loan_last_updated_by")
    recycle_bin = models.BooleanField(default=False)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)


class Dividend(models.Model):
    dividend_title = models.CharField(
        max_length=100,)
    account = models.ForeignKey(
        ChartOfAccount, null=True, on_delete=models.SET_NULL, related_name="account_dividend_drawn_from")
    financial_year = models.CharField(
        max_length=5, default="2024",)
    dividend_amount_paid = models.CharField(
        max_length=10, default="0.00",)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="dividend_created_by")
    last_updated_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="dividend_last_updated_by")
    recycle_bin = models.BooleanField(default=False)
    dividend_comment = models.CharField(
        max_length=500, default="", blank=True)


class Transaction(models.Model):
    transaction_number = models.CharField(
        max_length=50, unique=True, default="")
    transaction_mode_choices = [
        ("inflow", "Inflow"), ("outflow", "Outflow"), ("not_selected", "Not Selected")]
    transaction_mode = models.CharField(
        max_length=20, choices=transaction_mode_choices, default="not_selected", blank=False)
    transaction_amount = models.CharField(
        max_length=50, default="0.00",)
    currency_choices = [("usd", "USD"), ("gbp", "GBP"), ("eur", "EUR"),
                        ("kes", "KES"), ("not_selected", "Not Selected")]
    transaction_currency = models.CharField(
        max_length=30, choices=currency_choices, default="kes")
    exchange_rate = models.CharField(
        max_length=12, blank=True, default="1.00")
    expense = models.OneToOneField(
        Expense, null=True, on_delete=models.SET_NULL, related_name="expense_transaction")
    deposit = models.OneToOneField(
        Deposit, null=True, on_delete=models.SET_NULL, related_name="deposit_transaction")
    account = models.ForeignKey(
        ChartOfAccount, null=True, on_delete=models.SET_NULL, related_name="account_transactions")
    loan_payment = models.ForeignKey(
        LoanPortfolio, null=True, on_delete=models.CASCADE, related_name="loan_payment_history")
    customer_order_payment = models.OneToOneField(
        CustomerOrderPayment, null=True, on_delete=models.CASCADE, related_name="order_payment_transaction")
    dividend = models.OneToOneField(
        Dividend, null=True, on_delete=models.CASCADE, related_name="dividend_transaction")
    transaction_reference = models.CharField(
        max_length=50, blank=True, default="",)  # used to store mpesa code or bank transaction code for identification of the transaction
    # invoice = CustomerOrderInvoice()
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="transaction_created_by")
    last_updated_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="transaction_last_updated_by")
    recycle_bin = models.BooleanField(default=False)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)
    timestamp = models.DateTimeField(default=timezone.now)

    def save(self, *args, **kwargs):
        if not self.pk:
            numberReg = re.sub(
                r'[^0-9]', '', self.timestamp.strftime('%Y-%m-%d %H:%M:%S'))
            self.transaction_number = "TN"+numberReg
        super().save(*args, **kwargs)


class CustomerOrderInvoice(models.Model):
    customer_order = models.OneToOneField(
        CustomerOrder, null=True, on_delete=models.SET_NULL, related_name="customer_order_invoice")
    invoice_number = models.CharField(
        max_length=50, unique=True, default="")
    invoice_amount_due = models.CharField(
        max_length=50, default="0.00",)
    invoice_amount_paid = models.CharField(
        max_length=50, default="0.00",)
    currency_choices = [("usd", "USD"), ("gbp", "GBP"), ("eur", "EUR"),
                        ("kes", "KES"), ("not_selected", "Not Selected")]
    invoice_currency = models.CharField(
        max_length=30, choices=currency_choices, default="kes")
    invoice_settled = models.BooleanField(default=False)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)
    timestamp = models.DateTimeField(default=timezone.now)
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="customer_order_invoice_created_by")
    last_updated_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="customer_order_invoice_last_updated_by")

    def save(self, *args, **kwargs):
        if not self.pk:
            numberReg = re.sub(
                r'[^0-9]', '', self.timestamp.strftime('%Y-%m-%d %H:%M:%S'))
            self.invoice_number = "INV"+numberReg
        super().save(*args, **kwargs)


class JournalAccountDebit(models.Model):
    account_name_choices = [("cash", "Cash"), ("m_pesa", "M-PESA PAYBILL"), ("bank", "Bank"), ("accounts_receivable", "Accounts Receivable"), ("inventory", "Inventory"),
                            ("loans_receivable", "Loans Receivable"), ("loans_payable", "Loans Payable"), ("rent_expense", "Rent Expense"), ("operations_expense", "Operations Expense"), ("refund_expense", "Refund Expense"), ("fixed_assets", "Fixed Assets"), ("accumulated_depreciation", "Accumulated Depreciation"), ("salary_and_wages_expense", "Salary and Wages Expense"), ("utility_expense", "Utility Expense"), ("marketing_expense", "Marketing Expense"), ("transportation_expense", "Transportation Expense"), ("office_supply_expense", "Office Supply Expense"), ("insurance_expense", "Insurance Expense"), ("interest_expense", "Interest Expense"), ("tax_and_license_expense", "Tax and License Expense"), ("miscellaneous_expense", "Miscellaneous Expense"), ("not_selected", "Not Selected"), ("retained_earnings", "Retained Earnings"),]
    account_name = models.CharField(
        max_length=100, choices=account_name_choices, default="not_selected", unique=True)

    # class Meta:
    #     db_table = 'finance_and_accounting_journalaccountdebit'


class JournalAccountCredit(models.Model):
    account_name_choices = [
        ("accounts_payable", "Accounts Payable"), ("accrued_expense", "Accrued Expense"), ("accounts_receivable", "Accounts Receivable"), ("cash", "Cash"), ("m_pesa", "M-PESA PAYBILL"), ("bank", "Bank"), ("loans_payable", "Loans Payable"), ("customer_overpayment", "Customer Overpayment"), ("taxes_payable", "Taxes Payable"), ("supplier_advances", "Supplier Advances"), ("unearned_revenue", "Unearned Revenue"), ("owners_equity", "Owners Equity"), ("retained_earnings", "Retained Earnings"), ("sales_revenue", "Sales Revenue"), ("services_revenue", "Services Revenue"), ("interest_income", "Income Revenue"), ("miscellaneous_income", "Miscellaneous Income"), ("vehicle_and_equipment_lease_revenue", "Vehicle and Equipment Lease Revenue"), ("not_selected", "Not Selected")]
    account_name = models.CharField(
        max_length=100, choices=account_name_choices, default="not_selected", unique=True)

    # class Meta:
    #     db_table = 'finance_and_accounting_journalaccountcredit'


class Journal_Entry(models.Model):
    company_branch = models.ForeignKey(
        CompanyBranch, null=True, on_delete=models.SET_NULL, related_name="branch_journal_entries")
    transaction = models.ForeignKey(
        Transaction, null=True, on_delete=models.SET_NULL, related_name="transaction_journal_entries")
    customer_order_invoice = models.ForeignKey(
        CustomerOrderInvoice, null=True, on_delete=models.SET_NULL, related_name="journal_entry_customer_order_invoices")
    journal_entry_number = models.CharField(
        max_length=50, unique=True, default="")
    journal_description = models.CharField(
        max_length=500, default="", blank=True)
    created_on = models.DateTimeField(auto_now_add=True)
    journal_reference = models.CharField(
        max_length=500, default="", blank=True)  # will include customer order/invoice/payroll sheets/purchase order
    journal_tag = models.CharField(
        max_length=500, default="", blank=True)  # will store additional tags eg, for inventory sale, it will store the name of the inventory sold
    last_updated_on = models.DateTimeField(auto_now=True)
    timestamp = models.DateTimeField(default=timezone.now)
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="journal_entry_created_by")
    last_updated_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="journal_entry_last_updated_by")

    def save(self, *args, **kwargs):
        if not self.pk:
            numberReg = re.sub(
                r'[^0-9]', '', self.timestamp.strftime('%Y-%m-%d %H:%M:%S'))
            self.journal_entry_number = "JN"+numberReg
        super().save(*args, **kwargs)


class JournalEntryDocs(models.Model):
    journal_entry = models.ForeignKey(
        Journal_Entry, null=True, on_delete=models.CASCADE, related_name="journal_entry_documents")
    saved_document_path = models.CharField(
        max_length=100, default="not_selected", blank=True)


class DebitEntry(models.Model):
    journal_entry = models.ForeignKey(
        Journal_Entry, null=True, on_delete=models.CASCADE, related_name="journal_debit_entries")
    journal_account_debit = models.ForeignKey(
        JournalAccountDebit, null=True, on_delete=models.CASCADE, related_name="debit_entry_journal_account")
    debit_amount = models.CharField(
        max_length=50, default="0.00",)


class CreditEntry(models.Model):
    journal_entry = models.ForeignKey(
        Journal_Entry, null=True, on_delete=models.CASCADE, related_name="journal_credit_entries")
    journal_account_credit = models.ForeignKey(
        JournalAccountCredit, null=True, on_delete=models.CASCADE, related_name="credit_entry_journal_account")
    credit_amount = models.CharField(
        max_length=50, default="0.00",)


class Asset(models.Model):
    asset_name = models.CharField(
        max_length=100,)
    asset_category_choices = [
        ("office_equipment", "Office Equipment"), ("machinery", "Machinery"), ("vehicle", "Vehicle"), ("property", "Property"), ("not_selected", "Not Selected"),]
    asset_category = models.CharField(
        max_length=100, choices=asset_category_choices, default="not_selected",)
    serial_number = models.CharField(
        max_length=50, default="", blank=True)
    purchase_date = models.DateField()
    purchase_cost = models.CharField(
        max_length=10, default="0.00",)  # should be in the company's preferred currency
    vendor_name = models.CharField(
        max_length=100, default="", blank=True)
    invoice_number = models.CharField(
        max_length=100, default="", blank=True)
    useful_life_years = models.CharField(
        max_length=5, default="1",)
    residual_value = models.CharField(
        max_length=50, default="0.00", blank=True)  # the expected scrap value at the end of its useful life
    depreciation_method_choices = [
        ("straight_line", "Straight Line"), ("declining_balance", "Declining Balance"),]
    status_choices = [
        ("active", "Active"), ("disposed", "Disposed"), ("under_maintanenance", "Under Maintenance"), ("not_selected", "Not Selected"),]
    depreciation_method = models.CharField(
        max_length=100, choices=depreciation_method_choices, default="not_selected",)
    depreciation_rate = models.CharField(
        max_length=5, default="0.00", blank=True)
    status = models.CharField(
        max_length=100, choices=status_choices, default="not_selected",)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="asset_created_by")
    last_updated_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="asset_last_updated_by")
    recycle_bin = models.BooleanField(default=False)


# class AssetFinancialYear(models.Model):
#     asset = models.ForeignKey(
#         Asset, null=True, on_delete=models.CASCADE, related_name="asset_financial_years")
#     opening_value = models.CharField(
#         max_length=50, default="0.00",)
#     depreciation_expense = models.CharField(
#         max_length=50, default="0.00",)
#     closing_value = models.CharField(
#         max_length=50, default="0.00",)
#     financial_year = models.CharField(
#         max_length=5, default="0000",)

# create a default value for this year
class RetainedEarningFinancialYearOpening(models.Model):
    financial_year = models.CharField(max_length=6, default="", blank=True)
    opening_quantity = models.CharField(max_length=20, default="0", blank=True)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)


class CashBalanceFinancialYearOpening(models.Model):
    financial_year = models.CharField(max_length=6, default="", blank=True)
    cash_balance = models.CharField(max_length=20, default="0", blank=True)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)


# ---------------------------------------------------------------------------
# ACCOUNT HISTORY  (General Ledger – every debit/credit posts here)
# ---------------------------------------------------------------------------

REL_TYPE_CHOICES = [
    ("journal_entry", "Journal Entry"),
    ("invoice", "Invoice"),
    ("payment", "Payment"),
    ("expense", "Expense"),
    ("deposit", "Deposit"),
    ("transfer", "Transfer"),
    ("payroll", "Payroll"),
    ("purchase_order", "Purchase Order"),
    ("opening_balance", "Opening Balance"),
    ("other", "Other"),
]


class AccountHistory(models.Model):
    account = models.ForeignKey(
        ChartOfAccount, on_delete=models.PROTECT, related_name="account_history")
    journal_entry = models.ForeignKey(
        "ManualJournalEntry", null=True, blank=True, on_delete=models.SET_NULL,
        related_name="ledger_lines")
    debit = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    credit = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    description = models.CharField(max_length=500, default="", blank=True)
    transaction_date = models.DateField()
    rel_id = models.IntegerField(null=True, blank=True)
    rel_type = models.CharField(
        max_length=30, choices=REL_TYPE_CHOICES, default="other")
    reconciled = models.BooleanField(default=False)
    company_branch = models.ForeignKey(
        CompanyBranch, null=True, blank=True, on_delete=models.SET_NULL, related_name="branch_account_history")
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="account_history_created_by")
    created_on = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-transaction_date", "-id"]

    def __str__(self):
        return f"{self.account} | Dr:{self.debit} Cr:{self.credit} on {self.transaction_date}"


# ---------------------------------------------------------------------------
# ACCOUNT MAPPINGS  (configure which GL accounts are used for each event)
# ---------------------------------------------------------------------------

MAPPING_TYPE_CHOICES = [
    ("payment_method", "Payment Method"),
    ("expense_category", "Expense Category"),
    ("deposit_source", "Deposit Source"),
    ("tax", "Tax"),
    # Automatic posting rules (operational event → GL)
    ("invoice_posting", "Invoice Posting"),
    ("payment_posting_sale", "Payment Posting – Sales"),
    ("payment_posting_expense", "Payment Posting – Expenses"),
    ("delivery_posting", "Delivery / COGS Posting"),
    ("purchase_posting", "Purchase Bill Posting"),
    ("credit_note_posting", "Credit Note Posting"),
    ("payroll_posting", "Payroll Posting"),
]


class AccountMapping(models.Model):
    """
    Maps a business event key (e.g. payment method, expense type) to
    specific debit and credit GL accounts.
    """
    mapping_type = models.CharField(
        max_length=30, choices=MAPPING_TYPE_CHOICES)
    source_key = models.CharField(max_length=100)
    source_label = models.CharField(max_length=200, default="", blank=True)
    debit_account = models.ForeignKey(
        ChartOfAccount, null=True, blank=True, on_delete=models.SET_NULL,
        related_name="mapping_debit_account")
    credit_account = models.ForeignKey(
        ChartOfAccount, null=True, blank=True, on_delete=models.SET_NULL,
        related_name="mapping_credit_account")
    description = models.CharField(max_length=300, default="", blank=True)
    company_branch = models.ForeignKey(
        CompanyBranch, null=True, blank=True, on_delete=models.SET_NULL,
        related_name="branch_account_mappings")
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="mapping_created_by")
    last_updated_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="mapping_last_updated_by")
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = [["mapping_type", "source_key", "company_branch"]]

    def __str__(self):
        return f"{self.mapping_type}:{self.source_key}"


# ---------------------------------------------------------------------------
# ENHANCED MANUAL JOURNAL ENTRY  (uses real ChartOfAccount lines)
# ---------------------------------------------------------------------------

RECURRING_TYPE_CHOICES = [
    ("none", "None"),
    ("daily", "Daily"),
    ("weekly", "Weekly"),
    ("monthly", "Monthly"),
    ("yearly", "Yearly"),
]

JOURNAL_STATUS_CHOICES = [
    ("draft", "Draft"),
    ("posted", "Posted"),
    ("reversed", "Reversed"),
    ("cancelled", "Cancelled"),
]

JOURNAL_SOURCE_TYPE_CHOICES = [
    ("manual", "Manual Entry"),
    ("sales_invoice", "Sales Invoice"),
    ("customer_payment", "Customer Payment"),
    ("expense", "Expense"),
    ("deposit", "Deposit"),
    ("payroll", "Payroll"),
    ("purchase", "Purchase / AP"),
    ("inventory", "Inventory"),
    ("depreciation", "Depreciation"),
    ("year_end", "Year-End Close"),
    ("reversal", "Reversal"),
    ("other", "Other"),
]


class ManualJournalEntry(models.Model):
    company_branch = models.ForeignKey(
        CompanyBranch, null=True, on_delete=models.SET_NULL, related_name="branch_manual_journals")
    entry_number = models.CharField(max_length=50, unique=True, default="")
    entry_date = models.DateField()
    description = models.CharField(max_length=500, default="", blank=True)
    reference = models.CharField(max_length=200, default="", blank=True)
    status = models.CharField(
        max_length=12, choices=JOURNAL_STATUS_CHOICES, default="draft")
    source_type = models.CharField(
        max_length=30, choices=JOURNAL_SOURCE_TYPE_CHOICES, default="manual")
    source_id = models.CharField(max_length=100, default="", blank=True)
    is_recurring = models.BooleanField(default=False)
    recurring_type = models.CharField(
        max_length=10, choices=RECURRING_TYPE_CHOICES, default="none")
    recurring_cycles = models.IntegerField(default=0)
    cycles_created = models.IntegerField(default=0)
    parent_entry = models.ForeignKey(
        "self", null=True, blank=True, on_delete=models.SET_NULL, related_name="child_entries")
    reversal_of = models.ForeignKey(
        "self", null=True, blank=True, on_delete=models.SET_NULL, related_name="reversal_entries")
    posted_at = models.DateTimeField(null=True, blank=True)
    posted_by = models.ForeignKey(
        StaffProfile, null=True, blank=True, on_delete=models.SET_NULL,
        related_name="manual_journal_posted_by")
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="manual_journal_created_by")
    last_updated_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="manual_journal_updated_by")
    recycle_bin = models.BooleanField(default=False)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)
    timestamp = models.DateTimeField(default=timezone.now)

    def save(self, *args, **kwargs):
        if not self.pk and not self.entry_number:
            # Microsecond resolution avoids collisions when many entries are
            # created within the same second (e.g. batch/depreciation runs).
            numberReg = re.sub(
                r'[^0-9]', '', self.timestamp.strftime('%Y-%m-%d %H:%M:%S.%f'))
            self.entry_number = "MJ" + numberReg
        super().save(*args, **kwargs)

    def __str__(self):
        return self.entry_number


class ManualJournalLine(models.Model):
    journal_entry = models.ForeignKey(
        ManualJournalEntry, on_delete=models.CASCADE, related_name="journal_lines")
    account = models.ForeignKey(
        ChartOfAccount, on_delete=models.PROTECT, related_name="journal_entry_lines")
    debit = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    credit = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    description = models.CharField(max_length=300, default="", blank=True)
    line_order = models.PositiveSmallIntegerField(default=0)

    class Meta:
        ordering = ["line_order", "id"]

    def __str__(self):
        return f"{self.journal_entry.entry_number} | {self.account} Dr:{self.debit} Cr:{self.credit}"


# ---------------------------------------------------------------------------
# ACCOUNTING PERIOD  (fiscal period lock / close management)
# ---------------------------------------------------------------------------

class AccountingPeriod(models.Model):
    company_branch = models.ForeignKey(
        CompanyBranch, null=True, on_delete=models.SET_NULL, related_name="branch_accounting_periods")
    period_name = models.CharField(max_length=100)
    period_start_date = models.DateField()
    period_end_date = models.DateField()
    period_locked = models.BooleanField(default=False)
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="period_created_by")
    last_updated_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="period_updated_by")
    recycle_bin = models.BooleanField(default=False)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-period_start_date"]

    def __str__(self):
        return self.period_name


# ---------------------------------------------------------------------------
# ACCOUNTING SETTINGS  (per-branch general configuration)
# ---------------------------------------------------------------------------

MONTH_CHOICES = [
    ("january", "January"), ("february", "February"), ("march", "March"),
    ("april", "April"), ("may", "May"), ("june", "June"),
    ("july", "July"), ("august", "August"), ("september", "September"),
    ("october", "October"), ("november", "November"), ("december", "December"),
]


class AccountingSettings(models.Model):
    company_branch = models.OneToOneField(
        CompanyBranch, on_delete=models.CASCADE, related_name="accounting_settings")
    fiscal_year_start_month = models.CharField(
        max_length=20, choices=MONTH_CHOICES, default="january")
    tax_year_start_month = models.CharField(
        max_length=30, default="same_as_fiscal",
        help_text="'same_as_fiscal' or a month slug")
    accounting_method = models.CharField(
        max_length=10,
        choices=[("cash", "Cash"), ("accrual", "Accrual")],
        default="accrual")
    close_books = models.BooleanField(default=False)
    closing_date = models.DateField(null=True, blank=True)
    enable_account_numbers = models.BooleanField(default=True)
    show_account_numbers = models.BooleanField(default=True)
    updated_by = models.ForeignKey(
        StaffProfile, null=True, blank=True,
        on_delete=models.SET_NULL, related_name="settings_updated_by")
    updated_on = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"Settings – {self.company_branch}"


# ---------------------------------------------------------------------------
# EXTENDED FINANCE — multi-currency, audit, AP invoices, tax, depreciation
# ---------------------------------------------------------------------------

class CurrencyExchangeRate(models.Model):
    company_branch = models.ForeignKey(
        CompanyBranch, null=True, on_delete=models.CASCADE, related_name="currency_exchange_rates")
    from_currency = models.CharField(max_length=10, default="usd")
    to_currency = models.CharField(max_length=10, default="kes")
    rate_date = models.DateField()
    exchange_rate = models.DecimalField(max_digits=18, decimal_places=6, default=1)
    notes = models.CharField(max_length=300, default="", blank=True)
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="fx_rate_created_by")
    created_on = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-rate_date", "-id"]
        unique_together = [("company_branch", "from_currency", "to_currency", "rate_date")]


class FinanceAuditLog(models.Model):
    company_branch = models.ForeignKey(
        CompanyBranch, null=True, on_delete=models.CASCADE, related_name="finance_audit_logs")
    entity_type = models.CharField(max_length=80)
    entity_id = models.CharField(max_length=50)
    action = models.CharField(max_length=50)
    actor = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="finance_audit_actions")
    notes = models.TextField(default="", blank=True)
    before_data = models.TextField(default="", blank=True)
    after_data = models.TextField(default="", blank=True)
    created_on = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_on"]


class VendorInvoice(models.Model):
    MATCH_STATUS = [
        ("pending", "Pending"),
        ("matched", "Matched"),
        ("partial", "Partial Match"),
        ("exception", "Exception"),
    ]
    company_branch = models.ForeignKey(
        CompanyBranch, null=True, on_delete=models.SET_NULL, related_name="vendor_invoices")
    purchase_order = models.ForeignKey(
        PurchaseOrder, null=True, on_delete=models.SET_NULL, related_name="vendor_invoices")
    supplier_invoice_number = models.CharField(max_length=100, default="")
    invoice_date = models.DateField()
    invoice_amount = models.CharField(max_length=50, default="0.00")
    po_amount = models.CharField(max_length=50, default="0.00")
    received_amount = models.CharField(max_length=50, default="0.00")
    match_status = models.CharField(max_length=20, choices=MATCH_STATUS, default="pending")
    match_notes = models.TextField(default="", blank=True)
    attachment_reference = models.CharField(max_length=200, default="", blank=True)
    account_manager_id = models.CharField(max_length=50, default="", blank=True)
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="vendor_invoice_created_by")
    recycle_bin = models.BooleanField(default=False)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)


class TaxLossCarryForward(models.Model):
    company_branch = models.ForeignKey(
        CompanyBranch, null=True, on_delete=models.CASCADE, related_name="tax_loss_carry_forwards")
    tax_year = models.PositiveIntegerField()
    assessed_loss = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    utilized = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    balance = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    notes = models.TextField(default="", blank=True)
    created_on = models.DateTimeField(auto_now_add=True)
    updated_on = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = [("company_branch", "tax_year")]


class DepreciationScheduleLine(models.Model):
    asset = models.ForeignKey(
        Asset, on_delete=models.CASCADE, related_name="depreciation_schedule")
    period_month = models.DateField()
    depreciation_amount = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    posted = models.BooleanField(default=False)
    journal_entry = models.ForeignKey(
        ManualJournalEntry, null=True, blank=True, on_delete=models.SET_NULL,
        related_name="depreciation_lines")
    created_on = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["period_month"]
        unique_together = [("asset", "period_month")]


class MpesaStatementLine(models.Model):
    company_branch = models.ForeignKey(
        CompanyBranch, null=True, on_delete=models.CASCADE, related_name="mpesa_statement_lines")
    mpesa_account = models.ForeignKey(
        ChartOfAccount, null=True, on_delete=models.SET_NULL, related_name="mpesa_import_lines")
    transaction_date = models.DateField()
    reference_code = models.CharField(max_length=100, default="")
    payer_name = models.CharField(max_length=200, default="", blank=True)
    amount = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    matched = models.BooleanField(default=False)
    matched_history = models.ForeignKey(
        AccountHistory, null=True, blank=True, on_delete=models.SET_NULL,
        related_name="mpesa_matches")
    created_on = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-transaction_date", "-id"]


class ScheduledPaymentReminder(models.Model):
    REMINDER_TYPES = [
        ("ar_due", "AR Due"),
        ("ap_due", "AP Due"),
        ("credit_follow_up", "Credit Follow Up"),
    ]
    company_branch = models.ForeignKey(
        CompanyBranch, null=True, on_delete=models.CASCADE, related_name="payment_reminders")
    reminder_type = models.CharField(max_length=30, choices=REMINDER_TYPES)
    entity_type = models.CharField(max_length=50)
    entity_id = models.CharField(max_length=50)
    recipient_email = models.EmailField(max_length=100, default="", blank=True)
    due_date = models.DateField(null=True, blank=True)
    amount_due = models.CharField(max_length=50, default="0.00")
    sent = models.BooleanField(default=False)
    sent_on = models.DateTimeField(null=True, blank=True)
    last_error = models.CharField(max_length=300, default="", blank=True)
    created_on = models.DateTimeField(auto_now_add=True)


# ---------------------------------------------------------------------------
# WITHHOLDING TAX (WHVAT 2% / WHT) — KRA filing register
# ---------------------------------------------------------------------------

class WithholdingTaxEntry(models.Model):
    TAX_TYPES = [
        ("vat_withholding", "Withholding VAT (2%)"),
        ("income_withholding", "Withholding Income Tax"),
    ]
    STATUS = [
        ("pending", "Pending Filing"),
        ("filed", "Filed"),
    ]
    company_branch = models.ForeignKey(
        CompanyBranch, null=True, on_delete=models.CASCADE, related_name="withholding_tax_entries")
    supplier = models.ForeignKey(
        Supplier, null=True, blank=True, on_delete=models.SET_NULL, related_name="withholding_tax_entries")
    purchase_order = models.ForeignKey(
        PurchaseOrder, null=True, blank=True, on_delete=models.SET_NULL, related_name="withholding_tax_entries")
    vendor_invoice = models.ForeignKey(
        VendorInvoice, null=True, blank=True, on_delete=models.SET_NULL, related_name="withholding_tax_entries")
    tax_type = models.CharField(max_length=30, choices=TAX_TYPES, default="vat_withholding")
    supplier_name = models.CharField(max_length=200, default="", blank=True)
    supplier_pin = models.CharField(max_length=30, default="", blank=True)
    invoice_number = models.CharField(max_length=100, default="", blank=True)
    gross_amount = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    wht_rate = models.DecimalField(max_digits=6, decimal_places=2, default=2)
    wht_amount = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    certificate_number = models.CharField(max_length=80, default="", blank=True)
    period_date = models.DateField()
    status = models.CharField(max_length=20, choices=STATUS, default="pending")
    notes = models.CharField(max_length=300, default="", blank=True)
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="withholding_tax_created_by")
    recycle_bin = models.BooleanField(default=False)
    created_on = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-period_date", "-id"]


# ---------------------------------------------------------------------------
# MPESA DARAJA API CONFIG  (per-branch credentials for live pulls)
# ---------------------------------------------------------------------------

class MpesaApiConfig(models.Model):
    ENVIRONMENTS = [("sandbox", "Sandbox"), ("production", "Production")]
    company_branch = models.OneToOneField(
        CompanyBranch, on_delete=models.CASCADE, related_name="mpesa_api_config")
    environment = models.CharField(max_length=20, choices=ENVIRONMENTS, default="sandbox")
    consumer_key = models.CharField(max_length=200, default="", blank=True)
    consumer_secret = models.CharField(max_length=200, default="", blank=True)
    short_code = models.CharField(max_length=20, default="", blank=True)
    pass_key = models.CharField(max_length=200, default="", blank=True)
    initiator_name = models.CharField(max_length=100, default="", blank=True)
    security_credential = models.TextField(default="", blank=True)
    mpesa_account = models.ForeignKey(
        ChartOfAccount, null=True, blank=True, on_delete=models.SET_NULL, related_name="mpesa_api_accounts")
    is_active = models.BooleanField(default=False)
    last_synced_on = models.DateTimeField(null=True, blank=True)
    updated_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="mpesa_config_updated_by")
    updated_on = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"M-Pesa API – {self.company_branch} ({self.environment})"


# ---------------------------------------------------------------------------
# BATCH / SCHEDULED SUPPLIER PAYMENTS
# ---------------------------------------------------------------------------

class BatchPayment(models.Model):
    STATUS = [
        ("draft", "Draft"),
        ("executed", "Executed"),
        ("cancelled", "Cancelled"),
    ]
    company_branch = models.ForeignKey(
        CompanyBranch, null=True, on_delete=models.SET_NULL, related_name="batch_payments")
    batch_number = models.CharField(max_length=50, unique=True, default="")
    payment_date = models.DateField()
    source_account = models.ForeignKey(
        ChartOfAccount, null=True, on_delete=models.SET_NULL, related_name="batch_payment_source_accounts")
    payable_account = models.ForeignKey(
        ChartOfAccount, null=True, blank=True, on_delete=models.SET_NULL, related_name="batch_payment_payable_accounts")
    total_amount = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    status = models.CharField(max_length=20, choices=STATUS, default="draft")
    notes = models.CharField(max_length=300, default="", blank=True)
    journal_entry = models.ForeignKey(
        ManualJournalEntry, null=True, blank=True, on_delete=models.SET_NULL, related_name="batch_payments")
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="batch_payment_created_by")
    executed_by = models.ForeignKey(
        StaffProfile, null=True, blank=True, on_delete=models.SET_NULL, related_name="batch_payment_executed_by")
    executed_on = models.DateTimeField(null=True, blank=True)
    recycle_bin = models.BooleanField(default=False)
    created_on = models.DateTimeField(auto_now_add=True)
    timestamp = models.DateTimeField(default=timezone.now)

    class Meta:
        ordering = ["-id"]

    def save(self, *args, **kwargs):
        if not self.pk:
            stamp = re.sub(r"[^0-9]", "", self.timestamp.strftime("%Y-%m-%d %H:%M:%S"))
            self.batch_number = f"BP{stamp}"
        super().save(*args, **kwargs)

    def __str__(self):
        return self.batch_number


class BatchPaymentLine(models.Model):
    batch_payment = models.ForeignKey(
        BatchPayment, on_delete=models.CASCADE, related_name="lines")
    purchase_order = models.ForeignKey(
        PurchaseOrder, null=True, blank=True, on_delete=models.SET_NULL, related_name="batch_payment_lines")
    supplier_name = models.CharField(max_length=200, default="", blank=True)
    amount = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    wht_amount = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    net_amount = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    reference = models.CharField(max_length=120, default="", blank=True)


# ---------------------------------------------------------------------------
# PETTY CASH / IMPREST
# ---------------------------------------------------------------------------

class PettyCashFund(models.Model):
    company_branch = models.ForeignKey(
        CompanyBranch, null=True, on_delete=models.SET_NULL, related_name="petty_cash_funds")
    fund_number = models.CharField(max_length=50, unique=True, default="")
    fund_name = models.CharField(max_length=120, default="")
    petty_cash_account = models.ForeignKey(
        ChartOfAccount, null=True, on_delete=models.SET_NULL, related_name="petty_cash_fund_accounts")
    employee_advances_account = models.ForeignKey(
        ChartOfAccount, null=True, on_delete=models.SET_NULL, related_name="employee_advances_fund_accounts")
    bank_account = models.ForeignKey(
        ChartOfAccount, null=True, on_delete=models.SET_NULL, related_name="petty_cash_replenish_bank_accounts")
    custodian = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="petty_cash_custodian_funds")
    imprest_amount = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    current_balance = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    is_active = models.BooleanField(default=True)
    notes = models.TextField(default="", blank=True)
    created_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="petty_cash_funds_created")
    recycle_bin = models.BooleanField(default=False)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)
    timestamp = models.DateTimeField(default=timezone.now)

    class Meta:
        ordering = ["-id"]

    def save(self, *args, **kwargs):
        if not self.pk:
            stamp = re.sub(r"[^0-9]", "", self.timestamp.strftime("%Y-%m-%d %H:%M:%S"))
            self.fund_number = f"PCF{stamp}"
            if not self.current_balance and self.imprest_amount:
                self.current_balance = self.imprest_amount
        super().save(*args, **kwargs)


class PettyCashRequest(models.Model):
    STATUS = [
        ("draft", "Draft"),
        ("pending_approval", "Pending Approval"),
        ("approved", "Approved"),
        ("rejected", "Rejected"),
        ("disbursed", "Disbursed"),
        ("cancelled", "Cancelled"),
    ]
    fund = models.ForeignKey(
        PettyCashFund, on_delete=models.CASCADE, related_name="requests")
    request_number = models.CharField(max_length=50, unique=True, default="")
    requested_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="petty_cash_requests")
    purpose = models.TextField(default="")
    amount = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    status = models.CharField(max_length=30, choices=STATUS, default="draft")
    approved_by = models.ForeignKey(
        StaffProfile, null=True, blank=True, on_delete=models.SET_NULL, related_name="petty_cash_requests_approved")
    approved_on = models.DateTimeField(null=True, blank=True)
    rejection_reason = models.TextField(default="", blank=True)
    recycle_bin = models.BooleanField(default=False)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)
    timestamp = models.DateTimeField(default=timezone.now)

    class Meta:
        ordering = ["-id"]

    def save(self, *args, **kwargs):
        if not self.pk:
            stamp = re.sub(r"[^0-9]", "", self.timestamp.strftime("%Y-%m-%d %H:%M:%S"))
            self.request_number = f"PCR{stamp}"
        super().save(*args, **kwargs)


class PettyCashDisbursement(models.Model):
    request = models.OneToOneField(
        PettyCashRequest, on_delete=models.CASCADE, related_name="disbursement")
    disbursement_number = models.CharField(max_length=50, unique=True, default="")
    amount = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    disbursed_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="petty_cash_disbursements")
    journal_entry = models.ForeignKey(
        ManualJournalEntry, null=True, blank=True, on_delete=models.SET_NULL, related_name="petty_cash_disbursements")
    disbursed_on = models.DateTimeField(auto_now_add=True)
    timestamp = models.DateTimeField(default=timezone.now)

    class Meta:
        ordering = ["-id"]

    def save(self, *args, **kwargs):
        if not self.pk:
            stamp = re.sub(r"[^0-9]", "", self.timestamp.strftime("%Y-%m-%d %H:%M:%S"))
            self.disbursement_number = f"PCD{stamp}"
        super().save(*args, **kwargs)


class PettyCashRetirement(models.Model):
    STATUS = [
        ("draft", "Draft"),
        ("posted", "Posted"),
    ]
    disbursement = models.OneToOneField(
        PettyCashDisbursement, on_delete=models.CASCADE, related_name="retirement")
    retirement_number = models.CharField(max_length=50, unique=True, default="")
    retired_amount = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    receipt_reference = models.CharField(max_length=120, default="", blank=True)
    status = models.CharField(max_length=20, choices=STATUS, default="draft")
    journal_entry = models.ForeignKey(
        ManualJournalEntry, null=True, blank=True, on_delete=models.SET_NULL, related_name="petty_cash_retirements")
    retired_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="petty_cash_retirements")
    retired_on = models.DateTimeField(null=True, blank=True)
    timestamp = models.DateTimeField(default=timezone.now)

    class Meta:
        ordering = ["-id"]

    def save(self, *args, **kwargs):
        if not self.pk:
            stamp = re.sub(r"[^0-9]", "", self.timestamp.strftime("%Y-%m-%d %H:%M:%S"))
            self.retirement_number = f"PCRT{stamp}"
        super().save(*args, **kwargs)


class PettyCashRetirementLine(models.Model):
    retirement = models.ForeignKey(
        PettyCashRetirement, on_delete=models.CASCADE, related_name="lines")
    expense_account = models.ForeignKey(
        ChartOfAccount, null=True, on_delete=models.SET_NULL, related_name="petty_cash_retirement_lines")
    description = models.CharField(max_length=250, default="", blank=True)
    amount = models.DecimalField(max_digits=15, decimal_places=2, default=0)


class PettyCashReplenishment(models.Model):
    STATUS = [
        ("draft", "Draft"),
        ("posted", "Posted"),
    ]
    fund = models.ForeignKey(
        PettyCashFund, on_delete=models.CASCADE, related_name="replenishments")
    replenishment_number = models.CharField(max_length=50, unique=True, default="")
    amount = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    bank_account = models.ForeignKey(
        ChartOfAccount, null=True, on_delete=models.SET_NULL, related_name="petty_cash_replenishments")
    journal_entry = models.ForeignKey(
        ManualJournalEntry, null=True, blank=True, on_delete=models.SET_NULL, related_name="petty_cash_replenishment_journals")
    status = models.CharField(max_length=20, choices=STATUS, default="draft")
    replenished_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="petty_cash_replenishments")
    replenished_on = models.DateTimeField(null=True, blank=True)
    notes = models.TextField(default="", blank=True)
    timestamp = models.DateTimeField(default=timezone.now)

    class Meta:
        ordering = ["-id"]

    def save(self, *args, **kwargs):
        if not self.pk:
            stamp = re.sub(r"[^0-9]", "", self.timestamp.strftime("%Y-%m-%d %H:%M:%S"))
            self.replenishment_number = f"PCRP{stamp}"
        super().save(*args, **kwargs)


class PettyCashReconciliation(models.Model):
    STATUS = [
        ("pending", "Pending"),
        ("reconciled", "Reconciled"),
    ]
    fund = models.ForeignKey(
        PettyCashFund, on_delete=models.CASCADE, related_name="reconciliations")
    reconciliation_number = models.CharField(max_length=50, unique=True, default="")
    period_date = models.DateField()
    book_balance = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    physical_count = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    variance = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    status = models.CharField(max_length=20, choices=STATUS, default="pending")
    notes = models.TextField(default="", blank=True)
    reconciled_by = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="petty_cash_reconciliations")
    reconciled_on = models.DateTimeField(null=True, blank=True)
    timestamp = models.DateTimeField(default=timezone.now)

    class Meta:
        ordering = ["-period_date", "-id"]
        unique_together = [("fund", "period_date")]

    def save(self, *args, **kwargs):
        if not self.pk:
            stamp = re.sub(r"[^0-9]", "", self.timestamp.strftime("%Y-%m-%d %H:%M:%S"))
            self.reconciliation_number = f"PCRC{stamp}"
        self.variance = (self.physical_count or 0) - (self.book_balance or 0)
        super().save(*args, **kwargs)


class EmployeeAdvance(models.Model):
    SOURCE_CHOICES = [
        ("petty_cash", "Petty Cash"),
        ("hr", "HR"),
    ]
    RECOVERY_MODULE_CHOICES = [
        ("full", "Full Balance"),
        ("percentage", "Percentage of Balance"),
        ("fixed", "Fixed Installment"),
    ]
    STATUS_CHOICES = [
        ("open", "Open"),
        ("partial", "Partial"),
        ("recovered", "Recovered via Payroll"),
        ("settled", "Settled via Receipt"),
    ]
    staff_profile = models.ForeignKey(
        StaffProfile, on_delete=models.CASCADE, related_name="employee_advances")
    company_branch = models.ForeignKey(
        CompanyBranch, null=True, on_delete=models.SET_NULL, related_name="employee_advances")
    fund = models.ForeignKey(
        PettyCashFund, null=True, on_delete=models.SET_NULL, related_name="employee_advances")
    employee_advances_account = models.ForeignKey(
        ChartOfAccount, null=True, on_delete=models.SET_NULL, related_name="employee_advance_gl_accounts")
    petty_cash_disbursement = models.OneToOneField(
        PettyCashDisbursement, null=True, blank=True, on_delete=models.CASCADE,
        related_name="employee_advance")
    source = models.CharField(max_length=30, choices=SOURCE_CHOICES, default="petty_cash")
    recovery_module = models.CharField(
        max_length=20, choices=RECOVERY_MODULE_CHOICES, default="fixed")
    recovery_value = models.DecimalField(max_digits=15, decimal_places=2, default=100)
    reference_number = models.CharField(max_length=50, default="", blank=True)
    description = models.TextField(default="", blank=True)
    original_amount = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    balance_outstanding = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    amount_recovered_via_payroll = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    amount_settled_via_receipt = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="open")
    last_payroll_sheet = models.ForeignKey(
        "human_resource.PayrollSheet", null=True, blank=True, on_delete=models.SET_NULL,
        related_name="employee_advance_recoveries")
    recorded_by = models.ForeignKey(
        StaffProfile, null=True, blank=True, on_delete=models.SET_NULL,
        related_name="employee_advances_recorded")
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated_on = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-created_on"]


class PayrollAdvanceRecovery(models.Model):
    STATUS_CHOICES = [
        ("scheduled", "Scheduled"),
        ("recovered", "Recovered"),
        ("cancelled", "Cancelled"),
    ]
    employee_advance = models.ForeignKey(
        EmployeeAdvance, on_delete=models.CASCADE, related_name="payroll_recoveries")
    payroll_sheet = models.ForeignKey(
        "human_resource.PayrollSheet", on_delete=models.CASCADE, related_name="advance_recoveries")
    staff_profile = models.ForeignKey(
        StaffProfile, null=True, on_delete=models.SET_NULL, related_name="payroll_advance_recoveries")
    amount = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="scheduled")
    recovered_on = models.DateTimeField(null=True, blank=True)
    created_on = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_on"]

