import threading
from django.db import transaction
from rest_framework.filters import SearchFilter, OrderingFilter
from django_filters.rest_framework import DjangoFilterBackend
from django.db.models import Q, F, Sum, Exists, OuterRef, Prefetch
from savings.models import *
from savings.serializers import *
from rest_framework import viewsets
from django.contrib.auth import get_user_model
from rest_framework.response import Response
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.decorators import action
from rest_framework.parsers import JSONParser, FormParser, MultiPartParser
from questbanker_api.utils import get_current_user
from ledgers.models import InterBranchTransactions
from ledgers.serializers import SystemTransactionDetailsSerializer
from ledgers.ledgers_helper import generate_chart_of_account_code, generate_reference_no, post_transaction, get_chart_of_account_by_code,delete_savings_transaction
from .savings_helper import *
from datetime import datetime
from .savings_helper import generate_saving_account_code
from django.utils.timezone import make_aware
from django.utils import timezone
from exservices.exservices_helper import send_customer_sms, send_customer_single_sms, send_non_subscriber_single_sms
from loans.helper import calculate_flat_loan_schedule
from .data import savings_blocking_types
from customers.models import Customer
from notifications.notifications_helper import *
from general.helper import date_time_zone_convert
from users.models import Staff
from users.audit_log_helper import add_system_audit_trail


class SavingsProductView(viewsets.ModelViewSet):
    serializer_class = SavingsProductSerializer

    def get_queryset(self):
        organisation_id = get_current_user(
            self.request, 'organisation_id', None)
        return SavingsProduct.objects.filter(saving_product_org__id=organisation_id).order_by("product_name")

    def perform_create(self, serializer):
        # Save Product.
        request_data = self.request.data
        organisation_id = get_current_user(self.request, 'organisation_id', 1)
        #liability_account = OrganisationSubAccount.objects.filter(account_line ='liabilities', account_organisation=organisation_id, parent_id__isnull=True).first()
        liability_account = get_chart_of_account_by_code(
            'sys-211', Organisation.objects.get(pk=organisation_id))
        account_code = generate_chart_of_account_code(
            liability_account.id, 'liabilities', organisation_id)

        # Registering liability account for every saving product addded.
        product_account = OrganisationSubAccount(
            account_organisation=Organisation.objects.get(pk=organisation_id), status='active',
            account_code=account_code, account_name=request_data.get(
                'product_name'),
            account_type='user_defined', account_line='liabilities',
            added_by=self.request.user.id, parent_id=liability_account)
        product_account.save()

        if product_account:
            serializer.save(
                saving_product_added_by=get_user_model().objects.get(pk=self.request.user.id),
                saving_product_org=Organisation.objects.get(
                    pk=organisation_id),
                    accounts_chart=product_account)

    def perform_update(self, serializer):
        request_data = self.request.data
        branchid     = get_current_user(self.request, 'organisation_branch_id', 1) 
        branch       = OrganisationBranch.objects.get(id=branchid)
        
        max_withdraw_limit_charge = request_data.get(
            'max_withdraw_limit_charge')

        product       = SavingsProduct.objects.get(pk=self.kwargs.get('pk',self.kwargs.get('id')))
        old_details   = SavingsProductSerializer(product,read_only=True).data
        savingProduct = serializer.save()
        update_savings_account_statuses(
            account_ids=list(
                SavingAccount.objects.filter(account_product=savingProduct, deleted=False).values_list('id', flat=True)
            )
        )
        new_details   = SavingsProductSerializer(savingProduct,read_only=True).data
        message       = f'Updated Saving Product: {savingProduct.product_name}'
        add_system_audit_trail('savings','update_savings_product',message,'',old_details,new_details,self.request.user,branch)
         
        if max_withdraw_limit_charge:
            charge_field = {"charge_added_by": get_user_model().objects.get(
                pk=self.request.user.id), "saving_product": savingProduct}
            charge_field["charge_key"] = "max_withdraw_limit_charge"
            charge_field["charge_name"] = "max Withdrawal Limit Charge"
            charge_field["charge"] = max_withdraw_limit_charge
            
            prodt_max_withdraw_limit_charge = SavingProductCharge.objects.filter(
                charge_key="max_withdraw_limit_charge", saving_product=savingProduct).first()
            
            if prodt_max_withdraw_limit_charge:
                prodt_max_withdraw_limit_charge.charge = max_withdraw_limit_charge
                prodt_max_withdraw_limit_charge.save()
            else:
                SavingProductCharge.objects.create(**charge_field)


class SavingProductChargeView(viewsets.ModelViewSet):
    serializer_class = SavingProductChargeSerializer

    def get_queryset(self):
        filter_array = {}
        productid = self.request.query_params.get("productid", None)
        charge_filter = self.request.query_params.get("charge_filter", None)
        if productid is not None:
            filter_array['saving_product__id'] = productid
        if charge_filter is not None:
            filter_array['charge_key'] = charge_filter
        return SavingProductCharge.objects.filter(**filter_array)
    
    def perform_update(self, serializer):
        branchid    = get_current_user(self.request, 'organisation_branch_id', 1) 
        branch      = OrganisationBranch.objects.get(id=branchid)
        old_charge  = SavingProductCharge.objects.get(pk=self.kwargs.get('pk',self.kwargs.get('id')))
        serializer.save(charge_last_updated_by=self.request.user)

        old_charge_details  = SavingProductChargeSerializer(old_charge,read_only=True).data
        new_charge_details  = SavingProductChargeSerializer(SavingProductCharge.objects.get(pk=old_charge.id),read_only=True).data

        message = f'Updated: {old_charge.charge_name} For Product: {old_charge.saving_product.product_name}'
        add_system_audit_trail('savings','update_savings_product_charge',message,'',old_charge_details,new_charge_details,self.request.user,branch)
        
class SavingProductCustomChargeView(viewsets.ModelViewSet):
    serializer_class = SavingProductCustomChargeSerializer

    def get_queryset(self):
        filter_array = {}
        productid   = self.request.query_params.get("productid", None)
        transaction_type = self.request.query_params.get("transaction_type", None)
        if transaction_type:
            filter_array['transaction_type'] = transaction_type
        if productid is not None:
            filter_array['saving_product__id'] = productid
        return SavingProductCustomCharge.objects.filter(**filter_array)
    
    def perform_create(self, serializer):
        serializer.save(charge_added_by=self.request.user)
    
    def perform_update(self, serializer):
        branchid    = get_current_user(self.request, 'organisation_branch_id', 1) 
        branch      = OrganisationBranch.objects.get(id=branchid)
        old_charge  = SavingProductCustomCharge.objects.get(pk=self.kwargs.get('pk',self.kwargs.get('id')))
        old_details = SavingProductCustomChargeSerializer(old_charge,read_only=True).data
        new_charge  = serializer.save(charge_last_updated_by=self.request.user)
        new_details = SavingProductCustomChargeSerializer(new_charge,read_only=True).data  
       
        message = f'Updated: Default Charges For Product: {new_charge.saving_product.product_name}'
        add_system_audit_trail('savings','update_savings_product_custom_charge',message,'',old_details,new_details,self.request.user,branch)
        
class SavingProductInterestView(viewsets.ModelViewSet):
    serializer_class = SavingProductInterestSerializer

    def get_queryset(self):
        productid = self.request.query_params.get("productid", None)
        if productid is not None:
            return SavingProductInterest.objects.filter(saving_product__id=productid)
        return SavingProductInterest.objects.all()

    def perform_create(self, serializer):
        # Save Product Interest.
        request_data = self.request.data
        organisation_id = get_current_user(self.request, 'organisation_id', 1)
        liability_account = OrganisationSubAccount.objects.filter(
            account_line='liabilities', account_organisation=organisation_id, parent_id__isnull=True).first()
        account_code = generate_chart_of_account_code(
            liability_account.id, 'liabilities', organisation_id)
        saving_product = SavingsProduct.objects.get(
            pk=request_data.get('product'))

        if saving_product:
            # Registering liability account for every saving product interest addded.
            product_interest_account = OrganisationSubAccount(
                account_organisation=Organisation.objects.get(pk=organisation_id), status='active',
                account_code=account_code, account_name=saving_product.product_name+"'s interest",
                account_type='user_defined', account_line='liabilities',
                added_by=self.request.user.id, parent_id=liability_account)
            product_interest_account.save()

            if product_interest_account:
                savedProductInterest = serializer.save(
                    interest_added_by=get_user_model().objects.get(pk=self.request.user.id),
                    saving_product=saving_product,
                    interest_last_updated_by=get_user_model().objects.get(pk=self.request.user.id),
                    accounts_chart=product_interest_account)
                if savedProductInterest:
                    return Response({'message': 'Saving product interest successfully created'}, status=status.HTTP_200_OK)
            else:
                return Response({'message': 'Failed to create a saving product interest'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        return Response({'message': 'Failed to create saving product interest'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    def perform_update(self, serializer):
        # Update Product Interest.
        branchid      = get_current_user(self.request, 'organisation_branch_id', 1) 
        branch        = OrganisationBranch.objects.get(id=branchid)
        old_interest  = SavingProductInterest.objects.get(pk=self.kwargs.get('pk',self.kwargs.get('id')))
        old_details   = SavingProductInterestSerializer(old_interest,read_only=True).data
        updatedProductInterest = serializer.save(interest_last_updated_by=self.request.user)
        new_details = SavingProductInterestSerializer(updatedProductInterest,read_only=True).data
       
        message = f'Updated: {updatedProductInterest.saving_product.product_name} Interest'
        add_system_audit_trail('savings','update_savings_product_interest',message,'',old_details,new_details,self.request.user,branch)
        
        if updatedProductInterest:
            return Response({'message': 'Saving Product interest successfully updated'}, status=status.HTTP_200_OK)
        else:
            return Response({'message': 'Failed to update saving product interest'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


class SavingProductChargeAPIView(APIView):
    def get(self, request, format=None):
        charges_data = []
        charges = SavingProductCharge.objects.all()
        if charges:
            serializer = SavingProductChargeSerializer(charges, many=True)
            users_data = serializer.data
        return Response({
            "count": len(charges_data),
            "results": charges_data
        })

    def post(self, request):
        '''
        save - Saving Product Charges
        '''
        request_data = request.data
        # Save Product charges.
        is_error = False
        request_data = self.request.data
        product = request_data.get('product')
        default_charge_type = request_data.get('default_charge_type')
        deposit_charge = request_data.get('deposit_charge')
        withdrawal_charge = request_data.get('withdrawal_charge')
        transfer_charge = request_data.get('transfer_charge')
        ledger_fees = request_data.get('ledger_fees')
        ledger_fees_frequency = request_data.get('ledger_fees_frequency')
        acc_closing_fee = request_data.get('acc_closing_fee')

        # Registering income account for each charge on a saving product addded.
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        branchid        = get_current_user(self.request, 'organisation_branch_id', 1) 
        branch         = OrganisationBranch.objects.get(id=branchid)
        income_account = OrganisationSubAccount.objects.filter(
            account_line='income', account_organisation=organisation_id, parent_id__isnull=True).first()
        charge_account_field = {"account_organisation": Organisation.objects.get(pk=organisation_id), "status": "active", "account_type": "user_defined", "account_line": "income",
                                "added_by": self.request.user, "parent_id": income_account}

        if int(product) > 0:
            savingProduct = SavingsProduct.objects.get(pk=product)
            old_charges = {}
            new_charges = {}

            if savingProduct:
                old_charges["default_charge_type"]   = savingProduct.default_charge_type
                old_charges["ledger_fees_frequency"] = savingProduct.ledger_fees_frequency
                savingProduct.default_charge_type    = default_charge_type
                savingProduct.ledger_fees_frequency  = ledger_fees_frequency
                savingProduct.save()
                
                new_charges["default_charge_type"]   = default_charge_type
                new_charges["ledger_fees_frequency"] = ledger_fees_frequency

                charge_field = {"charge_added_by": get_user_model().objects.get(pk=self.request.user.id), "saving_product": savingProduct,
                                "charge_last_updated_by": get_user_model().objects.get(pk=self.request.user.id)}
                prodt_deposit_charge = SavingProductCharge.objects.filter(
                    charge_key="deposit_charge", saving_product=savingProduct).first()

                if prodt_deposit_charge:
                    # Update savings product's charge
                    old_charges[prodt_deposit_charge.charge_name] = prodt_deposit_charge.charge
                    prodt_deposit_charge.charge = deposit_charge
                    prodt_deposit_charge.charge_last_updated_by = get_user_model(
                    ).objects.get(pk=self.request.user.id)
                    prodt_deposit_charge.save()

                    new_charge = SavingProductCharge.objects.get(pk=prodt_deposit_charge.id)
                    new_charges[new_charge.charge_name] = new_charge.charge
                else:
                    savings_charge_coa_name = savingProduct.product_name + ": Deposit Charge"
                    savedProductChargeAccount = self.generate_product_charge_account(
                        income_account, organisation_id, charge_account_field, savings_charge_coa_name)
                    if savedProductChargeAccount is not None:
                        charge_field["charge_key"] = "deposit_charge"
                        charge_field["charge_name"] = "Default Charge on Deposits"
                        charge_field["charge"] = deposit_charge
                        charge_field["accounts_chart"] = savedProductChargeAccount
                        SavingProductCharge.objects.create(**charge_field)
                    else:
                        is_error = True

                prodt_withdrawal_charge = SavingProductCharge.objects.filter(
                    charge_key="withdrawal_charge", saving_product=savingProduct).first()
                if prodt_withdrawal_charge:
                    # Update savings product's charge
                    old_charges[prodt_withdrawal_charge.charge_name] = prodt_withdrawal_charge.charge
                    prodt_withdrawal_charge.charge = withdrawal_charge
                    prodt_withdrawal_charge.charge_last_updated_by = get_user_model(
                    ).objects.get(pk=self.request.user.id)
                    prodt_withdrawal_charge.save()
                    
                    new_charge = SavingProductCharge.objects.get(pk=prodt_withdrawal_charge.id)
                    new_charges[new_charge.charge_name] = new_charge.charge
                else:
                    savings_charge_coa_name = savingProduct.product_name + ": Withdrawal Charge"
                    savedProductChargeAccount = self.generate_product_charge_account(
                        income_account, organisation_id, charge_account_field, savings_charge_coa_name)
                    if savedProductChargeAccount is not None:
                        charge_field["charge_key"] = "withdrawal_charge"
                        charge_field["charge_name"] = "Default Charge on Withdrawal"
                        charge_field["charge"] = withdrawal_charge
                        charge_field["accounts_chart"] = savedProductChargeAccount
                        SavingProductCharge.objects.create(**charge_field)
                    else:
                        is_error = True

                prodt_transfer_charge = SavingProductCharge.objects.filter(
                    charge_key="transfer_charge", saving_product=savingProduct).first()
                if prodt_transfer_charge:
                    # Update savings product's charge
                    old_charges[prodt_transfer_charge.charge_name] = prodt_transfer_charge.charge
                    prodt_transfer_charge.charge = transfer_charge
                    prodt_transfer_charge.charge_last_updated_by = get_user_model(
                    ).objects.get(pk=self.request.user.id)
                    prodt_transfer_charge.save()

                    new_charge = SavingProductCharge.objects.get(pk=prodt_transfer_charge.id)
                    new_charges[new_charge.charge_name] = new_charge.charge
                else:
                    savings_charge_coa_name = savingProduct.product_name + ": Transfer Charge"
                    savedProductChargeAccount = self.generate_product_charge_account(
                        income_account, organisation_id, charge_account_field, savings_charge_coa_name)
                    if savedProductChargeAccount is not None:
                        charge_field["charge_key"] = "transfer_charge"
                        charge_field["charge_name"] = "Default Charge on Transfers"
                        charge_field["charge"] = transfer_charge
                        charge_field["accounts_chart"] = savedProductChargeAccount
                        SavingProductCharge.objects.create(**charge_field)
                    else:
                        is_error = True

                prodt_ledger_fees = SavingProductCharge.objects.filter(
                    charge_key="ledger_fees", saving_product=savingProduct).first()
                if prodt_ledger_fees:
                    # Update savings product's charge
                    old_charges[prodt_ledger_fees.charge_name] = prodt_ledger_fees.charge
                    prodt_ledger_fees.charge = ledger_fees
                    prodt_ledger_fees.charge_last_updated_by = get_user_model(
                    ).objects.get(pk=self.request.user.id)
                    prodt_ledger_fees.save()

                    new_charge = SavingProductCharge.objects.get(pk=prodt_ledger_fees.id)
                    new_charges[new_charge.charge_name] = new_charge.charge
                else:
                    savings_charge_coa_name = savingProduct.product_name + ": Ledger Fees"
                    savedProductChargeAccount = self.generate_product_charge_account(
                        income_account, organisation_id, charge_account_field, savings_charge_coa_name)
                    if savedProductChargeAccount is not None:
                        charge_field["charge_key"] = "ledger_fees"
                        charge_field["charge_name"] = "Ledger Fees"
                        charge_field["charge"] = ledger_fees
                        charge_field["accounts_chart"] = savedProductChargeAccount
                        SavingProductCharge.objects.create(**charge_field)
                    else:
                        is_error = True

                if acc_closing_fee not in [None, '']:
                    savingProduct.account_closure_fee = acc_closing_fee
                    savingProduct.save(update_fields=["account_closure_fee"])

                    prodt_acc_closing_fee = SavingProductCharge.objects.filter(
                        charge_key="acc_closing_fee", saving_product=savingProduct).first()
                    if prodt_acc_closing_fee:
                        # Update savings product's charge
                        old_charges[prodt_acc_closing_fee.charge_name] = prodt_acc_closing_fee.charge
                        prodt_acc_closing_fee.charge = acc_closing_fee
                        prodt_acc_closing_fee.charge_last_updated_by = get_user_model(
                        ).objects.get(pk=self.request.user.id)
                        prodt_acc_closing_fee.save()

                        new_charge = SavingProductCharge.objects.get(pk=prodt_acc_closing_fee.id)
                        new_charges[new_charge.charge_name] = new_charge.charge
                    else:
                        savings_charge_coa_name = savingProduct.product_name + ": Acc Closing Fees"
                        savedProductChargeAccount = self.generate_product_charge_account(
                            income_account, organisation_id, charge_account_field, savings_charge_coa_name)
                        if savedProductChargeAccount is not None:
                            charge_field["charge_key"] = "acc_closing_fee"
                            charge_field["charge_name"] = "Account Closing Fee"
                            charge_field["charge"] = acc_closing_fee
                            charge_field["accounts_chart"] = savedProductChargeAccount
                            SavingProductCharge.objects.create(**charge_field)
                        else:
                            is_error = True
            message = f'Updated: Default Charges For Product: {savingProduct.product_name}'
            add_system_audit_trail('savings','update_savings_product_charge',message,'',old_charges,new_charges,self.request.user,branch)

        if is_error:
            return Response({'message': 'Error while saving product charge'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        
        return Response({"message": "Saving product charges updated successfully"}, status=status.HTTP_200_OK)

    def generate_product_charge_account(self, income_account, organisation_id, charge_account_field, account_name):
        account_code = generate_chart_of_account_code(
            income_account.id, 'income', organisation_id)
        charge_account_field["account_code"] = account_code
        charge_account_field["account_name"] = account_name
        charge_account_field["added_by"] = self.request.user.id

        save_account = OrganisationSubAccount.objects.create(
            **charge_account_field)
        if save_account:
            return save_account
        else:
            return None
   
class SavingAccountView(viewsets.ModelViewSet):
    serializer_class = SavingAccountSerializer
    filter_backends = (SearchFilter, OrderingFilter, DjangoFilterBackend, )
    filterset_fields = ['account_customer', 'is_active']

    def _get_organisation_id(self):
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        if organisation_id and not isinstance(organisation_id, dict):
            return organisation_id

        user_branch = getattr(self.request.user, 'user_organisation_branch', None)
        if user_branch is not None:
            return getattr(user_branch, 'branch_organisation_id', None)

        return None

    def _get_aml_status_filter(self):
        aml_status = self.request.query_params.get("aml_status", None)
        if aml_status is not None:
            aml_status = str(aml_status).strip().lower()
            if aml_status in ['blocked', 'unblocked', 'all']:
                return aml_status

        aml_blocked = self.request.query_params.get("aml_blocked", None)
        if aml_blocked is not None and str(aml_blocked).lower() in ['true', '1', 'yes', 'on']:
            return 'blocked'

        return None

    def get_queryset(self):
        organisation_id = self._get_organisation_id()
        search = self.request.query_params.get("search", None)
        status = self.request.query_params.get("status", None)
        has_members = self.request.query_params.get("has_members", None)
        aml_status = self._get_aml_status_filter()
        organisation = self.request.query_params.get('org')
        additional_filters = Q(deleted=False)
        queryset = SavingAccount.objects.all()

        if organisation:
            update_savings_account_statuses(organisation_id=organisation)
            return SavingAccount.objects.filter(customer_branch__branch_organisation__id=organisation)

        if has_members:
            additional_filters &= Q(account_customer__branch_customer_type__has_members=has_members)

        if organisation_id:
            update_savings_account_statuses(organisation_id=organisation_id)
            additional_filters &= Q(customer_branch__branch_organisation__id=organisation_id)

        if status in ['active', 'inactive', 'dormant', 'pending', 'closure_pending', 'closed']:
            additional_filters &= Q(status=status)

        if aml_status in ['blocked', 'unblocked', 'all']:
            queryset = queryset.annotate(
                has_aml_verification=Exists(
                    SavingsAMLVerification.objects.filter(
                        saving_account_id=OuterRef('pk')
                    )
                )
            ).prefetch_related(
                Prefetch(
                    'aml_verifications',
                    queryset=SavingsAMLVerification.objects.select_related(
                        'verified_by',
                        'verified_by__user_staff',
                        'saving_account',
                        'saving_account__account_customer',
                        'saving_account__account_product',
                        'saving_account__account_product__currency',
                        'saving_account__customer_branch',
                    ).order_by('-verified_at', '-id'),
                    to_attr='latest_aml_verifications',
                )
            )

        if aml_status == 'blocked':
            additional_filters &= Q(is_aml_withdrawal_blocked=True)
        elif aml_status == 'unblocked':
            additional_filters &= Q(has_aml_verification=True)
            additional_filters &= Q(is_aml_withdrawal_blocked=False)
        elif aml_status == 'all':
            additional_filters &= (Q(is_aml_withdrawal_blocked=True) | Q(has_aml_verification=True))

        queryset = queryset.filter(additional_filters)

        if search:
            queryset = queryset.filter(
                Q(account_customer__member_number__icontains=search)
                | Q(account_customer__name__icontains=search)
                | Q(account_customer__old_member_number__icontains=search)
                | Q(account_no__icontains=search),
            )

        if aml_status == 'blocked':
            return queryset.order_by('-aml_blocked_at', '-id')

        if aml_status == 'unblocked':
            return queryset.order_by('-id')

        if aml_status == 'all':
            return queryset.order_by('-is_aml_withdrawal_blocked', '-aml_blocked_at', '-id')

        return queryset.order_by('-id')
        

    def perform_create(self, serializer):
        request_data = self.request.data
        organisation_branch_id = get_current_user(
            self.request, 'organisation_branch_id', 1)
        account_product = serializer.validated_data.get('account_product') or SavingsProduct.objects.get(
            pk=request_data.get('account_product')
        )
        open_date = serializer.validated_data.get('open_date', timezone.now())
        initial_state = get_savings_account_initial_state(account_product, open_date=open_date)
        saved_saving_account = serializer.save(
            account_no=generate_saving_account_code(organisation_branch_id),
            account_customer=Customer.objects.get(
                pk=request_data.get('account_customer')),
            account_product=account_product,
            customer_branch=OrganisationBranch.objects.get(
                pk=organisation_branch_id),
            status=initial_state['status'],
            is_active=initial_state['is_active'],
            saving_account_added_by=get_user_model().objects.get(pk=self.request.user.id),
            saving_account_last_updated_by=get_user_model().objects.get(pk=self.request.user.id)
        )

        if saved_saving_account:
            return Response({'message': 'Saving account successfully created'}, status=status.HTTP_200_OK)
        else:
            return Response({'message': 'Failed to create a saving account'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    def perform_update(self, serializer):
        branchid    = get_current_user(self.request, 'organisation_branch_id', 1) 
        branch      = OrganisationBranch.objects.get(id=branchid)
        account     = SavingAccount.objects.get(pk=self.kwargs.get('pk',self.kwargs.get('id')))
        old_details = SavingAccountSerializer(account,read_only=True).data
        new_account = serializer.save(saving_account_last_updated_by=self.request.user)

        new_details = SavingAccountSerializer(new_account,read_only=True).data
        approval_action = getattr(self, '_saving_account_approval_action', False)

        if approval_action:
            message = f'Approved Saving Account: {account.account_no} For Customer: {account.account_customer.name}'
            add_system_audit_trail('savings','update_savings_account',message,'',old_details,new_details,self.request.user,branch)
            self._send_account_approval_sms(new_account, branchid)
            self._saving_account_approval_action = False
        else:
            message = f'Updated Saving Account: {account.account_no} For Customer: {account.account_customer.name}'
            add_system_audit_trail('savings','update_savings_account',message,'',old_details,new_details,self.request.user,branch)

    @action(
        detail=True,
        methods=['post'],
        url_path='aml-unblock',
        parser_classes=[MultiPartParser, FormParser, JSONParser],
    )
    def aml_unblock(self, request, *args, **kwargs):
        account_id = self.kwargs.get('pk', self.kwargs.get('id'))
        account = (
            SavingAccount.objects.select_related(
                'account_customer',
                'account_product',
                'customer_branch',
                'aml_trigger_transaction',
            )
            .filter(pk=account_id, deleted=False)
            .first()
        )

        if not account:
            return Response(
                {"message": "Savings account not found."},
                status=status.HTTP_404_NOT_FOUND,
            )

        organisation_id = get_current_user(self.request, 'organisation_id', None)
        if organisation_id and int(account.customer_branch.branch_organisation_id) != int(organisation_id):
            return Response(
                {"message": "Savings account does not belong to your organisation."},
                status=status.HTTP_403_FORBIDDEN,
            )

        if not account.is_aml_withdrawal_blocked:
            return Response(
                {"message": "This account is not blocked for AML withdrawals."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        source_of_income = str(request.data.get('source_of_income', '')).strip()
        if not source_of_income:
            return Response(
                {"message": "Source of income is required."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        supporting_document = str(
            request.data.get('supporting_document')
            or request.data.get('supporting_document_url')
            or ''
        ).strip()
        branchid = get_current_user(self.request, 'organisation_branch_id', 1)
        branch = OrganisationBranch.objects.get(id=branchid)
        verified_by = get_user_model().objects.get(pk=self.request.user.id)
        old_details = SavingAccountSerializer(account, read_only=True).data

        with transaction.atomic():
            verification = SavingsAMLVerification.objects.create(
                saving_account=account,
                source_of_income=source_of_income,
                supporting_document=supporting_document or None,
                threshold_amount=account.aml_threshold_amount,
                trigger_amount=account.aml_trigger_amount,
                block_reason=account.aml_block_reason,
                blocked_at=account.aml_blocked_at,
                trigger_transaction=account.aml_trigger_transaction,
                verified_by=verified_by,
            )

            account.is_aml_withdrawal_blocked = False
            account.aml_is_flagged = False
            account.aml_blocked_at = None
            account.aml_flagged_at = None
            account.aml_threshold_amount = 0
            account.aml_trigger_amount = 0
            account.aml_trigger_transaction = None
            account.aml_block_reason = None
            account.last_updated = timezone.now()
            account.saving_account_last_updated_by = verified_by
            account.save(
                update_fields=[
                    'is_aml_withdrawal_blocked',
                    'aml_is_flagged',
                    'aml_blocked_at',
                    'aml_flagged_at',
                    'aml_threshold_amount',
                    'aml_trigger_amount',
                    'aml_trigger_transaction',
                    'aml_block_reason',
                    'last_updated',
                    'saving_account_last_updated_by',
                ]
            )

        new_details = SavingAccountSerializer(account, read_only=True).data
        message = (
            f'Verified source of income and unblocked AML withdrawal restrictions '
            f'on A/C No: {account.account_no} For Customer: {account.account_customer.name}'
        )
        add_system_audit_trail(
            'savings',
            'update_savings_account_aml_verification',
            message,
            '',
            old_details,
            new_details,
            self.request.user,
            branch,
        )

        return Response(
            {
                "message": "Source of income verified and account unblocked successfully.",
                "result": SavingsAMLVerificationSerializer(verification).data,
            },
            status=status.HTTP_200_OK,
        )

    def _send_account_approval_sms(self, account, branch_id):
        customer = getattr(account, 'account_customer', None)
        if not customer or not customer.telephone:
            return

        organisation = account.customer_branch.branch_organisation
        organisation_name = organisation.short_name or organisation.name
        lifecycle = get_savings_account_lifecycle(account)

        if lifecycle['reason'] == 'activation_period' and lifecycle['activation_date']:
            activation_date = timezone.localtime(lifecycle['activation_date']).strftime("%Y-%m-%d %H:%M")
            sms_msg = (
                f"Dear {customer.name.capitalize()}, your savings account "
                f"{account.account_no} for {account.account_product.product_name} has been approved "
                f"and will become active on {activation_date}. Thank you for banking with {organisation_name}."
            )
        else:
            sms_msg = (
                f"Dear {customer.name.capitalize()}, your savings account "
                f"{account.account_no} for {account.account_product.product_name} has been approved. "
                f"Thank you for banking with {organisation_name}."
            )

        response = send_customer_single_sms(customer, self.request.user, branch_id, sms_msg, '')
        if not response.get('status'):
            send_non_subscriber_single_sms(customer, self.request.user, branch_id, sms_msg, '')

    def _prepare_update_payload(self, instance, request_data):
        update_data = request_data.copy()
        request_status = str(update_data.get('status', '')).strip().lower()

        if request_status != 'approved':
            self._saving_account_approval_action = False
            return update_data, None

        if instance.status != 'pending':
            return None, Response(
                {"message": "Only pending savings accounts can be approved."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        opened_by_id = update_data.get('opened_by', None)
        if not opened_by_id:
            return None, Response(
                {"message": "opened_by is required when approving a pending savings account."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        organisation_id = get_current_user(self.request, 'organisation_id', None)
        opened_by = Staff.objects.filter(
            id=opened_by_id,
            staff_organisation_id=organisation_id,
            is_active=True,
        ).first()

        if not opened_by:
            return None, Response(
                {"message": "Selected officer is invalid for the current organisation."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        initial_state = get_savings_account_initial_state(
            instance.account_product,
            open_date=instance.open_date,
        )
        update_data['status'] = initial_state['status']
        update_data['is_active'] = initial_state['is_active']
        self._saving_account_approval_action = True
        return update_data, None

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        update_data, error_response = self._prepare_update_payload(instance, request.data)

        if error_response:
            return error_response

        serializer = self.get_serializer(instance, data=update_data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            instance._prefetched_objects_cache = {}

        return Response(serializer.data)

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)

    @action(detail=True, methods=['delete'], url_path='delete')
    def delete_account(self, request, pk=None):
        """Soft delete a savings account only if it has no transactions."""
        from loans.models import LoanApplicationWithHold

        try:
            account = SavingAccount.objects.get(pk=pk, deleted=False)
        except SavingAccount.DoesNotExist:
            return Response({'status': 'failed', 'message': 'Account not found.'}, status=404)

        # Check for any transactions
        has_transactions = SavingAccountTransactions.objects.filter(
            customer_account=account, deleted=False
        ).exists()

        if has_transactions:
            return Response({
                'status': 'failed',
                'message': 'This account has transactions and cannot be deleted.'
            }, status=400)

        # Soft delete the account
        account.deleted = True
        account.deleted_by = request.user
        account.deleted_at = timezone.now()
        account.status = 'closed'
        account.is_active = False
        account.save()

        # Soft delete related records
        SavingsBlockedAmount.objects.filter(
            customer_account=account, status='active'
        ).update(status='released', deleted=True, deleted_at=timezone.now(), deleted_by=request.user)

        LoanApplicationWithHold.objects.filter(
            account=account, status='held'
        ).update(status='released', deleted=True, deleted_at=timezone.now(), deleted_by=request.user)

        # Audit trail
        branchid = get_current_user(request, 'organisation_branch_id', 1)
        branch = OrganisationBranch.objects.get(id=branchid)
        message = f'Deleted Saving Account: {account.account_no} For Customer: {account.account_customer.name}'
        add_system_audit_trail('savings', 'delete_savings_account', message, '', {}, {}, request.user, branch)

        return Response({'status': 'success', 'message': f'Account {account.account_no} has been deleted.'})


class MobileAppSavingAccountsView(APIView):
    permission_classes = [IsAuthenticated]

    def _get_authenticated_customer(self, request):
        from mobileapp.mobile_auth_views import get_customer_from_token_request

        return get_customer_from_token_request(request)

    def get(self, request, format=None):
        customer = self._get_authenticated_customer(request)
        if not customer:
            return Response(
                {"status": False, "message": "Customer not found for authenticated user."},
                status=status.HTTP_404_NOT_FOUND,
            )

        update_savings_account_statuses(customer_id=customer.id)
        request_status = request.query_params.get('status', 'all')
        queryset = SavingAccount.objects.filter(
            account_customer=customer,
            deleted=False,
        ).select_related(
            'account_product',
            'account_customer',
            'customer_branch',
            'opened_by',
            'saving_account_added_by',
            'saving_account_last_updated_by',
        ).order_by('-id')

        if request_status and request_status != 'all':
            queryset = queryset.filter(status=request_status)

        return Response(
            {
                "status": True,
                "count": queryset.count(),
                "results": SavingAccountSerializer(queryset, many=True).data,
            },
            status=status.HTTP_200_OK,
        )

    def post(self, request, format=None):
        customer = self._get_authenticated_customer(request)
        if not customer:
            return Response(
                {"status": False, "message": "Customer not found for authenticated user."},
                status=status.HTTP_404_NOT_FOUND,
            )

        serializer = MobileAppSavingAccountCreateSerializer(
            data=request.data,
            context={"request": request, "customer": customer},
        )
        if not serializer.is_valid():
            return Response(
                {"status": False, "message": "Invalid data.", "errors": serializer.errors},
                status=status.HTTP_400_BAD_REQUEST,
            )

        organisation_branch = customer.customer_branch
        saving_account = serializer.save(
            account_no=generate_saving_account_code(organisation_branch.id),
            account_customer=customer,
            customer_branch=organisation_branch,
            opened_by=None,
            status='pending',
            is_active=False,
            saving_account_added_by=get_user_model().objects.get(pk=request.user.id),
            saving_account_last_updated_by=get_user_model().objects.get(pk=request.user.id),
        )

        return Response(
            {
                "status": True,
                "message": "Savings account request submitted successfully.",
                "result": SavingAccountSerializer(saving_account).data,
            },
            status=status.HTTP_201_CREATED,
        )


class SavingsBlockedAmountView(viewsets.ModelViewSet):
    serializer_class = SavingsBlockedAmountSerializer
    def get_queryset(self):
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        search = self.request.query_params.get("search", None)
        status = self.request.query_params.get("status", None)
        additional_filters = Q()
        if organisation_id:
            additional_filters.add(Q(**{"customer_branch__branch_organisation__id":organisation_id}), Q.AND)
        if status == 'active' or status == 'inactive':
            additional_filters.add(Q(**{"status":status}), Q.AND)
        if search:
            return SavingsBlockedAmount.objects.filter(Q(customer_account__account_customer__member_number__icontains=search) | Q(customer_account__account_customer__name__icontains=search)| Q(customer_account__account_customer__old_member_number__icontains=search),additional_filters)
        else:
            return SavingsBlockedAmount.objects.filter(additional_filters).order_by('-id')
        
    def perform_create(self, serializer):
        organisation_branch_id = get_current_user(
            self.request, 'organisation_branch_id', 1)
        saved_saving_account = serializer.save(
            customer_branch=OrganisationBranch.objects.get(pk=organisation_branch_id),
            blocked_amount_added_by=get_user_model().objects.get(pk=self.request.user.id),
            blocked_amount_last_updated_by=get_user_model().objects.get(pk=self.request.user.id)
        )
        if saved_saving_account:
            return Response({'message': 'Saving account successfully created'}, status=status.HTTP_200_OK)
        else:
            return Response({'message': 'Failed to create a saving account'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        
    def perform_update(self, serializer):
        branchid = get_current_user(self.request, 'organisation_branch_id', 1) 
        branch   = OrganisationBranch.objects.get(id=branchid)
        account_blocking  = SavingsBlockedAmount.objects.get(pk=self.kwargs.get('pk',self.kwargs.get('id')))
        updated_blocking  = serializer.save(blocked_amount_last_updated_by=get_user_model(
        ).objects.get(pk=self.request.user.id))
        old_details = SavingsBlockedAmountSerializer(account_blocking,read_only=True).data
        message     = f'Unblocked Account: {updated_blocking.customer_account.account_no} For Customer: {updated_blocking.customer_account.account_customer.name}'
        add_system_audit_trail('savings','update_savings_account_blocking',message,'',old_details,{},self.request.user,branch)
        

class SavingDepositsView(viewsets.ModelViewSet):
    serializer_class = SavingAccountTransactionsSerializer

    def get_queryset(self):
        filter_array = {}
        filter_array['transaction_type'] = 'deposit'
        filter_array['deleted'] = False
        end = self.request.GET.get('e', None)
        start = self.request.GET.get('s', None)
        search = self.request.GET.get('search', None)
        organisation_branch_id = get_current_user(
            self.request, 'organisation_branch_id', None)


        if organisation_branch_id is not None:
            filter_array['transaction__branch'] = OrganisationBranch.objects.get(pk=organisation_branch_id)
        if search:
            filter_array['customer_account__account_customer__name__icontains'] = search
        if start:
            filter_array['transaction__record_date__gte'] = make_aware(datetime.strptime(start + ' 00:00', '%Y-%m-%d %H:%M'))

        else:
            filter_array['transaction__record_date__gte'] = make_aware(datetime.strptime(datetime.today().strftime("%Y-%m-%d") + ' 00:00', '%Y-%m-%d %H:%M'))

        if end:
            filter_array['transaction__record_date__lte'] = make_aware(datetime.strptime(end + ' 23:59', '%Y-%m-%d %H:%M'))
        return SavingAccountTransactions.objects.filter(**filter_array).select_related(
            'transaction',
            'transaction__branch',
            'transaction__added_by__user_staff',
            'customer_account',
            'customer_account__account_customer',
            'customer_account__account_customer__branch_customer_type',
            'customer_account__account_product',
            'customer_account__account_product__currency',
        ).order_by('-id')
  
    def perform_create(self, serializer):
        charge = 0
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        branch_id = get_current_user(self.request, 'organisation_branch_id', None)
        inter_branch_trans = None

        debit_chart_id = self.request.data.get('debit_chart')
        amount = float(self.request.data.get('amount'))
        send_sms = self.request.data.get('send_sms')
        depositor_name = self.request.data.get('depositor')
        comment = self.request.data.get('heading')
        member_id = self.request.data.get('member_id',None)
        deposit_charge_amount = self.request.data.get('charge',0)
        customer_account = SavingAccount.objects.get(pk=self.request.data.get('credit_chart'))
        assert_savings_account_can_credit(customer_account, 'deposits', as_at=self.request.data.get('record_date'))
        # InterBranch chart
        interbranch_chart = get_inter_branch_chart(OrganisationBranch.objects.get(pk=branch_id), customer_account.customer_branch)

        reference_no = generate_reference_no(customer_account.account_product.accounts_chart.account_line, organisation_id, 'dep')
        deposit_charge = SavingProductCharge.objects.filter(saving_product=customer_account.account_product, charge_key='deposit_charge').first()
        
        if customer_account.account_product.deposit_custom_charge:
            charge = deposit_charge_amount
        else:
            if customer_account.account_product.default_charge_type == 'flat':
                charge = round(float(deposit_charge_amount)/100)*100
            if customer_account.account_product.default_charge_type != 'flat':
                charge_amount = (float(deposit_charge_amount)/100)*amount
                charge = round(charge_amount/100) * 100
                
        if amount > charge:
            transaction_fields = {
                "heading": 'Deposit: by ' + depositor_name + ' for ' + customer_account.account_customer.name + '-' + customer_account.account_customer.old_member_number,
                "coment": comment,
                "amount": amount,
                "credit_chart": customer_account.account_product.accounts_chart,
                "debit_chart": OrganisationSubAccount.objects.get(pk=debit_chart_id),
                "reference_no": reference_no,
                "voucher_no": self.request.data.get('voucher_no'),
                "record_date": self.request.data.get('record_date'),
                "payment_method": self.request.data.get('payment_method'),
                "added_by": get_user_model().objects.get(pk=self.request.user.id),
                "branch": OrganisationBranch.objects.get(pk=branch_id),
                "depositor_name": depositor_name,
            }

            # Handle inter-branch transactions update  -> soure branch
            if branch_id != customer_account.customer_branch.id:
                transaction_fields["heading"] = 'Inter-branch deposit by ' + depositor_name + ' for ' + customer_account.account_customer.name + '-' + customer_account.account_customer.old_member_number
                transaction_fields["branch"] = OrganisationBranch.objects.get(pk=branch_id)
                transaction_fields["credit_chart"] = interbranch_chart

            # Register deposit transactions
            saved_transaction = SystemTransactions.objects.create(**transaction_fields)

            if saved_transaction:
                save_trans = None
                # Handle inter-branch transactions update  -> destination branch
                if branch_id != customer_account.customer_branch.id:
                    transaction_fields["credit_chart"] = customer_account.account_product.accounts_chart
                    transaction_fields["branch"] = customer_account.customer_branch
                    transaction_fields["debit_chart"] = interbranch_chart
                    transaction_fields["payment_method"] = 'settlement'
                    inter_branch_trans = SystemTransactions.objects.create(**transaction_fields)

                    # Reconcile inter-branch transactions
                    if inter_branch_trans:
                        inter_branch_trans_field = {
                            "source_transaction": saved_transaction,
                            "destination_transaction": inter_branch_trans,
                            "added_by": get_user_model().objects.get(pk=self.request.user.id),
                        }
                        InterBranchTransactions.objects.create(**inter_branch_trans_field)

                        # Register saving deposit transaction mapping for inter-branch
                        save_trans = serializer.save(transaction_type='deposit', customer_account_id=customer_account.id, transaction_id=inter_branch_trans.id)
                else:
                    # add saving deposit transaction mapping for single branch
                    save_trans = serializer.save(transaction_type='deposit', customer_account_id=customer_account.id, transaction_id=saved_transaction.id)
                
                if save_trans:
                    mark_savings_account_for_aml_deposit(
                        customer_account,
                        amount,
                        transaction=save_trans.transaction,
                    )
                    # Register group deposit mapping. i.e a user deposited money to group account
                    if member_id:
                        membership = GroupMembership.objects.filter(member__id=member_id,group=customer_account.account_customer, active=True).first()
                        if membership:
                            group_trans_field = {
                                "membership": membership,
                                "savings": save_trans
                            }
                            GroupSavingTransaction.objects.create(**group_trans_field)

                    if charge > 0 and deposit_charge:
                        reference_no = generate_reference_no(deposit_charge.accounts_chart.account_line, organisation_id)
                        charge_fields = {
                            "heading": 'Deposit Charge: ' + ' on A/C No: ' + customer_account.account_no,
                            "coment": 'Deposit Charge: ('+f"{charge:,}"+') ' + ' on A/C No: ' + customer_account.account_no,
                            "amount": charge,
                            "credit_chart": deposit_charge.accounts_chart,
                            "debit_chart": customer_account.account_product.accounts_chart,
                            "reference_no": reference_no,
                            "payment_method": self.request.data.get('payment_method'),
                            "record_date": self.request.data.get('record_date'),
                            "added_by": get_user_model().objects.get(pk=self.request.user.id),
                            "branch": OrganisationBranch.objects.get(pk=branch_id),
                        }

                        # Handle inter-branch deposit charge transactions update  -> source branch
                        if branch_id != customer_account.customer_branch.id:
                            charge_fields["heading"] = 'Inter-branch deposit charge: (' + f"{charge:,}"+') ' + ' on A/C No: ' + customer_account.account_no
                            charge_fields["branch"] = OrganisationBranch.objects.get(pk=branch_id)
                            charge_fields["debit_chart"] = interbranch_chart
                        charge_transaction = SystemTransactions.objects.create(**charge_fields)

                        if charge_transaction:
                            # Handle inter-branch deposit charge transactions update  -> destination branch
                            if branch_id != customer_account.customer_branch.id:
                                charge_fields["debit_chart"] = customer_account.account_product.accounts_chart
                                charge_fields["branch"] = customer_account.customer_branch

                                charge_fields["credit_chart"] = interbranch_chart
                                charge_fields["payment_method"] = 'settlement'
                                inter_branch_trans = SystemTransactions.objects.create(**charge_fields)

                                # Reconcile inter-branch transactions
                                if inter_branch_trans:
                                    inter_branch_trans_field = {
                                        "source_transaction": charge_transaction,
                                        "destination_transaction": inter_branch_trans,
                                        "added_by": get_user_model().objects.get(pk=self.request.user.id),
                                    }
                                    InterBranchTransactions.objects.create(**inter_branch_trans_field)

                                # Register saving deposit charge transactions mapping for inter-branch
                                saving_charge_fields = {
                                    "customer_account": customer_account,
                                    "transaction": inter_branch_trans,
                                    "transaction_type": "deposit_charge",
                                    "parent_id": save_trans.id
                                }
                                SavingAccountTransactions.objects.create(**saving_charge_fields)
                            else:
                                # Register saving deposit charge transactions mapping for single branch
                                saving_charge_fields = {
                                    "customer_account": customer_account,
                                    "transaction": charge_transaction,
                                    "transaction_type": "deposit_charge",
                                    "parent_id": save_trans.id
                                }
                                SavingAccountTransactions.objects.create(**saving_charge_fields)

                    if send_sms:
                        data = {"sms_key": "cash_deposit_sms", "customer_account": customer_account, "user": self.request.user, "branch_id": branch_id, "save_trans": save_trans,"customer":customer_account.account_customer}
                        send_customer_sms(data)

                    sync_savings_account_lifecycle(customer_account)

                    # Create transaction receipt for deposit
                    try:
                        from vas.utils import create_savings_receipt
                        create_savings_receipt(
                            transaction_type='deposit',
                            customer_account=customer_account,
                            reference_number=reference_no,
                            generated_by=self.request.user,
                            amount=amount,
                            transaction_date=saved_transaction.record_date
                        )
                    except Exception as e:
                        print(f"Error creating deposit receipt: {e}")

                #if branch_id == customer_account.customer_branch.id:
                # Process account booking payments
                thread_multiple_booking_payments(customer_account, organisation_id, customer_account.customer_branch.id, self.request.user.id)


class PendingWithdrawalsView(viewsets.ModelViewSet):
    serializer_class = PendingWithdrawalsSerializer

    def get_queryset(self):
        organisation_branch_id = get_current_user(self.request, 'organisation_branch_id', None)
        if organisation_branch_id is not None:
            end = self.request.GET.get('e', None)
            start = self.request.GET.get('s', None)

            if start != None or end != None:
                if start:
                    start = make_aware(datetime.strptime(start + ' 00:00', '%Y-%m-%d %H:%M'))
                else:
                    start = make_aware(datetime.strptime(datetime.today().strftime("%Y-%m-%d") + ' 00:00', '%Y-%m-%d %H:%M'))
                if end:
                    end = make_aware(datetime.strptime(end + ' 23:59', '%Y-%m-%d %H:%M'))
                else:
                    end = make_aware(datetime.strptime(datetime.today().strftime("%Y-%m-%d") + ' 23:59', '%Y-%m-%d %H:%M'))

                return PendingWithdrawals.objects.filter(record_date__gte=start, record_date__lte=end, customer_branch=OrganisationBranch.objects.get(pk=organisation_branch_id), saving_account_transaction__isnull=True).order_by('-id')
            return PendingWithdrawals.objects.filter(customer_branch=OrganisationBranch.objects.get(pk=organisation_branch_id)).order_by('-id')
        return PendingWithdrawals.objects.all()

    def perform_create(self, serializer):
        branch_id = get_current_user(
            self.request, 'organisation_branch_id', None)
        customer_account = SavingAccount.objects.get(pk=self.request.data.get('customer_account'))
        assert_savings_account_can_debit(
            customer_account,
            'withdrawals',
            as_at=self.request.data.get('record_date'),
        )
        serializer.save(customer_branch=OrganisationBranch.objects.get(
            pk=branch_id), added_by=self.request.user)

    def perform_update(self, serializer):
        serializer.save(last_updated=datetime.now(),
                        last_updated_by=self.request.user)
        approved_withdraw = serializer.save()

        if approved_withdraw:
            save_user_notification({
                "heading":  "Saving Withdrawal Approvals",
                "message": f"Saving Withdrawal of Amount: {approved_withdraw.amount} for customer {approved_withdraw.customer_account.account_customer.name} member number {approved_withdraw.customer_account.account_customer.member_number} has been approved by {approved_withdraw.last_updated_by} as at {approved_withdraw.record_date.date()}",
                "branch":OrganisationBranch.objects.get(pk=approved_withdraw.customer_branch.id),
                "branch_name":approved_withdraw.customer_branch.name,
                "added_by":approved_withdraw.added_by,
                "last_updated_by":approved_withdraw.last_updated_by,
                "key":"savings_notifications"
            })


class SavingAccountClosureView(viewsets.ModelViewSet):
    serializer_class = SavingAccountClosureSerializer
    http_method_names = ['get', 'post']

    def get_queryset(self):
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        account_id = self.request.query_params.get('account', None)
        customer_id = self.request.query_params.get('customer', None)
        closure_status = self.request.query_params.get('status', None)

        filters = Q(deleted=False)
        if organisation_id:
            filters &= Q(saving_account__customer_branch__branch_organisation__id=organisation_id)
        if account_id:
            filters &= Q(saving_account__id=account_id)
        if customer_id:
            filters &= Q(saving_account__account_customer__id=customer_id)
        if closure_status:
            filters &= Q(status=closure_status)

        return SavingAccountClosure.objects.filter(filters).select_related(
            'saving_account',
            'saving_account__account_customer',
            'saving_account__account_product',
            'fee_collection_account',
            'withdrawal_destination_chart',
            'closure_added_by__user_staff',
            'closure_completed_by__user_staff',
        ).order_by('-id')

    def create(self, request, *args, **kwargs):
        organisation_id = get_current_user(request, 'organisation_id', None)
        branch_id = get_current_user(request, 'organisation_branch_id', None)
        saving_account_id = request.data.get('saving_account')
        fee_collection_account_id = request.data.get('fee_collection_account')

        if not saving_account_id:
            return Response(
                {"message": "saving_account is required."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        account = SavingAccount.objects.select_related(
            'account_product',
            'account_customer',
            'customer_branch',
        ).filter(
            pk=saving_account_id,
            deleted=False,
            customer_branch__branch_organisation__id=organisation_id,
        ).first()

        if not account:
            return Response(
                {"message": "Selected savings account was not found."},
                status=status.HTTP_404_NOT_FOUND,
            )

        fee_collection_account = OrganisationSubAccount.objects.filter(
            pk=fee_collection_account_id,
            account_organisation=organisation_id,
            account_line='income',
            status='active',
        ).first()

        if not fee_collection_account:
            return Response(
                {"message": "A valid income chart is required for the account closure fee."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        closure_fee = request.data.get('closure_fee', account.account_product.account_closure_fee or 0)

        try:
            closure = initiate_savings_account_closure(
                account=account,
                closure_fee=float(closure_fee or 0),
                fee_collection_account=fee_collection_account,
                record_date=request.data.get('record_date'),
                user=request.user,
                organisation_id=organisation_id,
                branch_id=branch_id,
            )
        except ValidationError as exc:
            return Response(exc.detail, status=status.HTTP_400_BAD_REQUEST)

        serializer = self.get_serializer(closure)
        return Response(serializer.data, status=status.HTTP_201_CREATED)

    @action(detail=True, methods=['post'])
    def finalize(self, request, pk=None):
        organisation_id = get_current_user(request, 'organisation_id', None)
        branch_id = get_current_user(request, 'organisation_branch_id', None)
        closure = self.get_object()
        destination_chart = None
        destination_chart_id = request.data.get('withdrawal_destination_chart') or request.data.get('credit_chart')
        withdrawable_balance = get_savings_account_closure_available_balance(closure.saving_account)

        if withdrawable_balance > 0:
            destination_chart = OrganisationSubAccount.objects.filter(
                pk=destination_chart_id,
                account_organisation=organisation_id,
                status='active',
            ).first()

            if not destination_chart:
                return Response(
                    {"message": "A valid destination account is required for the final closure withdrawal."},
                    status=status.HTTP_400_BAD_REQUEST,
                )

        try:
            closure = finalize_savings_account_closure(
                closure=closure,
                amount=request.data.get('amount', withdrawable_balance),
                destination_chart=destination_chart,
                payment_method=request.data.get('payment_method'),
                voucher_no=request.data.get('voucher_no'),
                record_date=request.data.get('record_date'),
                receiver=request.data.get('receiver'),
                send_sms=request.data.get('send_sms'),
                user=request.user,
                organisation_id=organisation_id,
                branch_id=branch_id,
            )
        except ValidationError as exc:
            return Response(exc.detail, status=status.HTTP_400_BAD_REQUEST)

        serializer = self.get_serializer(closure)
        return Response(serializer.data, status=status.HTTP_200_OK)


class SavingWithdrawalsView(viewsets.ModelViewSet):
    serializer_class = SavingAccountTransactionsSerializer

    def get_queryset(self):
        filter_array = {}
        filter_array['transaction_type'] = 'withdrawal'
        end = self.request.GET.get('e', None)
        start = self.request.GET.get('s', None)
        search = self.request.GET.get('search', None)
        organisation_branch_id = get_current_user(
            self.request, 'organisation_branch_id', None)

        if organisation_branch_id is not None:
            filter_array['transaction__branch'] = OrganisationBranch.objects.get(pk=organisation_branch_id)

        if search:
            filter_array['customer_account__account_customer__name__icontains'] = search

        if start:
            filter_array['transaction__record_date__gte'] = make_aware(datetime.strptime(start + ' 00:00', '%Y-%m-%d %H:%M'))
        else:
            filter_array['transaction__record_date__gte'] = make_aware(datetime.strptime(datetime.today().strftime("%Y-%m-%d") + ' 00:00', '%Y-%m-%d %H:%M'))

        if end:
            filter_array['transaction__record_date__lte'] = make_aware(datetime.strptime(end + ' 23:59', '%Y-%m-%d %H:%M'))
        
        
        filter_array['transaction__payment_method'] = 'cash'
        return SavingAccountTransactions.objects.filter(**filter_array).order_by('-id')

    def perform_create(self, serializer):
        charge = 0
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        branch_id = get_current_user(self.request, 'organisation_branch_id', None)
        inter_branch_trans = None

        credit_chart_id = self.request.data.get('credit_chart')
        record_date = self.request.data.get('record_date')
        withdrawal_charge_amount = self.request.data.get('charge',0)
        apply_charges = self.request.data.get('activate_charge', False)
        customer_account = SavingAccount.objects.get(pk=self.request.data.get('customer_account'))
        assert_savings_account_can_debit(customer_account, 'withdrawals', as_at=record_date)
        max_withdraws = customer_account.account_product.max_withdraws

        # InterBranch chart
        interbranch_chart = get_inter_branch_chart(OrganisationBranch.objects.get(pk=branch_id), customer_account.customer_branch)

        reference_no = generate_reference_no(customer_account.account_product.accounts_chart.account_line, organisation_id, 'wd')
        amount = float(self.request.data.get('amount'))
        receiver = self.request.data.get('receiver')
        send_sms = self.request.data.get('send_sms')
        # Default withdraw charge
        withdrawal_charge = SavingProductCharge.objects.filter(saving_product=customer_account.account_product, charge_key='withdrawal_charge').first()
        
        if apply_charges or apply_charges == 'true':
            if customer_account.account_product.withdraw_custom_charge:
                charge = float(withdrawal_charge_amount)
            else:
                # Default withdraw charge
                if withdrawal_charge and float(withdrawal_charge_amount) > 0:
                    if customer_account.account_product.default_charge_type == 'flat':
                        charge = round(float(withdrawal_charge_amount)/100)*100
                    if customer_account.account_product.default_charge_type != 'flat':
                        charge_amount = (float(withdrawal_charge_amount)/100)*amount
                        charge = round(charge_amount/100) * 100
            
                # Max validate withdraw amount
                if(float(max_withdraws) > 0) and amount > float(max_withdraws):
                    # Ignore the default charge and apply withdrawal limit charge
                    if customer_account.account_product.max_withdraw_limit_charge_type == 'flat':
                        charge = round(float(withdrawal_charge_amount)/100)*100
                    if customer_account.account_product.max_withdraw_limit_charge_type != 'flat':
                        charge_amount = (float(withdrawal_charge_amount)/100)*amount
                        charge = round(charge_amount/100) * 100

        if not receiver:
            receiver = customer_account.account_customer.name

        account_balance   = get_account_balance(customer_account.id)
        if account_balance['balance_raw'] >= (amount + charge):
            transaction_fields = {
                "heading": 'Withdrawal by ' + receiver + ' From: ' + customer_account.account_customer.name + '-' + customer_account.account_customer.old_member_number,
                "coment": customer_account.account_customer.name + 'has withdrawn ('+str(amount)+') ' + 'from' + ' A/C No: ' + customer_account.account_no,
                "amount": amount,
                "debit_chart": customer_account.account_product.accounts_chart,
                "credit_chart": OrganisationSubAccount.objects.get(pk=credit_chart_id),
                "reference_no": reference_no,
                "voucher_no": self.request.data.get('voucher_no'),
                "payment_method": self.request.data.get('payment_method'),
                "added_by": get_user_model().objects.get(pk=self.request.user.id),
                "branch": OrganisationBranch.objects.get(pk=branch_id),
                "record_date": record_date
            }

            # Handle inter-branch transactions update  -> soure branch
            if branch_id != customer_account.customer_branch.id:
                transaction_fields["heading"] = 'Inter-branch withdrawal by ' + receiver + ' From: ' + customer_account.account_customer.name + '-' + customer_account.account_customer.old_member_number
                transaction_fields["branch"] = OrganisationBranch.objects.get(pk=branch_id)
                transaction_fields["debit_chart"] = interbranch_chart

            # Register withdrawal transactions
            saved_transaction = SystemTransactions.objects.create(**transaction_fields)

            if saved_transaction:
                save_trans = None

                # Handle inter-branch transactions update  -> destination branch
                if branch_id != customer_account.customer_branch.id:
                    transaction_fields["debit_chart"] = customer_account.account_product.accounts_chart
                    transaction_fields["branch"] = customer_account.customer_branch
                    transaction_fields["credit_chart"] = interbranch_chart
                    transaction_fields["payment_method"] = 'settlement'
                    inter_branch_trans = SystemTransactions.objects.create(**transaction_fields)

                    # Reconcile inter-branch transactions
                    if inter_branch_trans:
                        inter_branch_trans_field = {
                            "source_transaction": saved_transaction,
                            "destination_transaction": inter_branch_trans,
                            "added_by": get_user_model().objects.get(pk=self.request.user.id),
                        }
                        InterBranchTransactions.objects.create(**inter_branch_trans_field)

                        # Register saving withdrawal transaction mapping for inter-branch
                        save_trans = serializer.save(customer_account_id=customer_account.id, transaction_type='withdrawal', transaction_id=inter_branch_trans.id)
                else:
                    # Register saving withdrawal transaction mapping for single branch
                    save_trans = serializer.save(customer_account_id=customer_account.id, transaction_type='withdrawal', transaction_id=saved_transaction.id)

                if save_trans:
                    status = self.request.data.get('status')

                    # Update pending withrawal with transaction Id. This indicate the approved withdrawal has been process.
                    if status == 'approved':
                        pending_withdrawal = self.request.data.get('pending_withdrawal')
                        pending_withdrawal_obj = PendingWithdrawals.objects.get(pk=pending_withdrawal)
                        pending_withdrawal_obj.saving_account_transaction = save_trans
                        pending_withdrawal_obj.save()

                    if charge > 0 and withdrawal_charge:
                        reference_no = generate_reference_no(withdrawal_charge.accounts_chart.account_line, organisation_id)
                        charge_fields = {
                            "heading": 'Saving withdrawal charge on A/C No: ' + customer_account.account_no,
                            "coment": 'Saving withdrawal charge on A/C No: ' + customer_account.account_no,
                            "amount": charge,
                            "credit_chart": withdrawal_charge.accounts_chart,
                            "debit_chart": customer_account.account_product.accounts_chart,
                            "reference_no": reference_no,
                            "payment_method": self.request.data.get('payment_method'),
                            "added_by": get_user_model().objects.get(pk=self.request.user.id),
                            "branch": OrganisationBranch.objects.get(pk=branch_id),
                            "record_date": record_date
                        }

                        # Handle inter-branch  withdrawal charges transactions update  -> source branch
                        if branch_id != customer_account.customer_branch.id:
                            charge_fields["heading"] = 'Inter-branch saving withdrawal charge on A/C No: ' + customer_account.account_no
                            charge_fields["branch"] = OrganisationBranch.objects.get(pk=branch_id)
                            charge_fields["debit_chart"] = interbranch_chart

                        charge_transaction = SystemTransactions.objects.create(**charge_fields)

                        if charge_transaction:

                            # Handle inter-branch  withdrawal charges transactions update  -> destination branch
                            if branch_id != customer_account.customer_branch.id:
                                charge_fields["debit_chart"] = customer_account.account_product.accounts_chart
                                charge_fields["branch"] = customer_account.customer_branch
                                charge_fields["credit_chart"] = interbranch_chart
                                charge_fields["payment_method"] = 'settlement'
                                inter_branch_trans = SystemTransactions.objects.create(**charge_fields)

                                # Reconcile inter-branch transactions
                                if inter_branch_trans:
                                    inter_branch_trans_field = {
                                        "source_transaction": charge_transaction,
                                        "destination_transaction": inter_branch_trans,
                                        "added_by": get_user_model().objects.get(pk=self.request.user.id),
                                    }
                                    InterBranchTransactions.objects.create(**inter_branch_trans_field)

                                # Register saving withdrawal charge transactions for inter-branch
                                saving_charge_fields = {
                                    "customer_account": customer_account,
                                    "transaction": inter_branch_trans,
                                    "parent_id": save_trans.id,
                                    "transaction_type": 'withdrawal_charge'
                                }
                                SavingAccountTransactions.objects.create(**saving_charge_fields)
                            else:
                                # Register saving withdrawal charge transactions for single branch
                                saving_charge_fields = {
                                    "customer_account": customer_account,
                                    "transaction": charge_transaction,
                                    "parent_id": save_trans.id,
                                    "transaction_type": 'withdrawal_charge'
                                }
                                SavingAccountTransactions.objects.create(**saving_charge_fields)

                    if send_sms:
                        data = {"sms_key": "cash_withdraw_sms", "customer_account": customer_account,
                                "user": self.request.user, "branch_id": branch_id, "save_trans": save_trans,"customer":customer_account.account_customer}
                        send_customer_sms(data)

                    sync_savings_account_lifecycle(customer_account)

                    # Create transaction receipt for withdrawal
                    try:
                        from vas.utils import create_savings_receipt
                        create_savings_receipt(
                            transaction_type='withdrawal',
                            customer_account=customer_account,
                            reference_number=reference_no,
                            generated_by=self.request.user,
                            amount=amount,
                            transaction_date=record_date
                        )
                    except Exception as e:
                        print(f"Error creating withdrawal receipt: {e}")


class SavingWithdrawalsAPIView(APIView):

    def post(self, request):
        '''
            save - Saving withdraws
        '''
        request_data = request.data
        is_error = False
        request_data = self.request.data
        withdraw_details = {
            "organisation_id": get_current_user(self.request, 'organisation_id', None),
            "organisation_branch_id": get_current_user(self.request, 'organisation_branch_id', None),
            "amount":request_data.get('amount'),
            "customer_account":request_data.get('customer_account'),
            "charge":request_data.get('charge',0),
            "credit_chart":request_data.get('credit_chart'),
            "reference_no":request_data.get('reference_no'),
            "voucher_no":request_data.get('voucher_no'),
            "payment_method":request_data.get('payment_method'),
            "added_by": get_user_model().objects.get(pk=self.request.user.id),
            "status":request_data.get('status'),
            "receiver":request_data.get('receiver'),
            "send_sms":request_data.get('send_sms'),
            "pending_withdrawal":request_data.get('pending_withdrawal',None),
            "record_date":request_data.get('record_date'),
            "member_list":request_data.get('member_list',[]),
        }

        save_trans = process_savings_withdraw(withdraw_details)
        if not save_trans:
            return Response({'message': 'Failed to process savings withdrawal'}, status=status.HTTP_400_BAD_REQUEST)
        return Response({"message": "Savings withdrawal processed successfully"}, status=status.HTTP_200_OK)


class SavingTransferView(viewsets.ModelViewSet):
    serializer_class = TransferTransactionsSerializer

    def get_queryset(self):
        end = self.request.GET.get('e', None)
        start = self.request.GET.get('s', None)
        search = self.request.GET.get('search', None)
        branch_id = get_current_user(
            self.request, 'organisation_branch_id', None)
        filter_array = Q()
        filter_array.add(Q(**{"reciever_transaction__customer_account__customer_branch__id":branch_id}), Q.OR)
        filter_array.add(Q(**{"reciever_transaction__customer_account__customer_branch__id":branch_id}), Q.OR)
        filter_array.add(Q(**{"sender_transaction__customer_account__customer_branch__id":branch_id}), Q.OR)
        
        if start:
            filter_array.add(Q(**{"sender_transaction__transaction__record_date__date__gte":start}), Q.AND)
        else:
            filter_array.add(Q(**{"sender_transaction__transaction__record_date__date__gte":datetime.today().strftime("%Y-%m-%d") }), Q.AND)
            
        if end:
            filter_array.add(Q(**{"sender_transaction__transaction__record_date__date__lte":end}), Q.AND)
        
        if search:
            filter_array.add(Q(**{"sender_transaction__customer_account__account_customer__name__icontains":search}), Q.AND)

        return TransferTransactions.objects.filter(filter_array)


class AccountBookingsView(viewsets.ModelViewSet):
    serializer_class = AccountBookingsSerializer

    def get_queryset(self):
        branch_id = get_current_user(
            self.request, 'organisation_branch_id', None)
        status   = self.request.GET.get('status', 'all')
        account  = self.request.GET.get('account',None)
        search   = self.request.GET.get('search',None)

        if search:
            return AccountBookings.objects.filter((Q(account__account_customer__member_number__icontains=search) | Q(account__account_customer__old_member_number__icontains=search) |Q(account__account_customer__name__icontains=search) |Q(account__account_no__contains=search))).order_by('-id')
        if account:
            return AccountBookings.objects.annotate(balance=F('amount') - Sum('booking_payment__reference_transaction__amount')).filter(reversed=False, account__id=account).filter(Q(balance=None) | Q(balance__gt=0)).order_by('id')
        if status == 'all':
            return AccountBookings.objects.filter(reversed=False, account__customer_branch__id=branch_id).order_by('id')
        elif status == 'pending':
            return AccountBookings.objects.annotate(balance=F('amount') - Sum('booking_payment__reference_transaction__amount')).filter(reversed=False, account__customer_branch__id=branch_id).filter(Q(balance=None) | Q(balance__gt=0)).order_by('id')
        else:
            return AccountBookings.objects.annotate(balance=F('amount') - Sum('booking_payment__reference_transaction__amount')).filter(reversed=False, account__customer_branch__id=branch_id, balance__lte=0).order_by('id')

    def perform_create(self, serializer):

        account_id = self.request.data.get('account')
        chart_id = self.request.data.get('chart')

        # Save Booking
        account_booking = serializer.save(
            account_id=account_id, chart_id=chart_id, added_by=self.request.user.id)

        # Deduct any available balance from the account.
        organisation_id = get_current_user(
            self.request, 'organisation_id', None)
        branch_id = get_current_user(
            self.request, 'organisation_branch_id', None)
        make_booking_payment(account_booking.id,
                             organisation_id, branch_id, self.request.user.id)

    def update(self, request, pk=None):
        record_date = self.request.data.get('record_date')

        # Set booking to reversed status.
        booking  = AccountBookings.objects.get(pk=pk)
        booking.reversed = True
        updated_booking  = booking.save()

        new_details  = AccountBookingsSerializer(booking,read_only=True).data
        old_details  = AccountBookingsSerializer(booking,read_only=True).data

        message      = f'Reverse Saving Account Booking on: {updated_booking.customer_account.account_no} For Customer: {updated_booking.customer_account.account_customer.name} Mem No: {booking.customer_account.account_customer.member_number}'
        branchid     = get_current_user(self.request, 'organisation_branch_id', 1) 
        branch       = OrganisationBranch.objects.get(id=branchid)
        add_system_audit_trail('savings','update_savings_account_booking',message,'',old_details,new_details,self.request.user,branch)
       
        # Reverse booking payment transactions.
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        transaction_prefix = 'inc-rev' if booking.booking_type == 'income' else ('ast-rev' if booking.booking_type == 'assets' else 'lb-t-rev')
        booking_payments = AccountBookingPayments.objects.filter(booking=booking)
        for payment in booking_payments:
            transaction_details = {
                "heading": payment.reference_transaction.heading + '-reversal',
                "amount": payment.reference_transaction.amount,
                "record_date":  record_date,
                "debit_chart_id": payment.reference_transaction.credit_chart.id,
                "credit_chart_id": payment.reference_transaction.debit_chart.id,
                "payment_method": 'offset',
                "voucher_no": "",
                "ref_no_prefix": transaction_prefix,
                "organisation_id": organisation_id,
                "branch_id": payment.reference_transaction.branch.id,
                "user_id": request.user.id
            }

            # Update original transaction type
            original_transaction = payment.reference_transaction
            original_transaction.transaction_type = 'reversed'
            original_transaction.save()

            # Save general transaction
            transaction = post_transaction(transaction_details)
            transaction.transaction_type = 'reversal'
            transaction.save()

            # Link reversal to parent transaction to savings account.
            offset = SavingAccountTransactions(customer_account=booking.account, transaction=transaction, transaction_type=transaction_details['payment_method'])
            offset.save()

            details = SystemTransactionsSerializer(payment.reference_transaction,read_only=True).data
            message = f'Reversed Booking Transaction For Customer: {booking.customer_account.account_customer.name} Mem No: {booking.customer_account.account_customer.member_number} Acc No: {booking.customer_account.account_no} Amount: {payment.reference_transaction.amount}. Ref No: {payment.reference_transaction.reference_no} Record Date: {payment.reference_transaction.record_date}'
            add_system_audit_trail('transaction_management','reverse_saving_transaction',message,'',details,{},self.request.user,branch) 
            
        return Response(self.serializer_class(booking).data, status=status.HTTP_200_OK)


class BulkSavingTranferView(APIView):

    def post(self, request):
        ''' make bulk transfers '''
        request_data = request.data
        recievers = request_data.get('recievers')
        error_message =  "error occurred"
        user = get_user_model().objects.get(pk=self.request.user.id)
        saved_count = 0

        for reciever in recievers:
            # Save sender transactions details
            saved_transaction = save_sender_transactions(user, request, reciever)
            sender_account   = SavingAccount.objects.get(pk=request.data.get('sender_id'))
            reciever_account = SavingAccount.objects.get(pk=reciever['id'])
            if not saved_transaction:
                    error_message += "Sender transaction not posted. Sender Name: "+sender_account.account_customer+" Reciver Name: "+reciever_account.account_customer+ "\n"
            if saved_transaction:
                # Save reciever transactions details
                saved_reciever = save_reciever_transactions(user, request, reciever)
                if not saved_reciever:
                    error_message += "Reciever transaction not posted. Sender Name: "+sender_account.account_customer+" Reciver Name: "+reciever_account.account_customer+ "\n"
                if saved_reciever:
                    # Reconcile inter-branch transactions
                    if sender_account.customer_branch != reciever_account.customer_branch:
                        inter_branch_trans_field = {
                            "source_transaction":saved_transaction.transaction,
                            "destination_transaction":saved_reciever.transaction,
                            "added_by":user,
                        }
                        InterBranchTransactions.objects.create(**inter_branch_trans_field)
                    # Save transfer transactions details mapping
                    saved_count += 1
                    transfer_fields = {
                        "sender_transaction": saved_transaction,
                        "reciever_transaction": saved_reciever
                    }
                    transfer_details = TransferTransactions.objects.create(**transfer_fields)
                    if transfer_details:
                        save_user_notification({
                            "heading":  "Saving Transfers",
                            "message": f"Saving Transfer of Amount: {transfer_details.sender_transaction.transaction.amount} from {transfer_details.sender_transaction.customer_account.account_customer.name} to  {transfer_details.reciever_transaction.customer_account.account_customer.name} as at {transfer_details.sender_transaction.date_added.date()}",
                            "branch":OrganisationBranch.objects.get(pk=transfer_details.sender_transaction.customer_account.customer_branch.id),
                            "branch_name":transfer_details.sender_transaction.customer_account.customer_branch.name,
                            "added_by":transfer_details.sender_transaction.transaction.added_by,
                            "last_updated_by":transfer_details.sender_transaction.transaction.added_by,
                            "key":"savings_notifications"
                        })
                    
        if saved_count > 0:
            return Response({"message": "Cash successfully transfered."}, status=status.HTTP_200_OK)
        return Response({"message": error_message}, status=status.HTTP_400_BAD_REQUEST)

class AccountTransactionsView(APIView):

    def get(self, request, format=None):
        '''
        Member statement.
        '''
        
        data = {
            'balance_bf': 0,
            'min_balance': 0,
            'chart': 0,
            'transactions': []
        }
        end = request.GET.get('e', None)
        start = request.GET.get('s', None)
        account_id = request.GET.get('a', None)
        account = SavingAccount.objects.filter(id=account_id)
        
        blocked_amount    = 0
        withheld_amount   = 0

        if len(account):
            account = account[0]
            #Compute Summary
            if end:
                end_bal   = get_account_balance(account,end)
                blocked_amount    = end_bal['blocked_amount']
                withheld_amount   = end_bal['with_held']
            
            # Generate balance BF
            money_in = SavingAccountTransactions.objects.filter(customer_account_id=account, transaction__record_date__date__lt=start,
                                                                transaction__credit_chart=account.account_product.accounts_chart, deleted=False).aggregate(total=Sum('transaction__amount'))['total']
            if not money_in:
                money_in = 0

            money_out = SavingAccountTransactions.objects.filter(customer_account_id=account, transaction__record_date__date__lt=start,
                                                                 transaction__debit_chart=account.account_product.accounts_chart, deleted=False).aggregate(total=Sum('transaction__amount'))['total']
            if not money_out:
                money_out = 0

            # Period transactions
            transactions = SavingAccountTransactions.objects.filter(
                customer_account_id=account, transaction__record_date__date__gte=start, transaction__record_date__date__lte=end, deleted=False).order_by('transaction__record_date__date', 'id')

            data = {
                'balance_bf': money_in - money_out,
                'min_balance': account.account_product.min_balance,
                'chart': account.account_product.accounts_chart.id,
                'transactions': SavingAccountStatementSerializer(transactions, many=True).data,
                'blocked_amount':blocked_amount,
                'withheld_amount':withheld_amount
            }

        return Response(data, status=status.HTTP_200_OK)


class FixedDepositsView(viewsets.ModelViewSet):
    serializer_class = FixedDepositSerializer
    http_method_names = ['get', 'post', 'patch']

    def get_queryset(self):
        filter_array = {}
        end = self.request.GET.get('e', None)
        start = self.request.GET.get('s', None)
        search = self.request.GET.get('search', None)
        pk = self.kwargs.get('pk', None)
        
        organisation_branch_id = get_current_user(self.request, 'organisation_branch_id', 1)
        
        if not pk and organisation_branch_id:
            filter_array['branch__id'] = organisation_branch_id

        if search:
            filter_array['saving_account__account_customer__name'] = search

        if start:
            filter_array['record_date__gte'] = make_aware(datetime.strptime(start + ' 00:00', '%Y-%m-%d %H:%M'))
       
        if end:
            filter_array['record_date__lte'] = make_aware(datetime.strptime(end + ' 23:59', '%Y-%m-%d %H:%M')) 
        
        return FixedDeposit.objects.filter(**filter_array).order_by('-id')
    
    def perform_update(self, serializer):
        grace_period = self.request.data.get('grace_period') or 0
        schedule_fields_updated = any(
            field in serializer.validated_data
            for field in (
                'amount',
                'period',
                'interest',
                'record_date',
                'frequency',
                'period_type',
            )
        ) or 'grace_period' in self.request.data

        with transaction.atomic():
            fixed_deposit = serializer.save(
                fixed_deposit_last_updated_by=self.request.user
            )

            if schedule_fields_updated and fixed_deposit.reference_transaction:
                data = {
                    "amount": fixed_deposit.amount,
                    "record_date": fixed_deposit.record_date,
                }
                SystemTransactions.objects.filter(
                    id=fixed_deposit.reference_transaction.id
                ).update(**data)

                # edit interbranch fixed deposits
                inter_branch = InterBranchTransactions.objects.filter(
                    Q(source_transaction__id=fixed_deposit.reference_transaction.id)
                    | Q(destination_transaction__id=fixed_deposit.reference_transaction.id)
                ).first()
                if inter_branch:
                    SystemTransactions.objects.filter(
                        id=inter_branch.source_transaction.id
                    ).update(**data)
                    SystemTransactions.objects.filter(
                        id=inter_branch.destination_transaction.id
                    ).update(**data)

            if schedule_fields_updated:
                FixedDepositSchedule.objects.active().filter(
                    fixed_deposit=fixed_deposit
                ).update(
                    deleted=True,
                    deleted_at=timezone.now(),
                    deleted_by_id=self.request.user.id,
                )

                schedule_data = {
                    "loan_amount": float(fixed_deposit.amount),
                    "period_type": fixed_deposit.period_type,
                    "loan_period": int(fixed_deposit.period),
                    "int_rate": float(fixed_deposit.interest),
                    "frequency": int(fixed_deposit.frequency),
                    "loan_start_date": fixed_deposit.record_date.isoformat(),
                    "grace_period": grace_period,
                    "grace_period_type": 'pay_none',
                }
                schedules = calculate_flat_loan_schedule(
                    request=self.request, loan_data=schedule_data
                )

                schedule_entries = []
                for schedule in schedules:
                    schedule_entries.append(
                        FixedDepositSchedule(
                            status='pending'
                            if schedule['interest_expected'] > 0
                            else 'paid',
                            fixed_deposit=fixed_deposit,
                            expected_date=schedule['expected_date'],
                            interest=schedule['interest_expected'],
                            fixed_deposit_added_by=self.request.user,
                        )
                    )

                FixedDepositSchedule.objects.bulk_create(schedule_entries)

    def perform_create(self, serializer):
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        organisation_branch_id = get_current_user(self.request, 'organisation_branch_id', None)
        saving_account_id = self.request.data.get('saving_account')
        record_date = self.request.data.get('record_date_str')
        grace_period = self.request.data.get('grace_period')
        period_type = self.request.data.get('period_type')
        frequency = self.request.data.get('frequency')
        interest = self.request.data.get('interest')
        amount = self.request.data.get('amount')
        period = self.request.data.get('period')

        savings_account = SavingAccount.objects.get(pk=saving_account_id)
        assert_savings_account_can_debit(savings_account, 'fixed deposit funding', as_at=record_date)
        heading = 'Fixed deposit deduction (' + savings_account.account_customer.name + '-' + savings_account.account_customer.old_member_number + ')'

        # Deduct money from customer account.
        fixed_deposit_ledger_code = 'sys-2111'
        fixed_deposit_ledger = OrganisationSubAccount.objects.filter(account_code=fixed_deposit_ledger_code, account_organisation_id=organisation_id)

        # Generate reference number
        reference_no = generate_reference_no(fixed_deposit_ledger[0].account_line, organisation_id, 'fx-d')

        # Save Deduction
        transaction = None
        transaction_2 = None
        if organisation_branch_id != savings_account.customer_branch.id:
            # interbranch fixing
            # post source branch transaction
            source_branch = OrganisationBranch.objects.get(pk=savings_account.customer_branch.id)
            destination_branch = OrganisationBranch.objects.get(pk=organisation_branch_id)
            interbranch_chart = get_inter_branch_chart(source_branch, destination_branch)
            
            transaction = SystemTransactions(amount=amount, heading= 'Interbranch ' + heading, record_date=record_date, payment_method='offset', reference_no=reference_no, credit_chart=interbranch_chart, debit_chart=savings_account.account_product.accounts_chart, branch_id=savings_account.customer_branch.id, added_by=self.request.user)
            transaction.save()

            # interbranch transaction 2
            transaction_2 = SystemTransactions(amount=amount, heading= 'Interbranch ' + heading, record_date=record_date, payment_method='offset', reference_no=reference_no, credit_chart=fixed_deposit_ledger[0], debit_chart=interbranch_chart, branch_id=organisation_branch_id, added_by=self.request.user)
            transaction_2.save()
            
            if transaction and transaction_2:
                inter_branch_trans_field = {
                    "source_transaction": transaction,
                    "destination_transaction": transaction_2,
                    "added_by": get_user_model().objects.get(pk=self.request.user.id),
                }
                InterBranchTransactions.objects.create(**inter_branch_trans_field)
            
        else:
            transaction = SystemTransactions(amount=amount, heading=heading, record_date=record_date, payment_method='offset', reference_no=reference_no, credit_chart=fixed_deposit_ledger[0], debit_chart=savings_account.account_product.accounts_chart, branch_id=savings_account.customer_branch.id, added_by=self.request.user)
            transaction.save()

        deduction_details = {
            "customer_account": savings_account,
            "transaction": transaction,
            "transaction_type": 'fixed-deposit'
        }

        SavingAccountTransactions.objects.create(**deduction_details)
        reference_transaction = transaction_2 if transaction_2 is not None else transaction
        fixed_deposit = serializer.save(branch=OrganisationBranch.objects.get(pk=organisation_branch_id), fixed_deposit_added_by=self.request.user, saving_account=savings_account, reference_transaction=reference_transaction)

        # Generate schedule
        schedule_data = {
            "loan_amount" : float(amount),
            "period_type": period_type,
            "loan_period": int(period),
            "int_rate": float(interest),
            "frequency": int(frequency),
            "loan_start_date": record_date,
            "grace_period": grace_period,
            "grace_period_type": 'pay_none'
        }
        schedules = calculate_flat_loan_schedule(request=self.request, loan_data=schedule_data)

        # save FD schedule
        for schedule in schedules:
            if schedule['interest_expected'] > 0:
                status = 'pending'
            else:
                status = 'paid'
            entry = FixedDepositSchedule(status=status, fixed_deposit=fixed_deposit, expected_date=schedule['expected_date'], interest=schedule['interest_expected'], fixed_deposit_added_by=self.request.user)
            entry.save()
    
    def destroy(self, request, *args, **kwargs):
        try:
            reference_transaction = None
            instance = self.get_object()
            reference_transaction = instance.reference_transaction

            add_system_audit_trail(
                'savings', 'delete_fixed_deposit',
                f'Deleted Fixed Deposit for {instance.saving_account.account_customer.name} ({instance.saving_account.account_no})',
                '', {}, {}, request.user,
                instance.branch
            )

            from django.utils import timezone
            now = timezone.now()
            user_id = request.user.id
            df = dict(deleted=True, deleted_by_id=user_id, deleted_at=now)

            transaction_1 = None
            transaction_2 = None
            inter_branch = InterBranchTransactions.objects.filter(
                Q(source_transaction__id=instance.reference_transaction.id)
                | Q(destination_transaction__id=instance.reference_transaction.id)
            ).first()
            if inter_branch:
                transaction_1 = inter_branch.source_transaction.id
                transaction_2 = inter_branch.destination_transaction.id

            FixedDepositSchedule.objects.filter(fixed_deposit=instance).update(**df)
            SavingAccountTransactions.objects.filter(transaction=instance.reference_transaction).update(**df)
            FixedDeposit.objects.filter(pk=instance.pk).update(**df)

            if reference_transaction:
                SystemTransactions.objects.filter(id=reference_transaction.id).update(**df)
            if transaction_1:
                SystemTransactions.objects.filter(id=transaction_1).update(**df)
            if transaction_2:
                SystemTransactions.objects.filter(id=transaction_2).update(**df)

        except Http404:
            pass
        return Response(status=status.HTTP_204_NO_CONTENT)
        
class SavingsAccountBalancesView(APIView):

    def get(self, request, format=None):
        customer_id = request.GET.get('customer_id', None)
        response = []
        if customer_id:
            update_savings_account_statuses(customer_id=customer_id)
        customer_accounts = SavingAccount.objects.filter(account_customer__id=customer_id, is_active=True).order_by('id')
        for customer_account in customer_accounts:
            accounts = get_account_balance(customer_account)    
            accounts['account_number'] = customer_account.account_no
            response.append(accounts)
        
        return Response({"count":len(response), "results":response}, status=status.HTTP_200_OK)
    
class CustomerFixedDepositsView(viewsets.ModelViewSet):
    serializer_class = CustomerFixedDepositSerializer
    http_method_names = ['get', 'put']

    def get_queryset(self):
        customer_id = self.request.GET.get('c', None)
        if customer_id:
            return Customer.objects.filter(id=customer_id).order_by('-id')

        return []

    def update(self, request, pk=None):

        # Pay outstanding schedules
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        record_date = self.request.data.get('record_date')
        amount  = float(self.request.data.get('amount'))

        fixed_deposit  = FixedDeposit.objects.get(pk=pk)

        fd_pay_details = {"restrict_schedule":False}
        fd_pay_details['user_id'] =  self.request.user.id
        fd_pay_details['branch_id'] = fixed_deposit.branch.id
        fd_pay_details['saving_account'] = fixed_deposit.saving_account
        fd_pay_details['organisation_id'] = organisation_id
        fd_pay_details['withholding_tax_active'] = fixed_deposit.withholding_tax
    
        general_setting   = OrganisationSetting.objects.filter(org_setting__id=organisation_id,setting_key='restrict_schedule').first()
        if general_setting:
            if general_setting.setting_value == 'on':
                fd_pay_details['restrict_schedule'] = True
                
        if amount > 0:
            schedules = FixedDepositSchedule.objects.active().filter(fixed_deposit=fixed_deposit, status='pending')
            if fd_pay_details['restrict_schedule'] == True:
                interest     = 0
                covered_int = amount
                schedule_date = None
                last_instalment_date = schedules[len(schedules) - 1].expected_date
                for schedule in schedules:
                    interest += schedule.interest
                    covered_int -= schedule.interest
                    if covered_int <= 0:
                        schedule_date = schedule.expected_date

                if amount > 0:
                    fd_pay_details['interest'] = interest
                    fd_pay_details['amount']   = amount

                    # Soft delete unpaid schedules before creating merged schedule.
                    schedules.update(deleted=True, deleted_at=timezone.now(), deleted_by_id=request.user.id)
                    add_system_audit_trail('transaction_management', 'delete_fd_unpaid_schedules',
                        f'Merged unpaid FD schedules for fixed deposit id: {fixed_deposit.id}',
                        '', {}, {}, request.user, fixed_deposit.saving_account.customer_branch)

                    # Create two schedules, one to take paid interest and the other to take unpiad interest.
                    paid_schedule = FixedDepositSchedule(fixed_deposit=fixed_deposit, expected_date=schedule_date, interest=amount)
                    paid_schedule.save()
                    pay_fixed_deposit_schedule(paid_schedule)

                    # Only do this if the amount paid is less than the pa
                    if interest > amount:
                        unpaid = interest - amount
                        closed_schedule = FixedDepositSchedule(status='closed', fixed_deposit=fixed_deposit, expected_date=last_instalment_date, interest=unpaid)
                        closed_schedule.save()
            else:
                for schedule in schedules:
                    if amount > 0:
                        if amount >= schedule.interest:
                            pay_fixed_deposit_schedule(schedule)
                            amount =  amount - schedule.interest
                        else:
                            pay_fixed_deposit_schedule(schedule, amount)
                    else:
                        schedule.status = 'closed'
                        schedule.save()

        # Transfer principal to member account.
        savings_account = fixed_deposit.saving_account
        heading = 'Fixed deposit principal (' + savings_account.account_customer.name + '-' + savings_account.account_customer.old_member_number + ')'

        fixed_deposit_ledger_code = 'sys-2111'
        fixed_deposit_ledger = OrganisationSubAccount.objects.filter(account_code=fixed_deposit_ledger_code, account_organisation_id=organisation_id)

        reference_no = generate_reference_no(fixed_deposit_ledger[0].account_line, organisation_id, 'fx-d-p')

        transaction_details = {
            "amount": fixed_deposit.amount,
            "heading": heading, 
            "record_date": record_date, 
            "payment_method":'offset', 
            "reference_no":reference_no, 
            "credit_chart":savings_account.account_product.accounts_chart, 
            "debit_chart":fixed_deposit_ledger[0], 
            "branch_id":savings_account.customer_branch.id, 
            "added_by":self.request.user
        }

        # inter-branch payment
        transaction_1 = None
        branch_id = fixed_deposit.branch.id
        if branch_id != savings_account.customer_branch.id:

            source_branch = OrganisationBranch.objects.get(pk=branch_id)
            destination_branch = OrganisationBranch.objects.get(pk=savings_account.customer_branch.id)
            interbranch_chart = get_inter_branch_chart(source_branch, destination_branch)

            heading = 'Interbranch '+ transaction_details['heading']
            transaction_details['heading'] = heading
            
            # second transaction
            transaction1_data = transaction_details
            transaction1_data['heading'] = heading
            transaction1_data['debit_chart'] = fixed_deposit_ledger[0]
            transaction1_data['credit_chart'] = interbranch_chart
            transaction1_data['branch_id'] = branch_id

            transaction_1 = SystemTransactions(**transaction1_data)
            transaction_1.save()

            #transaction1
            transaction_details['debit_chart'] = interbranch_chart
            transaction_details['credit_chart'] = savings_account.account_product.accounts_chart
            transaction_details['branch_id'] = savings_account.customer_branch.id


        # Save Payments
        transaction = SystemTransactions(**transaction_details)
        transaction.save()

        deduction_details = {
            "customer_account": savings_account,
            "transaction": transaction,
            "transaction_type": 'fixed-deposit'
        }
        save_trans = SavingAccountTransactions.objects.create(**deduction_details)
        # SavingAccountTransactions.objects.create(**deduction_details)
        # interbranch mapping
        if transaction_1 and transaction:
            inter_branch_trans_field = {
                "source_transaction": transaction_1,
                "destination_transaction": transaction,
                "added_by": None,
            }
            InterBranchTransactions.objects.create(**inter_branch_trans_field)

        # Close FD
        fixed_deposit.closure_transaction = transaction_1 if transaction_1 is not None else transaction
        fixed_deposit.status = 'closed'
        fixed_deposit.save()
        send_sms = True  
        fd_schedule = FixedDepositSchedule.objects.active().filter(fixed_deposit=fixed_deposit)
        interest_amount = fd_schedule.aggregate(total_interest=Sum('interest'))['total_interest'] or 0

        if send_sms:
            sms_data = {
                "sms_key": "fixed_deposit_closure_sms",
                "customer": fixed_deposit.saving_account.account_customer,
                "customer_account": fixed_deposit.saving_account,
                "user": request.user,
                "branch_id": fixed_deposit.branch.id,
                "interest_amount": interest_amount,  # total interest from all schedules
                "save_trans": save_trans,
                "sms_msg": ""  # will be auto-filled by template
          }
        send_customer_sms(sms_data)
        return Response(status=status.HTTP_200_OK)
    
class SavingsSettingsDataView(APIView):
    def get(self, request, format=None):
        data_type = request.GET.get('type', None)
        if data_type == 'account_blocking_types':
            return Response({"count":len(savings_blocking_types), "results":savings_blocking_types}, status=status.HTTP_200_OK)
        return Response({"count":0, "results":[]}, status=status.HTTP_200_OK)


class GroupMembersSavingsView(APIView):
    
    def get(self, request, format=None):
        end = request.GET.get('e', None)
        start = request.GET.get('s', None)
        account_id = request.GET.get('a', None)
        action     = request.GET.get('action', None)
        if action == 'ledger':
            chart = 0
            account = SavingAccount.objects.filter(id=account_id).first()
            member_list = []
            chart = account.account_product.accounts_chart.id
            if account:
                memberships = GroupMembership.objects.filter(group=account.account_customer, active=True)
                if memberships:
                    for membership in memberships:
                        balance_bf  = 0
                        filter_array    = {"savings__customer_account":account,"membership":membership,"savings__transaction__transaction_type":"normal"}
                        filter_array_bf = {"savings__customer_account":account,"membership":membership,"savings__transaction__transaction_type":"normal"}
                        transactons  = {}
                        
                        if start:
                            filter_array['savings__transaction__record_date__date__gte'] = start
                            filter_array_bf['savings__transaction__record_date__date__lt'] = start
                        else:
                            filter_array['savings__transaction__record_date__date__gte'] = datetime.today().strftime("%Y-%m-%d")
                            filter_array_bf['savings__transaction__record_date__date__lt'] = datetime.today().strftime("%Y-%m-%d")
                        if end:
                            filter_array['savings__transaction__record_date__date__lte'] = end

                        group_savings = GroupSavingTransaction.objects.filter(**filter_array)
                        group_savings_transactions_bf = GroupSavingTransaction.objects.filter(**filter_array_bf)

                        if len(group_savings_transactions_bf) > 0:
                             transactons_bf  = GroupSavingTransactionSerializer(group_savings_transactions_bf, many=True).data
                             for transacton_bf in transactons_bf:
                                if chart == float(transacton_bf['credit_chart']):
                                    balance_bf += float(transacton_bf['amount'])
                                else:
                                    balance_bf -= float(transacton_bf['amount'])
            
                        if group_savings:
                            transactons = GroupSavingTransactionSerializer(group_savings, many=True).data
                        member_list.append({
                            "id":membership.member.id,
                            "customer_name":membership.member.name,
                            "customer_member_number":membership.member.member_number,
                            "customer_type":membership.member.branch_customer_type.customer_type,
                            "balance_bf":balance_bf,
                            "transactions":transactons,
                            "chart":chart
                        })
                return Response({"count":len(member_list), "results":member_list}, status=status.HTTP_200_OK)
            return Response({"count":0, "results":[]}, status=status.HTTP_200_OK)
        
        if action == 'account_balance':
            response = {
              'money_in':0,
              'money_out':0,
              'balance':0
            }
            
            account = SavingAccount.objects.filter(id=account_id).first()
            member_list = []
            chart = account.account_product.accounts_chart.id
            if account:
                memberships = GroupMembership.objects.filter(group=account.account_customer,active=True)
                if memberships:
                    for membership in memberships:
                        filter_array = {"savings__customer_account":account,"membership__id":membership.id,"savings__transaction__transaction_type":"normal"}
                        group_savings = GroupSavingTransaction.objects.filter(**filter_array)
                        if group_savings:
                            for group_saving in group_savings:
                                if chart == group_saving.savings.transaction.credit_chart.id:
                                    response['money_in'] += group_saving.savings.transaction.amount
                                    response['balance'] += group_saving.savings.transaction.amount
                                else:
                                    response['money_out'] += group_saving.savings.transaction.amount
                                    response['balance'] -= group_saving.savings.transaction.amount
            return Response(response, status=status.HTTP_200_OK)
        if action == 'member_balance':
            response = {
              'money_in':0,
              'money_out':0,
              'balance':0
            }
            member_id       = request.GET.get('member_id', None)
            account = SavingAccount.objects.filter(id=account_id).first()
            member_list = []
            chart = account.account_product.accounts_chart.id
            if account:
                membership = GroupMembership.objects.filter(member__id=member_id,active=True).first()
                if membership:
                    filter_array = {"savings__customer_account":account,"membership__id":membership.id,"savings__transaction__transaction_type":"normal"}
                    group_savings = GroupSavingTransaction.objects.filter(**filter_array)
                    if group_savings:
                        transactons = GroupSavingTransactionSerializer(group_savings, many=True).data
                        for group_saving in group_savings:
                            if chart == group_saving.savings.transaction.credit_chart.id:
                                response['money_in'] += group_saving.savings.transaction.amount
                                response['balance'] += group_saving.savings.transaction.amount
                            else:
                                response['money_out'] += group_saving.savings.transaction.amount
                                response['balance'] -= group_saving.savings.transaction.amount
            return Response(response, status=status.HTTP_200_OK)
        if action == 'member_transaction':
            chart = 0
            member  = request.GET.get('member', None)
            account = SavingAccount.objects.filter(id=account_id).first()
            transactons = []
            balance_bf  = 0
            chart = account.account_product.accounts_chart.id
            if account:
                membership = GroupMembership.objects.filter(group=account.account_customer,member__id=member, active=True).first()
                if membership:
                    filter_array    = {"savings__customer_account":account,"membership":membership,"savings__transaction__transaction_type":"normal"}
                    filter_array_bf = {"savings__customer_account":account,"membership":membership,"savings__transaction__transaction_type":"normal"}
                    if start:
                        filter_array['savings__transaction__record_date__date__gte'] = start
                        filter_array_bf['savings__transaction__record_date__date__lt'] = start
                    else:
                        filter_array['savings__transaction__record_date__date__gte'] = datetime.today().strftime("%Y-%m-%d")
                        filter_array_bf['savings__transaction__record_date__date__lt'] = datetime.today().strftime("%Y-%m-%d")
                    if end:
                        filter_array['savings__transaction__record_date__date__lte'] = end
                   
                    group_savings = GroupSavingTransaction.objects.filter(**filter_array)
                    group_savings_transactions_bf = GroupSavingTransaction.objects.filter(**filter_array_bf)
                    if len(group_savings_transactions_bf) > 0:
                            transactons_bf  = GroupSavingTransactionSerializer(group_savings_transactions_bf, many=True).data
                            for transacton_bf in transactons_bf:
                                if chart == float(transacton_bf['credit_chart']):
                                    balance_bf += float(transacton_bf['amount'])
                                else:
                                    balance_bf -= float(transacton_bf['amount'])

                    if group_savings:
                        transactons = GroupSavingTransactionSerializer(group_savings, many=True).data
                        
            return Response({"count":len(transactons), "results":transactons,"balance_bf":balance_bf}, status=status.HTTP_200_OK)
        
    def post(self, request):
        action   = request.data.get('action')
        response = {}
        if action == 'deposit':
            #save bulk deposits
            response = process_group_deposits(self.request.user,request)
        if action == 'withdraw':
            #save bulk deposits
            response = process_group_withdraws(self.request.user,request)
        return Response(response, status=status.HTTP_200_OK)

class GroupSavingTransactionViewSet(viewsets.ModelViewSet):
    serializer_class = GroupSavingTransactionSerializer
    http_method_names = ['get', 'post', 'patch']
    def get_queryset(self):
        members = []
        group_id = self.request.GET.get('group', None)
        if group_id:
            members = GroupMembership.objects.filter(group_id=group_id, active=True)
        
            return members
        return GroupMembership.objects.all()

class BulkSavingsChargeInitiationView(viewsets.ModelViewSet):
    serializer_class = BulkSavingsChargeInitiationSerializer

    def get_queryset(self):
        organisation_id = get_current_user(self.request, 'organisation_id', 1)
        status = self.request.query_params.get("status", None)
        branch = self.request.query_params.get("branch", None)
        start  = self.request.query_params.get("s", None)
        end    = self.request.query_params.get("e", None)

        filters = Q(deleted=False)
        filters &= Q(branch__branch_organisation__id=organisation_id)

        if branch:
            filters &= Q(branch__id=branch)

        if status:
            filters &= Q(status=status)

        # Filter by record_date OR date range
        if start and end:
            filters &= Q(
                Q(record_date__date__gte=start, record_date__date__lte=end) |
                Q(start_date__date__gte=start, end_date__date__lte=end)
            )
        elif start:
            filters &= Q(
                Q(record_date__date__gte=start) |
                Q(start_date__date__gte=start)
            )
        elif end:
            filters &= Q(
                Q(record_date__date__lte=end) |
                Q(end_date__date__lte=end)
            )

        return BulkSavingsChargeInitiation.objects.filter(filters).order_by('-id')

    def perform_create(self, serializer):
        saved_initiation = serializer.save(
            charge_added_by=get_user_model().objects.get(pk=self.request.user.id)
        )
        if saved_initiation:
            # Convert record_date if exists
            if saved_initiation.record_date:
                saved_initiation.record_date = date_time_zone_convert(saved_initiation.record_date)
            saved_initiation.save()

    def perform_update(self, serializer):
        request_data = self.request.data
        action = request_data.get('action')

        initiation = serializer.save()  # save common fields

        # Convert record_date if exists
        if initiation.record_date:
            initiation.record_date = date_time_zone_convert(initiation.record_date)
            initiation.save()

        if action == 'process':
            threading.Thread(
                target=process_bulk_saving_charges,
                args=(initiation, self.request.user.id)
            ).start()

        elif action == 'reverse_transaction':
            reason = request_data.get('reason')
            reversal_date = request_data.get('reversal_date')
            record_date = date_time_zone_convert(reversal_date) if reversal_date else None
            threading.Thread(
                target=reverse_bulk_saving_charges,
                args=(initiation, record_date, reason, self.request.user.id)
            ).start()

        elif action == 'delete_transaction':
            reason = request_data.get('reason')
            # Check if there are any processed transactions
            has_transactions = BulkSavingsChargesTransactions.objects.filter(
                initiation=initiation
            ).exists()
            
            if has_transactions:
                # If there are transactions, use the delete helper function
                delete_bulk_saving_charges(initiation, reason, self.request.user.id)
            
            # Mark as deleted
            initiation.deleted = True
            initiation.deleted_at = timezone.now()
            initiation.deleted_by = self.request.user
            initiation.save()
            add_system_audit_trail('transaction_management', 'delete_bulk_charge_initiation',
                f'Deleted Bulk Savings Charge Initiation: {initiation.heading}',
                reason, {}, {}, self.request.user, initiation.branch)
    
    def destroy(self, request, *args, **kwargs):
        """Custom delete method to handle bulk savings charge initiation deletion"""
        try:
            instance = self.get_object()
            reason = request.data.get('reason', 'Bulk charge initiation deleted')
            
            # Check if there are any processed transactions
            has_transactions = BulkSavingsChargesTransactions.objects.filter(
                initiation=instance
            ).exists()
            
            if has_transactions:
                # If there are transactions, use the delete helper function
                delete_bulk_saving_charges(instance, reason, self.request.user.id)
            
            # Mark as deleted instead of hard delete
            instance.deleted = True
            instance.deleted_at = timezone.now()
            instance.deleted_by = request.user
            instance.save()
            add_system_audit_trail('transaction_management', 'delete_bulk_charge_initiation',
                f'Deleted Bulk Savings Charge Initiation: {instance.heading}',
                reason, {}, {}, request.user, instance.branch)
            
            return Response(status=status.HTTP_204_NO_CONTENT)
            
        except Exception as e:
            return Response(
                {'error': f'Failed to delete bulk charge initiation: {str(e)}'}, 
                status=status.HTTP_400_BAD_REQUEST
            )
  
class BulkSavingsChargesTransactionsView(viewsets.ModelViewSet):
    serializer_class = BulkSavingsChargesTransactionsSerializer
    http_method_names = ['get', 'post', 'patch']  # Disable delete for individual transactions
    
    def get_queryset(self):
        organisation_id = get_current_user(self.request, 'organisation_id', 1)
        start = self.request.query_params.get("s", None)
        end   = self.request.query_params.get("e", None)
        initiation_id   = self.request.query_params.get("initiation_id", None)
        additional_filters = Q()
        additional_filters.add(Q(**{"saving_transaction__transaction__branch__branch_organisation__id":organisation_id}), Q.AND)
        if initiation_id:
            additional_filters.add(Q(**{"initiation__id":initiation_id}), Q.AND)
        if start:
            additional_filters.add(Q(**{"saving_transaction__transaction__record_date__date__gte":start}), Q.AND)
        if end:
            additional_filters.add(Q(**{"saving_transaction__transaction__record_date__date__lte":end}), Q.AND)
        return BulkSavingsChargesTransactions.objects.filter(additional_filters).order_by('-id')


class SavingsProductInterestPaymentView(viewsets.ModelViewSet):
    serializer_class = SavingsProductInterestPaymentSerializer
    filter_backends  = (DjangoFilterBackend, )
    filterset_fields = ['status']
    
    def get_queryset(self):
        organisation_branch_id = get_current_user(self.request, 'organisation_branch_id', None)
        return SavingsProductInterestPayment.objects.filter(branch__id=organisation_branch_id)

    def perform_update(self, serializer):
        request_data = self.request.data
        request_status = request_data.get('status', None)
        interest_payment = serializer.save(last_updated = timezone.now(), last_updated_id = self.request.user.id)
        if request_status != 'processed' and interest_payment.processing_status != 'Ready':
            process_payments = threading.Thread(target=generate_savings_interest_file, args=(interest_payment,))
            process_payments.start()
        if request_status == 'processed':
            if interest_payment:
                process_payments = threading.Thread(target=process_saving_products_interest_payments, args=(interest_payment.id,self.request.user,))
                process_payments.start()
    
    def get_serializer_context(self):
        context = super().get_serializer_context()
        context.update({"as_at":self.request.query_params.get("as_at", None)})
        return context
    
class SavingsInterestPaymentTransactionView(viewsets.ModelViewSet):
    serializer_class = InterestPaymentTransactionSerializer
    filter_backends  = (DjangoFilterBackend, )

    def get_queryset(self):
        interest_payment = self.request.query_params.get("interest_payment", None)
        filter_array = {"interest_payment__id":interest_payment,"savings__transaction__transaction_type":"normal","savings__transaction_type":"deposit","deleted":False}
        return InterestPaymentTransaction.objects.filter(**filter_array) 

class InterestsPaymentsAPIView(APIView):
    
    def get(self,request, format=None):
        query_filters     = {}
        payment_interests = []
        total_interest    = 0
        request_data = self.request.query_params
        status       = request_data.get('status','pending')
        acc_int      = request_data.get('acc_int',None)
 
        query_filters['page'] = request_data.get('page',1)
        query_filters['page_size'] = request_data.get('page_size',500)
        query_filters['search'] = request_data.get('search',None)

        if acc_int  == 'account-interest':
            payment_id       = request_data.get('payment_id',None)
            interest_payment = SavingsProductInterestPayment.objects.filter(id=payment_id).first()
            if interest_payment:
                payment_interests = get_account_interest_accrued(interest_payment,query_filters)
            return Response(payment_interests)
        else:
            branch_id    = get_current_user(self.request,'organisation_branch_id', 0)
            interest_payments = SavingsProductInterestPayment.objects.filter(branch__id=branch_id,status=status)
            if interest_payments:
                for interest_payment in interest_payments:
                    total_interest = 0
                    branch         = interest_payment.branch
                    organisation   = interest_payment.branch.branch_organisation
                    csv_file_name  = f'{settings.STATIC_ROOT}/reports/{organisation.id}/savings-interest/{branch.id}/{interest_payment.id}_accrued_interest.csv'
                    
                    if os.path.exists(csv_file_name):
                        total_interest = get_total_interest_accrued(interest_payment)
                        
                    interest_payments_details = SavingsProductInterestPaymentSerializer(interest_payment).data
                    interest_payments_details["total_interest"]   =  total_interest 
                    interest_payments_details["accounts_awarded"] = 0
                    interest_payments_details["amount_awarded"]   = 0
                    interest_payments_details["tax_on_interest"]  = 0
                    interest_payments_details["account_balance"]  = 0

                    filter_array     = {"interest_payment":interest_payment,"savings__transaction__transaction_type":"normal","savings__transaction_type":"deposit"}

                    accounts_awarded = InterestPaymentTransaction.objects.filter(**filter_array).aggregate(total=Count('savings__transaction'))['total']
                    if accounts_awarded:
                        interest_payments_details["accounts_awarded"] = accounts_awarded
                    
                    amount_awarded   = InterestPaymentTransaction.objects.filter(**filter_array).aggregate(total=Sum('savings__transaction__amount'))['total']
                    if amount_awarded:
                        interest_payments_details["amount_awarded"] = amount_awarded
                    
                    filter_tax_array = {"interest_payment":interest_payment,"savings__transaction__transaction_type":"normal","savings__transaction_type":"savings-interest-tax"}
                    tax_on_interest  = InterestPaymentTransaction.objects.filter(**filter_tax_array).aggregate(total=Sum('savings__transaction__amount'))['total']
                    if tax_on_interest:
                        interest_payments_details["tax_on_interest"] = tax_on_interest

                    payment_interests.append(interest_payments_details)

        return Response({"results":payment_interests,"count":len(payment_interests)})
    
    def post(self, request, format=None):
        action   = request.data.get('action')

        if action == 'initiate-payment':
            print("**********************     initializing interest payments      ****************************************")
            products  = request.data.get('products', [])
            from_date  = request.data.get('from_date', None)
            to_date  = request.data.get('to_date', None)
            record_date = request.data.get('record_date', None)

            organisation_id = get_current_user(self.request, 'organisation_id', 0)
            organisation_branch_id = get_current_user(self.request, 'organisation_branch_id', 0)

            products_list = []
            for product in products:
                products_list.append(product.get('value', 0))

            savings_products = SavingsProduct.objects.filter(id__in=products_list, saving_product_org__id=organisation_id)
            if not savings_products:
                return Response({"message": "No matching savings products found."}, status=status.HTTP_400_BAD_REQUEST)

            skipped = []
            for savings_product in savings_products:
                active_accounts = SavingAccount.objects.filter(account_product=savings_product, status='active').exists()
                if not active_accounts:
                    skipped.append(f"{savings_product.product_name}: no active accounts")
                    continue

                interest_setting = SavingProductInterest.objects.filter(saving_product=savings_product, is_active=True).first()
                if not interest_setting:
                    skipped.append(f"{savings_product.product_name}: no active interest setting")
                    continue

                if not interest_setting.int_rate or interest_setting.int_rate <= 0:
                    skipped.append(f"{savings_product.product_name}: interest rate is 0")
                    continue

                queue_saving_interest_payments(savings_product, from_date, to_date, record_date, organisation_branch_id)

            if skipped:
                return Response({"message": "Some products were skipped", "skipped": skipped}, status=status.HTTP_200_OK)
            return Response({"message": "Interest Payment initiated successfully"})
        
        if action == 'upload-interest-payment':
            import json as json_lib
            file = request.FILES.get('file')
            record_date = request.data.get('record_date', None)
            from_date = request.data.get('from_date', None)
            to_date = request.data.get('to_date', None)
            products = json_lib.loads(request.data.get('products', '[]'))

            if not file:
                return Response({"message": "File is required."}, status=status.HTTP_400_BAD_REQUEST)

            organisation_id = get_current_user(self.request, 'organisation_id', 0)
            organisation_branch_id = get_current_user(self.request, 'organisation_branch_id', 0)
            branch = OrganisationBranch.objects.filter(id=organisation_branch_id).first()

            if not branch:
                return Response({"message": "Branch not found."}, status=status.HTTP_400_BAD_REQUEST)

            try:
                uploaded_df = pd.read_csv(file)
            except Exception:
                return Response({"message": "Failed to read uploaded file. Ensure it is a valid CSV."}, status=status.HTTP_400_BAD_REQUEST)

            send_sms = request.data.get('send_sms', 'false') == 'true'

            required_cols = ['Account Number', 'Interest Amount']
            for col in required_cols:
                if col not in uploaded_df.columns:
                    return Response({"message": f"Missing required column: {col}"}, status=status.HTTP_400_BAD_REQUEST)

            uploaded_df['Interest Amount'] = pd.to_numeric(uploaded_df['Interest Amount'], errors='coerce').fillna(0)
            uploaded_df = uploaded_df[uploaded_df['Interest Amount'] > 0]

            if uploaded_df.empty:
                return Response({"message": "No valid interest amounts found in the file."}, status=status.HTTP_400_BAD_REQUEST)

            savings_products = SavingsProduct.objects.filter(id__in=products, saving_product_org__id=organisation_id)
            if not savings_products:
                return Response({"message": "No matching savings products found."}, status=status.HTTP_400_BAD_REQUEST)

            organisation = branch.branch_organisation

            for savings_product in savings_products:
                interest_setting = SavingProductInterest.objects.filter(saving_product=savings_product, is_active=True).first()
                int_rate = interest_setting.int_rate if interest_setting else 0
                tax_on_int = interest_setting.tax_on_int if interest_setting else 0
                min_balance = interest_setting.min_balance if interest_setting else 0

                product_accounts = SavingAccount.objects.filter(account_product=savings_product, status='active')
                product_account_nos = set(product_accounts.values_list('account_no', flat=True))

                product_df = uploaded_df[uploaded_df['Account Number'].astype(str).isin([str(a) for a in product_account_nos])]
                if product_df.empty:
                    continue

                interest_payment = SavingsProductInterestPayment.objects.create(
                    int_rate=int_rate,
                    tax_on_int=tax_on_int,
                    frequency=interest_setting.frequency if interest_setting else 0,
                    frequency_type=interest_setting.frequency_type if interest_setting else 'd',
                    send_sms=send_sms,
                    min_balance=min_balance,
                    record_date=record_date,
                    from_date=from_date,
                    to_date=to_date,
                    exp_payment_date=record_date,
                    product=savings_product,
                    branch=branch,
                    processing_status="Ready"
                )

                # Build CSV in the same format the system uses
                csv_rows = []
                for _, row in product_df.iterrows():
                    acc = product_accounts.select_related('account_customer').filter(account_no=str(row['Account Number'])).first()
                    if acc:
                        customer = acc.account_customer
                        csv_rows.append({
                            'id': acc.id,
                            'account_no': acc.account_no,
                            'customer_id': customer.id,
                            'customer_name': customer.name,
                            'customer_member_number': customer.member_number,
                            'customer_old_member_number': customer.old_member_number or '',
                            'as_at': to_date,
                            'balance_actual': 0,
                            'daily_int': float(row['Interest Amount']),
                            'min_balance': min_balance,
                        })

                if csv_rows:
                    organisation_directory = f"{settings.STATIC_ROOT}/reports/{organisation.id}/savings-interest/{branch.id}"
                    os.makedirs(organisation_directory, exist_ok=True)
                    csv_file_name = f"{organisation_directory}/{interest_payment.id}_accrued_interest.csv"
                    csv_df = pd.DataFrame(csv_rows)
                    csv_df.to_csv(csv_file_name, index=False)

            return Response({"message": "Interest Payment uploaded successfully."})

        if action == 'delete-payments':
            interest_payment_ids  = self.request.data.get('interest_payment_ids')
            if interest_payment_ids:
                interest_payments = SavingsProductInterestPayment.objects.filter(id__in=interest_payment_ids)
                if interest_payments:
                    for interest_payment in interest_payments:
                        int_transactions = InterestPaymentTransaction.objects.filter(interest_payment=interest_payment)
                        if int_transactions:
                            for int_transaction in int_transactions:
                                transaction_data = SystemTransactionDetailsSerializer(int_transaction.savings.transaction).data
                                transaction_data['transaction_details']['id'] = int_transaction.savings.id
                                delete_savings_transaction(transaction_data, self.request.user.id, "Savings Interest deletion", interest_payment.branch)
                        interest_payment.status='pending'
                        interest_payment.save()
            return Response({"message":"Interest Payment deleted successfully"})
        return Response({"message":"Payment updated successfully"}, status=status.HTTP_200_OK)


class SchoolFeesOrganIntegrationView(viewsets.ModelViewSet):
    serializer_class = SchoolFeesOrganIntegrationSerializer

    def get_queryset(self):
        return SchoolFeesOrganIntegration.objects.all().order_by("id")
    
    def perform_create(self, serializer):
        serializer.save(added_by = self.request.user)



class savingsReconciliation(viewsets.ViewSet):

    def list(self, request, *args, **kwargs):
        organisation_id = get_current_user(self.request, 'organisation_id', None)

        query = """
            SELECT T.heading, T.amount AS transaction_amount, T.reference_no,
                T.record_date, T.date_added, T.deleted, T.id, T.branch_id 
            FROM system_transactions T
            LEFT JOIN saving_account_transactions S 
            ON T.id = S.transaction_id 
            WHERE S.transaction_id IS NULL 
            AND T.deleted = FALSE
            AND (
                T.credit_chart_id IN (
                    SELECT DISTINCT accounts_chart_id 
                    FROM savings_product 
                    WHERE saving_product_org_id = %s
                ) 
                OR T.debit_chart_id IN (
                    SELECT DISTINCT accounts_chart_id 
                    FROM savings_product 
                    WHERE saving_product_org_id = %s
                )
            );
        """
        with connection.cursor() as cursor:
            cursor.execute(query, [organisation_id, organisation_id])
            rows = cursor.fetchall()

            columns = [col[0] for col in cursor.description]
            # Convert rows to a list of dictionaries
            results = [dict(zip(columns, row)) for row in rows]

        return Response({"message": "Savings Reconciliations", "results": results}, status=status.HTTP_200_OK)


class CustomerOrganObligationsView(viewsets.ModelViewSet):
    serializer_class = CutomerOrganObligationSerializer

    def get_queryset(self):
        organisation_id = get_current_user(self.request, 'organisation_id', 0)
        obligation_choice    = self.request.query_params.get("obligation_choice", None)
        if obligation_choice:
            return CustomerObligations.objects.filter(organisation__id=organisation_id, obligation_choice = obligation_choice).order_by("id")

        return CustomerObligations.objects.filter(organisation__id=organisation_id).order_by("id")
    
    def perform_create(self, serializer):
        organisation_id = get_current_user(self.request, 'organisation_id', 0)
        serializer.save(added_by = self.request.user, organisation = Organisation.objects.get(pk=organisation_id))


class CustomerSubscribeObligationsView(viewsets.ModelViewSet):
    serializer_class = CutomerSubscribeObligationSerializer

    def get_queryset(self):
        customer    = self.request.query_params.get("customer", 0)
        id    = self.request.query_params.get("id", None)
        source_chart    = self.request.query_params.get("source_chart", None)
        
        if id is not None and customer and source_chart is not None:
            return CustomerObligationSubscription.objects.filter(customer__id=customer, obligation__id=id, customer_source_chart__id=source_chart).order_by("id")
        return CustomerObligationSubscription.objects.filter(customer__id=customer).order_by("id")
    
    def perform_create(self, serializer):
        serializer.save(added_by = self.request.user)


class AccountMergeRequestView(APIView):
    """Handle account merge/transaction transfer requests"""

    def get(self, request, format=None):
        """List merge requests - pending ones for approvers, all for the branch"""
        organisation_branch_id = get_current_user(request, 'organisation_branch_id', None)
        status_filter = request.GET.get('status', '')
        customer_id = request.GET.get('customer_id', '')

        queryset = AccountMergeRequest.objects.filter(
            organisation_branch__id=organisation_branch_id
        ).order_by('-date_added')

        if status_filter:
            queryset = queryset.filter(status=status_filter)
        if customer_id:
            queryset = queryset.filter(customer__id=customer_id)

        results = []
        for req in queryset:
            results.append({
                'id': req.id,
                'customer_id': req.customer.id,
                'customer_name': req.customer.name,
                'customer_member_number': req.customer.member_number,
                'source_account_id': req.source_account.id,
                'source_account_no': req.source_account.account_no,
                'source_product': req.source_account.account_product.product_name,
                'destination_account_id': req.destination_account.id,
                'destination_account_no': req.destination_account.account_no,
                'destination_product': req.destination_account.account_product.product_name,
                'transfer_mode': req.transfer_mode,
                'transfer_from_date': req.transfer_from_date,
                'status': req.status,
                'reason': req.reason,
                'rejection_reason': req.rejection_reason,
                'is_cross_product': req.is_cross_product,
                'requested_by': str(req.requested_by),
                'approved_by': str(req.approved_by) if req.approved_by else None,
                'date_added': req.date_added,
                'date_actioned': req.date_actioned,
            })

        return Response({'count': len(results), 'results': results})

    def post(self, request, format=None):
        """Create a new merge request"""
        source_account_id = request.data.get('source_account_id')
        destination_account_id = request.data.get('destination_account_id')
        transfer_mode = request.data.get('transfer_mode')
        transfer_from_date = request.data.get('transfer_from_date')
        reason = request.data.get('reason', '')

        organisation_branch_id = get_current_user(request, 'organisation_branch_id', None)

        # Validate accounts exist
        try:
            source_account = SavingAccount.objects.get(pk=source_account_id)
            destination_account = SavingAccount.objects.get(pk=destination_account_id)
        except SavingAccount.DoesNotExist:
            return Response({'status': 'failed', 'message': 'Invalid account selected.'}, status=400)

        # NEVER allow cross-customer transfers
        if source_account.account_customer.id != destination_account.account_customer.id:
            return Response({'status': 'failed', 'message': 'Cannot transfer transactions between different customers.'}, status=400)

        # Cannot merge same account
        if source_account.id == destination_account.id:
            return Response({'status': 'failed', 'message': 'Source and destination accounts cannot be the same.'}, status=400)

        # Check if source account already has a pending merge request
        existing = AccountMergeRequest.objects.filter(
            source_account=source_account, status='pending'
        ).exists()
        if existing:
            return Response({'status': 'failed', 'message': 'This account already has a pending merge request.'}, status=400)

        is_cross_product = source_account.account_product.id != destination_account.account_product.id

        merge_request = AccountMergeRequest.objects.create(
            customer=source_account.account_customer,
            source_account=source_account,
            destination_account=destination_account,
            transfer_mode=transfer_mode,
            transfer_from_date=transfer_from_date if transfer_mode == 'from_date' else None,
            reason=reason,
            is_cross_product=is_cross_product,
            requested_by=request.user,
            organisation_branch_id=organisation_branch_id,
        )

        return Response({'status': 'success', 'message': 'Merge request submitted for approval.', 'id': merge_request.id})

    def put(self, request, format=None):
        """Approve, reject, or reverse a merge request"""
        merge_request_id = request.data.get('merge_request_id')
        action = request.data.get('action')  # 'approve', 'reject', or 'reverse'
        rejection_reason = request.data.get('rejection_reason', '')

        if action == 'reverse':
            try:
                merge_request = AccountMergeRequest.objects.get(pk=merge_request_id, status='completed')
            except AccountMergeRequest.DoesNotExist:
                return Response({'status': 'failed', 'message': 'Only completed merges can be reversed.'}, status=400)
        else:
            try:
                merge_request = AccountMergeRequest.objects.get(pk=merge_request_id, status='pending')
            except AccountMergeRequest.DoesNotExist:
                return Response({'status': 'failed', 'message': 'Merge request not found or already actioned.'}, status=400)

        # Verify user has approval permission
        organisation_branch_id = get_current_user(request, 'organisation_branch_id', None)
        branch = OrganisationBranch.objects.get(pk=organisation_branch_id)
        org_setting = OrganisationSetting.objects.filter(
            org_setting=branch.branch_organisation,
            setting_key='account_merge_approvers'
        ).first()

        if org_setting:
            import json
            approver_ids = json.loads(org_setting.setting_value) if org_setting.setting_value else []
            if str(request.user.id) not in [str(a) for a in approver_ids]:
                return Response({'status': 'failed', 'message': 'You are not authorized to approve merge requests.'}, status=403)

        if action == 'reject':
            merge_request.status = 'rejected'
            merge_request.rejection_reason = rejection_reason
            merge_request.approved_by = request.user
            merge_request.date_actioned = timezone.now()
            merge_request.save()
            return Response({'status': 'success', 'message': 'Merge request rejected.'})

        if action == 'approve':
            merge_request.status = 'approved'
            merge_request.approved_by = request.user
            merge_request.date_actioned = timezone.now()
            merge_request.save()

            # Execute the merge
            self.execute_merge(merge_request)
            return Response({'status': 'success', 'message': 'Merge request approved and processed.'})

        if action == 'reverse':
            self.reverse_merge(merge_request)
            return Response({'status': 'success', 'message': 'Merge has been reversed successfully.'})

        return Response({'status': 'failed', 'message': 'Invalid action.'}, status=400)

    def execute_merge(self, merge_request):
        """Transfer transactions from source to destination account"""
        source = merge_request.source_account
        destination = merge_request.destination_account
        source_chart = source.account_product.accounts_chart
        dest_chart = destination.account_product.accounts_chart

        if merge_request.transfer_mode == 'all':
            transactions = SavingAccountTransactions.objects.filter(customer_account=source)
        elif merge_request.transfer_mode == 'from_date':
            transactions = SavingAccountTransactions.objects.filter(
                customer_account=source,
                date_added__gte=merge_request.transfer_from_date
            )

        # Store the IDs of transactions being moved
        transaction_ids = list(transactions.values_list('id', flat=True))
        merge_request.transferred_transaction_ids = transaction_ids

        # Update chart references on underlying SystemTransactions if cross-product
        for sat in transactions.select_related('transaction'):
            sys_trans = sat.transaction
            updated = False
            if sys_trans.credit_chart_id == source_chart.id:
                sys_trans.credit_chart = dest_chart
                updated = True
            if sys_trans.debit_chart_id == source_chart.id:
                sys_trans.debit_chart = dest_chart
                updated = True
            if updated:
                sys_trans.save()

        # Move all selected SavingAccountTransactions to destination
        transactions.update(customer_account=destination)

        if merge_request.transfer_mode == 'all':
            source.deleted = True
            source.deleted_at = timezone.now()
            source.status = 'closed'
            source.save()
        elif merge_request.transfer_mode == 'from_date':
            source.status = 'closed'
            source.is_active = False
            source.save()

        merge_request.status = 'completed'
        merge_request.save()

    def reverse_merge(self, merge_request):
        """Reverse a completed merge - move only the originally transferred transactions back"""
        source = merge_request.source_account
        destination = merge_request.destination_account
        source_chart = source.account_product.accounts_chart
        dest_chart = destination.account_product.accounts_chart

        transaction_ids = merge_request.transferred_transaction_ids or []
        if not transaction_ids:
            return

        transactions = SavingAccountTransactions.objects.filter(id__in=transaction_ids)

        # Revert chart references back to source chart
        for sat in transactions.select_related('transaction'):
            sys_trans = sat.transaction
            updated = False
            if sys_trans.credit_chart_id == dest_chart.id:
                sys_trans.credit_chart = source_chart
                updated = True
            if sys_trans.debit_chart_id == dest_chart.id:
                sys_trans.debit_chart = source_chart
                updated = True
            if updated:
                sys_trans.save()

        # Move transactions back to source
        transactions.update(customer_account=source)

        # Restore source account
        source.deleted = False
        source.deleted_at = None
        source.status = 'active'
        source.is_active = True
        source.save()

        merge_request.status = 'reversed'
        merge_request.save()
